00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """``tornado.web`` provides a simple web framework with asynchronous
00018 features that allow it to scale to large numbers of open connections,
00019 making it ideal for `long polling
00020 <http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_.
00021
00022 Here is a simple "Hello, world" example app::
00023
00024 import tornado.ioloop
00025 import tornado.web
00026
00027 class MainHandler(tornado.web.RequestHandler):
00028 def get(self):
00029 self.write("Hello, world")
00030
00031 if __name__ == "__main__":
00032 application = tornado.web.Application([
00033 (r"/", MainHandler),
00034 ])
00035 application.listen(8888)
00036 tornado.ioloop.IOLoop.instance().start()
00037
00038 See the :doc:`guide` for additional information.
00039
00040 Thread-safety notes
00041 -------------------
00042
00043 In general, methods on `RequestHandler` and elsewhere in Tornado are
00044 not thread-safe. In particular, methods such as
00045 `~RequestHandler.write()`, `~RequestHandler.finish()`, and
00046 `~RequestHandler.flush()` must only be called from the main thread. If
00047 you use multiple threads it is important to use `.IOLoop.add_callback`
00048 to transfer control back to the main thread before finishing the
00049 request.
00050
00051 """
00052
00053 from __future__ import absolute_import, division, print_function, with_statement
00054
00055
00056 import base64
00057 import binascii
00058 import datetime
00059 import email.utils
00060 import functools
00061 import gzip
00062 import hashlib
00063 import hmac
00064 import mimetypes
00065 import numbers
00066 import os.path
00067 import re
00068 import stat
00069 import sys
00070 import threading
00071 import time
00072 import tornado
00073 import traceback
00074 import types
00075
00076 from tornado.concurrent import Future, is_future
00077 from tornado import escape
00078 from tornado import gen
00079 from tornado import httputil
00080 from tornado import iostream
00081 from tornado import locale
00082 from tornado.log import access_log, app_log, gen_log
00083 from tornado import stack_context
00084 from tornado import template
00085 from tornado.escape import utf8, _unicode
00086 from tornado.util import bytes_type, import_object, ObjectDict, raise_exc_info, unicode_type, _websocket_mask
00087
00088 try:
00089 from io import BytesIO
00090 except ImportError:
00091 from cStringIO import StringIO as BytesIO
00092
00093 try:
00094 import Cookie
00095 except ImportError:
00096 import http.cookies as Cookie
00097
00098 try:
00099 import urlparse
00100 except ImportError:
00101 import urllib.parse as urlparse
00102
00103 try:
00104 from urllib import urlencode
00105 except ImportError:
00106 from urllib.parse import urlencode
00107
00108
00109 MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
00110 """The oldest signed value version supported by this version of Tornado.
00111
00112 Signed values older than this version cannot be decoded.
00113
00114 .. versionadded:: 3.2.1
00115 """
00116
00117 MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2
00118 """The newest signed value version supported by this version of Tornado.
00119
00120 Signed values newer than this version cannot be decoded.
00121
00122 .. versionadded:: 3.2.1
00123 """
00124
00125 DEFAULT_SIGNED_VALUE_VERSION = 2
00126 """The signed value version produced by `.RequestHandler.create_signed_value`.
00127
00128 May be overridden by passing a ``version`` keyword argument.
00129
00130 .. versionadded:: 3.2.1
00131 """
00132
00133 DEFAULT_SIGNED_VALUE_MIN_VERSION = 1
00134 """The oldest signed value accepted by `.RequestHandler.get_secure_cookie`.
00135
00136 May be overrided by passing a ``min_version`` keyword argument.
00137
00138 .. versionadded:: 3.2.1
00139 """
00140
00141
00142 class RequestHandler(object):
00143 """Subclass this class and define `get()` or `post()` to make a handler.
00144
00145 If you want to support more methods than the standard GET/HEAD/POST, you
00146 should override the class variable ``SUPPORTED_METHODS`` in your
00147 `RequestHandler` subclass.
00148 """
00149 SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
00150 "OPTIONS")
00151
00152 _template_loaders = {}
00153 _template_loader_lock = threading.Lock()
00154 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
00155
00156 def __init__(self, application, request, **kwargs):
00157 super(RequestHandler, self).__init__()
00158
00159 self.application = application
00160 self.request = request
00161 self._headers_written = False
00162 self._finished = False
00163 self._auto_finish = True
00164 self._transforms = None
00165 self._prepared_future = None
00166 self.path_args = None
00167 self.path_kwargs = None
00168 self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
00169 application.ui_methods.items())
00170
00171
00172
00173
00174
00175 self.ui["_tt_modules"] = _UIModuleNamespace(self,
00176 application.ui_modules)
00177 self.ui["modules"] = self.ui["_tt_modules"]
00178 self.clear()
00179 self.request.connection.set_close_callback(self.on_connection_close)
00180 self.initialize(**kwargs)
00181
00182 def initialize(self):
00183 """Hook for subclass initialization.
00184
00185 A dictionary passed as the third argument of a url spec will be
00186 supplied as keyword arguments to initialize().
00187
00188 Example::
00189
00190 class ProfileHandler(RequestHandler):
00191 def initialize(self, database):
00192 self.database = database
00193
00194 def get(self, username):
00195 ...
00196
00197 app = Application([
00198 (r'/user/(.*)', ProfileHandler, dict(database=database)),
00199 ])
00200 """
00201 pass
00202
00203 @property
00204 def settings(self):
00205 """An alias for `self.application.settings <Application.settings>`."""
00206 return self.application.settings
00207
00208 def head(self, *args, **kwargs):
00209 raise HTTPError(405)
00210
00211 def get(self, *args, **kwargs):
00212 raise HTTPError(405)
00213
00214 def post(self, *args, **kwargs):
00215 raise HTTPError(405)
00216
00217 def delete(self, *args, **kwargs):
00218 raise HTTPError(405)
00219
00220 def patch(self, *args, **kwargs):
00221 raise HTTPError(405)
00222
00223 def put(self, *args, **kwargs):
00224 raise HTTPError(405)
00225
00226 def options(self, *args, **kwargs):
00227 raise HTTPError(405)
00228
00229 def prepare(self):
00230 """Called at the beginning of a request before `get`/`post`/etc.
00231
00232 Override this method to perform common initialization regardless
00233 of the request method.
00234
00235 Asynchronous support: Decorate this method with `.gen.coroutine`
00236 or `.return_future` to make it asynchronous (the
00237 `asynchronous` decorator cannot be used on `prepare`).
00238 If this method returns a `.Future` execution will not proceed
00239 until the `.Future` is done.
00240
00241 .. versionadded:: 3.1
00242 Asynchronous support.
00243 """
00244 pass
00245
00246 def on_finish(self):
00247 """Called after the end of a request.
00248
00249 Override this method to perform cleanup, logging, etc.
00250 This method is a counterpart to `prepare`. ``on_finish`` may
00251 not produce any output, as it is called after the response
00252 has been sent to the client.
00253 """
00254 pass
00255
00256 def on_connection_close(self):
00257 """Called in async handlers if the client closed the connection.
00258
00259 Override this to clean up resources associated with
00260 long-lived connections. Note that this method is called only if
00261 the connection was closed during asynchronous processing; if you
00262 need to do cleanup after every request override `on_finish`
00263 instead.
00264
00265 Proxies may keep a connection open for a time (perhaps
00266 indefinitely) after the client has gone away, so this method
00267 may not be called promptly after the end user closes their
00268 connection.
00269 """
00270 if _has_stream_request_body(self.__class__):
00271 if not self.request.body.done():
00272 self.request.body.set_exception(iostream.StreamClosedError())
00273
00274 def clear(self):
00275 """Resets all headers and content for this response."""
00276 self._headers = httputil.HTTPHeaders({
00277 "Server": "TornadoServer/%s" % tornado.version,
00278 "Content-Type": "text/html; charset=UTF-8",
00279 "Date": httputil.format_timestamp(time.time()),
00280 })
00281 self.set_default_headers()
00282 self._write_buffer = []
00283 self._status_code = 200
00284 self._reason = httputil.responses[200]
00285
00286 def set_default_headers(self):
00287 """Override this to set HTTP headers at the beginning of the request.
00288
00289 For example, this is the place to set a custom ``Server`` header.
00290 Note that setting such headers in the normal flow of request
00291 processing may not do what you want, since headers may be reset
00292 during error handling.
00293 """
00294 pass
00295
00296 def set_status(self, status_code, reason=None):
00297 """Sets the status code for our response.
00298
00299 :arg int status_code: Response status code. If ``reason`` is ``None``,
00300 it must be present in `httplib.responses <http.client.responses>`.
00301 :arg string reason: Human-readable reason phrase describing the status
00302 code. If ``None``, it will be filled in from
00303 `httplib.responses <http.client.responses>`.
00304 """
00305 self._status_code = status_code
00306 if reason is not None:
00307 self._reason = escape.native_str(reason)
00308 else:
00309 try:
00310 self._reason = httputil.responses[status_code]
00311 except KeyError:
00312 raise ValueError("unknown status code %d", status_code)
00313
00314 def get_status(self):
00315 """Returns the status code for our response."""
00316 return self._status_code
00317
00318 def set_header(self, name, value):
00319 """Sets the given response header name and value.
00320
00321 If a datetime is given, we automatically format it according to the
00322 HTTP specification. If the value is not a string, we convert it to
00323 a string. All header values are then encoded as UTF-8.
00324 """
00325 self._headers[name] = self._convert_header_value(value)
00326
00327 def add_header(self, name, value):
00328 """Adds the given response header and value.
00329
00330 Unlike `set_header`, `add_header` may be called multiple times
00331 to return multiple values for the same header.
00332 """
00333 self._headers.add(name, self._convert_header_value(value))
00334
00335 def clear_header(self, name):
00336 """Clears an outgoing header, undoing a previous `set_header` call.
00337
00338 Note that this method does not apply to multi-valued headers
00339 set by `add_header`.
00340 """
00341 if name in self._headers:
00342 del self._headers[name]
00343
00344 _INVALID_HEADER_CHAR_RE = re.compile(br"[\x00-\x1f]")
00345
00346 def _convert_header_value(self, value):
00347 if isinstance(value, bytes_type):
00348 pass
00349 elif isinstance(value, unicode_type):
00350 value = value.encode('utf-8')
00351 elif isinstance(value, numbers.Integral):
00352
00353 return str(value)
00354 elif isinstance(value, datetime.datetime):
00355 return httputil.format_timestamp(value)
00356 else:
00357 raise TypeError("Unsupported header value %r" % value)
00358
00359
00360
00361 if (len(value) > 4000 or
00362 RequestHandler._INVALID_HEADER_CHAR_RE.search(value)):
00363 raise ValueError("Unsafe header value %r", value)
00364 return value
00365
00366 _ARG_DEFAULT = []
00367
00368 def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
00369 """Returns the value of the argument with the given name.
00370
00371 If default is not provided, the argument is considered to be
00372 required, and we raise a `MissingArgumentError` if it is missing.
00373
00374 If the argument appears in the url more than once, we return the
00375 last value.
00376
00377 The returned value is always unicode.
00378 """
00379 return self._get_argument(name, default, self.request.arguments, strip)
00380
00381 def get_arguments(self, name, strip=True):
00382 """Returns a list of the arguments with the given name.
00383
00384 If the argument is not present, returns an empty list.
00385
00386 The returned values are always unicode.
00387 """
00388 return self._get_arguments(name, self.request.arguments, strip)
00389
00390 def get_body_argument(self, name, default=_ARG_DEFAULT, strip=True):
00391 """Returns the value of the argument with the given name
00392 from the request body.
00393
00394 If default is not provided, the argument is considered to be
00395 required, and we raise a `MissingArgumentError` if it is missing.
00396
00397 If the argument appears in the url more than once, we return the
00398 last value.
00399
00400 The returned value is always unicode.
00401
00402 .. versionadded:: 3.2
00403 """
00404 return self._get_argument(name, default, self.request.body_arguments, strip)
00405
00406 def get_body_arguments(self, name, strip=True):
00407 """Returns a list of the body arguments with the given name.
00408
00409 If the argument is not present, returns an empty list.
00410
00411 The returned values are always unicode.
00412
00413 .. versionadded:: 3.2
00414 """
00415 return self._get_arguments(name, self.request.body_arguments, strip)
00416
00417 def get_query_argument(self, name, default=_ARG_DEFAULT, strip=True):
00418 """Returns the value of the argument with the given name
00419 from the request query string.
00420
00421 If default is not provided, the argument is considered to be
00422 required, and we raise a `MissingArgumentError` if it is missing.
00423
00424 If the argument appears in the url more than once, we return the
00425 last value.
00426
00427 The returned value is always unicode.
00428
00429 .. versionadded:: 3.2
00430 """
00431 return self._get_argument(name, default, self.request.query_arguments, strip)
00432
00433 def get_query_arguments(self, name, strip=True):
00434 """Returns a list of the query arguments with the given name.
00435
00436 If the argument is not present, returns an empty list.
00437
00438 The returned values are always unicode.
00439
00440 .. versionadded:: 3.2
00441 """
00442 return self._get_arguments(name, self.request.query_arguments, strip)
00443
00444 def _get_argument(self, name, default, source, strip=True):
00445 args = self._get_arguments(name, source, strip=strip)
00446 if not args:
00447 if default is self._ARG_DEFAULT:
00448 raise MissingArgumentError(name)
00449 return default
00450 return args[-1]
00451
00452 def _get_arguments(self, name, source, strip=True):
00453 values = []
00454 for v in source.get(name, []):
00455 v = self.decode_argument(v, name=name)
00456 if isinstance(v, unicode_type):
00457
00458
00459 v = RequestHandler._remove_control_chars_regex.sub(" ", v)
00460 if strip:
00461 v = v.strip()
00462 values.append(v)
00463 return values
00464
00465 def decode_argument(self, value, name=None):
00466 """Decodes an argument from the request.
00467
00468 The argument has been percent-decoded and is now a byte string.
00469 By default, this method decodes the argument as utf-8 and returns
00470 a unicode string, but this may be overridden in subclasses.
00471
00472 This method is used as a filter for both `get_argument()` and for
00473 values extracted from the url and passed to `get()`/`post()`/etc.
00474
00475 The name of the argument is provided if known, but may be None
00476 (e.g. for unnamed groups in the url regex).
00477 """
00478 try:
00479 return _unicode(value)
00480 except UnicodeDecodeError:
00481 raise HTTPError(400, "Invalid unicode in %s: %r" %
00482 (name or "url", value[:40]))
00483
00484 @property
00485 def cookies(self):
00486 """An alias for `self.request.cookies <.httputil.HTTPServerRequest.cookies>`."""
00487 return self.request.cookies
00488
00489 def get_cookie(self, name, default=None):
00490 """Gets the value of the cookie with the given name, else default."""
00491 if self.request.cookies is not None and name in self.request.cookies:
00492 return self.request.cookies[name].value
00493 return default
00494
00495 def set_cookie(self, name, value, domain=None, expires=None, path="/",
00496 expires_days=None, **kwargs):
00497 """Sets the given cookie name/value with the given options.
00498
00499 Additional keyword arguments are set on the Cookie.Morsel
00500 directly.
00501 See http://docs.python.org/library/cookie.html#morsel-objects
00502 for available attributes.
00503 """
00504
00505 name = escape.native_str(name)
00506 value = escape.native_str(value)
00507 if re.search(r"[\x00-\x20]", name + value):
00508
00509 raise ValueError("Invalid cookie %r: %r" % (name, value))
00510 if not hasattr(self, "_new_cookie"):
00511 self._new_cookie = Cookie.SimpleCookie()
00512 if name in self._new_cookie:
00513 del self._new_cookie[name]
00514 self._new_cookie[name] = value
00515 morsel = self._new_cookie[name]
00516 if domain:
00517 morsel["domain"] = domain
00518 if expires_days is not None and not expires:
00519 expires = datetime.datetime.utcnow() + datetime.timedelta(
00520 days=expires_days)
00521 if expires:
00522 morsel["expires"] = httputil.format_timestamp(expires)
00523 if path:
00524 morsel["path"] = path
00525 for k, v in kwargs.items():
00526 if k == 'max_age':
00527 k = 'max-age'
00528 morsel[k] = v
00529
00530 def clear_cookie(self, name, path="/", domain=None):
00531 """Deletes the cookie with the given name.
00532
00533 Due to limitations of the cookie protocol, you must pass the same
00534 path and domain to clear a cookie as were used when that cookie
00535 was set (but there is no way to find out on the server side
00536 which values were used for a given cookie).
00537 """
00538 expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
00539 self.set_cookie(name, value="", path=path, expires=expires,
00540 domain=domain)
00541
00542 def clear_all_cookies(self, path="/", domain=None):
00543 """Deletes all the cookies the user sent with this request.
00544
00545 See `clear_cookie` for more information on the path and domain
00546 parameters.
00547
00548 .. versionchanged:: 3.2
00549
00550 Added the ``path`` and ``domain`` parameters.
00551 """
00552 for name in self.request.cookies:
00553 self.clear_cookie(name, path=path, domain=domain)
00554
00555 def set_secure_cookie(self, name, value, expires_days=30, version=None,
00556 **kwargs):
00557 """Signs and timestamps a cookie so it cannot be forged.
00558
00559 You must specify the ``cookie_secret`` setting in your Application
00560 to use this method. It should be a long, random sequence of bytes
00561 to be used as the HMAC secret for the signature.
00562
00563 To read a cookie set with this method, use `get_secure_cookie()`.
00564
00565 Note that the ``expires_days`` parameter sets the lifetime of the
00566 cookie in the browser, but is independent of the ``max_age_days``
00567 parameter to `get_secure_cookie`.
00568
00569 Secure cookies may contain arbitrary byte values, not just unicode
00570 strings (unlike regular cookies)
00571
00572 .. versionchanged:: 3.2.1
00573
00574 Added the ``version`` argument. Introduced cookie version 2
00575 and made it the default.
00576 """
00577 self.set_cookie(name, self.create_signed_value(name, value,
00578 version=version),
00579 expires_days=expires_days, **kwargs)
00580
00581 def create_signed_value(self, name, value, version=None):
00582 """Signs and timestamps a string so it cannot be forged.
00583
00584 Normally used via set_secure_cookie, but provided as a separate
00585 method for non-cookie uses. To decode a value not stored
00586 as a cookie use the optional value argument to get_secure_cookie.
00587
00588 .. versionchanged:: 3.2.1
00589
00590 Added the ``version`` argument. Introduced cookie version 2
00591 and made it the default.
00592 """
00593 self.require_setting("cookie_secret", "secure cookies")
00594 return create_signed_value(self.application.settings["cookie_secret"],
00595 name, value, version=version)
00596
00597 def get_secure_cookie(self, name, value=None, max_age_days=31,
00598 min_version=None):
00599 """Returns the given signed cookie if it validates, or None.
00600
00601 The decoded cookie value is returned as a byte string (unlike
00602 `get_cookie`).
00603
00604 .. versionchanged:: 3.2.1
00605
00606 Added the ``min_version`` argument. Introduced cookie version 2;
00607 both versions 1 and 2 are accepted by default.
00608 """
00609 self.require_setting("cookie_secret", "secure cookies")
00610 if value is None:
00611 value = self.get_cookie(name)
00612 return decode_signed_value(self.application.settings["cookie_secret"],
00613 name, value, max_age_days=max_age_days,
00614 min_version=min_version)
00615
00616 def redirect(self, url, permanent=False, status=None):
00617 """Sends a redirect to the given (optionally relative) URL.
00618
00619 If the ``status`` argument is specified, that value is used as the
00620 HTTP status code; otherwise either 301 (permanent) or 302
00621 (temporary) is chosen based on the ``permanent`` argument.
00622 The default is 302 (temporary).
00623 """
00624 if self._headers_written:
00625 raise Exception("Cannot redirect after headers have been written")
00626 if status is None:
00627 status = 301 if permanent else 302
00628 else:
00629 assert isinstance(status, int) and 300 <= status <= 399
00630 self.set_status(status)
00631 self.set_header("Location", urlparse.urljoin(utf8(self.request.uri),
00632 utf8(url)))
00633 self.finish()
00634
00635 def write(self, chunk):
00636 """Writes the given chunk to the output buffer.
00637
00638 To write the output to the network, use the flush() method below.
00639
00640 If the given chunk is a dictionary, we write it as JSON and set
00641 the Content-Type of the response to be ``application/json``.
00642 (if you want to send JSON as a different ``Content-Type``, call
00643 set_header *after* calling write()).
00644
00645 Note that lists are not converted to JSON because of a potential
00646 cross-site security vulnerability. All JSON output should be
00647 wrapped in a dictionary. More details at
00648 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
00649 https://github.com/facebook/tornado/issues/1009
00650 """
00651 if self._finished:
00652 raise RuntimeError("Cannot write() after finish(). May be caused "
00653 "by using async operations without the "
00654 "@asynchronous decorator.")
00655 if not isinstance(chunk, (bytes_type, unicode_type, dict)):
00656 raise TypeError("write() only accepts bytes, unicode, and dict objects")
00657 if isinstance(chunk, dict):
00658 chunk = escape.json_encode(chunk)
00659 self.set_header("Content-Type", "application/json; charset=UTF-8")
00660 chunk = utf8(chunk)
00661 self._write_buffer.append(chunk)
00662
00663 def render(self, template_name, **kwargs):
00664 """Renders the template with the given arguments as the response."""
00665 html = self.render_string(template_name, **kwargs)
00666
00667
00668 js_embed = []
00669 js_files = []
00670 css_embed = []
00671 css_files = []
00672 html_heads = []
00673 html_bodies = []
00674 for module in getattr(self, "_active_modules", {}).values():
00675 embed_part = module.embedded_javascript()
00676 if embed_part:
00677 js_embed.append(utf8(embed_part))
00678 file_part = module.javascript_files()
00679 if file_part:
00680 if isinstance(file_part, (unicode_type, bytes_type)):
00681 js_files.append(file_part)
00682 else:
00683 js_files.extend(file_part)
00684 embed_part = module.embedded_css()
00685 if embed_part:
00686 css_embed.append(utf8(embed_part))
00687 file_part = module.css_files()
00688 if file_part:
00689 if isinstance(file_part, (unicode_type, bytes_type)):
00690 css_files.append(file_part)
00691 else:
00692 css_files.extend(file_part)
00693 head_part = module.html_head()
00694 if head_part:
00695 html_heads.append(utf8(head_part))
00696 body_part = module.html_body()
00697 if body_part:
00698 html_bodies.append(utf8(body_part))
00699
00700 def is_absolute(path):
00701 return any(path.startswith(x) for x in ["/", "http:", "https:"])
00702 if js_files:
00703
00704 paths = []
00705 unique_paths = set()
00706 for path in js_files:
00707 if not is_absolute(path):
00708 path = self.static_url(path)
00709 if path not in unique_paths:
00710 paths.append(path)
00711 unique_paths.add(path)
00712 js = ''.join('<script src="' + escape.xhtml_escape(p) +
00713 '" type="text/javascript"></script>'
00714 for p in paths)
00715 sloc = html.rindex(b'</body>')
00716 html = html[:sloc] + utf8(js) + b'\n' + html[sloc:]
00717 if js_embed:
00718 js = b'<script type="text/javascript">\n//<![CDATA[\n' + \
00719 b'\n'.join(js_embed) + b'\n//]]>\n</script>'
00720 sloc = html.rindex(b'</body>')
00721 html = html[:sloc] + js + b'\n' + html[sloc:]
00722 if css_files:
00723 paths = []
00724 unique_paths = set()
00725 for path in css_files:
00726 if not is_absolute(path):
00727 path = self.static_url(path)
00728 if path not in unique_paths:
00729 paths.append(path)
00730 unique_paths.add(path)
00731 css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
00732 'type="text/css" rel="stylesheet"/>'
00733 for p in paths)
00734 hloc = html.index(b'</head>')
00735 html = html[:hloc] + utf8(css) + b'\n' + html[hloc:]
00736 if css_embed:
00737 css = b'<style type="text/css">\n' + b'\n'.join(css_embed) + \
00738 b'\n</style>'
00739 hloc = html.index(b'</head>')
00740 html = html[:hloc] + css + b'\n' + html[hloc:]
00741 if html_heads:
00742 hloc = html.index(b'</head>')
00743 html = html[:hloc] + b''.join(html_heads) + b'\n' + html[hloc:]
00744 if html_bodies:
00745 hloc = html.index(b'</body>')
00746 html = html[:hloc] + b''.join(html_bodies) + b'\n' + html[hloc:]
00747 self.finish(html)
00748
00749 def render_string(self, template_name, **kwargs):
00750 """Generate the given template with the given arguments.
00751
00752 We return the generated byte string (in utf8). To generate and
00753 write a template as a response, use render() above.
00754 """
00755
00756 template_path = self.get_template_path()
00757 if not template_path:
00758 frame = sys._getframe(0)
00759 web_file = frame.f_code.co_filename
00760 while frame.f_code.co_filename == web_file:
00761 frame = frame.f_back
00762 template_path = os.path.dirname(frame.f_code.co_filename)
00763 with RequestHandler._template_loader_lock:
00764 if template_path not in RequestHandler._template_loaders:
00765 loader = self.create_template_loader(template_path)
00766 RequestHandler._template_loaders[template_path] = loader
00767 else:
00768 loader = RequestHandler._template_loaders[template_path]
00769 t = loader.load(template_name)
00770 namespace = self.get_template_namespace()
00771 namespace.update(kwargs)
00772 return t.generate(**namespace)
00773
00774 def get_template_namespace(self):
00775 """Returns a dictionary to be used as the default template namespace.
00776
00777 May be overridden by subclasses to add or modify values.
00778
00779 The results of this method will be combined with additional
00780 defaults in the `tornado.template` module and keyword arguments
00781 to `render` or `render_string`.
00782 """
00783 namespace = dict(
00784 handler=self,
00785 request=self.request,
00786 current_user=self.current_user,
00787 locale=self.locale,
00788 _=self.locale.translate,
00789 static_url=self.static_url,
00790 xsrf_form_html=self.xsrf_form_html,
00791 reverse_url=self.reverse_url
00792 )
00793 namespace.update(self.ui)
00794 return namespace
00795
00796 def create_template_loader(self, template_path):
00797 """Returns a new template loader for the given path.
00798
00799 May be overridden by subclasses. By default returns a
00800 directory-based loader on the given path, using the
00801 ``autoescape`` application setting. If a ``template_loader``
00802 application setting is supplied, uses that instead.
00803 """
00804 settings = self.application.settings
00805 if "template_loader" in settings:
00806 return settings["template_loader"]
00807 kwargs = {}
00808 if "autoescape" in settings:
00809
00810
00811 kwargs["autoescape"] = settings["autoescape"]
00812 return template.Loader(template_path, **kwargs)
00813
00814 def flush(self, include_footers=False, callback=None):
00815 """Flushes the current output buffer to the network.
00816
00817 The ``callback`` argument, if given, can be used for flow control:
00818 it will be run when all flushed data has been written to the socket.
00819 Note that only one flush callback can be outstanding at a time;
00820 if another flush occurs before the previous flush's callback
00821 has been run, the previous callback will be discarded.
00822
00823 .. versionchanged:: 4.0
00824 Now returns a `.Future` if no callback is given.
00825 """
00826 chunk = b"".join(self._write_buffer)
00827 self._write_buffer = []
00828 if not self._headers_written:
00829 self._headers_written = True
00830 for transform in self._transforms:
00831 self._status_code, self._headers, chunk = \
00832 transform.transform_first_chunk(
00833 self._status_code, self._headers, chunk, include_footers)
00834
00835 if self.request.method == "HEAD":
00836 chunk = None
00837
00838
00839
00840
00841 if hasattr(self, "_new_cookie"):
00842 for cookie in self._new_cookie.values():
00843 self.add_header("Set-Cookie", cookie.OutputString(None))
00844
00845 start_line = httputil.ResponseStartLine(self.request.version,
00846 self._status_code,
00847 self._reason)
00848 return self.request.connection.write_headers(
00849 start_line, self._headers, chunk, callback=callback)
00850 else:
00851 for transform in self._transforms:
00852 chunk = transform.transform_chunk(chunk, include_footers)
00853
00854 if self.request.method != "HEAD":
00855 return self.request.connection.write(chunk, callback=callback)
00856 else:
00857 future = Future()
00858 future.set_result(None)
00859 return future
00860
00861 def finish(self, chunk=None):
00862 """Finishes this response, ending the HTTP request."""
00863 if self._finished:
00864 raise RuntimeError("finish() called twice. May be caused "
00865 "by using async operations without the "
00866 "@asynchronous decorator.")
00867
00868 if chunk is not None:
00869 self.write(chunk)
00870
00871
00872
00873 if not self._headers_written:
00874 if (self._status_code == 200 and
00875 self.request.method in ("GET", "HEAD") and
00876 "Etag" not in self._headers):
00877 self.set_etag_header()
00878 if self.check_etag_header():
00879 self._write_buffer = []
00880 self.set_status(304)
00881 if self._status_code == 304:
00882 assert not self._write_buffer, "Cannot send body with 304"
00883 self._clear_headers_for_304()
00884 elif "Content-Length" not in self._headers:
00885 content_length = sum(len(part) for part in self._write_buffer)
00886 self.set_header("Content-Length", content_length)
00887
00888 if hasattr(self.request, "connection"):
00889
00890
00891
00892
00893 self.request.connection.set_close_callback(None)
00894
00895 self.flush(include_footers=True)
00896 self.request.finish()
00897 self._log()
00898 self._finished = True
00899 self.on_finish()
00900
00901
00902 self.ui = None
00903
00904 def send_error(self, status_code=500, **kwargs):
00905 """Sends the given HTTP error code to the browser.
00906
00907 If `flush()` has already been called, it is not possible to send
00908 an error, so this method will simply terminate the response.
00909 If output has been written but not yet flushed, it will be discarded
00910 and replaced with the error page.
00911
00912 Override `write_error()` to customize the error page that is returned.
00913 Additional keyword arguments are passed through to `write_error`.
00914 """
00915 if self._headers_written:
00916 gen_log.error("Cannot send error response after headers written")
00917 if not self._finished:
00918 self.finish()
00919 return
00920 self.clear()
00921
00922 reason = None
00923 if 'exc_info' in kwargs:
00924 exception = kwargs['exc_info'][1]
00925 if isinstance(exception, HTTPError) and exception.reason:
00926 reason = exception.reason
00927 self.set_status(status_code, reason=reason)
00928 try:
00929 self.write_error(status_code, **kwargs)
00930 except Exception:
00931 app_log.error("Uncaught exception in write_error", exc_info=True)
00932 if not self._finished:
00933 self.finish()
00934
00935 def write_error(self, status_code, **kwargs):
00936 """Override to implement custom error pages.
00937
00938 ``write_error`` may call `write`, `render`, `set_header`, etc
00939 to produce output as usual.
00940
00941 If this error was caused by an uncaught exception (including
00942 HTTPError), an ``exc_info`` triple will be available as
00943 ``kwargs["exc_info"]``. Note that this exception may not be
00944 the "current" exception for purposes of methods like
00945 ``sys.exc_info()`` or ``traceback.format_exc``.
00946 """
00947 if self.settings.get("serve_traceback") and "exc_info" in kwargs:
00948
00949 self.set_header('Content-Type', 'text/plain')
00950 for line in traceback.format_exception(*kwargs["exc_info"]):
00951 self.write(line)
00952 self.finish()
00953 else:
00954 self.finish("<html><title>%(code)d: %(message)s</title>"
00955 "<body>%(code)d: %(message)s</body></html>" % {
00956 "code": status_code,
00957 "message": self._reason,
00958 })
00959
00960 @property
00961 def locale(self):
00962 """The local for the current session.
00963
00964 Determined by either `get_user_locale`, which you can override to
00965 set the locale based on, e.g., a user preference stored in a
00966 database, or `get_browser_locale`, which uses the ``Accept-Language``
00967 header.
00968 """
00969 if not hasattr(self, "_locale"):
00970 self._locale = self.get_user_locale()
00971 if not self._locale:
00972 self._locale = self.get_browser_locale()
00973 assert self._locale
00974 return self._locale
00975
00976 def get_user_locale(self):
00977 """Override to determine the locale from the authenticated user.
00978
00979 If None is returned, we fall back to `get_browser_locale()`.
00980
00981 This method should return a `tornado.locale.Locale` object,
00982 most likely obtained via a call like ``tornado.locale.get("en")``
00983 """
00984 return None
00985
00986 def get_browser_locale(self, default="en_US"):
00987 """Determines the user's locale from ``Accept-Language`` header.
00988
00989 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
00990 """
00991 if "Accept-Language" in self.request.headers:
00992 languages = self.request.headers["Accept-Language"].split(",")
00993 locales = []
00994 for language in languages:
00995 parts = language.strip().split(";")
00996 if len(parts) > 1 and parts[1].startswith("q="):
00997 try:
00998 score = float(parts[1][2:])
00999 except (ValueError, TypeError):
01000 score = 0.0
01001 else:
01002 score = 1.0
01003 locales.append((parts[0], score))
01004 if locales:
01005 locales.sort(key=lambda pair: pair[1], reverse=True)
01006 codes = [l[0] for l in locales]
01007 return locale.get(*codes)
01008 return locale.get(default)
01009
01010 @property
01011 def current_user(self):
01012 """The authenticated user for this request.
01013
01014 This is a cached version of `get_current_user`, which you can
01015 override to set the user based on, e.g., a cookie. If that
01016 method is not overridden, this method always returns None.
01017
01018 We lazy-load the current user the first time this method is called
01019 and cache the result after that.
01020 """
01021 if not hasattr(self, "_current_user"):
01022 self._current_user = self.get_current_user()
01023 return self._current_user
01024
01025 @current_user.setter
01026 def current_user(self, value):
01027 self._current_user = value
01028
01029 def get_current_user(self):
01030 """Override to determine the current user from, e.g., a cookie."""
01031 return None
01032
01033 def get_login_url(self):
01034 """Override to customize the login URL based on the request.
01035
01036 By default, we use the ``login_url`` application setting.
01037 """
01038 self.require_setting("login_url", "@tornado.web.authenticated")
01039 return self.application.settings["login_url"]
01040
01041 def get_template_path(self):
01042 """Override to customize template path for each handler.
01043
01044 By default, we use the ``template_path`` application setting.
01045 Return None to load templates relative to the calling file.
01046 """
01047 return self.application.settings.get("template_path")
01048
01049 @property
01050 def xsrf_token(self):
01051 """The XSRF-prevention token for the current user/session.
01052
01053 To prevent cross-site request forgery, we set an '_xsrf' cookie
01054 and include the same '_xsrf' value as an argument with all POST
01055 requests. If the two do not match, we reject the form submission
01056 as a potential forgery.
01057
01058 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
01059
01060 .. versionchanged:: 3.2.2
01061 The xsrf token will now be have a random mask applied in every
01062 request, which makes it safe to include the token in pages
01063 that are compressed. See http://breachattack.com for more
01064 information on the issue fixed by this change. Old (version 1)
01065 cookies will be converted to version 2 when this method is called
01066 unless the ``xsrf_cookie_version`` `Application` setting is
01067 set to 1.
01068 """
01069 if not hasattr(self, "_xsrf_token"):
01070 version, token, timestamp = self._get_raw_xsrf_token()
01071 output_version = self.settings.get("xsrf_cookie_version", 2)
01072 if output_version == 1:
01073 self._xsrf_token = binascii.b2a_hex(token)
01074 elif output_version == 2:
01075 mask = os.urandom(4)
01076 self._xsrf_token = b"|".join([
01077 b"2",
01078 binascii.b2a_hex(mask),
01079 binascii.b2a_hex(_websocket_mask(mask, token)),
01080 utf8(str(int(timestamp)))])
01081 else:
01082 raise ValueError("unknown xsrf cookie version %d",
01083 output_version)
01084 if version is None:
01085 expires_days = 30 if self.current_user else None
01086 self.set_cookie("_xsrf", self._xsrf_token,
01087 expires_days=expires_days)
01088 return self._xsrf_token
01089
01090 def _get_raw_xsrf_token(self):
01091 """Read or generate the xsrf token in its raw form.
01092
01093 The raw_xsrf_token is a tuple containing:
01094
01095 * version: the version of the cookie from which this token was read,
01096 or None if we generated a new token in this request.
01097 * token: the raw token data; random (non-ascii) bytes.
01098 * timestamp: the time this token was generated (will not be accurate
01099 for version 1 cookies)
01100 """
01101 if not hasattr(self, '_raw_xsrf_token'):
01102 cookie = self.get_cookie("_xsrf")
01103 if cookie:
01104 version, token, timestamp = self._decode_xsrf_token(cookie)
01105 else:
01106 version, token, timestamp = None, None, None
01107 if token is None:
01108 version = None
01109 token = os.urandom(16)
01110 timestamp = time.time()
01111 self._raw_xsrf_token = (version, token, timestamp)
01112 return self._raw_xsrf_token
01113
01114 def _decode_xsrf_token(self, cookie):
01115 """Convert a cookie string into a the tuple form returned by
01116 _get_raw_xsrf_token.
01117 """
01118 m = _signed_value_version_re.match(utf8(cookie))
01119 if m:
01120 version = int(m.group(1))
01121 if version == 2:
01122 _, mask, masked_token, timestamp = cookie.split("|")
01123 mask = binascii.a2b_hex(utf8(mask))
01124 token = _websocket_mask(
01125 mask, binascii.a2b_hex(utf8(masked_token)))
01126 timestamp = int(timestamp)
01127 return version, token, timestamp
01128 else:
01129
01130 return None, None, None
01131 else:
01132 version = 1
01133 try:
01134 token = binascii.a2b_hex(utf8(cookie))
01135 except (binascii.Error, TypeError):
01136 token = utf8(cookie)
01137
01138 timestamp = int(time.time())
01139 return (version, token, timestamp)
01140
01141 def check_xsrf_cookie(self):
01142 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
01143
01144 To prevent cross-site request forgery, we set an ``_xsrf``
01145 cookie and include the same value as a non-cookie
01146 field with all ``POST`` requests. If the two do not match, we
01147 reject the form submission as a potential forgery.
01148
01149 The ``_xsrf`` value may be set as either a form field named ``_xsrf``
01150 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
01151 (the latter is accepted for compatibility with Django).
01152
01153 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
01154
01155 Prior to release 1.1.1, this check was ignored if the HTTP header
01156 ``X-Requested-With: XMLHTTPRequest`` was present. This exception
01157 has been shown to be insecure and has been removed. For more
01158 information please see
01159 http://www.djangoproject.com/weblog/2011/feb/08/security/
01160 http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
01161
01162 .. versionchanged:: 3.2.2
01163 Added support for cookie version 2. Both versions 1 and 2 are
01164 supported.
01165 """
01166 token = (self.get_argument("_xsrf", None) or
01167 self.request.headers.get("X-Xsrftoken") or
01168 self.request.headers.get("X-Csrftoken"))
01169 if not token:
01170 raise HTTPError(403, "'_xsrf' argument missing from POST")
01171 _, token, _ = self._decode_xsrf_token(token)
01172 _, expected_token, _ = self._get_raw_xsrf_token()
01173 if not _time_independent_equals(utf8(token), utf8(expected_token)):
01174 raise HTTPError(403, "XSRF cookie does not match POST argument")
01175
01176 def xsrf_form_html(self):
01177 """An HTML ``<input/>`` element to be included with all POST forms.
01178
01179 It defines the ``_xsrf`` input value, which we check on all POST
01180 requests to prevent cross-site request forgery. If you have set
01181 the ``xsrf_cookies`` application setting, you must include this
01182 HTML within all of your HTML forms.
01183
01184 In a template, this method should be called with ``{% module
01185 xsrf_form_html() %}``
01186
01187 See `check_xsrf_cookie()` above for more information.
01188 """
01189 return '<input type="hidden" name="_xsrf" value="' + \
01190 escape.xhtml_escape(self.xsrf_token) + '"/>'
01191
01192 def static_url(self, path, include_host=None, **kwargs):
01193 """Returns a static URL for the given relative static file path.
01194
01195 This method requires you set the ``static_path`` setting in your
01196 application (which specifies the root directory of your static
01197 files).
01198
01199 This method returns a versioned url (by default appending
01200 ``?v=<signature>``), which allows the static files to be
01201 cached indefinitely. This can be disabled by passing
01202 ``include_version=False`` (in the default implementation;
01203 other static file implementations are not required to support
01204 this, but they may support other options).
01205
01206 By default this method returns URLs relative to the current
01207 host, but if ``include_host`` is true the URL returned will be
01208 absolute. If this handler has an ``include_host`` attribute,
01209 that value will be used as the default for all `static_url`
01210 calls that do not pass ``include_host`` as a keyword argument.
01211
01212 """
01213 self.require_setting("static_path", "static_url")
01214 get_url = self.settings.get("static_handler_class",
01215 StaticFileHandler).make_static_url
01216
01217 if include_host is None:
01218 include_host = getattr(self, "include_host", False)
01219
01220 if include_host:
01221 base = self.request.protocol + "://" + self.request.host
01222 else:
01223 base = ""
01224
01225 return base + get_url(self.settings, path, **kwargs)
01226
01227 def require_setting(self, name, feature="this feature"):
01228 """Raises an exception if the given app setting is not defined."""
01229 if not self.application.settings.get(name):
01230 raise Exception("You must define the '%s' setting in your "
01231 "application to use %s" % (name, feature))
01232
01233 def reverse_url(self, name, *args):
01234 """Alias for `Application.reverse_url`."""
01235 return self.application.reverse_url(name, *args)
01236
01237 def compute_etag(self):
01238 """Computes the etag header to be used for this request.
01239
01240 By default uses a hash of the content written so far.
01241
01242 May be overridden to provide custom etag implementations,
01243 or may return None to disable tornado's default etag support.
01244 """
01245 hasher = hashlib.sha1()
01246 for part in self._write_buffer:
01247 hasher.update(part)
01248 return '"%s"' % hasher.hexdigest()
01249
01250 def set_etag_header(self):
01251 """Sets the response's Etag header using ``self.compute_etag()``.
01252
01253 Note: no header will be set if ``compute_etag()`` returns ``None``.
01254
01255 This method is called automatically when the request is finished.
01256 """
01257 etag = self.compute_etag()
01258 if etag is not None:
01259 self.set_header("Etag", etag)
01260
01261 def check_etag_header(self):
01262 """Checks the ``Etag`` header against requests's ``If-None-Match``.
01263
01264 Returns ``True`` if the request's Etag matches and a 304 should be
01265 returned. For example::
01266
01267 self.set_etag_header()
01268 if self.check_etag_header():
01269 self.set_status(304)
01270 return
01271
01272 This method is called automatically when the request is finished,
01273 but may be called earlier for applications that override
01274 `compute_etag` and want to do an early check for ``If-None-Match``
01275 before completing the request. The ``Etag`` header should be set
01276 (perhaps with `set_etag_header`) before calling this method.
01277 """
01278 etag = self._headers.get("Etag")
01279 inm = utf8(self.request.headers.get("If-None-Match", ""))
01280 return bool(etag and inm and inm.find(etag) >= 0)
01281
01282 def _stack_context_handle_exception(self, type, value, traceback):
01283 try:
01284
01285
01286
01287
01288 raise_exc_info((type, value, traceback))
01289 except Exception:
01290 self._handle_request_exception(value)
01291 return True
01292
01293 @gen.coroutine
01294 def _execute(self, transforms, *args, **kwargs):
01295 """Executes this request with the given output transforms."""
01296 self._transforms = transforms
01297 try:
01298 if self.request.method not in self.SUPPORTED_METHODS:
01299 raise HTTPError(405)
01300 self.path_args = [self.decode_argument(arg) for arg in args]
01301 self.path_kwargs = dict((k, self.decode_argument(v, name=k))
01302 for (k, v) in kwargs.items())
01303
01304
01305 if self.request.method not in ("GET", "HEAD", "OPTIONS") and \
01306 self.application.settings.get("xsrf_cookies"):
01307 self.check_xsrf_cookie()
01308
01309 result = self.prepare()
01310 if is_future(result):
01311 result = yield result
01312 if result is not None:
01313 raise TypeError("Expected None, got %r" % result)
01314 if self._prepared_future is not None:
01315
01316
01317 self._prepared_future.set_result(None)
01318 if self._finished:
01319 return
01320
01321 if _has_stream_request_body(self.__class__):
01322
01323
01324
01325
01326 try:
01327 yield self.request.body
01328 except iostream.StreamClosedError:
01329 return
01330
01331 method = getattr(self, self.request.method.lower())
01332 result = method(*self.path_args, **self.path_kwargs)
01333 if is_future(result):
01334 result = yield result
01335 if result is not None:
01336 raise TypeError("Expected None, got %r" % result)
01337 if self._auto_finish and not self._finished:
01338 self.finish()
01339 except Exception as e:
01340 self._handle_request_exception(e)
01341 if (self._prepared_future is not None and
01342 not self._prepared_future.done()):
01343
01344
01345
01346 self._prepared_future.set_result(None)
01347
01348 def data_received(self, chunk):
01349 """Implement this method to handle streamed request data.
01350
01351 Requires the `.stream_request_body` decorator.
01352 """
01353 raise NotImplementedError()
01354
01355 def _log(self):
01356 """Logs the current request.
01357
01358 Sort of deprecated since this functionality was moved to the
01359 Application, but left in place for the benefit of existing apps
01360 that have overridden this method.
01361 """
01362 self.application.log_request(self)
01363
01364 def _request_summary(self):
01365 return self.request.method + " " + self.request.uri + \
01366 " (" + self.request.remote_ip + ")"
01367
01368 def _handle_request_exception(self, e):
01369 if isinstance(e, Finish):
01370
01371 if not self._finished:
01372 self.finish()
01373 return
01374 self.log_exception(*sys.exc_info())
01375 if self._finished:
01376
01377
01378
01379 return
01380 if isinstance(e, HTTPError):
01381 if e.status_code not in httputil.responses and not e.reason:
01382 gen_log.error("Bad HTTP status code: %d", e.status_code)
01383 self.send_error(500, exc_info=sys.exc_info())
01384 else:
01385 self.send_error(e.status_code, exc_info=sys.exc_info())
01386 else:
01387 self.send_error(500, exc_info=sys.exc_info())
01388
01389 def log_exception(self, typ, value, tb):
01390 """Override to customize logging of uncaught exceptions.
01391
01392 By default logs instances of `HTTPError` as warnings without
01393 stack traces (on the ``tornado.general`` logger), and all
01394 other exceptions as errors with stack traces (on the
01395 ``tornado.application`` logger).
01396
01397 .. versionadded:: 3.1
01398 """
01399 if isinstance(value, HTTPError):
01400 if value.log_message:
01401 format = "%d %s: " + value.log_message
01402 args = ([value.status_code, self._request_summary()] +
01403 list(value.args))
01404 gen_log.warning(format, *args)
01405 else:
01406 app_log.error("Uncaught exception %s\n%r", self._request_summary(),
01407 self.request, exc_info=(typ, value, tb))
01408
01409 def _ui_module(self, name, module):
01410 def render(*args, **kwargs):
01411 if not hasattr(self, "_active_modules"):
01412 self._active_modules = {}
01413 if name not in self._active_modules:
01414 self._active_modules[name] = module(self)
01415 rendered = self._active_modules[name].render(*args, **kwargs)
01416 return rendered
01417 return render
01418
01419 def _ui_method(self, method):
01420 return lambda *args, **kwargs: method(self, *args, **kwargs)
01421
01422 def _clear_headers_for_304(self):
01423
01424
01425
01426
01427 headers = ["Allow", "Content-Encoding", "Content-Language",
01428 "Content-Length", "Content-MD5", "Content-Range",
01429 "Content-Type", "Last-Modified"]
01430 for h in headers:
01431 self.clear_header(h)
01432
01433
01434 def asynchronous(method):
01435 """Wrap request handler methods with this if they are asynchronous.
01436
01437 This decorator is unnecessary if the method is also decorated with
01438 ``@gen.coroutine`` (it is legal but unnecessary to use the two
01439 decorators together, in which case ``@asynchronous`` must be
01440 first).
01441
01442 This decorator should only be applied to the :ref:`HTTP verb
01443 methods <verbs>`; its behavior is undefined for any other method.
01444 This decorator does not *make* a method asynchronous; it tells
01445 the framework that the method *is* asynchronous. For this decorator
01446 to be useful the method must (at least sometimes) do something
01447 asynchronous.
01448
01449 If this decorator is given, the response is not finished when the
01450 method returns. It is up to the request handler to call
01451 `self.finish() <RequestHandler.finish>` to finish the HTTP
01452 request. Without this decorator, the request is automatically
01453 finished when the ``get()`` or ``post()`` method returns. Example::
01454
01455 class MyRequestHandler(web.RequestHandler):
01456 @web.asynchronous
01457 def get(self):
01458 http = httpclient.AsyncHTTPClient()
01459 http.fetch("http://friendfeed.com/", self._on_download)
01460
01461 def _on_download(self, response):
01462 self.write("Downloaded!")
01463 self.finish()
01464
01465 .. versionadded:: 3.1
01466 The ability to use ``@gen.coroutine`` without ``@asynchronous``.
01467 """
01468
01469 from tornado.ioloop import IOLoop
01470 @functools.wraps(method)
01471 def wrapper(self, *args, **kwargs):
01472 self._auto_finish = False
01473 with stack_context.ExceptionStackContext(
01474 self._stack_context_handle_exception):
01475 result = method(self, *args, **kwargs)
01476 if isinstance(result, Future):
01477
01478
01479
01480
01481
01482
01483 def future_complete(f):
01484 f.result()
01485 if not self._finished:
01486 self.finish()
01487 IOLoop.current().add_future(result, future_complete)
01488
01489
01490
01491
01492
01493 return None
01494 return result
01495 return wrapper
01496
01497
01498 def stream_request_body(cls):
01499 """Apply to `RequestHandler` subclasses to enable streaming body support.
01500
01501 This decorator implies the following changes:
01502
01503 * `.HTTPServerRequest.body` is undefined, and body arguments will not
01504 be included in `RequestHandler.get_argument`.
01505 * `RequestHandler.prepare` is called when the request headers have been
01506 read instead of after the entire body has been read.
01507 * The subclass must define a method ``data_received(self, data):``, which
01508 will be called zero or more times as data is available. Note that
01509 if the request has an empty body, ``data_received`` may not be called.
01510 * ``prepare`` and ``data_received`` may return Futures (such as via
01511 ``@gen.coroutine``, in which case the next method will not be called
01512 until those futures have completed.
01513 * The regular HTTP method (``post``, ``put``, etc) will be called after
01514 the entire body has been read.
01515
01516 There is a subtle interaction between ``data_received`` and asynchronous
01517 ``prepare``: The first call to ``data_recieved`` may occur at any point
01518 after the call to ``prepare`` has returned *or yielded*.
01519 """
01520 if not issubclass(cls, RequestHandler):
01521 raise TypeError("expected subclass of RequestHandler, got %r", cls)
01522 cls._stream_request_body = True
01523 return cls
01524
01525
01526 def _has_stream_request_body(cls):
01527 if not issubclass(cls, RequestHandler):
01528 raise TypeError("expected subclass of RequestHandler, got %r", cls)
01529 return getattr(cls, '_stream_request_body', False)
01530
01531
01532 def removeslash(method):
01533 """Use this decorator to remove trailing slashes from the request path.
01534
01535 For example, a request to ``/foo/`` would redirect to ``/foo`` with this
01536 decorator. Your request handler mapping should use a regular expression
01537 like ``r'/foo/*'`` in conjunction with using the decorator.
01538 """
01539 @functools.wraps(method)
01540 def wrapper(self, *args, **kwargs):
01541 if self.request.path.endswith("/"):
01542 if self.request.method in ("GET", "HEAD"):
01543 uri = self.request.path.rstrip("/")
01544 if uri:
01545 if self.request.query:
01546 uri += "?" + self.request.query
01547 self.redirect(uri, permanent=True)
01548 return
01549 else:
01550 raise HTTPError(404)
01551 return method(self, *args, **kwargs)
01552 return wrapper
01553
01554
01555 def addslash(method):
01556 """Use this decorator to add a missing trailing slash to the request path.
01557
01558 For example, a request to ``/foo`` would redirect to ``/foo/`` with this
01559 decorator. Your request handler mapping should use a regular expression
01560 like ``r'/foo/?'`` in conjunction with using the decorator.
01561 """
01562 @functools.wraps(method)
01563 def wrapper(self, *args, **kwargs):
01564 if not self.request.path.endswith("/"):
01565 if self.request.method in ("GET", "HEAD"):
01566 uri = self.request.path + "/"
01567 if self.request.query:
01568 uri += "?" + self.request.query
01569 self.redirect(uri, permanent=True)
01570 return
01571 raise HTTPError(404)
01572 return method(self, *args, **kwargs)
01573 return wrapper
01574
01575
01576 class Application(httputil.HTTPServerConnectionDelegate):
01577 """A collection of request handlers that make up a web application.
01578
01579 Instances of this class are callable and can be passed directly to
01580 HTTPServer to serve the application::
01581
01582 application = web.Application([
01583 (r"/", MainPageHandler),
01584 ])
01585 http_server = httpserver.HTTPServer(application)
01586 http_server.listen(8080)
01587 ioloop.IOLoop.instance().start()
01588
01589 The constructor for this class takes in a list of `URLSpec` objects
01590 or (regexp, request_class) tuples. When we receive requests, we
01591 iterate over the list in order and instantiate an instance of the
01592 first request class whose regexp matches the request path.
01593 The request class can be specified as either a class object or a
01594 (fully-qualified) name.
01595
01596 Each tuple can contain additional elements, which correspond to the
01597 arguments to the `URLSpec` constructor. (Prior to Tornado 3.2, this
01598 only tuples of two or three elements were allowed).
01599
01600 A dictionary may be passed as the third element of the tuple,
01601 which will be used as keyword arguments to the handler's
01602 constructor and `~RequestHandler.initialize` method. This pattern
01603 is used for the `StaticFileHandler` in this example (note that a
01604 `StaticFileHandler` can be installed automatically with the
01605 static_path setting described below)::
01606
01607 application = web.Application([
01608 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
01609 ])
01610
01611 We support virtual hosts with the `add_handlers` method, which takes in
01612 a host regular expression as the first argument::
01613
01614 application.add_handlers(r"www\.myhost\.com", [
01615 (r"/article/([0-9]+)", ArticleHandler),
01616 ])
01617
01618 You can serve static files by sending the ``static_path`` setting
01619 as a keyword argument. We will serve those files from the
01620 ``/static/`` URI (this is configurable with the
01621 ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
01622 and ``/robots.txt`` from the same directory. A custom subclass of
01623 `StaticFileHandler` can be specified with the
01624 ``static_handler_class`` setting.
01625
01626 """
01627 def __init__(self, handlers=None, default_host="", transforms=None,
01628 **settings):
01629 if transforms is None:
01630 self.transforms = []
01631 if settings.get("compress_response") or settings.get("gzip"):
01632 self.transforms.append(GZipContentEncoding)
01633 else:
01634 self.transforms = transforms
01635 self.handlers = []
01636 self.named_handlers = {}
01637 self.default_host = default_host
01638 self.settings = settings
01639 self.ui_modules = {'linkify': _linkify,
01640 'xsrf_form_html': _xsrf_form_html,
01641 'Template': TemplateModule,
01642 }
01643 self.ui_methods = {}
01644 self._load_ui_modules(settings.get("ui_modules", {}))
01645 self._load_ui_methods(settings.get("ui_methods", {}))
01646 if self.settings.get("static_path"):
01647 path = self.settings["static_path"]
01648 handlers = list(handlers or [])
01649 static_url_prefix = settings.get("static_url_prefix",
01650 "/static/")
01651 static_handler_class = settings.get("static_handler_class",
01652 StaticFileHandler)
01653 static_handler_args = settings.get("static_handler_args", {})
01654 static_handler_args['path'] = path
01655 for pattern in [re.escape(static_url_prefix) + r"(.*)",
01656 r"/(favicon\.ico)", r"/(robots\.txt)"]:
01657 handlers.insert(0, (pattern, static_handler_class,
01658 static_handler_args))
01659 if handlers:
01660 self.add_handlers(".*$", handlers)
01661
01662 if self.settings.get('debug'):
01663 self.settings.setdefault('autoreload', True)
01664 self.settings.setdefault('compiled_template_cache', False)
01665 self.settings.setdefault('static_hash_cache', False)
01666 self.settings.setdefault('serve_traceback', True)
01667
01668
01669 if self.settings.get('autoreload'):
01670 from tornado import autoreload
01671 autoreload.start()
01672
01673 def listen(self, port, address="", **kwargs):
01674 """Starts an HTTP server for this application on the given port.
01675
01676 This is a convenience alias for creating an `.HTTPServer`
01677 object and calling its listen method. Keyword arguments not
01678 supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the
01679 `.HTTPServer` constructor. For advanced uses
01680 (e.g. multi-process mode), do not use this method; create an
01681 `.HTTPServer` and call its
01682 `.TCPServer.bind`/`.TCPServer.start` methods directly.
01683
01684 Note that after calling this method you still need to call
01685 ``IOLoop.instance().start()`` to start the server.
01686 """
01687
01688
01689 from tornado.httpserver import HTTPServer
01690 server = HTTPServer(self, **kwargs)
01691 server.listen(port, address)
01692
01693 def add_handlers(self, host_pattern, host_handlers):
01694 """Appends the given handlers to our handler list.
01695
01696 Host patterns are processed sequentially in the order they were
01697 added. All matching patterns will be considered.
01698 """
01699 if not host_pattern.endswith("$"):
01700 host_pattern += "$"
01701 handlers = []
01702
01703
01704
01705
01706
01707 if self.handlers and self.handlers[-1][0].pattern == '.*$':
01708 self.handlers.insert(-1, (re.compile(host_pattern), handlers))
01709 else:
01710 self.handlers.append((re.compile(host_pattern), handlers))
01711
01712 for spec in host_handlers:
01713 if isinstance(spec, (tuple, list)):
01714 assert len(spec) in (2, 3, 4)
01715 spec = URLSpec(*spec)
01716 handlers.append(spec)
01717 if spec.name:
01718 if spec.name in self.named_handlers:
01719 app_log.warning(
01720 "Multiple handlers named %s; replacing previous value",
01721 spec.name)
01722 self.named_handlers[spec.name] = spec
01723
01724 def add_transform(self, transform_class):
01725 self.transforms.append(transform_class)
01726
01727 def _get_host_handlers(self, request):
01728 host = request.host.lower().split(':')[0]
01729 matches = []
01730 for pattern, handlers in self.handlers:
01731 if pattern.match(host):
01732 matches.extend(handlers)
01733
01734 if not matches and "X-Real-Ip" not in request.headers:
01735 for pattern, handlers in self.handlers:
01736 if pattern.match(self.default_host):
01737 matches.extend(handlers)
01738 return matches or None
01739
01740 def _load_ui_methods(self, methods):
01741 if isinstance(methods, types.ModuleType):
01742 self._load_ui_methods(dict((n, getattr(methods, n))
01743 for n in dir(methods)))
01744 elif isinstance(methods, list):
01745 for m in methods:
01746 self._load_ui_methods(m)
01747 else:
01748 for name, fn in methods.items():
01749 if not name.startswith("_") and hasattr(fn, "__call__") \
01750 and name[0].lower() == name[0]:
01751 self.ui_methods[name] = fn
01752
01753 def _load_ui_modules(self, modules):
01754 if isinstance(modules, types.ModuleType):
01755 self._load_ui_modules(dict((n, getattr(modules, n))
01756 for n in dir(modules)))
01757 elif isinstance(modules, list):
01758 for m in modules:
01759 self._load_ui_modules(m)
01760 else:
01761 assert isinstance(modules, dict)
01762 for name, cls in modules.items():
01763 try:
01764 if issubclass(cls, UIModule):
01765 self.ui_modules[name] = cls
01766 except TypeError:
01767 pass
01768
01769 def start_request(self, connection):
01770
01771 return _RequestDispatcher(self, connection)
01772
01773 def __call__(self, request):
01774
01775 dispatcher = _RequestDispatcher(self, None)
01776 dispatcher.set_request(request)
01777 return dispatcher.execute()
01778
01779 def reverse_url(self, name, *args):
01780 """Returns a URL path for handler named ``name``
01781
01782 The handler must be added to the application as a named `URLSpec`.
01783
01784 Args will be substituted for capturing groups in the `URLSpec` regex.
01785 They will be converted to strings if necessary, encoded as utf8,
01786 and url-escaped.
01787 """
01788 if name in self.named_handlers:
01789 return self.named_handlers[name].reverse(*args)
01790 raise KeyError("%s not found in named urls" % name)
01791
01792 def log_request(self, handler):
01793 """Writes a completed HTTP request to the logs.
01794
01795 By default writes to the python root logger. To change
01796 this behavior either subclass Application and override this method,
01797 or pass a function in the application settings dictionary as
01798 ``log_function``.
01799 """
01800 if "log_function" in self.settings:
01801 self.settings["log_function"](handler)
01802 return
01803 if handler.get_status() < 400:
01804 log_method = access_log.info
01805 elif handler.get_status() < 500:
01806 log_method = access_log.warning
01807 else:
01808 log_method = access_log.error
01809 request_time = 1000.0 * handler.request.request_time()
01810 log_method("%d %s %.2fms", handler.get_status(),
01811 handler._request_summary(), request_time)
01812
01813
01814 class _RequestDispatcher(httputil.HTTPMessageDelegate):
01815 def __init__(self, application, connection):
01816 self.application = application
01817 self.connection = connection
01818 self.request = None
01819 self.chunks = []
01820 self.handler_class = None
01821 self.handler_kwargs = None
01822 self.path_args = []
01823 self.path_kwargs = {}
01824
01825 def headers_received(self, start_line, headers):
01826 self.set_request(httputil.HTTPServerRequest(
01827 connection=self.connection, start_line=start_line, headers=headers))
01828 if self.stream_request_body:
01829 self.request.body = Future()
01830 return self.execute()
01831
01832 def set_request(self, request):
01833 self.request = request
01834 self._find_handler()
01835 self.stream_request_body = _has_stream_request_body(self.handler_class)
01836
01837 def _find_handler(self):
01838
01839
01840 app = self.application
01841 handlers = app._get_host_handlers(self.request)
01842 if not handlers:
01843 self.handler_class = RedirectHandler
01844 self.handler_kwargs = dict(url="http://" + app.default_host + "/")
01845 return
01846 for spec in handlers:
01847 match = spec.regex.match(self.request.path)
01848 if match:
01849 self.handler_class = spec.handler_class
01850 self.handler_kwargs = spec.kwargs
01851 if spec.regex.groups:
01852
01853
01854
01855
01856 if spec.regex.groupindex:
01857 self.path_kwargs = dict(
01858 (str(k), _unquote_or_none(v))
01859 for (k, v) in match.groupdict().items())
01860 else:
01861 self.path_args = [_unquote_or_none(s)
01862 for s in match.groups()]
01863 return
01864 if app.settings.get('default_handler_class'):
01865 self.handler_class = app.settings['default_handler_class']
01866 self.handler_kwargs = app.settings.get(
01867 'default_handler_args', {})
01868 else:
01869 self.handler_class = ErrorHandler
01870 self.handler_kwargs = dict(status_code=404)
01871
01872 def data_received(self, data):
01873 if self.stream_request_body:
01874 return self.handler.data_received(data)
01875 else:
01876 self.chunks.append(data)
01877
01878 def finish(self):
01879 if self.stream_request_body:
01880 self.request.body.set_result(None)
01881 else:
01882 self.request.body = b''.join(self.chunks)
01883 self.request._parse_body()
01884 self.execute()
01885
01886 def on_connection_close(self):
01887 if self.stream_request_body:
01888 self.handler.on_connection_close()
01889 else:
01890 self.chunks = None
01891
01892 def execute(self):
01893
01894
01895
01896 if not self.application.settings.get("compiled_template_cache", True):
01897 with RequestHandler._template_loader_lock:
01898 for loader in RequestHandler._template_loaders.values():
01899 loader.reset()
01900 if not self.application.settings.get('static_hash_cache', True):
01901 StaticFileHandler.reset()
01902
01903 self.handler = self.handler_class(self.application, self.request,
01904 **self.handler_kwargs)
01905 transforms = [t(self.request) for t in self.application.transforms]
01906
01907 if self.stream_request_body:
01908 self.handler._prepared_future = Future()
01909
01910
01911
01912
01913
01914 self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
01915
01916
01917
01918 return self.handler._prepared_future
01919
01920
01921 class HTTPError(Exception):
01922 """An exception that will turn into an HTTP error response.
01923
01924 Raising an `HTTPError` is a convenient alternative to calling
01925 `RequestHandler.send_error` since it automatically ends the
01926 current function.
01927
01928 To customize the response sent with an `HTTPError`, override
01929 `RequestHandler.write_error`.
01930
01931 :arg int status_code: HTTP status code. Must be listed in
01932 `httplib.responses <http.client.responses>` unless the ``reason``
01933 keyword argument is given.
01934 :arg string log_message: Message to be written to the log for this error
01935 (will not be shown to the user unless the `Application` is in debug
01936 mode). May contain ``%s``-style placeholders, which will be filled
01937 in with remaining positional parameters.
01938 :arg string reason: Keyword-only argument. The HTTP "reason" phrase
01939 to pass in the status line along with ``status_code``. Normally
01940 determined automatically from ``status_code``, but can be used
01941 to use a non-standard numeric code.
01942 """
01943 def __init__(self, status_code, log_message=None, *args, **kwargs):
01944 self.status_code = status_code
01945 self.log_message = log_message
01946 self.args = args
01947 self.reason = kwargs.get('reason', None)
01948
01949 def __str__(self):
01950 message = "HTTP %d: %s" % (
01951 self.status_code,
01952 self.reason or httputil.responses.get(self.status_code, 'Unknown'))
01953 if self.log_message:
01954 return message + " (" + (self.log_message % self.args) + ")"
01955 else:
01956 return message
01957
01958
01959 class Finish(Exception):
01960 """An exception that ends the request without producing an error response.
01961
01962 When `Finish` is raised in a `RequestHandler`, the request will end
01963 (calling `RequestHandler.finish` if it hasn't already been called),
01964 but the outgoing response will not be modified and the error-handling
01965 methods (including `RequestHandler.write_error`) will not be called.
01966
01967 This can be a more convenient way to implement custom error pages
01968 than overriding ``write_error`` (especially in library code)::
01969
01970 if self.current_user is None:
01971 self.set_status(401)
01972 self.set_header('WWW-Authenticate', 'Basic realm="something"')
01973 raise Finish()
01974 """
01975 pass
01976
01977
01978 class MissingArgumentError(HTTPError):
01979 """Exception raised by `RequestHandler.get_argument`.
01980
01981 This is a subclass of `HTTPError`, so if it is uncaught a 400 response
01982 code will be used instead of 500 (and a stack trace will not be logged).
01983
01984 .. versionadded:: 3.1
01985 """
01986 def __init__(self, arg_name):
01987 super(MissingArgumentError, self).__init__(
01988 400, 'Missing argument %s' % arg_name)
01989 self.arg_name = arg_name
01990
01991
01992 class ErrorHandler(RequestHandler):
01993 """Generates an error response with ``status_code`` for all requests."""
01994 def initialize(self, status_code):
01995 self.set_status(status_code)
01996
01997 def prepare(self):
01998 raise HTTPError(self._status_code)
01999
02000 def check_xsrf_cookie(self):
02001
02002
02003
02004 pass
02005
02006
02007 class RedirectHandler(RequestHandler):
02008 """Redirects the client to the given URL for all GET requests.
02009
02010 You should provide the keyword argument ``url`` to the handler, e.g.::
02011
02012 application = web.Application([
02013 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
02014 ])
02015 """
02016 def initialize(self, url, permanent=True):
02017 self._url = url
02018 self._permanent = permanent
02019
02020 def get(self):
02021 self.redirect(self._url, permanent=self._permanent)
02022
02023
02024 class StaticFileHandler(RequestHandler):
02025 """A simple handler that can serve static content from a directory.
02026
02027 A `StaticFileHandler` is configured automatically if you pass the
02028 ``static_path`` keyword argument to `Application`. This handler
02029 can be customized with the ``static_url_prefix``, ``static_handler_class``,
02030 and ``static_handler_args`` settings.
02031
02032 To map an additional path to this handler for a static data directory
02033 you would add a line to your application like::
02034
02035 application = web.Application([
02036 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
02037 ])
02038
02039 The handler constructor requires a ``path`` argument, which specifies the
02040 local root directory of the content to be served.
02041
02042 Note that a capture group in the regex is required to parse the value for
02043 the ``path`` argument to the get() method (different than the constructor
02044 argument above); see `URLSpec` for details.
02045
02046 To maximize the effectiveness of browser caching, this class supports
02047 versioned urls (by default using the argument ``?v=``). If a version
02048 is given, we instruct the browser to cache this file indefinitely.
02049 `make_static_url` (also available as `RequestHandler.static_url`) can
02050 be used to construct a versioned url.
02051
02052 This handler is intended primarily for use in development and light-duty
02053 file serving; for heavy traffic it will be more efficient to use
02054 a dedicated static file server (such as nginx or Apache). We support
02055 the HTTP ``Accept-Ranges`` mechanism to return partial content (because
02056 some browsers require this functionality to be present to seek in
02057 HTML5 audio or video), but this handler should not be used with
02058 files that are too large to fit comfortably in memory.
02059
02060 **Subclassing notes**
02061
02062 This class is designed to be extensible by subclassing, but because
02063 of the way static urls are generated with class methods rather than
02064 instance methods, the inheritance patterns are somewhat unusual.
02065 Be sure to use the ``@classmethod`` decorator when overriding a
02066 class method. Instance methods may use the attributes ``self.path``
02067 ``self.absolute_path``, and ``self.modified``.
02068
02069 Subclasses should only override methods discussed in this section;
02070 overriding other methods is error-prone. Overriding
02071 ``StaticFileHandler.get`` is particularly problematic due to the
02072 tight coupling with ``compute_etag`` and other methods.
02073
02074 To change the way static urls are generated (e.g. to match the behavior
02075 of another server or CDN), override `make_static_url`, `parse_url_path`,
02076 `get_cache_time`, and/or `get_version`.
02077
02078 To replace all interaction with the filesystem (e.g. to serve
02079 static content from a database), override `get_content`,
02080 `get_content_size`, `get_modified_time`, `get_absolute_path`, and
02081 `validate_absolute_path`.
02082
02083 .. versionchanged:: 3.1
02084 Many of the methods for subclasses were added in Tornado 3.1.
02085 """
02086 CACHE_MAX_AGE = 86400 * 365 * 10
02087
02088 _static_hashes = {}
02089 _lock = threading.Lock()
02090
02091 def initialize(self, path, default_filename=None):
02092 self.root = path
02093 self.default_filename = default_filename
02094
02095 @classmethod
02096 def reset(cls):
02097 with cls._lock:
02098 cls._static_hashes = {}
02099
02100 def head(self, path):
02101 return self.get(path, include_body=False)
02102
02103 @gen.coroutine
02104 def get(self, path, include_body=True):
02105
02106 self.path = self.parse_url_path(path)
02107 del path
02108 absolute_path = self.get_absolute_path(self.root, self.path)
02109 self.absolute_path = self.validate_absolute_path(
02110 self.root, absolute_path)
02111 if self.absolute_path is None:
02112 return
02113
02114 self.modified = self.get_modified_time()
02115 self.set_headers()
02116
02117 if self.should_return_304():
02118 self.set_status(304)
02119 return
02120
02121 request_range = None
02122 range_header = self.request.headers.get("Range")
02123 if range_header:
02124
02125
02126 request_range = httputil._parse_request_range(range_header)
02127
02128 size = self.get_content_size()
02129 if request_range:
02130 start, end = request_range
02131 if (start is not None and start >= size) or end == 0:
02132
02133
02134
02135 self.set_status(416)
02136 self.set_header("Content-Type", "text/plain")
02137 self.set_header("Content-Range", "bytes */%s" % (size, ))
02138 return
02139 if start is not None and start < 0:
02140 start += size
02141 if end is not None and end > size:
02142
02143
02144 end = size
02145
02146
02147
02148
02149 if size != (end or size) - (start or 0):
02150 self.set_status(206)
02151 self.set_header("Content-Range",
02152 httputil._get_content_range(start, end, size))
02153 else:
02154 start = end = None
02155
02156 if start is not None and end is not None:
02157 content_length = end - start
02158 elif end is not None:
02159 content_length = end
02160 elif start is not None:
02161 content_length = size - start
02162 else:
02163 content_length = size
02164 self.set_header("Content-Length", content_length)
02165
02166 if include_body:
02167 content = self.get_content(self.absolute_path, start, end)
02168 if isinstance(content, bytes_type):
02169 content = [content]
02170 for chunk in content:
02171 self.write(chunk)
02172 yield self.flush()
02173 else:
02174 assert self.request.method == "HEAD"
02175
02176 def compute_etag(self):
02177 """Sets the ``Etag`` header based on static url version.
02178
02179 This allows efficient ``If-None-Match`` checks against cached
02180 versions, and sends the correct ``Etag`` for a partial response
02181 (i.e. the same ``Etag`` as the full file).
02182
02183 .. versionadded:: 3.1
02184 """
02185 version_hash = self._get_cached_version(self.absolute_path)
02186 if not version_hash:
02187 return None
02188 return '"%s"' % (version_hash, )
02189
02190 def set_headers(self):
02191 """Sets the content and caching headers on the response.
02192
02193 .. versionadded:: 3.1
02194 """
02195 self.set_header("Accept-Ranges", "bytes")
02196 self.set_etag_header()
02197
02198 if self.modified is not None:
02199 self.set_header("Last-Modified", self.modified)
02200
02201 content_type = self.get_content_type()
02202 if content_type:
02203 self.set_header("Content-Type", content_type)
02204
02205 cache_time = self.get_cache_time(self.path, self.modified, content_type)
02206 if cache_time > 0:
02207 self.set_header("Expires", datetime.datetime.utcnow() +
02208 datetime.timedelta(seconds=cache_time))
02209 self.set_header("Cache-Control", "max-age=" + str(cache_time))
02210
02211 self.set_extra_headers(self.path)
02212
02213 def should_return_304(self):
02214 """Returns True if the headers indicate that we should return 304.
02215
02216 .. versionadded:: 3.1
02217 """
02218 if self.check_etag_header():
02219 return True
02220
02221
02222
02223 ims_value = self.request.headers.get("If-Modified-Since")
02224 if ims_value is not None:
02225 date_tuple = email.utils.parsedate(ims_value)
02226 if date_tuple is not None:
02227 if_since = datetime.datetime(*date_tuple[:6])
02228 if if_since >= self.modified:
02229 return True
02230
02231 return False
02232
02233 @classmethod
02234 def get_absolute_path(cls, root, path):
02235 """Returns the absolute location of ``path`` relative to ``root``.
02236
02237 ``root`` is the path configured for this `StaticFileHandler`
02238 (in most cases the ``static_path`` `Application` setting).
02239
02240 This class method may be overridden in subclasses. By default
02241 it returns a filesystem path, but other strings may be used
02242 as long as they are unique and understood by the subclass's
02243 overridden `get_content`.
02244
02245 .. versionadded:: 3.1
02246 """
02247 abspath = os.path.abspath(os.path.join(root, path))
02248 return abspath
02249
02250 def validate_absolute_path(self, root, absolute_path):
02251 """Validate and return the absolute path.
02252
02253 ``root`` is the configured path for the `StaticFileHandler`,
02254 and ``path`` is the result of `get_absolute_path`
02255
02256 This is an instance method called during request processing,
02257 so it may raise `HTTPError` or use methods like
02258 `RequestHandler.redirect` (return None after redirecting to
02259 halt further processing). This is where 404 errors for missing files
02260 are generated.
02261
02262 This method may modify the path before returning it, but note that
02263 any such modifications will not be understood by `make_static_url`.
02264
02265 In instance methods, this method's result is available as
02266 ``self.absolute_path``.
02267
02268 .. versionadded:: 3.1
02269 """
02270 root = os.path.abspath(root)
02271
02272
02273 if not (absolute_path + os.path.sep).startswith(root):
02274 raise HTTPError(403, "%s is not in root static directory",
02275 self.path)
02276 if (os.path.isdir(absolute_path) and
02277 self.default_filename is not None):
02278
02279
02280
02281 if not self.request.path.endswith("/"):
02282 self.redirect(self.request.path + "/", permanent=True)
02283 return
02284 absolute_path = os.path.join(absolute_path, self.default_filename)
02285 if not os.path.exists(absolute_path):
02286 raise HTTPError(404)
02287 if not os.path.isfile(absolute_path):
02288 raise HTTPError(403, "%s is not a file", self.path)
02289 return absolute_path
02290
02291 @classmethod
02292 def get_content(cls, abspath, start=None, end=None):
02293 """Retrieve the content of the requested resource which is located
02294 at the given absolute path.
02295
02296 This class method may be overridden by subclasses. Note that its
02297 signature is different from other overridable class methods
02298 (no ``settings`` argument); this is deliberate to ensure that
02299 ``abspath`` is able to stand on its own as a cache key.
02300
02301 This method should either return a byte string or an iterator
02302 of byte strings. The latter is preferred for large files
02303 as it helps reduce memory fragmentation.
02304
02305 .. versionadded:: 3.1
02306 """
02307 with open(abspath, "rb") as file:
02308 if start is not None:
02309 file.seek(start)
02310 if end is not None:
02311 remaining = end - (start or 0)
02312 else:
02313 remaining = None
02314 while True:
02315 chunk_size = 64 * 1024
02316 if remaining is not None and remaining < chunk_size:
02317 chunk_size = remaining
02318 chunk = file.read(chunk_size)
02319 if chunk:
02320 if remaining is not None:
02321 remaining -= len(chunk)
02322 yield chunk
02323 else:
02324 if remaining is not None:
02325 assert remaining == 0
02326 return
02327
02328 @classmethod
02329 def get_content_version(cls, abspath):
02330 """Returns a version string for the resource at the given path.
02331
02332 This class method may be overridden by subclasses. The
02333 default implementation is a hash of the file's contents.
02334
02335 .. versionadded:: 3.1
02336 """
02337 data = cls.get_content(abspath)
02338 hasher = hashlib.md5()
02339 if isinstance(data, bytes_type):
02340 hasher.update(data)
02341 else:
02342 for chunk in data:
02343 hasher.update(chunk)
02344 return hasher.hexdigest()
02345
02346 def _stat(self):
02347 if not hasattr(self, '_stat_result'):
02348 self._stat_result = os.stat(self.absolute_path)
02349 return self._stat_result
02350
02351 def get_content_size(self):
02352 """Retrieve the total size of the resource at the given path.
02353
02354 This method may be overridden by subclasses.
02355
02356 .. versionadded:: 3.1
02357
02358 .. versionchanged:: 4.0
02359 This method is now always called, instead of only when
02360 partial results are requested.
02361 """
02362 stat_result = self._stat()
02363 return stat_result[stat.ST_SIZE]
02364
02365 def get_modified_time(self):
02366 """Returns the time that ``self.absolute_path`` was last modified.
02367
02368 May be overridden in subclasses. Should return a `~datetime.datetime`
02369 object or None.
02370
02371 .. versionadded:: 3.1
02372 """
02373 stat_result = self._stat()
02374 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
02375 return modified
02376
02377 def get_content_type(self):
02378 """Returns the ``Content-Type`` header to be used for this request.
02379
02380 .. versionadded:: 3.1
02381 """
02382 mime_type, encoding = mimetypes.guess_type(self.absolute_path)
02383 return mime_type
02384
02385 def set_extra_headers(self, path):
02386 """For subclass to add extra headers to the response"""
02387 pass
02388
02389 def get_cache_time(self, path, modified, mime_type):
02390 """Override to customize cache control behavior.
02391
02392 Return a positive number of seconds to make the result
02393 cacheable for that amount of time or 0 to mark resource as
02394 cacheable for an unspecified amount of time (subject to
02395 browser heuristics).
02396
02397 By default returns cache expiry of 10 years for resources requested
02398 with ``v`` argument.
02399 """
02400 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
02401
02402 @classmethod
02403 def make_static_url(cls, settings, path, include_version=True):
02404 """Constructs a versioned url for the given path.
02405
02406 This method may be overridden in subclasses (but note that it
02407 is a class method rather than an instance method). Subclasses
02408 are only required to implement the signature
02409 ``make_static_url(cls, settings, path)``; other keyword
02410 arguments may be passed through `~RequestHandler.static_url`
02411 but are not standard.
02412
02413 ``settings`` is the `Application.settings` dictionary. ``path``
02414 is the static path being requested. The url returned should be
02415 relative to the current host.
02416
02417 ``include_version`` determines whether the generated URL should
02418 include the query string containing the version hash of the
02419 file corresponding to the given ``path``.
02420
02421 """
02422 url = settings.get('static_url_prefix', '/static/') + path
02423 if not include_version:
02424 return url
02425
02426 version_hash = cls.get_version(settings, path)
02427 if not version_hash:
02428 return url
02429
02430 return '%s?v=%s' % (url, version_hash)
02431
02432 def parse_url_path(self, url_path):
02433 """Converts a static URL path into a filesystem path.
02434
02435 ``url_path`` is the path component of the URL with
02436 ``static_url_prefix`` removed. The return value should be
02437 filesystem path relative to ``static_path``.
02438
02439 This is the inverse of `make_static_url`.
02440 """
02441 if os.path.sep != "/":
02442 url_path = url_path.replace("/", os.path.sep)
02443 return url_path
02444
02445 @classmethod
02446 def get_version(cls, settings, path):
02447 """Generate the version string to be used in static URLs.
02448
02449 ``settings`` is the `Application.settings` dictionary and ``path``
02450 is the relative location of the requested asset on the filesystem.
02451 The returned value should be a string, or ``None`` if no version
02452 could be determined.
02453
02454 .. versionchanged:: 3.1
02455 This method was previously recommended for subclasses to override;
02456 `get_content_version` is now preferred as it allows the base
02457 class to handle caching of the result.
02458 """
02459 abs_path = cls.get_absolute_path(settings['static_path'], path)
02460 return cls._get_cached_version(abs_path)
02461
02462 @classmethod
02463 def _get_cached_version(cls, abs_path):
02464 with cls._lock:
02465 hashes = cls._static_hashes
02466 if abs_path not in hashes:
02467 try:
02468 hashes[abs_path] = cls.get_content_version(abs_path)
02469 except Exception:
02470 gen_log.error("Could not open static file %r", abs_path)
02471 hashes[abs_path] = None
02472 hsh = hashes.get(abs_path)
02473 if hsh:
02474 return hsh
02475 return None
02476
02477
02478 class FallbackHandler(RequestHandler):
02479 """A `RequestHandler` that wraps another HTTP server callback.
02480
02481 The fallback is a callable object that accepts an
02482 `~.httputil.HTTPServerRequest`, such as an `Application` or
02483 `tornado.wsgi.WSGIContainer`. This is most useful to use both
02484 Tornado ``RequestHandlers`` and WSGI in the same server. Typical
02485 usage::
02486
02487 wsgi_app = tornado.wsgi.WSGIContainer(
02488 django.core.handlers.wsgi.WSGIHandler())
02489 application = tornado.web.Application([
02490 (r"/foo", FooHandler),
02491 (r".*", FallbackHandler, dict(fallback=wsgi_app),
02492 ])
02493 """
02494 def initialize(self, fallback):
02495 self.fallback = fallback
02496
02497 def prepare(self):
02498 self.fallback(self.request)
02499 self._finished = True
02500
02501
02502 class OutputTransform(object):
02503 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
02504
02505 Applications are not expected to create their own OutputTransforms
02506 or interact with them directly; the framework chooses which transforms
02507 (if any) to apply.
02508 """
02509 def __init__(self, request):
02510 pass
02511
02512 def transform_first_chunk(self, status_code, headers, chunk, finishing):
02513 return status_code, headers, chunk
02514
02515 def transform_chunk(self, chunk, finishing):
02516 return chunk
02517
02518
02519 class GZipContentEncoding(OutputTransform):
02520 """Applies the gzip content encoding to the response.
02521
02522 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
02523
02524 .. versionchanged:: 4.0
02525 Now compresses all mime types beginning with ``text/``, instead
02526 of just a whitelist. (the whitelist is still used for certain
02527 non-text mime types).
02528 """
02529
02530
02531 CONTENT_TYPES = set(["application/javascript", "application/x-javascript",
02532 "application/xml", "application/atom+xml",
02533 "application/json", "application/xhtml+xml"])
02534 MIN_LENGTH = 5
02535
02536 def __init__(self, request):
02537 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
02538
02539 def _compressible_type(self, ctype):
02540 return ctype.startswith('text/') or ctype in self.CONTENT_TYPES
02541
02542 def transform_first_chunk(self, status_code, headers, chunk, finishing):
02543 if 'Vary' in headers:
02544 headers['Vary'] += b', Accept-Encoding'
02545 else:
02546 headers['Vary'] = b'Accept-Encoding'
02547 if self._gzipping:
02548 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
02549 self._gzipping = self._compressible_type(ctype) and \
02550 (not finishing or len(chunk) >= self.MIN_LENGTH) and \
02551 ("Content-Encoding" not in headers)
02552 if self._gzipping:
02553 headers["Content-Encoding"] = "gzip"
02554 self._gzip_value = BytesIO()
02555 self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
02556 chunk = self.transform_chunk(chunk, finishing)
02557 if "Content-Length" in headers:
02558
02559
02560
02561
02562 if finishing:
02563 headers["Content-Length"] = str(len(chunk))
02564 else:
02565 del headers["Content-Length"]
02566 return status_code, headers, chunk
02567
02568 def transform_chunk(self, chunk, finishing):
02569 if self._gzipping:
02570 self._gzip_file.write(chunk)
02571 if finishing:
02572 self._gzip_file.close()
02573 else:
02574 self._gzip_file.flush()
02575 chunk = self._gzip_value.getvalue()
02576 self._gzip_value.truncate(0)
02577 self._gzip_value.seek(0)
02578 return chunk
02579
02580
02581 def authenticated(method):
02582 """Decorate methods with this to require that the user be logged in.
02583
02584 If the user is not logged in, they will be redirected to the configured
02585 `login url <RequestHandler.get_login_url>`.
02586
02587 If you configure a login url with a query parameter, Tornado will
02588 assume you know what you're doing and use it as-is. If not, it
02589 will add a `next` parameter so the login page knows where to send
02590 you once you're logged in.
02591 """
02592 @functools.wraps(method)
02593 def wrapper(self, *args, **kwargs):
02594 if not self.current_user:
02595 if self.request.method in ("GET", "HEAD"):
02596 url = self.get_login_url()
02597 if "?" not in url:
02598 if urlparse.urlsplit(url).scheme:
02599
02600 next_url = self.request.full_url()
02601 else:
02602 next_url = self.request.uri
02603 url += "?" + urlencode(dict(next=next_url))
02604 self.redirect(url)
02605 return
02606 raise HTTPError(403)
02607 return method(self, *args, **kwargs)
02608 return wrapper
02609
02610
02611 class UIModule(object):
02612 """A re-usable, modular UI unit on a page.
02613
02614 UI modules often execute additional queries, and they can include
02615 additional CSS and JavaScript that will be included in the output
02616 page, which is automatically inserted on page render.
02617 """
02618 def __init__(self, handler):
02619 self.handler = handler
02620 self.request = handler.request
02621 self.ui = handler.ui
02622 self.locale = handler.locale
02623
02624 @property
02625 def current_user(self):
02626 return self.handler.current_user
02627
02628 def render(self, *args, **kwargs):
02629 """Overridden in subclasses to return this module's output."""
02630 raise NotImplementedError()
02631
02632 def embedded_javascript(self):
02633 """Returns a JavaScript string that will be embedded in the page."""
02634 return None
02635
02636 def javascript_files(self):
02637 """Returns a list of JavaScript files required by this module."""
02638 return None
02639
02640 def embedded_css(self):
02641 """Returns a CSS string that will be embedded in the page."""
02642 return None
02643
02644 def css_files(self):
02645 """Returns a list of CSS files required by this module."""
02646 return None
02647
02648 def html_head(self):
02649 """Returns a CSS string that will be put in the <head/> element"""
02650 return None
02651
02652 def html_body(self):
02653 """Returns an HTML string that will be put in the <body/> element"""
02654 return None
02655
02656 def render_string(self, path, **kwargs):
02657 """Renders a template and returns it as a string."""
02658 return self.handler.render_string(path, **kwargs)
02659
02660
02661 class _linkify(UIModule):
02662 def render(self, text, **kwargs):
02663 return escape.linkify(text, **kwargs)
02664
02665
02666 class _xsrf_form_html(UIModule):
02667 def render(self):
02668 return self.handler.xsrf_form_html()
02669
02670
02671 class TemplateModule(UIModule):
02672 """UIModule that simply renders the given template.
02673
02674 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
02675 but the module version gets its own namespace (with kwargs passed to
02676 Template()) instead of inheriting the outer template's namespace.
02677
02678 Templates rendered through this module also get access to UIModule's
02679 automatic javascript/css features. Simply call set_resources
02680 inside the template and give it keyword arguments corresponding to
02681 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
02682 Note that these resources are output once per template file, not once
02683 per instantiation of the template, so they must not depend on
02684 any arguments to the template.
02685 """
02686 def __init__(self, handler):
02687 super(TemplateModule, self).__init__(handler)
02688
02689 self._resource_list = []
02690 self._resource_dict = {}
02691
02692 def render(self, path, **kwargs):
02693 def set_resources(**kwargs):
02694 if path not in self._resource_dict:
02695 self._resource_list.append(kwargs)
02696 self._resource_dict[path] = kwargs
02697 else:
02698 if self._resource_dict[path] != kwargs:
02699 raise ValueError("set_resources called with different "
02700 "resources for the same template")
02701 return ""
02702 return self.render_string(path, set_resources=set_resources,
02703 **kwargs)
02704
02705 def _get_resources(self, key):
02706 return (r[key] for r in self._resource_list if key in r)
02707
02708 def embedded_javascript(self):
02709 return "\n".join(self._get_resources("embedded_javascript"))
02710
02711 def javascript_files(self):
02712 result = []
02713 for f in self._get_resources("javascript_files"):
02714 if isinstance(f, (unicode_type, bytes_type)):
02715 result.append(f)
02716 else:
02717 result.extend(f)
02718 return result
02719
02720 def embedded_css(self):
02721 return "\n".join(self._get_resources("embedded_css"))
02722
02723 def css_files(self):
02724 result = []
02725 for f in self._get_resources("css_files"):
02726 if isinstance(f, (unicode_type, bytes_type)):
02727 result.append(f)
02728 else:
02729 result.extend(f)
02730 return result
02731
02732 def html_head(self):
02733 return "".join(self._get_resources("html_head"))
02734
02735 def html_body(self):
02736 return "".join(self._get_resources("html_body"))
02737
02738
02739 class _UIModuleNamespace(object):
02740 """Lazy namespace which creates UIModule proxies bound to a handler."""
02741 def __init__(self, handler, ui_modules):
02742 self.handler = handler
02743 self.ui_modules = ui_modules
02744
02745 def __getitem__(self, key):
02746 return self.handler._ui_module(key, self.ui_modules[key])
02747
02748 def __getattr__(self, key):
02749 try:
02750 return self[key]
02751 except KeyError as e:
02752 raise AttributeError(str(e))
02753
02754
02755 class URLSpec(object):
02756 """Specifies mappings between URLs and handlers."""
02757 def __init__(self, pattern, handler, kwargs=None, name=None):
02758 """Parameters:
02759
02760 * ``pattern``: Regular expression to be matched. Any groups
02761 in the regex will be passed in to the handler's get/post/etc
02762 methods as arguments.
02763
02764 * ``handler``: `RequestHandler` subclass to be invoked.
02765
02766 * ``kwargs`` (optional): A dictionary of additional arguments
02767 to be passed to the handler's constructor.
02768
02769 * ``name`` (optional): A name for this handler. Used by
02770 `Application.reverse_url`.
02771 """
02772 if not pattern.endswith('$'):
02773 pattern += '$'
02774 self.regex = re.compile(pattern)
02775 assert len(self.regex.groupindex) in (0, self.regex.groups), \
02776 ("groups in url regexes must either be all named or all "
02777 "positional: %r" % self.regex.pattern)
02778
02779 if isinstance(handler, str):
02780
02781
02782 handler = import_object(handler)
02783
02784 self.handler_class = handler
02785 self.kwargs = kwargs or {}
02786 self.name = name
02787 self._path, self._group_count = self._find_groups()
02788
02789 def __repr__(self):
02790 return '%s(%r, %s, kwargs=%r, name=%r)' % \
02791 (self.__class__.__name__, self.regex.pattern,
02792 self.handler_class, self.kwargs, self.name)
02793
02794 def _find_groups(self):
02795 """Returns a tuple (reverse string, group count) for a url.
02796
02797 For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
02798 would return ('/%s/%s/', 2).
02799 """
02800 pattern = self.regex.pattern
02801 if pattern.startswith('^'):
02802 pattern = pattern[1:]
02803 if pattern.endswith('$'):
02804 pattern = pattern[:-1]
02805
02806 if self.regex.groups != pattern.count('('):
02807
02808
02809 return (None, None)
02810
02811 pieces = []
02812 for fragment in pattern.split('('):
02813 if ')' in fragment:
02814 paren_loc = fragment.index(')')
02815 if paren_loc >= 0:
02816 pieces.append('%s' + fragment[paren_loc + 1:])
02817 else:
02818 pieces.append(fragment)
02819
02820 return (''.join(pieces), self.regex.groups)
02821
02822 def reverse(self, *args):
02823 assert self._path is not None, \
02824 "Cannot reverse url regex " + self.regex.pattern
02825 assert len(args) == self._group_count, "required number of arguments "\
02826 "not found"
02827 if not len(args):
02828 return self._path
02829 converted_args = []
02830 for a in args:
02831 if not isinstance(a, (unicode_type, bytes_type)):
02832 a = str(a)
02833 converted_args.append(escape.url_escape(utf8(a), plus=False))
02834 return self._path % tuple(converted_args)
02835
02836 url = URLSpec
02837
02838
02839 if hasattr(hmac, 'compare_digest'):
02840 _time_independent_equals = hmac.compare_digest
02841 else:
02842 def _time_independent_equals(a, b):
02843 if len(a) != len(b):
02844 return False
02845 result = 0
02846 if isinstance(a[0], int):
02847 for x, y in zip(a, b):
02848 result |= x ^ y
02849 else:
02850 for x, y in zip(a, b):
02851 result |= ord(x) ^ ord(y)
02852 return result == 0
02853
02854
02855 def create_signed_value(secret, name, value, version=None, clock=None):
02856 if version is None:
02857 version = DEFAULT_SIGNED_VALUE_VERSION
02858 if clock is None:
02859 clock = time.time
02860 timestamp = utf8(str(int(clock())))
02861 value = base64.b64encode(utf8(value))
02862 if version == 1:
02863 signature = _create_signature_v1(secret, name, value, timestamp)
02864 value = b"|".join([value, timestamp, signature])
02865 return value
02866 elif version == 2:
02867
02868
02869
02870
02871
02872
02873
02874
02875
02876
02877
02878
02879
02880
02881 def format_field(s):
02882 return utf8("%d:" % len(s)) + utf8(s)
02883 to_sign = b"|".join([
02884 b"2|1:0",
02885 format_field(timestamp),
02886 format_field(name),
02887 format_field(value),
02888 b''])
02889 signature = _create_signature_v2(secret, to_sign)
02890 return to_sign + signature
02891 else:
02892 raise ValueError("Unsupported version %d" % version)
02893
02894
02895 _signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$")
02896
02897
02898 def decode_signed_value(secret, name, value, max_age_days=31, clock=None, min_version=None):
02899 if clock is None:
02900 clock = time.time
02901 if min_version is None:
02902 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
02903 if min_version > 2:
02904 raise ValueError("Unsupported min_version %d" % min_version)
02905 if not value:
02906 return None
02907
02908
02909
02910
02911 value = utf8(value)
02912 m = _signed_value_version_re.match(value)
02913 if m is None:
02914 version = 1
02915 else:
02916 try:
02917 version = int(m.group(1))
02918 if version > 999:
02919
02920
02921
02922
02923
02924
02925 version = 1
02926 except ValueError:
02927 version = 1
02928
02929 if version < min_version:
02930 return None
02931 if version == 1:
02932 return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
02933 elif version == 2:
02934 return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
02935 else:
02936 return None
02937
02938
02939 def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
02940 parts = utf8(value).split(b"|")
02941 if len(parts) != 3:
02942 return None
02943 signature = _create_signature_v1(secret, name, parts[0], parts[1])
02944 if not _time_independent_equals(parts[2], signature):
02945 gen_log.warning("Invalid cookie signature %r", value)
02946 return None
02947 timestamp = int(parts[1])
02948 if timestamp < clock() - max_age_days * 86400:
02949 gen_log.warning("Expired cookie %r", value)
02950 return None
02951 if timestamp > clock() + 31 * 86400:
02952
02953
02954
02955
02956
02957 gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
02958 return None
02959 if parts[1].startswith(b"0"):
02960 gen_log.warning("Tampered cookie %r", value)
02961 return None
02962 try:
02963 return base64.b64decode(parts[0])
02964 except Exception:
02965 return None
02966
02967
02968 def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
02969 def _consume_field(s):
02970 length, _, rest = s.partition(b':')
02971 n = int(length)
02972 field_value = rest[:n]
02973
02974
02975 if rest[n:n + 1] != b'|':
02976 raise ValueError("malformed v2 signed value field")
02977 rest = rest[n + 1:]
02978 return field_value, rest
02979 rest = value[2:]
02980 try:
02981 key_version, rest = _consume_field(rest)
02982 timestamp, rest = _consume_field(rest)
02983 name_field, rest = _consume_field(rest)
02984 value_field, rest = _consume_field(rest)
02985 except ValueError:
02986 return None
02987 passed_sig = rest
02988 signed_string = value[:-len(passed_sig)]
02989 expected_sig = _create_signature_v2(secret, signed_string)
02990 if not _time_independent_equals(passed_sig, expected_sig):
02991 return None
02992 if name_field != utf8(name):
02993 return None
02994 timestamp = int(timestamp)
02995 if timestamp < clock() - max_age_days * 86400:
02996
02997 return None
02998 try:
02999 return base64.b64decode(value_field)
03000 except Exception:
03001 return None
03002
03003
03004 def _create_signature_v1(secret, *parts):
03005 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
03006 for part in parts:
03007 hash.update(utf8(part))
03008 return utf8(hash.hexdigest())
03009
03010
03011 def _create_signature_v2(secret, s):
03012 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
03013 hash.update(utf8(s))
03014 return utf8(hash.hexdigest())
03015
03016
03017 def _unquote_or_none(s):
03018 """None-safe wrapper around url_unescape to handle unamteched optional
03019 groups correctly.
03020
03021 Note that args are passed as bytes so the handler can decide what
03022 encoding to use.
03023 """
03024 if s is None:
03025 return s
03026 return escape.url_unescape(s, encoding=None, plus=False)