~0x44/nova/extdoc

« back to all changes in this revision

Viewing changes to vendor/tornado/tornado/web.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright 2009 Facebook
 
4
#
 
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
6
# not use this file except in compliance with the License. You may obtain
 
7
# a copy of the License at
 
8
#
 
9
#     http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
11
# Unless required by applicable law or agreed to in writing, software
 
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
14
# License for the specific language governing permissions and limitations
 
15
# under the License.
 
16
 
 
17
"""The Tornado web framework.
 
18
 
 
19
The Tornado web framework looks a bit like web.py (http://webpy.org/) or
 
20
Google's webapp (http://code.google.com/appengine/docs/python/tools/webapp/),
 
21
but with additional tools and optimizations to take advantage of the
 
22
Tornado non-blocking web server and tools.
 
23
 
 
24
Here is the canonical "Hello, world" example app:
 
25
 
 
26
    import tornado.httpserver
 
27
    import tornado.ioloop
 
28
    import tornado.web
 
29
 
 
30
    class MainHandler(tornado.web.RequestHandler):
 
31
        def get(self):
 
32
            self.write("Hello, world")
 
33
 
 
34
    if __name__ == "__main__":
 
35
        application = tornado.web.Application([
 
36
            (r"/", MainHandler),
 
37
        ])
 
38
        http_server = tornado.httpserver.HTTPServer(application)
 
39
        http_server.listen(8888)
 
40
        tornado.ioloop.IOLoop.instance().start()
 
41
 
 
42
See the Tornado walkthrough on GitHub for more details and a good
 
43
getting started guide.
 
44
"""
 
45
 
 
46
import base64
 
47
import binascii
 
48
import calendar
 
49
import Cookie
 
50
import cStringIO
 
51
import datetime
 
52
import email.utils
 
53
import escape
 
54
import functools
 
55
import gzip
 
56
import hashlib
 
57
import hmac
 
58
import httplib
 
59
import locale
 
60
import logging
 
61
import mimetypes
 
62
import os.path
 
63
import re
 
64
import stat
 
65
import sys
 
66
import template
 
67
import time
 
68
import types
 
69
import urllib
 
70
import urlparse
 
71
import uuid
 
72
 
 
73
_log = logging.getLogger('tornado.web')
 
74
 
 
75
class RequestHandler(object):
 
76
    """Subclass this class and define get() or post() to make a handler.
 
77
 
 
78
    If you want to support more methods than the standard GET/HEAD/POST, you
 
79
    should override the class variable SUPPORTED_METHODS in your
 
80
    RequestHandler class.
 
81
    """
 
82
    SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PUT")
 
83
 
 
84
    def __init__(self, application, request, transforms=None):
 
85
        self.application = application
 
86
        self.request = request
 
87
        self._headers_written = False
 
88
        self._finished = False
 
89
        self._auto_finish = True
 
90
        self._transforms = transforms or []
 
91
        self.ui = _O((n, self._ui_method(m)) for n, m in
 
92
                     application.ui_methods.iteritems())
 
93
        self.ui["modules"] = _O((n, self._ui_module(n, m)) for n, m in
 
94
                                application.ui_modules.iteritems())
 
95
        self.clear()
 
96
        # Check since connection is not available in WSGI
 
97
        if hasattr(self.request, "connection"):
 
98
            self.request.connection.stream.set_close_callback(
 
99
                self.on_connection_close)
 
100
 
 
101
    @property
 
102
    def settings(self):
 
103
        return self.application.settings
 
104
 
 
105
    def head(self, *args, **kwargs):
 
106
        raise HTTPError(405)
 
107
 
 
108
    def get(self, *args, **kwargs):
 
109
        raise HTTPError(405)
 
110
 
 
111
    def post(self, *args, **kwargs):
 
112
        raise HTTPError(405)
 
113
 
 
114
    def delete(self, *args, **kwargs):
 
115
        raise HTTPError(405)
 
116
 
 
117
    def put(self, *args, **kwargs):
 
118
        raise HTTPError(405)
 
119
 
 
120
    def prepare(self):
 
121
        """Called before the actual handler method.
 
122
 
 
123
        Useful to override in a handler if you want a common bottleneck for
 
124
        all of your requests.
 
125
        """
 
126
        pass
 
127
 
 
128
    def on_connection_close(self):
 
129
        """Called in async handlers if the client closed the connection.
 
130
 
 
131
        You may override this to clean up resources associated with
 
132
        long-lived connections.
 
133
 
 
134
        Note that the select()-based implementation of IOLoop does not detect
 
135
        closed connections and so this method will not be called until
 
136
        you try (and fail) to produce some output.  The epoll- and kqueue-
 
137
        based implementations should detect closed connections even while
 
138
        the request is idle.
 
139
        """
 
140
        pass
 
141
 
 
142
    def clear(self):
 
143
        """Resets all headers and content for this response."""
 
144
        self._headers = {
 
145
            "Server": "TornadoServer/0.1",
 
146
            "Content-Type": "text/html; charset=UTF-8",
 
147
        }
 
148
        if not self.request.supports_http_1_1():
 
149
            if self.request.headers.get("Connection") == "Keep-Alive":
 
150
                self.set_header("Connection", "Keep-Alive")
 
151
        self._write_buffer = []
 
152
        self._status_code = 200
 
153
 
 
154
    def set_status(self, status_code):
 
155
        """Sets the status code for our response."""
 
156
        assert status_code in httplib.responses
 
157
        self._status_code = status_code
 
158
 
 
159
    def set_header(self, name, value):
 
160
        """Sets the given response header name and value.
 
161
 
 
162
        If a datetime is given, we automatically format it according to the
 
163
        HTTP specification. If the value is not a string, we convert it to
 
164
        a string. All header values are then encoded as UTF-8.
 
165
        """
 
166
        if isinstance(value, datetime.datetime):
 
167
            t = calendar.timegm(value.utctimetuple())
 
168
            value = email.utils.formatdate(t, localtime=False, usegmt=True)
 
169
        elif isinstance(value, int) or isinstance(value, long):
 
170
            value = str(value)
 
171
        else:
 
172
            value = _utf8(value)
 
173
            # If \n is allowed into the header, it is possible to inject
 
174
            # additional headers or split the request. Also cap length to
 
175
            # prevent obviously erroneous values.
 
176
            safe_value = re.sub(r"[\x00-\x1f]", " ", value)[:4000]
 
177
            if safe_value != value:
 
178
                raise ValueError("Unsafe header value %r", value)
 
179
        self._headers[name] = value
 
180
 
 
181
    _ARG_DEFAULT = []
 
182
    def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
 
183
        """Returns the value of the argument with the given name.
 
184
 
 
185
        If default is not provided, the argument is considered to be
 
186
        required, and we throw an HTTP 404 exception if it is missing.
 
187
 
 
188
        The returned value is always unicode.
 
189
        """
 
190
        values = self.request.arguments.get(name, None)
 
191
        if values is None:
 
192
            if default is self._ARG_DEFAULT:
 
193
                raise HTTPError(404, "Missing argument %s" % name)
 
194
            return default
 
195
        # Get rid of any weird control chars
 
196
        value = re.sub(r"[\x00-\x08\x0e-\x1f]", " ", values[-1])
 
197
        value = _unicode(value)
 
198
        if strip: value = value.strip()
 
199
        return value
 
200
 
 
201
    @property
 
202
    def cookies(self):
 
203
        """A dictionary of Cookie.Morsel objects."""
 
204
        if not hasattr(self, "_cookies"):
 
205
            self._cookies = Cookie.BaseCookie()
 
206
            if "Cookie" in self.request.headers:
 
207
                try:
 
208
                    self._cookies.load(self.request.headers["Cookie"])
 
209
                except:
 
210
                    self.clear_all_cookies()
 
211
        return self._cookies
 
212
 
 
213
    def get_cookie(self, name, default=None):
 
214
        """Gets the value of the cookie with the given name, else default."""
 
215
        if name in self.cookies:
 
216
            return self.cookies[name].value
 
217
        return default
 
218
 
 
219
    def set_cookie(self, name, value, domain=None, expires=None, path="/",
 
220
                   expires_days=None):
 
221
        """Sets the given cookie name/value with the given options."""
 
222
        name = _utf8(name)
 
223
        value = _utf8(value)
 
224
        if re.search(r"[\x00-\x20]", name + value):
 
225
            # Don't let us accidentally inject bad stuff
 
226
            raise ValueError("Invalid cookie %r: %r" % (name, value))
 
227
        if not hasattr(self, "_new_cookies"):
 
228
            self._new_cookies = []
 
229
        new_cookie = Cookie.BaseCookie()
 
230
        self._new_cookies.append(new_cookie)
 
231
        new_cookie[name] = value
 
232
        if domain:
 
233
            new_cookie[name]["domain"] = domain
 
234
        if expires_days is not None and not expires:
 
235
            expires = datetime.datetime.utcnow() + datetime.timedelta(
 
236
                days=expires_days)
 
237
        if expires:
 
238
            timestamp = calendar.timegm(expires.utctimetuple())
 
239
            new_cookie[name]["expires"] = email.utils.formatdate(
 
240
                timestamp, localtime=False, usegmt=True)
 
241
        if path:
 
242
            new_cookie[name]["path"] = path
 
243
 
 
244
    def clear_cookie(self, name, path="/", domain=None):
 
245
        """Deletes the cookie with the given name."""
 
246
        expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
 
247
        self.set_cookie(name, value="", path=path, expires=expires,
 
248
                        domain=domain)
 
249
 
 
250
    def clear_all_cookies(self):
 
251
        """Deletes all the cookies the user sent with this request."""
 
252
        for name in self.cookies.iterkeys():
 
253
            self.clear_cookie(name)
 
254
 
 
255
    def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
 
256
        """Signs and timestamps a cookie so it cannot be forged.
 
257
 
 
258
        You must specify the 'cookie_secret' setting in your Application
 
259
        to use this method. It should be a long, random sequence of bytes
 
260
        to be used as the HMAC secret for the signature.
 
261
 
 
262
        To read a cookie set with this method, use get_secure_cookie().
 
263
        """
 
264
        timestamp = str(int(time.time()))
 
265
        value = base64.b64encode(value)
 
266
        signature = self._cookie_signature(name, value, timestamp)
 
267
        value = "|".join([value, timestamp, signature])
 
268
        self.set_cookie(name, value, expires_days=expires_days, **kwargs)
 
269
 
 
270
    def get_secure_cookie(self, name, include_name=True, value=None):
 
271
        """Returns the given signed cookie if it validates, or None.
 
272
 
 
273
        In older versions of Tornado (0.1 and 0.2), we did not include the
 
274
        name of the cookie in the cookie signature. To read these old-style
 
275
        cookies, pass include_name=False to this method. Otherwise, all
 
276
        attempts to read old-style cookies will fail (and you may log all
 
277
        your users out whose cookies were written with a previous Tornado
 
278
        version).
 
279
        """
 
280
        if value is None: value = self.get_cookie(name)
 
281
        if not value: return None
 
282
        parts = value.split("|")
 
283
        if len(parts) != 3: return None
 
284
        if include_name:
 
285
            signature = self._cookie_signature(name, parts[0], parts[1])
 
286
        else:
 
287
            signature = self._cookie_signature(parts[0], parts[1])
 
288
        if not _time_independent_equals(parts[2], signature):
 
289
            _log.warning("Invalid cookie signature %r", value)
 
290
            return None
 
291
        timestamp = int(parts[1])
 
292
        if timestamp < time.time() - 31 * 86400:
 
293
            _log.warning("Expired cookie %r", value)
 
294
            return None
 
295
        try:
 
296
            return base64.b64decode(parts[0])
 
297
        except:
 
298
            return None
 
299
 
 
300
    def _cookie_signature(self, *parts):
 
301
        self.require_setting("cookie_secret", "secure cookies")
 
302
        hash = hmac.new(self.application.settings["cookie_secret"],
 
303
                        digestmod=hashlib.sha1)
 
304
        for part in parts: hash.update(part)
 
305
        return hash.hexdigest()
 
306
 
 
307
    def redirect(self, url, permanent=False):
 
308
        """Sends a redirect to the given (optionally relative) URL."""
 
309
        if self._headers_written:
 
310
            raise Exception("Cannot redirect after headers have been written")
 
311
        self.set_status(301 if permanent else 302)
 
312
        # Remove whitespace
 
313
        url = re.sub(r"[\x00-\x20]+", "", _utf8(url))
 
314
        self.set_header("Location", urlparse.urljoin(self.request.uri, url))
 
315
        self.finish()
 
316
 
 
317
    def write(self, chunk):
 
318
        """Writes the given chunk to the output buffer.
 
319
 
 
320
        To write the output to the network, use the flush() method below.
 
321
 
 
322
        If the given chunk is a dictionary, we write it as JSON and set
 
323
        the Content-Type of the response to be text/javascript.
 
324
        """
 
325
        assert not self._finished
 
326
        if isinstance(chunk, dict):
 
327
            chunk = escape.json_encode(chunk)
 
328
            self.set_header("Content-Type", "text/javascript; charset=UTF-8")
 
329
        chunk = _utf8(chunk)
 
330
        self._write_buffer.append(chunk)
 
331
 
 
332
    def render(self, template_name, **kwargs):
 
333
        """Renders the template with the given arguments as the response."""
 
334
        html = self.render_string(template_name, **kwargs)
 
335
 
 
336
        # Insert the additional JS and CSS added by the modules on the page
 
337
        js_embed = []
 
338
        js_files = []
 
339
        css_embed = []
 
340
        css_files = []
 
341
        html_heads = []
 
342
        html_bodies = []
 
343
        for module in getattr(self, "_active_modules", {}).itervalues():
 
344
            embed_part = module.embedded_javascript()
 
345
            if embed_part: js_embed.append(_utf8(embed_part))
 
346
            file_part = module.javascript_files()
 
347
            if file_part:
 
348
                if isinstance(file_part, basestring):
 
349
                    js_files.append(file_part)
 
350
                else:
 
351
                    js_files.extend(file_part)
 
352
            embed_part = module.embedded_css()
 
353
            if embed_part: css_embed.append(_utf8(embed_part))
 
354
            file_part = module.css_files()
 
355
            if file_part:
 
356
                if isinstance(file_part, basestring):
 
357
                    css_files.append(file_part)
 
358
                else:
 
359
                    css_files.extend(file_part)
 
360
            head_part = module.html_head()
 
361
            if head_part: html_heads.append(_utf8(head_part))
 
362
            body_part = module.html_body()
 
363
            if body_part: html_bodies.append(_utf8(body_part))
 
364
        if js_files:
 
365
            # Maintain order of JavaScript files given by modules
 
366
            paths = []
 
367
            unique_paths = set()
 
368
            for path in js_files:
 
369
                if not path.startswith("/") and not path.startswith("http:"):
 
370
                    path = self.static_url(path)
 
371
                if path not in unique_paths:
 
372
                    paths.append(path)
 
373
                    unique_paths.add(path)
 
374
            js = ''.join('<script src="' + escape.xhtml_escape(p) +
 
375
                         '" type="text/javascript"></script>'
 
376
                         for p in paths)
 
377
            sloc = html.rindex('</body>')
 
378
            html = html[:sloc] + js + '\n' + html[sloc:]
 
379
        if js_embed:
 
380
            js = '<script type="text/javascript">\n//<![CDATA[\n' + \
 
381
                '\n'.join(js_embed) + '\n//]]>\n</script>'
 
382
            sloc = html.rindex('</body>')
 
383
            html = html[:sloc] + js + '\n' + html[sloc:]
 
384
        if css_files:
 
385
            paths = set()
 
386
            for path in css_files:
 
387
                if not path.startswith("/") and not path.startswith("http:"):
 
388
                    paths.add(self.static_url(path))
 
389
                else:
 
390
                    paths.add(path)
 
391
            css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
 
392
                          'type="text/css" rel="stylesheet"/>'
 
393
                          for p in paths)
 
394
            hloc = html.index('</head>')
 
395
            html = html[:hloc] + css + '\n' + html[hloc:]
 
396
        if css_embed:
 
397
            css = '<style type="text/css">\n' + '\n'.join(css_embed) + \
 
398
                '\n</style>'
 
399
            hloc = html.index('</head>')
 
400
            html = html[:hloc] + css + '\n' + html[hloc:]
 
401
        if html_heads:
 
402
            hloc = html.index('</head>')
 
403
            html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]
 
404
        if html_bodies:
 
405
            hloc = html.index('</body>')
 
406
            html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]
 
407
        self.finish(html)
 
408
 
 
409
    def render_string(self, template_name, **kwargs):
 
410
        """Generate the given template with the given arguments.
 
411
 
 
412
        We return the generated string. To generate and write a template
 
413
        as a response, use render() above.
 
414
        """
 
415
        # If no template_path is specified, use the path of the calling file
 
416
        template_path = self.application.settings.get("template_path")
 
417
        if not template_path:
 
418
            frame = sys._getframe(0)
 
419
            web_file = frame.f_code.co_filename
 
420
            while frame.f_code.co_filename == web_file:
 
421
                frame = frame.f_back
 
422
            template_path = os.path.dirname(frame.f_code.co_filename)
 
423
        if not getattr(RequestHandler, "_templates", None):
 
424
            RequestHandler._templates = {}
 
425
        if template_path not in RequestHandler._templates:
 
426
            loader = self.application.settings.get("template_loader") or\
 
427
              template.Loader(template_path)
 
428
            RequestHandler._templates[template_path] = loader
 
429
        t = RequestHandler._templates[template_path].load(template_name)
 
430
        args = dict(
 
431
            handler=self,
 
432
            request=self.request,
 
433
            current_user=self.current_user,
 
434
            locale=self.locale,
 
435
            _=self.locale.translate,
 
436
            static_url=self.static_url,
 
437
            xsrf_form_html=self.xsrf_form_html,
 
438
            reverse_url=self.application.reverse_url
 
439
        )
 
440
        args.update(self.ui)
 
441
        args.update(kwargs)
 
442
        return t.generate(**args)
 
443
 
 
444
    def flush(self, include_footers=False):
 
445
        """Flushes the current output buffer to the nextwork."""
 
446
        if self.application._wsgi:
 
447
            raise Exception("WSGI applications do not support flush()")
 
448
 
 
449
        chunk = "".join(self._write_buffer)
 
450
        self._write_buffer = []
 
451
        if not self._headers_written:
 
452
            self._headers_written = True
 
453
            for transform in self._transforms:
 
454
                self._headers, chunk = transform.transform_first_chunk(
 
455
                    self._headers, chunk, include_footers)
 
456
            headers = self._generate_headers()
 
457
        else:
 
458
            for transform in self._transforms:
 
459
                chunk = transform.transform_chunk(chunk, include_footers)
 
460
            headers = ""
 
461
 
 
462
        # Ignore the chunk and only write the headers for HEAD requests
 
463
        if self.request.method == "HEAD":
 
464
            if headers: self.request.write(headers)
 
465
            return
 
466
 
 
467
        if headers or chunk:
 
468
            self.request.write(headers + chunk)
 
469
 
 
470
    def finish(self, chunk=None):
 
471
        """Finishes this response, ending the HTTP request."""
 
472
        assert not self._finished
 
473
        if chunk is not None: self.write(chunk)
 
474
 
 
475
        # Automatically support ETags and add the Content-Length header if
 
476
        # we have not flushed any content yet.
 
477
        if not self._headers_written:
 
478
            if (self._status_code == 200 and self.request.method == "GET" and
 
479
                "Etag" not in self._headers):
 
480
                hasher = hashlib.sha1()
 
481
                for part in self._write_buffer:
 
482
                    hasher.update(part)
 
483
                etag = '"%s"' % hasher.hexdigest()
 
484
                inm = self.request.headers.get("If-None-Match")
 
485
                if inm and inm.find(etag) != -1:
 
486
                    self._write_buffer = []
 
487
                    self.set_status(304)
 
488
                else:
 
489
                    self.set_header("Etag", etag)
 
490
            if "Content-Length" not in self._headers:
 
491
                content_length = sum(len(part) for part in self._write_buffer)
 
492
                self.set_header("Content-Length", content_length)
 
493
 
 
494
        if not self.application._wsgi:
 
495
            self.flush(include_footers=True)
 
496
            self.request.finish()
 
497
            self._log()
 
498
        self._finished = True
 
499
 
 
500
    def send_error(self, status_code=500, **kwargs):
 
501
        """Sends the given HTTP error code to the browser.
 
502
 
 
503
        We also send the error HTML for the given error code as returned by
 
504
        get_error_html. Override that method if you want custom error pages
 
505
        for your application.
 
506
        """
 
507
        if self._headers_written:
 
508
            _log.error("Cannot send error response after headers written")
 
509
            if not self._finished:
 
510
                self.finish()
 
511
            return
 
512
        self.clear()
 
513
        self.set_status(status_code)
 
514
        message = self.get_error_html(status_code, **kwargs)
 
515
        self.finish(message)
 
516
 
 
517
    def get_error_html(self, status_code, **kwargs):
 
518
        """Override to implement custom error pages.
 
519
 
 
520
        If this error was caused by an uncaught exception, the
 
521
        exception object can be found in kwargs e.g. kwargs['exception']
 
522
        """
 
523
        return "<html><title>%(code)d: %(message)s</title>" \
 
524
               "<body>%(code)d: %(message)s</body></html>" % {
 
525
            "code": status_code,
 
526
            "message": httplib.responses[status_code],
 
527
        }
 
528
 
 
529
    @property
 
530
    def locale(self):
 
531
        """The local for the current session.
 
532
 
 
533
        Determined by either get_user_locale, which you can override to
 
534
        set the locale based on, e.g., a user preference stored in a
 
535
        database, or get_browser_locale, which uses the Accept-Language
 
536
        header.
 
537
        """
 
538
        if not hasattr(self, "_locale"):
 
539
            self._locale = self.get_user_locale()
 
540
            if not self._locale:
 
541
                self._locale = self.get_browser_locale()
 
542
                assert self._locale
 
543
        return self._locale
 
544
 
 
545
    def get_user_locale(self):
 
546
        """Override to determine the locale from the authenticated user.
 
547
 
 
548
        If None is returned, we use the Accept-Language header.
 
549
        """
 
550
        return None
 
551
 
 
552
    def get_browser_locale(self, default="en_US"):
 
553
        """Determines the user's locale from Accept-Language header.
 
554
 
 
555
        See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
 
556
        """
 
557
        if "Accept-Language" in self.request.headers:
 
558
            languages = self.request.headers["Accept-Language"].split(",")
 
559
            locales = []
 
560
            for language in languages:
 
561
                parts = language.strip().split(";")
 
562
                if len(parts) > 1 and parts[1].startswith("q="):
 
563
                    try:
 
564
                        score = float(parts[1][2:])
 
565
                    except (ValueError, TypeError):
 
566
                        score = 0.0
 
567
                else:
 
568
                    score = 1.0
 
569
                locales.append((parts[0], score))
 
570
            if locales:
 
571
                locales.sort(key=lambda (l, s): s, reverse=True)
 
572
                codes = [l[0] for l in locales]
 
573
                return locale.get(*codes)
 
574
        return locale.get(default)
 
575
 
 
576
    @property
 
577
    def current_user(self):
 
578
        """The authenticated user for this request.
 
579
 
 
580
        Determined by either get_current_user, which you can override to
 
581
        set the user based on, e.g., a cookie. If that method is not
 
582
        overridden, this method always returns None.
 
583
 
 
584
        We lazy-load the current user the first time this method is called
 
585
        and cache the result after that.
 
586
        """
 
587
        if not hasattr(self, "_current_user"):
 
588
            self._current_user = self.get_current_user()
 
589
        return self._current_user
 
590
 
 
591
    def get_current_user(self):
 
592
        """Override to determine the current user from, e.g., a cookie."""
 
593
        return None
 
594
 
 
595
    def get_login_url(self):
 
596
        """Override to customize the login URL based on the request.
 
597
 
 
598
        By default, we use the 'login_url' application setting.
 
599
        """
 
600
        self.require_setting("login_url", "@tornado.web.authenticated")
 
601
        return self.application.settings["login_url"]
 
602
 
 
603
    @property
 
604
    def xsrf_token(self):
 
605
        """The XSRF-prevention token for the current user/session.
 
606
 
 
607
        To prevent cross-site request forgery, we set an '_xsrf' cookie
 
608
        and include the same '_xsrf' value as an argument with all POST
 
609
        requests. If the two do not match, we reject the form submission
 
610
        as a potential forgery.
 
611
 
 
612
        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
 
613
        """
 
614
        if not hasattr(self, "_xsrf_token"):
 
615
            token = self.get_cookie("_xsrf")
 
616
            if not token:
 
617
                token = binascii.b2a_hex(uuid.uuid4().bytes)
 
618
                expires_days = 30 if self.current_user else None
 
619
                self.set_cookie("_xsrf", token, expires_days=expires_days)
 
620
            self._xsrf_token = token
 
621
        return self._xsrf_token
 
622
 
 
623
    def check_xsrf_cookie(self):
 
624
        """Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
 
625
 
 
626
        To prevent cross-site request forgery, we set an '_xsrf' cookie
 
627
        and include the same '_xsrf' value as an argument with all POST
 
628
        requests. If the two do not match, we reject the form submission
 
629
        as a potential forgery.
 
630
 
 
631
        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
 
632
        """
 
633
        if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
 
634
            return
 
635
        token = self.get_argument("_xsrf", None)
 
636
        if not token:
 
637
            raise HTTPError(403, "'_xsrf' argument missing from POST")
 
638
        if self.xsrf_token != token:
 
639
            raise HTTPError(403, "XSRF cookie does not match POST argument")
 
640
 
 
641
    def xsrf_form_html(self):
 
642
        """An HTML <input/> element to be included with all POST forms.
 
643
 
 
644
        It defines the _xsrf input value, which we check on all POST
 
645
        requests to prevent cross-site request forgery. If you have set
 
646
        the 'xsrf_cookies' application setting, you must include this
 
647
        HTML within all of your HTML forms.
 
648
 
 
649
        See check_xsrf_cookie() above for more information.
 
650
        """
 
651
        return '<input type="hidden" name="_xsrf" value="' + \
 
652
            escape.xhtml_escape(self.xsrf_token) + '"/>'
 
653
 
 
654
    def static_url(self, path):
 
655
        """Returns a static URL for the given relative static file path.
 
656
 
 
657
        This method requires you set the 'static_path' setting in your
 
658
        application (which specifies the root directory of your static
 
659
        files).
 
660
 
 
661
        We append ?v=<signature> to the returned URL, which makes our
 
662
        static file handler set an infinite expiration header on the
 
663
        returned content. The signature is based on the content of the
 
664
        file.
 
665
 
 
666
        If this handler has a "include_host" attribute, we include the
 
667
        full host for every static URL, including the "http://". Set
 
668
        this attribute for handlers whose output needs non-relative static
 
669
        path names.
 
670
        """
 
671
        self.require_setting("static_path", "static_url")
 
672
        if not hasattr(RequestHandler, "_static_hashes"):
 
673
            RequestHandler._static_hashes = {}
 
674
        hashes = RequestHandler._static_hashes
 
675
        if path not in hashes:
 
676
            try:
 
677
                f = open(os.path.join(
 
678
                    self.application.settings["static_path"], path))
 
679
                hashes[path] = hashlib.md5(f.read()).hexdigest()
 
680
                f.close()
 
681
            except:
 
682
                _log.error("Could not open static file %r", path)
 
683
                hashes[path] = None
 
684
        base = self.request.protocol + "://" + self.request.host \
 
685
            if getattr(self, "include_host", False) else ""
 
686
        static_url_prefix = self.settings.get('static_url_prefix', '/static/')
 
687
        if hashes.get(path):
 
688
            return base + static_url_prefix + path + "?v=" + hashes[path][:5]
 
689
        else:
 
690
            return base + static_url_prefix + path
 
691
 
 
692
    def async_callback(self, callback, *args, **kwargs):
 
693
        """Wrap callbacks with this if they are used on asynchronous requests.
 
694
 
 
695
        Catches exceptions and properly finishes the request.
 
696
        """
 
697
        if callback is None:
 
698
            return None
 
699
        if args or kwargs:
 
700
            callback = functools.partial(callback, *args, **kwargs)
 
701
        def wrapper(*args, **kwargs):
 
702
            try:
 
703
                return callback(*args, **kwargs)
 
704
            except Exception, e:
 
705
                if self._headers_written:
 
706
                    _log.error("Exception after headers written",
 
707
                                  exc_info=True)
 
708
                else:
 
709
                    self._handle_request_exception(e)
 
710
        return wrapper
 
711
 
 
712
    def require_setting(self, name, feature="this feature"):
 
713
        """Raises an exception if the given app setting is not defined."""
 
714
        if not self.application.settings.get(name):
 
715
            raise Exception("You must define the '%s' setting in your "
 
716
                            "application to use %s" % (name, feature))
 
717
 
 
718
    def reverse_url(self, name, *args):
 
719
        return self.application.reverse_url(name, *args)
 
720
 
 
721
    def _execute(self, transforms, *args, **kwargs):
 
722
        """Executes this request with the given output transforms."""
 
723
        self._transforms = transforms
 
724
        try:
 
725
            if self.request.method not in self.SUPPORTED_METHODS:
 
726
                raise HTTPError(405)
 
727
            # If XSRF cookies are turned on, reject form submissions without
 
728
            # the proper cookie
 
729
            if self.request.method == "POST" and \
 
730
               self.application.settings.get("xsrf_cookies"):
 
731
                self.check_xsrf_cookie()
 
732
            self.prepare()
 
733
            if not self._finished:
 
734
                getattr(self, self.request.method.lower())(*args, **kwargs)
 
735
                if self._auto_finish and not self._finished:
 
736
                    self.finish()
 
737
        except Exception, e:
 
738
            self._handle_request_exception(e)
 
739
 
 
740
    def _generate_headers(self):
 
741
        lines = [self.request.version + " " + str(self._status_code) + " " +
 
742
                 httplib.responses[self._status_code]]
 
743
        lines.extend(["%s: %s" % (n, v) for n, v in self._headers.iteritems()])
 
744
        for cookie_dict in getattr(self, "_new_cookies", []):
 
745
            for cookie in cookie_dict.values():
 
746
                lines.append("Set-Cookie: " + cookie.OutputString(None))
 
747
        return "\r\n".join(lines) + "\r\n\r\n"
 
748
 
 
749
    def _log(self):
 
750
        if self._status_code < 400:
 
751
            log_method = _log.info
 
752
        elif self._status_code < 500:
 
753
            log_method = _log.warning
 
754
        else:
 
755
            log_method = _log.error
 
756
        request_time = 1000.0 * self.request.request_time()
 
757
        log_method("%d %s %.2fms", self._status_code,
 
758
                   self._request_summary(), request_time)
 
759
 
 
760
    def _request_summary(self):
 
761
        return self.request.method + " " + self.request.uri + " (" + \
 
762
            self.request.remote_ip + ")"
 
763
 
 
764
    def _handle_request_exception(self, e):
 
765
        if isinstance(e, HTTPError):
 
766
            if e.log_message:
 
767
                format = "%d %s: " + e.log_message
 
768
                args = [e.status_code, self._request_summary()] + list(e.args)
 
769
                _log.warning(format, *args)
 
770
            if e.status_code not in httplib.responses:
 
771
                _log.error("Bad HTTP status code: %d", e.status_code)
 
772
                self.send_error(500, exception=e)
 
773
            else:
 
774
                self.send_error(e.status_code, exception=e)
 
775
        else:
 
776
            _log.error("Uncaught exception %s\n%r", self._request_summary(),
 
777
                          self.request, exc_info=e)
 
778
            self.send_error(500, exception=e)
 
779
 
 
780
    def _ui_module(self, name, module):
 
781
        def render(*args, **kwargs):
 
782
            if not hasattr(self, "_active_modules"):
 
783
                self._active_modules = {}
 
784
            if name not in self._active_modules:
 
785
                self._active_modules[name] = module(self)
 
786
            rendered = self._active_modules[name].render(*args, **kwargs)
 
787
            return rendered
 
788
        return render
 
789
 
 
790
    def _ui_method(self, method):
 
791
        return lambda *args, **kwargs: method(self, *args, **kwargs)
 
792
 
 
793
 
 
794
def asynchronous(method):
 
795
    """Wrap request handler methods with this if they are asynchronous.
 
796
 
 
797
    If this decorator is given, the response is not finished when the
 
798
    method returns. It is up to the request handler to call self.finish()
 
799
    to finish the HTTP request. Without this decorator, the request is
 
800
    automatically finished when the get() or post() method returns.
 
801
 
 
802
       class MyRequestHandler(web.RequestHandler):
 
803
           @web.asynchronous
 
804
           def get(self):
 
805
              http = httpclient.AsyncHTTPClient()
 
806
              http.fetch("http://friendfeed.com/", self._on_download)
 
807
 
 
808
           def _on_download(self, response):
 
809
              self.write("Downloaded!")
 
810
              self.finish()
 
811
 
 
812
    """
 
813
    @functools.wraps(method)
 
814
    def wrapper(self, *args, **kwargs):
 
815
        if self.application._wsgi:
 
816
            raise Exception("@asynchronous is not supported for WSGI apps")
 
817
        self._auto_finish = False
 
818
        return method(self, *args, **kwargs)
 
819
    return wrapper
 
820
 
 
821
 
 
822
def removeslash(method):
 
823
    """Use this decorator to remove trailing slashes from the request path.
 
824
 
 
825
    For example, a request to '/foo/' would redirect to '/foo' with this
 
826
    decorator. Your request handler mapping should use a regular expression
 
827
    like r'/foo/*' in conjunction with using the decorator.
 
828
    """
 
829
    @functools.wraps(method)
 
830
    def wrapper(self, *args, **kwargs):
 
831
        if self.request.path.endswith("/"):
 
832
            if self.request.method == "GET":
 
833
                uri = self.request.path.rstrip("/")
 
834
                if self.request.query: uri += "?" + self.request.query
 
835
                self.redirect(uri)
 
836
                return
 
837
            raise HTTPError(404)
 
838
        return method(self, *args, **kwargs)
 
839
    return wrapper
 
840
 
 
841
 
 
842
def addslash(method):
 
843
    """Use this decorator to add a missing trailing slash to the request path.
 
844
 
 
845
    For example, a request to '/foo' would redirect to '/foo/' with this
 
846
    decorator. Your request handler mapping should use a regular expression
 
847
    like r'/foo/?' in conjunction with using the decorator.
 
848
    """
 
849
    @functools.wraps(method)
 
850
    def wrapper(self, *args, **kwargs):
 
851
        if not self.request.path.endswith("/"):
 
852
            if self.request.method == "GET":
 
853
                uri = self.request.path + "/"
 
854
                if self.request.query: uri += "?" + self.request.query
 
855
                self.redirect(uri)
 
856
                return
 
857
            raise HTTPError(404)
 
858
        return method(self, *args, **kwargs)
 
859
    return wrapper
 
860
 
 
861
 
 
862
class Application(object):
 
863
    """A collection of request handlers that make up a web application.
 
864
 
 
865
    Instances of this class are callable and can be passed directly to
 
866
    HTTPServer to serve the application:
 
867
 
 
868
        application = web.Application([
 
869
            (r"/", MainPageHandler),
 
870
        ])
 
871
        http_server = httpserver.HTTPServer(application)
 
872
        http_server.listen(8080)
 
873
        ioloop.IOLoop.instance().start()
 
874
 
 
875
    The constructor for this class takes in a list of URLSpec objects
 
876
    or (regexp, request_class) tuples. When we receive requests, we
 
877
    iterate over the list in order and instantiate an instance of the
 
878
    first request class whose regexp matches the request path.
 
879
 
 
880
    Each tuple can contain an optional third element, which should be a
 
881
    dictionary if it is present. That dictionary is passed as keyword
 
882
    arguments to the contructor of the handler. This pattern is used
 
883
    for the StaticFileHandler below:
 
884
 
 
885
        application = web.Application([
 
886
            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
 
887
        ])
 
888
 
 
889
    We support virtual hosts with the add_handlers method, which takes in
 
890
    a host regular expression as the first argument:
 
891
 
 
892
        application.add_handlers(r"www\.myhost\.com", [
 
893
            (r"/article/([0-9]+)", ArticleHandler),
 
894
        ])
 
895
 
 
896
    You can serve static files by sending the static_path setting as a
 
897
    keyword argument. We will serve those files from the /static/ URI
 
898
    (this is configurable with the static_url_prefix setting),
 
899
    and we will serve /favicon.ico and /robots.txt from the same directory.
 
900
    """
 
901
    def __init__(self, handlers=None, default_host="", transforms=None,
 
902
                 wsgi=False, **settings):
 
903
        if transforms is None:
 
904
            self.transforms = []
 
905
            if settings.get("gzip"):
 
906
                self.transforms.append(GZipContentEncoding)
 
907
            self.transforms.append(ChunkedTransferEncoding)
 
908
        else:
 
909
            self.transforms = transforms
 
910
        self.handlers = []
 
911
        self.named_handlers = {}
 
912
        self.default_host = default_host
 
913
        self.settings = settings
 
914
        self.ui_modules = {}
 
915
        self.ui_methods = {}
 
916
        self._wsgi = wsgi
 
917
        self._load_ui_modules(settings.get("ui_modules", {}))
 
918
        self._load_ui_methods(settings.get("ui_methods", {}))
 
919
        if self.settings.get("static_path"):
 
920
            path = self.settings["static_path"]
 
921
            handlers = list(handlers or [])
 
922
            static_url_prefix = settings.get("static_url_prefix",
 
923
                                             "/static/")
 
924
            handlers = [
 
925
                (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
 
926
                 dict(path=path)),
 
927
                (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
 
928
                (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
 
929
            ] + handlers
 
930
        if handlers: self.add_handlers(".*$", handlers)
 
931
 
 
932
        # Automatically reload modified modules
 
933
        if self.settings.get("debug") and not wsgi:
 
934
            import autoreload
 
935
            autoreload.start()
 
936
 
 
937
    def add_handlers(self, host_pattern, host_handlers):
 
938
        """Appends the given handlers to our handler list."""
 
939
        if not host_pattern.endswith("$"):
 
940
            host_pattern += "$"
 
941
        handlers = []
 
942
        # The handlers with the wildcard host_pattern are a special
 
943
        # case - they're added in the constructor but should have lower
 
944
        # precedence than the more-precise handlers added later.
 
945
        # If a wildcard handler group exists, it should always be last
 
946
        # in the list, so insert new groups just before it.
 
947
        if self.handlers and self.handlers[-1][0].pattern == '.*$':
 
948
            self.handlers.insert(-1, (re.compile(host_pattern), handlers))
 
949
        else:
 
950
            self.handlers.append((re.compile(host_pattern), handlers))
 
951
 
 
952
        for spec in host_handlers:
 
953
            if type(spec) is type(()):
 
954
                assert len(spec) in (2, 3)
 
955
                pattern = spec[0]
 
956
                handler = spec[1]
 
957
                if len(spec) == 3:
 
958
                    kwargs = spec[2]
 
959
                else:
 
960
                    kwargs = {}
 
961
                spec = URLSpec(pattern, handler, kwargs)
 
962
            handlers.append(spec)
 
963
            if spec.name:
 
964
                if spec.name in self.named_handlers:
 
965
                    _log.warning(
 
966
                        "Multiple handlers named %s; replacing previous value",
 
967
                        spec.name)
 
968
                self.named_handlers[spec.name] = spec
 
969
 
 
970
    def add_transform(self, transform_class):
 
971
        """Adds the given OutputTransform to our transform list."""
 
972
        self.transforms.append(transform_class)
 
973
 
 
974
    def _get_host_handlers(self, request):
 
975
        host = request.host.lower().split(':')[0]
 
976
        for pattern, handlers in self.handlers:
 
977
            if pattern.match(host):
 
978
                return handlers
 
979
        # Look for default host if not behind load balancer (for debugging)
 
980
        if "X-Real-Ip" not in request.headers:
 
981
            for pattern, handlers in self.handlers:
 
982
                if pattern.match(self.default_host):
 
983
                    return handlers
 
984
        return None
 
985
 
 
986
    def _load_ui_methods(self, methods):
 
987
        if type(methods) is types.ModuleType:
 
988
            self._load_ui_methods(dict((n, getattr(methods, n))
 
989
                                       for n in dir(methods)))
 
990
        elif isinstance(methods, list):
 
991
            for m in list: self._load_ui_methods(m)
 
992
        else:
 
993
            for name, fn in methods.iteritems():
 
994
                if not name.startswith("_") and hasattr(fn, "__call__") \
 
995
                   and name[0].lower() == name[0]:
 
996
                    self.ui_methods[name] = fn
 
997
 
 
998
    def _load_ui_modules(self, modules):
 
999
        if type(modules) is types.ModuleType:
 
1000
            self._load_ui_modules(dict((n, getattr(modules, n))
 
1001
                                       for n in dir(modules)))
 
1002
        elif isinstance(modules, list):
 
1003
            for m in list: self._load_ui_modules(m)
 
1004
        else:
 
1005
            assert isinstance(modules, dict)
 
1006
            for name, cls in modules.iteritems():
 
1007
                try:
 
1008
                    if issubclass(cls, UIModule):
 
1009
                        self.ui_modules[name] = cls
 
1010
                except TypeError:
 
1011
                    pass
 
1012
 
 
1013
    def __call__(self, request):
 
1014
        """Called by HTTPServer to execute the request."""
 
1015
        transforms = [t(request) for t in self.transforms]
 
1016
        handler = None
 
1017
        args = []
 
1018
        kwargs = {}
 
1019
        handlers = self._get_host_handlers(request)
 
1020
        if not handlers:
 
1021
            handler = RedirectHandler(
 
1022
                request, "http://" + self.default_host + "/")
 
1023
        else:
 
1024
            for spec in handlers:
 
1025
                match = spec.regex.match(request.path)
 
1026
                if match:
 
1027
                    handler = spec.handler_class(self, request, **spec.kwargs)
 
1028
                    # Pass matched groups to the handler.  Since
 
1029
                    # match.groups() includes both named and unnamed groups,
 
1030
                    # we want to use either groups or groupdict but not both.
 
1031
                    kwargs = match.groupdict()
 
1032
                    if kwargs:
 
1033
                        args = []
 
1034
                    else:
 
1035
                        args = match.groups()
 
1036
                    break
 
1037
            if not handler:
 
1038
                handler = ErrorHandler(self, request, 404)
 
1039
 
 
1040
        # In debug mode, re-compile templates and reload static files on every
 
1041
        # request so you don't need to restart to see changes
 
1042
        if self.settings.get("debug"):
 
1043
            if getattr(RequestHandler, "_templates", None):
 
1044
              map(lambda loader: loader.reset(),
 
1045
                  RequestHandler._templates.values())
 
1046
            RequestHandler._static_hashes = {}
 
1047
 
 
1048
        handler._execute(transforms, *args, **kwargs)
 
1049
        return handler
 
1050
 
 
1051
    def reverse_url(self, name, *args):
 
1052
        """Returns a URL path for handler named `name`
 
1053
 
 
1054
        The handler must be added to the application as a named URLSpec
 
1055
        """
 
1056
        if name in self.named_handlers:
 
1057
            return self.named_handlers[name].reverse(*args)
 
1058
        raise KeyError("%s not found in named urls" % name)
 
1059
 
 
1060
 
 
1061
class HTTPError(Exception):
 
1062
    """An exception that will turn into an HTTP error response."""
 
1063
    def __init__(self, status_code, log_message=None, *args):
 
1064
        self.status_code = status_code
 
1065
        self.log_message = log_message
 
1066
        self.args = args
 
1067
 
 
1068
    def __str__(self):
 
1069
        message = "HTTP %d: %s" % (
 
1070
            self.status_code, httplib.responses[self.status_code])
 
1071
        if self.log_message:
 
1072
            return message + " (" + (self.log_message % self.args) + ")"
 
1073
        else:
 
1074
            return message
 
1075
 
 
1076
 
 
1077
class ErrorHandler(RequestHandler):
 
1078
    """Generates an error response with status_code for all requests."""
 
1079
    def __init__(self, application, request, status_code):
 
1080
        RequestHandler.__init__(self, application, request)
 
1081
        self.set_status(status_code)
 
1082
 
 
1083
    def prepare(self):
 
1084
        raise HTTPError(self._status_code)
 
1085
 
 
1086
 
 
1087
class RedirectHandler(RequestHandler):
 
1088
    """Redirects the client to the given URL for all GET requests.
 
1089
 
 
1090
    You should provide the keyword argument "url" to the handler, e.g.:
 
1091
 
 
1092
        application = web.Application([
 
1093
            (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
 
1094
        ])
 
1095
    """
 
1096
    def __init__(self, application, request, url, permanent=True):
 
1097
        RequestHandler.__init__(self, application, request)
 
1098
        self._url = url
 
1099
        self._permanent = permanent
 
1100
 
 
1101
    def get(self):
 
1102
        self.redirect(self._url, permanent=self._permanent)
 
1103
 
 
1104
 
 
1105
class StaticFileHandler(RequestHandler):
 
1106
    """A simple handler that can serve static content from a directory.
 
1107
 
 
1108
    To map a path to this handler for a static data directory /var/www,
 
1109
    you would add a line to your application like:
 
1110
 
 
1111
        application = web.Application([
 
1112
            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
 
1113
        ])
 
1114
 
 
1115
    The local root directory of the content should be passed as the "path"
 
1116
    argument to the handler.
 
1117
 
 
1118
    To support aggressive browser caching, if the argument "v" is given
 
1119
    with the path, we set an infinite HTTP expiration header. So, if you
 
1120
    want browsers to cache a file indefinitely, send them to, e.g.,
 
1121
    /static/images/myimage.png?v=xxx.
 
1122
    """
 
1123
    def __init__(self, application, request, path):
 
1124
        RequestHandler.__init__(self, application, request)
 
1125
        self.root = os.path.abspath(path) + os.path.sep
 
1126
 
 
1127
    def head(self, path):
 
1128
        self.get(path, include_body=False)
 
1129
 
 
1130
    def get(self, path, include_body=True):
 
1131
        abspath = os.path.abspath(os.path.join(self.root, path))
 
1132
        if not abspath.startswith(self.root):
 
1133
            raise HTTPError(403, "%s is not in root static directory", path)
 
1134
        if not os.path.exists(abspath):
 
1135
            raise HTTPError(404)
 
1136
        if not os.path.isfile(abspath):
 
1137
            raise HTTPError(403, "%s is not a file", path)
 
1138
 
 
1139
        stat_result = os.stat(abspath)
 
1140
        modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
 
1141
 
 
1142
        self.set_header("Last-Modified", modified)
 
1143
        if "v" in self.request.arguments:
 
1144
            self.set_header("Expires", datetime.datetime.utcnow() + \
 
1145
                                       datetime.timedelta(days=365*10))
 
1146
            self.set_header("Cache-Control", "max-age=" + str(86400*365*10))
 
1147
        else:
 
1148
            self.set_header("Cache-Control", "public")
 
1149
        mime_type, encoding = mimetypes.guess_type(abspath)
 
1150
        if mime_type:
 
1151
            self.set_header("Content-Type", mime_type)
 
1152
 
 
1153
        # Check the If-Modified-Since, and don't send the result if the
 
1154
        # content has not been modified
 
1155
        ims_value = self.request.headers.get("If-Modified-Since")
 
1156
        if ims_value is not None:
 
1157
            date_tuple = email.utils.parsedate(ims_value)
 
1158
            if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
 
1159
            if if_since >= modified:
 
1160
                self.set_status(304)
 
1161
                return
 
1162
 
 
1163
        if not include_body:
 
1164
            return
 
1165
        self.set_header("Content-Length", stat_result[stat.ST_SIZE])
 
1166
        file = open(abspath, "rb")
 
1167
        try:
 
1168
            self.write(file.read())
 
1169
        finally:
 
1170
            file.close()
 
1171
 
 
1172
 
 
1173
class FallbackHandler(RequestHandler):
 
1174
    """A RequestHandler that wraps another HTTP server callback.
 
1175
 
 
1176
    The fallback is a callable object that accepts an HTTPRequest,
 
1177
    such as an Application or tornado.wsgi.WSGIContainer.  This is most
 
1178
    useful to use both tornado RequestHandlers and WSGI in the same server.
 
1179
    Typical usage:
 
1180
        wsgi_app = tornado.wsgi.WSGIContainer(
 
1181
            django.core.handlers.wsgi.WSGIHandler())
 
1182
        application = tornado.web.Application([
 
1183
            (r"/foo", FooHandler),
 
1184
            (r".*", FallbackHandler, dict(fallback=wsgi_app),
 
1185
        ])
 
1186
    """
 
1187
    def __init__(self, app, request, fallback):
 
1188
        RequestHandler.__init__(self, app, request)
 
1189
        self.fallback = fallback
 
1190
 
 
1191
    def prepare(self):
 
1192
        self.fallback(self.request)
 
1193
        self._finished = True
 
1194
 
 
1195
 
 
1196
class OutputTransform(object):
 
1197
    """A transform modifies the result of an HTTP request (e.g., GZip encoding)
 
1198
 
 
1199
    A new transform instance is created for every request. See the
 
1200
    ChunkedTransferEncoding example below if you want to implement a
 
1201
    new Transform.
 
1202
    """
 
1203
    def __init__(self, request):
 
1204
        pass
 
1205
 
 
1206
    def transform_first_chunk(self, headers, chunk, finishing):
 
1207
        return headers, chunk
 
1208
 
 
1209
    def transform_chunk(self, chunk, finishing):
 
1210
        return chunk
 
1211
 
 
1212
 
 
1213
class GZipContentEncoding(OutputTransform):
 
1214
    """Applies the gzip content encoding to the response.
 
1215
 
 
1216
    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
 
1217
    """
 
1218
    CONTENT_TYPES = set([
 
1219
        "text/plain", "text/html", "text/css", "text/xml",
 
1220
        "application/x-javascript", "application/xml", "application/atom+xml",
 
1221
        "text/javascript", "application/json", "application/xhtml+xml"])
 
1222
    MIN_LENGTH = 5
 
1223
 
 
1224
    def __init__(self, request):
 
1225
        self._gzipping = request.supports_http_1_1() and \
 
1226
            "gzip" in request.headers.get("Accept-Encoding", "")
 
1227
 
 
1228
    def transform_first_chunk(self, headers, chunk, finishing):
 
1229
        if self._gzipping:
 
1230
            ctype = headers.get("Content-Type", "").split(";")[0]
 
1231
            self._gzipping = (ctype in self.CONTENT_TYPES) and \
 
1232
                (not finishing or len(chunk) >= self.MIN_LENGTH) and \
 
1233
                (finishing or "Content-Length" not in headers) and \
 
1234
                ("Content-Encoding" not in headers)
 
1235
        if self._gzipping:
 
1236
            headers["Content-Encoding"] = "gzip"
 
1237
            self._gzip_value = cStringIO.StringIO()
 
1238
            self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
 
1239
            self._gzip_pos = 0
 
1240
            chunk = self.transform_chunk(chunk, finishing)
 
1241
            if "Content-Length" in headers:
 
1242
                headers["Content-Length"] = str(len(chunk))
 
1243
        return headers, chunk
 
1244
 
 
1245
    def transform_chunk(self, chunk, finishing):
 
1246
        if self._gzipping:
 
1247
            self._gzip_file.write(chunk)
 
1248
            if finishing:
 
1249
                self._gzip_file.close()
 
1250
            else:
 
1251
                self._gzip_file.flush()
 
1252
            chunk = self._gzip_value.getvalue()
 
1253
            if self._gzip_pos > 0:
 
1254
                chunk = chunk[self._gzip_pos:]
 
1255
            self._gzip_pos += len(chunk)
 
1256
        return chunk
 
1257
 
 
1258
 
 
1259
class ChunkedTransferEncoding(OutputTransform):
 
1260
    """Applies the chunked transfer encoding to the response.
 
1261
 
 
1262
    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
 
1263
    """
 
1264
    def __init__(self, request):
 
1265
        self._chunking = request.supports_http_1_1()
 
1266
 
 
1267
    def transform_first_chunk(self, headers, chunk, finishing):
 
1268
        if self._chunking:
 
1269
            # No need to chunk the output if a Content-Length is specified
 
1270
            if "Content-Length" in headers or "Transfer-Encoding" in headers:
 
1271
                self._chunking = False
 
1272
            else:
 
1273
                headers["Transfer-Encoding"] = "chunked"
 
1274
                chunk = self.transform_chunk(chunk, finishing)
 
1275
        return headers, chunk
 
1276
 
 
1277
    def transform_chunk(self, block, finishing):
 
1278
        if self._chunking:
 
1279
            # Don't write out empty chunks because that means END-OF-STREAM
 
1280
            # with chunked encoding
 
1281
            if block:
 
1282
                block = ("%x" % len(block)) + "\r\n" + block + "\r\n"
 
1283
            if finishing:
 
1284
                block += "0\r\n\r\n"
 
1285
        return block
 
1286
 
 
1287
 
 
1288
def authenticated(method):
 
1289
    """Decorate methods with this to require that the user be logged in."""
 
1290
    @functools.wraps(method)
 
1291
    def wrapper(self, *args, **kwargs):
 
1292
        if not self.current_user:
 
1293
            if self.request.method == "GET":
 
1294
                url = self.get_login_url()
 
1295
                if "?" not in url:
 
1296
                    url += "?" + urllib.urlencode(dict(next=self.request.uri))
 
1297
                self.redirect(url)
 
1298
                return
 
1299
            raise HTTPError(403)
 
1300
        return method(self, *args, **kwargs)
 
1301
    return wrapper
 
1302
 
 
1303
 
 
1304
class UIModule(object):
 
1305
    """A UI re-usable, modular unit on a page.
 
1306
 
 
1307
    UI modules often execute additional queries, and they can include
 
1308
    additional CSS and JavaScript that will be included in the output
 
1309
    page, which is automatically inserted on page render.
 
1310
    """
 
1311
    def __init__(self, handler):
 
1312
        self.handler = handler
 
1313
        self.request = handler.request
 
1314
        self.ui = handler.ui
 
1315
        self.current_user = handler.current_user
 
1316
        self.locale = handler.locale
 
1317
 
 
1318
    def render(self, *args, **kwargs):
 
1319
        raise NotImplementedError()
 
1320
 
 
1321
    def embedded_javascript(self):
 
1322
        """Returns a JavaScript string that will be embedded in the page."""
 
1323
        return None
 
1324
 
 
1325
    def javascript_files(self):
 
1326
        """Returns a list of JavaScript files required by this module."""
 
1327
        return None
 
1328
 
 
1329
    def embedded_css(self):
 
1330
        """Returns a CSS string that will be embedded in the page."""
 
1331
        return None
 
1332
 
 
1333
    def css_files(self):
 
1334
        """Returns a list of JavaScript files required by this module."""
 
1335
        return None
 
1336
 
 
1337
    def html_head(self):
 
1338
        """Returns a CSS string that will be put in the <head/> element"""
 
1339
        return None
 
1340
 
 
1341
    def html_body(self):
 
1342
        """Returns an HTML string that will be put in the <body/> element"""
 
1343
        return None
 
1344
 
 
1345
    def render_string(self, path, **kwargs):
 
1346
        return self.handler.render_string(path, **kwargs)
 
1347
 
 
1348
class URLSpec(object):
 
1349
    """Specifies mappings between URLs and handlers."""
 
1350
    def __init__(self, pattern, handler_class, kwargs={}, name=None):
 
1351
        """Creates a URLSpec.
 
1352
 
 
1353
        Parameters:
 
1354
        pattern: Regular expression to be matched.  Any groups in the regex
 
1355
            will be passed in to the handler's get/post/etc methods as
 
1356
            arguments.
 
1357
        handler_class: RequestHandler subclass to be invoked.
 
1358
        kwargs (optional): A dictionary of additional arguments to be passed
 
1359
            to the handler's constructor.
 
1360
        name (optional): A name for this handler.  Used by
 
1361
            Application.reverse_url.
 
1362
        """
 
1363
        if not pattern.endswith('$'):
 
1364
            pattern += '$'
 
1365
        self.regex = re.compile(pattern)
 
1366
        self.handler_class = handler_class
 
1367
        self.kwargs = kwargs
 
1368
        self.name = name
 
1369
        self._path, self._group_count = self._find_groups()
 
1370
 
 
1371
    def _find_groups(self):
 
1372
        """Returns a tuple (reverse string, group count) for a url.
 
1373
 
 
1374
        For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
 
1375
        would return ('/%s/%s/', 2).
 
1376
        """
 
1377
        pattern = self.regex.pattern
 
1378
        if pattern.startswith('^'):
 
1379
            pattern = pattern[1:]
 
1380
        if pattern.endswith('$'):
 
1381
            pattern = pattern[:-1]
 
1382
 
 
1383
        if self.regex.groups != pattern.count('('):
 
1384
            # The pattern is too complicated for our simplistic matching,
 
1385
            # so we can't support reversing it.
 
1386
            return (None, None)
 
1387
 
 
1388
        pieces = []
 
1389
        for fragment in pattern.split('('):
 
1390
            if ')' in fragment:
 
1391
                paren_loc = fragment.index(')')
 
1392
                if paren_loc >= 0:
 
1393
                    pieces.append('%s' + fragment[paren_loc + 1:])
 
1394
            else:
 
1395
                pieces.append(fragment)
 
1396
 
 
1397
        return (''.join(pieces), self.regex.groups)
 
1398
 
 
1399
    def reverse(self, *args):
 
1400
        assert self._path is not None, \
 
1401
            "Cannot reverse url regex " + self.regex.pattern
 
1402
        assert len(args) == self._group_count, "required number of arguments "\
 
1403
            "not found"
 
1404
        if not len(args):
 
1405
            return self._path
 
1406
        return self._path % tuple([str(a) for a in args])
 
1407
 
 
1408
url = URLSpec
 
1409
 
 
1410
def _utf8(s):
 
1411
    if isinstance(s, unicode):
 
1412
        return s.encode("utf-8")
 
1413
    assert isinstance(s, str)
 
1414
    return s
 
1415
 
 
1416
 
 
1417
def _unicode(s):
 
1418
    if isinstance(s, str):
 
1419
        try:
 
1420
            return s.decode("utf-8")
 
1421
        except UnicodeDecodeError:
 
1422
            raise HTTPError(400, "Non-utf8 argument")
 
1423
    assert isinstance(s, unicode)
 
1424
    return s
 
1425
 
 
1426
 
 
1427
def _time_independent_equals(a, b):
 
1428
    if len(a) != len(b):
 
1429
        return False
 
1430
    result = 0
 
1431
    for x, y in zip(a, b):
 
1432
        result |= ord(x) ^ ord(y)
 
1433
    return result == 0
 
1434
 
 
1435
 
 
1436
class _O(dict):
 
1437
    """Makes a dictionary behave like an object."""
 
1438
    def __getattr__(self, name):
 
1439
        try:
 
1440
            return self[name]
 
1441
        except KeyError:
 
1442
            raise AttributeError(name)
 
1443
 
 
1444
    def __setattr__(self, name, value):
 
1445
        self[name] = value