00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """
00018 The Tornado web framework looks a bit like web.py (http://webpy.org/) or
00019 Google's webapp (http://code.google.com/appengine/docs/python/tools/webapp/),
00020 but with additional tools and optimizations to take advantage of the
00021 Tornado non-blocking web server and tools.
00022
00023 Here is the canonical "Hello, world" example app::
00024
00025 import tornado.ioloop
00026 import tornado.web
00027
00028 class MainHandler(tornado.web.RequestHandler):
00029 def get(self):
00030 self.write("Hello, world")
00031
00032 if __name__ == "__main__":
00033 application = tornado.web.Application([
00034 (r"/", MainHandler),
00035 ])
00036 application.listen(8888)
00037 tornado.ioloop.IOLoop.instance().start()
00038
00039 See the Tornado walkthrough on http://tornadoweb.org for more details
00040 and a good getting started guide.
00041
00042 Thread-safety notes
00043 -------------------
00044
00045 In general, methods on RequestHandler and elsewhere in tornado are not
00046 thread-safe. In particular, methods such as write(), finish(), and
00047 flush() must only be called from the main thread. If you use multiple
00048 threads it is important to use IOLoop.add_callback to transfer control
00049 back to the main thread before finishing the request.
00050 """
00051
00052 from __future__ import absolute_import, division, with_statement
00053
00054 import Cookie
00055 import base64
00056 import binascii
00057 import calendar
00058 import datetime
00059 import email.utils
00060 import functools
00061 import gzip
00062 import hashlib
00063 import hmac
00064 import httplib
00065 import itertools
00066 import logging
00067 import mimetypes
00068 import os.path
00069 import re
00070 import stat
00071 import sys
00072 import threading
00073 import time
00074 import tornado
00075 import traceback
00076 import types
00077 import urllib
00078 import urlparse
00079 import uuid
00080
00081 from tornado import escape
00082 from tornado import locale
00083 from tornado import stack_context
00084 from tornado import template
00085 from tornado.escape import utf8, _unicode
00086 from tornado.util import b, bytes_type, import_object, ObjectDict, raise_exc_info
00087
00088 try:
00089 from io import BytesIO
00090 except ImportError:
00091 from cStringIO import StringIO as BytesIO
00092
00093
00094 class RequestHandler(object):
00095 """Subclass this class and define get() or post() to make a handler.
00096
00097 If you want to support more methods than the standard GET/HEAD/POST, you
00098 should override the class variable SUPPORTED_METHODS in your
00099 RequestHandler class.
00100 """
00101 SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
00102 "OPTIONS")
00103
00104 _template_loaders = {}
00105 _template_loader_lock = threading.Lock()
00106
00107 def __init__(self, application, request, **kwargs):
00108 self.application = application
00109 self.request = request
00110 self._headers_written = False
00111 self._finished = False
00112 self._auto_finish = True
00113 self._transforms = None
00114 self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
00115 application.ui_methods.iteritems())
00116
00117
00118
00119
00120
00121 self.ui["_modules"] = ObjectDict((n, self._ui_module(n, m)) for n, m in
00122 application.ui_modules.iteritems())
00123 self.ui["modules"] = self.ui["_modules"]
00124 self.clear()
00125
00126 if getattr(self.request, "connection", None):
00127 self.request.connection.stream.set_close_callback(
00128 self.on_connection_close)
00129 self.initialize(**kwargs)
00130
00131 def initialize(self):
00132 """Hook for subclass initialization.
00133
00134 A dictionary passed as the third argument of a url spec will be
00135 supplied as keyword arguments to initialize().
00136
00137 Example::
00138
00139 class ProfileHandler(RequestHandler):
00140 def initialize(self, database):
00141 self.database = database
00142
00143 def get(self, username):
00144 ...
00145
00146 app = Application([
00147 (r'/user/(.*)', ProfileHandler, dict(database=database)),
00148 ])
00149 """
00150 pass
00151
00152 @property
00153 def settings(self):
00154 """An alias for `self.application.settings`."""
00155 return self.application.settings
00156
00157 def head(self, *args, **kwargs):
00158 raise HTTPError(405)
00159
00160 def get(self, *args, **kwargs):
00161 raise HTTPError(405)
00162
00163 def post(self, *args, **kwargs):
00164 raise HTTPError(405)
00165
00166 def delete(self, *args, **kwargs):
00167 raise HTTPError(405)
00168
00169 def patch(self, *args, **kwargs):
00170 raise HTTPError(405)
00171
00172 def put(self, *args, **kwargs):
00173 raise HTTPError(405)
00174
00175 def options(self, *args, **kwargs):
00176 raise HTTPError(405)
00177
00178 def prepare(self):
00179 """Called at the beginning of a request before `get`/`post`/etc.
00180
00181 Override this method to perform common initialization regardless
00182 of the request method.
00183 """
00184 pass
00185
00186 def on_finish(self):
00187 """Called after the end of a request.
00188
00189 Override this method to perform cleanup, logging, etc.
00190 This method is a counterpart to `prepare`. ``on_finish`` may
00191 not produce any output, as it is called after the response
00192 has been sent to the client.
00193 """
00194 pass
00195
00196 def on_connection_close(self):
00197 """Called in async handlers if the client closed the connection.
00198
00199 Override this to clean up resources associated with
00200 long-lived connections. Note that this method is called only if
00201 the connection was closed during asynchronous processing; if you
00202 need to do cleanup after every request override `on_finish`
00203 instead.
00204
00205 Proxies may keep a connection open for a time (perhaps
00206 indefinitely) after the client has gone away, so this method
00207 may not be called promptly after the end user closes their
00208 connection.
00209 """
00210 pass
00211
00212 def clear(self):
00213 """Resets all headers and content for this response."""
00214
00215
00216
00217
00218
00219 self._headers = {
00220 "Server": "TornadoServer/%s" % tornado.version,
00221 "Content-Type": "text/html; charset=UTF-8",
00222 }
00223 self._list_headers = []
00224 self.set_default_headers()
00225 if not self.request.supports_http_1_1():
00226 if self.request.headers.get("Connection") == "Keep-Alive":
00227 self.set_header("Connection", "Keep-Alive")
00228 self._write_buffer = []
00229 self._status_code = 200
00230
00231 def set_default_headers(self):
00232 """Override this to set HTTP headers at the beginning of the request.
00233
00234 For example, this is the place to set a custom ``Server`` header.
00235 Note that setting such headers in the normal flow of request
00236 processing may not do what you want, since headers may be reset
00237 during error handling.
00238 """
00239 pass
00240
00241 def set_status(self, status_code):
00242 """Sets the status code for our response."""
00243 assert status_code in httplib.responses
00244 self._status_code = status_code
00245
00246 def get_status(self):
00247 """Returns the status code for our response."""
00248 return self._status_code
00249
00250 def set_header(self, name, value):
00251 """Sets the given response header name and value.
00252
00253 If a datetime is given, we automatically format it according to the
00254 HTTP specification. If the value is not a string, we convert it to
00255 a string. All header values are then encoded as UTF-8.
00256 """
00257 self._headers[name] = self._convert_header_value(value)
00258
00259 def add_header(self, name, value):
00260 """Adds the given response header and value.
00261
00262 Unlike `set_header`, `add_header` may be called multiple times
00263 to return multiple values for the same header.
00264 """
00265 self._list_headers.append((name, self._convert_header_value(value)))
00266
00267 def clear_header(self, name):
00268 """Clears an outgoing header, undoing a previous `set_header` call.
00269
00270 Note that this method does not apply to multi-valued headers
00271 set by `add_header`.
00272 """
00273 if name in self._headers:
00274 del self._headers[name]
00275
00276 def _convert_header_value(self, value):
00277 if isinstance(value, bytes_type):
00278 pass
00279 elif isinstance(value, unicode):
00280 value = value.encode('utf-8')
00281 elif isinstance(value, (int, long)):
00282
00283 return str(value)
00284 elif isinstance(value, datetime.datetime):
00285 t = calendar.timegm(value.utctimetuple())
00286 return email.utils.formatdate(t, localtime=False, usegmt=True)
00287 else:
00288 raise TypeError("Unsupported header value %r" % value)
00289
00290
00291
00292 if len(value) > 4000 or re.search(b(r"[\x00-\x1f]"), value):
00293 raise ValueError("Unsafe header value %r", value)
00294 return value
00295
00296 _ARG_DEFAULT = []
00297
00298 def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
00299 """Returns the value of the argument with the given name.
00300
00301 If default is not provided, the argument is considered to be
00302 required, and we throw an HTTP 400 exception if it is missing.
00303
00304 If the argument appears in the url more than once, we return the
00305 last value.
00306
00307 The returned value is always unicode.
00308 """
00309 args = self.get_arguments(name, strip=strip)
00310 if not args:
00311 if default is self._ARG_DEFAULT:
00312 raise HTTPError(400, "Missing argument %s" % name)
00313 return default
00314 return args[-1]
00315
00316 def get_arguments(self, name, strip=True):
00317 """Returns a list of the arguments with the given name.
00318
00319 If the argument is not present, returns an empty list.
00320
00321 The returned values are always unicode.
00322 """
00323 values = []
00324 for v in self.request.arguments.get(name, []):
00325 v = self.decode_argument(v, name=name)
00326 if isinstance(v, unicode):
00327
00328
00329 v = re.sub(r"[\x00-\x08\x0e-\x1f]", " ", v)
00330 if strip:
00331 v = v.strip()
00332 values.append(v)
00333 return values
00334
00335 def decode_argument(self, value, name=None):
00336 """Decodes an argument from the request.
00337
00338 The argument has been percent-decoded and is now a byte string.
00339 By default, this method decodes the argument as utf-8 and returns
00340 a unicode string, but this may be overridden in subclasses.
00341
00342 This method is used as a filter for both get_argument() and for
00343 values extracted from the url and passed to get()/post()/etc.
00344
00345 The name of the argument is provided if known, but may be None
00346 (e.g. for unnamed groups in the url regex).
00347 """
00348 return _unicode(value)
00349
00350 @property
00351 def cookies(self):
00352 return self.request.cookies
00353
00354 def get_cookie(self, name, default=None):
00355 """Gets the value of the cookie with the given name, else default."""
00356 if self.request.cookies is not None and name in self.request.cookies:
00357 return self.request.cookies[name].value
00358 return default
00359
00360 def set_cookie(self, name, value, domain=None, expires=None, path="/",
00361 expires_days=None, **kwargs):
00362 """Sets the given cookie name/value with the given options.
00363
00364 Additional keyword arguments are set on the Cookie.Morsel
00365 directly.
00366 See http://docs.python.org/library/cookie.html#morsel-objects
00367 for available attributes.
00368 """
00369
00370 name = escape.native_str(name)
00371 value = escape.native_str(value)
00372 if re.search(r"[\x00-\x20]", name + value):
00373
00374 raise ValueError("Invalid cookie %r: %r" % (name, value))
00375 if not hasattr(self, "_new_cookie"):
00376 self._new_cookie = Cookie.SimpleCookie()
00377 if name in self._new_cookie:
00378 del self._new_cookie[name]
00379 self._new_cookie[name] = value
00380 morsel = self._new_cookie[name]
00381 if domain:
00382 morsel["domain"] = domain
00383 if expires_days is not None and not expires:
00384 expires = datetime.datetime.utcnow() + datetime.timedelta(
00385 days=expires_days)
00386 if expires:
00387 timestamp = calendar.timegm(expires.utctimetuple())
00388 morsel["expires"] = email.utils.formatdate(
00389 timestamp, localtime=False, usegmt=True)
00390 if path:
00391 morsel["path"] = path
00392 for k, v in kwargs.iteritems():
00393 if k == 'max_age':
00394 k = 'max-age'
00395 morsel[k] = v
00396
00397 def clear_cookie(self, name, path="/", domain=None):
00398 """Deletes the cookie with the given name."""
00399 expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
00400 self.set_cookie(name, value="", path=path, expires=expires,
00401 domain=domain)
00402
00403 def clear_all_cookies(self):
00404 """Deletes all the cookies the user sent with this request."""
00405 for name in self.request.cookies.iterkeys():
00406 self.clear_cookie(name)
00407
00408 def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
00409 """Signs and timestamps a cookie so it cannot be forged.
00410
00411 You must specify the ``cookie_secret`` setting in your Application
00412 to use this method. It should be a long, random sequence of bytes
00413 to be used as the HMAC secret for the signature.
00414
00415 To read a cookie set with this method, use `get_secure_cookie()`.
00416
00417 Note that the ``expires_days`` parameter sets the lifetime of the
00418 cookie in the browser, but is independent of the ``max_age_days``
00419 parameter to `get_secure_cookie`.
00420
00421 Secure cookies may contain arbitrary byte values, not just unicode
00422 strings (unlike regular cookies)
00423 """
00424 self.set_cookie(name, self.create_signed_value(name, value),
00425 expires_days=expires_days, **kwargs)
00426
00427 def create_signed_value(self, name, value):
00428 """Signs and timestamps a string so it cannot be forged.
00429
00430 Normally used via set_secure_cookie, but provided as a separate
00431 method for non-cookie uses. To decode a value not stored
00432 as a cookie use the optional value argument to get_secure_cookie.
00433 """
00434 self.require_setting("cookie_secret", "secure cookies")
00435 return create_signed_value(self.application.settings["cookie_secret"],
00436 name, value)
00437
00438 def get_secure_cookie(self, name, value=None, max_age_days=31):
00439 """Returns the given signed cookie if it validates, or None.
00440
00441 The decoded cookie value is returned as a byte string (unlike
00442 `get_cookie`).
00443 """
00444 self.require_setting("cookie_secret", "secure cookies")
00445 if value is None:
00446 value = self.get_cookie(name)
00447 return decode_signed_value(self.application.settings["cookie_secret"],
00448 name, value, max_age_days=max_age_days)
00449
00450 def redirect(self, url, permanent=False, status=None):
00451 """Sends a redirect to the given (optionally relative) URL.
00452
00453 If the ``status`` argument is specified, that value is used as the
00454 HTTP status code; otherwise either 301 (permanent) or 302
00455 (temporary) is chosen based on the ``permanent`` argument.
00456 The default is 302 (temporary).
00457 """
00458 if self._headers_written:
00459 raise Exception("Cannot redirect after headers have been written")
00460 if status is None:
00461 status = 301 if permanent else 302
00462 else:
00463 assert isinstance(status, int) and 300 <= status <= 399
00464 self.set_status(status)
00465
00466 url = re.sub(b(r"[\x00-\x20]+"), "", utf8(url))
00467 self.set_header("Location", urlparse.urljoin(utf8(self.request.uri),
00468 url))
00469 self.finish()
00470
00471 def write(self, chunk):
00472 """Writes the given chunk to the output buffer.
00473
00474 To write the output to the network, use the flush() method below.
00475
00476 If the given chunk is a dictionary, we write it as JSON and set
00477 the Content-Type of the response to be application/json.
00478 (if you want to send JSON as a different Content-Type, call
00479 set_header *after* calling write()).
00480
00481 Note that lists are not converted to JSON because of a potential
00482 cross-site security vulnerability. All JSON output should be
00483 wrapped in a dictionary. More details at
00484 http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
00485 """
00486 if self._finished:
00487 raise RuntimeError("Cannot write() after finish(). May be caused "
00488 "by using async operations without the "
00489 "@asynchronous decorator.")
00490 if isinstance(chunk, dict):
00491 chunk = escape.json_encode(chunk)
00492 self.set_header("Content-Type", "application/json; charset=UTF-8")
00493 chunk = utf8(chunk)
00494 self._write_buffer.append(chunk)
00495
00496 def render(self, template_name, **kwargs):
00497 """Renders the template with the given arguments as the response."""
00498 html = self.render_string(template_name, **kwargs)
00499
00500
00501 js_embed = []
00502 js_files = []
00503 css_embed = []
00504 css_files = []
00505 html_heads = []
00506 html_bodies = []
00507 for module in getattr(self, "_active_modules", {}).itervalues():
00508 embed_part = module.embedded_javascript()
00509 if embed_part:
00510 js_embed.append(utf8(embed_part))
00511 file_part = module.javascript_files()
00512 if file_part:
00513 if isinstance(file_part, (unicode, bytes_type)):
00514 js_files.append(file_part)
00515 else:
00516 js_files.extend(file_part)
00517 embed_part = module.embedded_css()
00518 if embed_part:
00519 css_embed.append(utf8(embed_part))
00520 file_part = module.css_files()
00521 if file_part:
00522 if isinstance(file_part, (unicode, bytes_type)):
00523 css_files.append(file_part)
00524 else:
00525 css_files.extend(file_part)
00526 head_part = module.html_head()
00527 if head_part:
00528 html_heads.append(utf8(head_part))
00529 body_part = module.html_body()
00530 if body_part:
00531 html_bodies.append(utf8(body_part))
00532
00533 def is_absolute(path):
00534 return any(path.startswith(x) for x in ["/", "http:", "https:"])
00535 if js_files:
00536
00537 paths = []
00538 unique_paths = set()
00539 for path in js_files:
00540 if not is_absolute(path):
00541 path = self.static_url(path)
00542 if path not in unique_paths:
00543 paths.append(path)
00544 unique_paths.add(path)
00545 js = ''.join('<script src="' + escape.xhtml_escape(p) +
00546 '" type="text/javascript"></script>'
00547 for p in paths)
00548 sloc = html.rindex(b('</body>'))
00549 html = html[:sloc] + utf8(js) + b('\n') + html[sloc:]
00550 if js_embed:
00551 js = b('<script type="text/javascript">\n//<![CDATA[\n') + \
00552 b('\n').join(js_embed) + b('\n//]]>\n</script>')
00553 sloc = html.rindex(b('</body>'))
00554 html = html[:sloc] + js + b('\n') + html[sloc:]
00555 if css_files:
00556 paths = []
00557 unique_paths = set()
00558 for path in css_files:
00559 if not is_absolute(path):
00560 path = self.static_url(path)
00561 if path not in unique_paths:
00562 paths.append(path)
00563 unique_paths.add(path)
00564 css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
00565 'type="text/css" rel="stylesheet"/>'
00566 for p in paths)
00567 hloc = html.index(b('</head>'))
00568 html = html[:hloc] + utf8(css) + b('\n') + html[hloc:]
00569 if css_embed:
00570 css = b('<style type="text/css">\n') + b('\n').join(css_embed) + \
00571 b('\n</style>')
00572 hloc = html.index(b('</head>'))
00573 html = html[:hloc] + css + b('\n') + html[hloc:]
00574 if html_heads:
00575 hloc = html.index(b('</head>'))
00576 html = html[:hloc] + b('').join(html_heads) + b('\n') + html[hloc:]
00577 if html_bodies:
00578 hloc = html.index(b('</body>'))
00579 html = html[:hloc] + b('').join(html_bodies) + b('\n') + html[hloc:]
00580 self.finish(html)
00581
00582 def render_string(self, template_name, **kwargs):
00583 """Generate the given template with the given arguments.
00584
00585 We return the generated string. To generate and write a template
00586 as a response, use render() above.
00587 """
00588
00589 template_path = self.get_template_path()
00590 if not template_path:
00591 frame = sys._getframe(0)
00592 web_file = frame.f_code.co_filename
00593 while frame.f_code.co_filename == web_file:
00594 frame = frame.f_back
00595 template_path = os.path.dirname(frame.f_code.co_filename)
00596 with RequestHandler._template_loader_lock:
00597 if template_path not in RequestHandler._template_loaders:
00598 loader = self.create_template_loader(template_path)
00599 RequestHandler._template_loaders[template_path] = loader
00600 else:
00601 loader = RequestHandler._template_loaders[template_path]
00602 t = loader.load(template_name)
00603 args = dict(
00604 handler=self,
00605 request=self.request,
00606 current_user=self.current_user,
00607 locale=self.locale,
00608 _=self.locale.translate,
00609 static_url=self.static_url,
00610 xsrf_form_html=self.xsrf_form_html,
00611 reverse_url=self.reverse_url
00612 )
00613 args.update(self.ui)
00614 args.update(kwargs)
00615 return t.generate(**args)
00616
00617 def create_template_loader(self, template_path):
00618 settings = self.application.settings
00619 if "template_loader" in settings:
00620 return settings["template_loader"]
00621 kwargs = {}
00622 if "autoescape" in settings:
00623
00624
00625 kwargs["autoescape"] = settings["autoescape"]
00626 return template.Loader(template_path, **kwargs)
00627
00628 def flush(self, include_footers=False, callback=None):
00629 """Flushes the current output buffer to the network.
00630
00631 The ``callback`` argument, if given, can be used for flow control:
00632 it will be run when all flushed data has been written to the socket.
00633 Note that only one flush callback can be outstanding at a time;
00634 if another flush occurs before the previous flush's callback
00635 has been run, the previous callback will be discarded.
00636 """
00637 if self.application._wsgi:
00638 raise Exception("WSGI applications do not support flush()")
00639
00640 chunk = b("").join(self._write_buffer)
00641 self._write_buffer = []
00642 if not self._headers_written:
00643 self._headers_written = True
00644 for transform in self._transforms:
00645 self._status_code, self._headers, chunk = \
00646 transform.transform_first_chunk(
00647 self._status_code, self._headers, chunk, include_footers)
00648 headers = self._generate_headers()
00649 else:
00650 for transform in self._transforms:
00651 chunk = transform.transform_chunk(chunk, include_footers)
00652 headers = b("")
00653
00654
00655 if self.request.method == "HEAD":
00656 if headers:
00657 self.request.write(headers, callback=callback)
00658 return
00659
00660 self.request.write(headers + chunk, callback=callback)
00661
00662 def finish(self, chunk=None):
00663 """Finishes this response, ending the HTTP request."""
00664 if self._finished:
00665 raise RuntimeError("finish() called twice. May be caused "
00666 "by using async operations without the "
00667 "@asynchronous decorator.")
00668
00669 if chunk is not None:
00670 self.write(chunk)
00671
00672
00673
00674 if not self._headers_written:
00675 if (self._status_code == 200 and
00676 self.request.method in ("GET", "HEAD") and
00677 "Etag" not in self._headers):
00678 etag = self.compute_etag()
00679 if etag is not None:
00680 self.set_header("Etag", etag)
00681 inm = self.request.headers.get("If-None-Match")
00682 if inm and inm.find(etag) != -1:
00683 self._write_buffer = []
00684 self.set_status(304)
00685 if self._status_code == 304:
00686 assert not self._write_buffer, "Cannot send body with 304"
00687 self._clear_headers_for_304()
00688 elif "Content-Length" not in self._headers:
00689 content_length = sum(len(part) for part in self._write_buffer)
00690 self.set_header("Content-Length", content_length)
00691
00692 if hasattr(self.request, "connection"):
00693
00694
00695
00696
00697 self.request.connection.stream.set_close_callback(None)
00698
00699 if not self.application._wsgi:
00700 self.flush(include_footers=True)
00701 self.request.finish()
00702 self._log()
00703 self._finished = True
00704 self.on_finish()
00705
00706 def send_error(self, status_code=500, **kwargs):
00707 """Sends the given HTTP error code to the browser.
00708
00709 If `flush()` has already been called, it is not possible to send
00710 an error, so this method will simply terminate the response.
00711 If output has been written but not yet flushed, it will be discarded
00712 and replaced with the error page.
00713
00714 Override `write_error()` to customize the error page that is returned.
00715 Additional keyword arguments are passed through to `write_error`.
00716 """
00717 if self._headers_written:
00718 logging.error("Cannot send error response after headers written")
00719 if not self._finished:
00720 self.finish()
00721 return
00722 self.clear()
00723 self.set_status(status_code)
00724 try:
00725 self.write_error(status_code, **kwargs)
00726 except Exception:
00727 logging.error("Uncaught exception in write_error", exc_info=True)
00728 if not self._finished:
00729 self.finish()
00730
00731 def write_error(self, status_code, **kwargs):
00732 """Override to implement custom error pages.
00733
00734 ``write_error`` may call `write`, `render`, `set_header`, etc
00735 to produce output as usual.
00736
00737 If this error was caused by an uncaught exception, an ``exc_info``
00738 triple will be available as ``kwargs["exc_info"]``. Note that this
00739 exception may not be the "current" exception for purposes of
00740 methods like ``sys.exc_info()`` or ``traceback.format_exc``.
00741
00742 For historical reasons, if a method ``get_error_html`` exists,
00743 it will be used instead of the default ``write_error`` implementation.
00744 ``get_error_html`` returned a string instead of producing output
00745 normally, and had different semantics for exception handling.
00746 Users of ``get_error_html`` are encouraged to convert their code
00747 to override ``write_error`` instead.
00748 """
00749 if hasattr(self, 'get_error_html'):
00750 if 'exc_info' in kwargs:
00751 exc_info = kwargs.pop('exc_info')
00752 kwargs['exception'] = exc_info[1]
00753 try:
00754
00755 raise_exc_info(exc_info)
00756 except Exception:
00757 self.finish(self.get_error_html(status_code, **kwargs))
00758 else:
00759 self.finish(self.get_error_html(status_code, **kwargs))
00760 return
00761 if self.settings.get("debug") and "exc_info" in kwargs:
00762
00763 self.set_header('Content-Type', 'text/plain')
00764 for line in traceback.format_exception(*kwargs["exc_info"]):
00765 self.write(line)
00766 self.finish()
00767 else:
00768 self.finish("<html><title>%(code)d: %(message)s</title>"
00769 "<body>%(code)d: %(message)s</body></html>" % {
00770 "code": status_code,
00771 "message": httplib.responses[status_code],
00772 })
00773
00774 @property
00775 def locale(self):
00776 """The local for the current session.
00777
00778 Determined by either get_user_locale, which you can override to
00779 set the locale based on, e.g., a user preference stored in a
00780 database, or get_browser_locale, which uses the Accept-Language
00781 header.
00782 """
00783 if not hasattr(self, "_locale"):
00784 self._locale = self.get_user_locale()
00785 if not self._locale:
00786 self._locale = self.get_browser_locale()
00787 assert self._locale
00788 return self._locale
00789
00790 def get_user_locale(self):
00791 """Override to determine the locale from the authenticated user.
00792
00793 If None is returned, we fall back to get_browser_locale().
00794
00795 This method should return a tornado.locale.Locale object,
00796 most likely obtained via a call like tornado.locale.get("en")
00797 """
00798 return None
00799
00800 def get_browser_locale(self, default="en_US"):
00801 """Determines the user's locale from Accept-Language header.
00802
00803 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
00804 """
00805 if "Accept-Language" in self.request.headers:
00806 languages = self.request.headers["Accept-Language"].split(",")
00807 locales = []
00808 for language in languages:
00809 parts = language.strip().split(";")
00810 if len(parts) > 1 and parts[1].startswith("q="):
00811 try:
00812 score = float(parts[1][2:])
00813 except (ValueError, TypeError):
00814 score = 0.0
00815 else:
00816 score = 1.0
00817 locales.append((parts[0], score))
00818 if locales:
00819 locales.sort(key=lambda (l, s): s, reverse=True)
00820 codes = [l[0] for l in locales]
00821 return locale.get(*codes)
00822 return locale.get(default)
00823
00824 @property
00825 def current_user(self):
00826 """The authenticated user for this request.
00827
00828 Determined by either get_current_user, which you can override to
00829 set the user based on, e.g., a cookie. If that method is not
00830 overridden, this method always returns None.
00831
00832 We lazy-load the current user the first time this method is called
00833 and cache the result after that.
00834 """
00835 if not hasattr(self, "_current_user"):
00836 self._current_user = self.get_current_user()
00837 return self._current_user
00838
00839 def get_current_user(self):
00840 """Override to determine the current user from, e.g., a cookie."""
00841 return None
00842
00843 def get_login_url(self):
00844 """Override to customize the login URL based on the request.
00845
00846 By default, we use the 'login_url' application setting.
00847 """
00848 self.require_setting("login_url", "@tornado.web.authenticated")
00849 return self.application.settings["login_url"]
00850
00851 def get_template_path(self):
00852 """Override to customize template path for each handler.
00853
00854 By default, we use the 'template_path' application setting.
00855 Return None to load templates relative to the calling file.
00856 """
00857 return self.application.settings.get("template_path")
00858
00859 @property
00860 def xsrf_token(self):
00861 """The XSRF-prevention token for the current user/session.
00862
00863 To prevent cross-site request forgery, we set an '_xsrf' cookie
00864 and include the same '_xsrf' value as an argument with all POST
00865 requests. If the two do not match, we reject the form submission
00866 as a potential forgery.
00867
00868 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
00869 """
00870 if not hasattr(self, "_xsrf_token"):
00871 token = self.get_cookie("_xsrf")
00872 if not token:
00873 token = binascii.b2a_hex(uuid.uuid4().bytes)
00874 expires_days = 30 if self.current_user else None
00875 self.set_cookie("_xsrf", token, expires_days=expires_days)
00876 self._xsrf_token = token
00877 return self._xsrf_token
00878
00879 def check_xsrf_cookie(self):
00880 """Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
00881
00882 To prevent cross-site request forgery, we set an '_xsrf'
00883 cookie and include the same value as a non-cookie
00884 field with all POST requests. If the two do not match, we
00885 reject the form submission as a potential forgery.
00886
00887 The _xsrf value may be set as either a form field named _xsrf
00888 or in a custom HTTP header named X-XSRFToken or X-CSRFToken
00889 (the latter is accepted for compatibility with Django).
00890
00891 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
00892
00893 Prior to release 1.1.1, this check was ignored if the HTTP header
00894 "X-Requested-With: XMLHTTPRequest" was present. This exception
00895 has been shown to be insecure and has been removed. For more
00896 information please see
00897 http://www.djangoproject.com/weblog/2011/feb/08/security/
00898 http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
00899 """
00900 token = (self.get_argument("_xsrf", None) or
00901 self.request.headers.get("X-Xsrftoken") or
00902 self.request.headers.get("X-Csrftoken"))
00903 if not token:
00904 raise HTTPError(403, "'_xsrf' argument missing from POST")
00905 if self.xsrf_token != token:
00906 raise HTTPError(403, "XSRF cookie does not match POST argument")
00907
00908 def xsrf_form_html(self):
00909 """An HTML <input/> element to be included with all POST forms.
00910
00911 It defines the _xsrf input value, which we check on all POST
00912 requests to prevent cross-site request forgery. If you have set
00913 the 'xsrf_cookies' application setting, you must include this
00914 HTML within all of your HTML forms.
00915
00916 See check_xsrf_cookie() above for more information.
00917 """
00918 return '<input type="hidden" name="_xsrf" value="' + \
00919 escape.xhtml_escape(self.xsrf_token) + '"/>'
00920
00921 def static_url(self, path, include_host=None):
00922 """Returns a static URL for the given relative static file path.
00923
00924 This method requires you set the 'static_path' setting in your
00925 application (which specifies the root directory of your static
00926 files).
00927
00928 We append ?v=<signature> to the returned URL, which makes our
00929 static file handler set an infinite expiration header on the
00930 returned content. The signature is based on the content of the
00931 file.
00932
00933 By default this method returns URLs relative to the current
00934 host, but if ``include_host`` is true the URL returned will be
00935 absolute. If this handler has an ``include_host`` attribute,
00936 that value will be used as the default for all `static_url`
00937 calls that do not pass ``include_host`` as a keyword argument.
00938 """
00939 self.require_setting("static_path", "static_url")
00940 static_handler_class = self.settings.get(
00941 "static_handler_class", StaticFileHandler)
00942
00943 if include_host is None:
00944 include_host = getattr(self, "include_host", False)
00945
00946 if include_host:
00947 base = self.request.protocol + "://" + self.request.host
00948 else:
00949 base = ""
00950 return base + static_handler_class.make_static_url(self.settings, path)
00951
00952 def async_callback(self, callback, *args, **kwargs):
00953 """Obsolete - catches exceptions from the wrapped function.
00954
00955 This function is unnecessary since Tornado 1.1.
00956 """
00957 if callback is None:
00958 return None
00959 if args or kwargs:
00960 callback = functools.partial(callback, *args, **kwargs)
00961
00962 def wrapper(*args, **kwargs):
00963 try:
00964 return callback(*args, **kwargs)
00965 except Exception, e:
00966 if self._headers_written:
00967 logging.error("Exception after headers written",
00968 exc_info=True)
00969 else:
00970 self._handle_request_exception(e)
00971 return wrapper
00972
00973 def require_setting(self, name, feature="this feature"):
00974 """Raises an exception if the given app setting is not defined."""
00975 if not self.application.settings.get(name):
00976 raise Exception("You must define the '%s' setting in your "
00977 "application to use %s" % (name, feature))
00978
00979 def reverse_url(self, name, *args):
00980 """Alias for `Application.reverse_url`."""
00981 return self.application.reverse_url(name, *args)
00982
00983 def compute_etag(self):
00984 """Computes the etag header to be used for this request.
00985
00986 May be overridden to provide custom etag implementations,
00987 or may return None to disable tornado's default etag support.
00988 """
00989 hasher = hashlib.sha1()
00990 for part in self._write_buffer:
00991 hasher.update(part)
00992 return '"%s"' % hasher.hexdigest()
00993
00994 def _stack_context_handle_exception(self, type, value, traceback):
00995 try:
00996
00997
00998
00999
01000 raise_exc_info((type, value, traceback))
01001 except Exception:
01002 self._handle_request_exception(value)
01003 return True
01004
01005 def _execute(self, transforms, *args, **kwargs):
01006 """Executes this request with the given output transforms."""
01007 self._transforms = transforms
01008 try:
01009 if self.request.method not in self.SUPPORTED_METHODS:
01010 raise HTTPError(405)
01011
01012
01013 if self.request.method not in ("GET", "HEAD", "OPTIONS") and \
01014 self.application.settings.get("xsrf_cookies"):
01015 self.check_xsrf_cookie()
01016 self.prepare()
01017 if not self._finished:
01018 args = [self.decode_argument(arg) for arg in args]
01019 kwargs = dict((k, self.decode_argument(v, name=k))
01020 for (k, v) in kwargs.iteritems())
01021 getattr(self, self.request.method.lower())(*args, **kwargs)
01022 if self._auto_finish and not self._finished:
01023 self.finish()
01024 except Exception, e:
01025 self._handle_request_exception(e)
01026
01027 def _generate_headers(self):
01028 lines = [utf8(self.request.version + " " +
01029 str(self._status_code) +
01030 " " + httplib.responses[self._status_code])]
01031 lines.extend([(utf8(n) + b(": ") + utf8(v)) for n, v in
01032 itertools.chain(self._headers.iteritems(), self._list_headers)])
01033 if hasattr(self, "_new_cookie"):
01034 for cookie in self._new_cookie.values():
01035 lines.append(utf8("Set-Cookie: " + cookie.OutputString(None)))
01036 return b("\r\n").join(lines) + b("\r\n\r\n")
01037
01038 def _log(self):
01039 """Logs the current request.
01040
01041 Sort of deprecated since this functionality was moved to the
01042 Application, but left in place for the benefit of existing apps
01043 that have overridden this method.
01044 """
01045 self.application.log_request(self)
01046
01047 def _request_summary(self):
01048 return self.request.method + " " + self.request.uri + \
01049 " (" + self.request.remote_ip + ")"
01050
01051 def _handle_request_exception(self, e):
01052 if isinstance(e, HTTPError):
01053 if e.log_message:
01054 format = "%d %s: " + e.log_message
01055 args = [e.status_code, self._request_summary()] + list(e.args)
01056 logging.warning(format, *args)
01057 if e.status_code not in httplib.responses:
01058 logging.error("Bad HTTP status code: %d", e.status_code)
01059 self.send_error(500, exc_info=sys.exc_info())
01060 else:
01061 self.send_error(e.status_code, exc_info=sys.exc_info())
01062 else:
01063 logging.error("Uncaught exception %s\n%r", self._request_summary(),
01064 self.request, exc_info=True)
01065 self.send_error(500, exc_info=sys.exc_info())
01066
01067 def _ui_module(self, name, module):
01068 def render(*args, **kwargs):
01069 if not hasattr(self, "_active_modules"):
01070 self._active_modules = {}
01071 if name not in self._active_modules:
01072 self._active_modules[name] = module(self)
01073 rendered = self._active_modules[name].render(*args, **kwargs)
01074 return rendered
01075 return render
01076
01077 def _ui_method(self, method):
01078 return lambda *args, **kwargs: method(self, *args, **kwargs)
01079
01080 def _clear_headers_for_304(self):
01081
01082
01083
01084
01085 headers = ["Allow", "Content-Encoding", "Content-Language",
01086 "Content-Length", "Content-MD5", "Content-Range",
01087 "Content-Type", "Last-Modified"]
01088 for h in headers:
01089 self.clear_header(h)
01090
01091
01092 def asynchronous(method):
01093 """Wrap request handler methods with this if they are asynchronous.
01094
01095 If this decorator is given, the response is not finished when the
01096 method returns. It is up to the request handler to call self.finish()
01097 to finish the HTTP request. Without this decorator, the request is
01098 automatically finished when the get() or post() method returns. ::
01099
01100 class MyRequestHandler(web.RequestHandler):
01101 @web.asynchronous
01102 def get(self):
01103 http = httpclient.AsyncHTTPClient()
01104 http.fetch("http://friendfeed.com/", self._on_download)
01105
01106 def _on_download(self, response):
01107 self.write("Downloaded!")
01108 self.finish()
01109
01110 """
01111 @functools.wraps(method)
01112 def wrapper(self, *args, **kwargs):
01113 if self.application._wsgi:
01114 raise Exception("@asynchronous is not supported for WSGI apps")
01115 self._auto_finish = False
01116 with stack_context.ExceptionStackContext(
01117 self._stack_context_handle_exception):
01118 return method(self, *args, **kwargs)
01119 return wrapper
01120
01121
01122 def removeslash(method):
01123 """Use this decorator to remove trailing slashes from the request path.
01124
01125 For example, a request to ``'/foo/'`` would redirect to ``'/foo'`` with this
01126 decorator. Your request handler mapping should use a regular expression
01127 like ``r'/foo/*'`` in conjunction with using the decorator.
01128 """
01129 @functools.wraps(method)
01130 def wrapper(self, *args, **kwargs):
01131 if self.request.path.endswith("/"):
01132 if self.request.method in ("GET", "HEAD"):
01133 uri = self.request.path.rstrip("/")
01134 if uri:
01135 if self.request.query:
01136 uri += "?" + self.request.query
01137 self.redirect(uri, permanent=True)
01138 return
01139 else:
01140 raise HTTPError(404)
01141 return method(self, *args, **kwargs)
01142 return wrapper
01143
01144
01145 def addslash(method):
01146 """Use this decorator to add a missing trailing slash to the request path.
01147
01148 For example, a request to '/foo' would redirect to '/foo/' with this
01149 decorator. Your request handler mapping should use a regular expression
01150 like r'/foo/?' in conjunction with using the decorator.
01151 """
01152 @functools.wraps(method)
01153 def wrapper(self, *args, **kwargs):
01154 if not self.request.path.endswith("/"):
01155 if self.request.method in ("GET", "HEAD"):
01156 uri = self.request.path + "/"
01157 if self.request.query:
01158 uri += "?" + self.request.query
01159 self.redirect(uri, permanent=True)
01160 return
01161 raise HTTPError(404)
01162 return method(self, *args, **kwargs)
01163 return wrapper
01164
01165
01166 class Application(object):
01167 """A collection of request handlers that make up a web application.
01168
01169 Instances of this class are callable and can be passed directly to
01170 HTTPServer to serve the application::
01171
01172 application = web.Application([
01173 (r"/", MainPageHandler),
01174 ])
01175 http_server = httpserver.HTTPServer(application)
01176 http_server.listen(8080)
01177 ioloop.IOLoop.instance().start()
01178
01179 The constructor for this class takes in a list of URLSpec objects
01180 or (regexp, request_class) tuples. When we receive requests, we
01181 iterate over the list in order and instantiate an instance of the
01182 first request class whose regexp matches the request path.
01183
01184 Each tuple can contain an optional third element, which should be a
01185 dictionary if it is present. That dictionary is passed as keyword
01186 arguments to the contructor of the handler. This pattern is used
01187 for the StaticFileHandler below (note that a StaticFileHandler
01188 can be installed automatically with the static_path setting described
01189 below)::
01190
01191 application = web.Application([
01192 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
01193 ])
01194
01195 We support virtual hosts with the add_handlers method, which takes in
01196 a host regular expression as the first argument::
01197
01198 application.add_handlers(r"www\.myhost\.com", [
01199 (r"/article/([0-9]+)", ArticleHandler),
01200 ])
01201
01202 You can serve static files by sending the static_path setting as a
01203 keyword argument. We will serve those files from the /static/ URI
01204 (this is configurable with the static_url_prefix setting),
01205 and we will serve /favicon.ico and /robots.txt from the same directory.
01206 A custom subclass of StaticFileHandler can be specified with the
01207 static_handler_class setting.
01208
01209 .. attribute:: settings
01210
01211 Additonal keyword arguments passed to the constructor are saved in the
01212 `settings` dictionary, and are often referred to in documentation as
01213 "application settings".
01214 """
01215 def __init__(self, handlers=None, default_host="", transforms=None,
01216 wsgi=False, **settings):
01217 if transforms is None:
01218 self.transforms = []
01219 if settings.get("gzip"):
01220 self.transforms.append(GZipContentEncoding)
01221 self.transforms.append(ChunkedTransferEncoding)
01222 else:
01223 self.transforms = transforms
01224 self.handlers = []
01225 self.named_handlers = {}
01226 self.default_host = default_host
01227 self.settings = settings
01228 self.ui_modules = {'linkify': _linkify,
01229 'xsrf_form_html': _xsrf_form_html,
01230 'Template': TemplateModule,
01231 }
01232 self.ui_methods = {}
01233 self._wsgi = wsgi
01234 self._load_ui_modules(settings.get("ui_modules", {}))
01235 self._load_ui_methods(settings.get("ui_methods", {}))
01236 if self.settings.get("static_path"):
01237 path = self.settings["static_path"]
01238 handlers = list(handlers or [])
01239 static_url_prefix = settings.get("static_url_prefix",
01240 "/static/")
01241 static_handler_class = settings.get("static_handler_class",
01242 StaticFileHandler)
01243 static_handler_args = settings.get("static_handler_args", {})
01244 static_handler_args['path'] = path
01245 for pattern in [re.escape(static_url_prefix) + r"(.*)",
01246 r"/(favicon\.ico)", r"/(robots\.txt)"]:
01247 handlers.insert(0, (pattern, static_handler_class,
01248 static_handler_args))
01249 if handlers:
01250 self.add_handlers(".*$", handlers)
01251
01252
01253 if self.settings.get("debug") and not wsgi:
01254 from tornado import autoreload
01255 autoreload.start()
01256
01257 def listen(self, port, address="", **kwargs):
01258 """Starts an HTTP server for this application on the given port.
01259
01260 This is a convenience alias for creating an HTTPServer object
01261 and calling its listen method. Keyword arguments not
01262 supported by HTTPServer.listen are passed to the HTTPServer
01263 constructor. For advanced uses (e.g. preforking), do not use
01264 this method; create an HTTPServer and call its bind/start
01265 methods directly.
01266
01267 Note that after calling this method you still need to call
01268 IOLoop.instance().start() to start the server.
01269 """
01270
01271
01272 from tornado.httpserver import HTTPServer
01273 server = HTTPServer(self, **kwargs)
01274 server.listen(port, address)
01275
01276 def add_handlers(self, host_pattern, host_handlers):
01277 """Appends the given handlers to our handler list.
01278
01279 Note that host patterns are processed sequentially in the
01280 order they were added, and only the first matching pattern is
01281 used. This means that all handlers for a given host must be
01282 added in a single add_handlers call.
01283 """
01284 if not host_pattern.endswith("$"):
01285 host_pattern += "$"
01286 handlers = []
01287
01288
01289
01290
01291
01292 if self.handlers and self.handlers[-1][0].pattern == '.*$':
01293 self.handlers.insert(-1, (re.compile(host_pattern), handlers))
01294 else:
01295 self.handlers.append((re.compile(host_pattern), handlers))
01296
01297 for spec in host_handlers:
01298 if type(spec) is type(()):
01299 assert len(spec) in (2, 3)
01300 pattern = spec[0]
01301 handler = spec[1]
01302
01303 if isinstance(handler, str):
01304
01305
01306 handler = import_object(handler)
01307
01308 if len(spec) == 3:
01309 kwargs = spec[2]
01310 else:
01311 kwargs = {}
01312 spec = URLSpec(pattern, handler, kwargs)
01313 handlers.append(spec)
01314 if spec.name:
01315 if spec.name in self.named_handlers:
01316 logging.warning(
01317 "Multiple handlers named %s; replacing previous value",
01318 spec.name)
01319 self.named_handlers[spec.name] = spec
01320
01321 def add_transform(self, transform_class):
01322 """Adds the given OutputTransform to our transform list."""
01323 self.transforms.append(transform_class)
01324
01325 def _get_host_handlers(self, request):
01326 host = request.host.lower().split(':')[0]
01327 for pattern, handlers in self.handlers:
01328 if pattern.match(host):
01329 return handlers
01330
01331 if "X-Real-Ip" not in request.headers:
01332 for pattern, handlers in self.handlers:
01333 if pattern.match(self.default_host):
01334 return handlers
01335 return None
01336
01337 def _load_ui_methods(self, methods):
01338 if type(methods) is types.ModuleType:
01339 self._load_ui_methods(dict((n, getattr(methods, n))
01340 for n in dir(methods)))
01341 elif isinstance(methods, list):
01342 for m in methods:
01343 self._load_ui_methods(m)
01344 else:
01345 for name, fn in methods.iteritems():
01346 if not name.startswith("_") and hasattr(fn, "__call__") \
01347 and name[0].lower() == name[0]:
01348 self.ui_methods[name] = fn
01349
01350 def _load_ui_modules(self, modules):
01351 if type(modules) is types.ModuleType:
01352 self._load_ui_modules(dict((n, getattr(modules, n))
01353 for n in dir(modules)))
01354 elif isinstance(modules, list):
01355 for m in modules:
01356 self._load_ui_modules(m)
01357 else:
01358 assert isinstance(modules, dict)
01359 for name, cls in modules.iteritems():
01360 try:
01361 if issubclass(cls, UIModule):
01362 self.ui_modules[name] = cls
01363 except TypeError:
01364 pass
01365
01366 def __call__(self, request):
01367 """Called by HTTPServer to execute the request."""
01368 transforms = [t(request) for t in self.transforms]
01369 handler = None
01370 args = []
01371 kwargs = {}
01372 handlers = self._get_host_handlers(request)
01373 if not handlers:
01374 handler = RedirectHandler(
01375 self, request, url="http://" + self.default_host + "/")
01376 else:
01377 for spec in handlers:
01378 match = spec.regex.match(request.path)
01379 if match:
01380 handler = spec.handler_class(self, request, **spec.kwargs)
01381 if spec.regex.groups:
01382
01383
01384 def unquote(s):
01385 if s is None:
01386 return s
01387 return escape.url_unescape(s, encoding=None)
01388
01389
01390
01391
01392
01393
01394 if spec.regex.groupindex:
01395 kwargs = dict(
01396 (str(k), unquote(v))
01397 for (k, v) in match.groupdict().iteritems())
01398 else:
01399 args = [unquote(s) for s in match.groups()]
01400 break
01401 if not handler:
01402 handler = ErrorHandler(self, request, status_code=404)
01403
01404
01405
01406 if self.settings.get("debug"):
01407 with RequestHandler._template_loader_lock:
01408 for loader in RequestHandler._template_loaders.values():
01409 loader.reset()
01410 StaticFileHandler.reset()
01411
01412 handler._execute(transforms, *args, **kwargs)
01413 return handler
01414
01415 def reverse_url(self, name, *args):
01416 """Returns a URL path for handler named `name`
01417
01418 The handler must be added to the application as a named URLSpec.
01419
01420 Args will be substituted for capturing groups in the URLSpec regex.
01421 They will be converted to strings if necessary, encoded as utf8,
01422 and url-escaped.
01423 """
01424 if name in self.named_handlers:
01425 return self.named_handlers[name].reverse(*args)
01426 raise KeyError("%s not found in named urls" % name)
01427
01428 def log_request(self, handler):
01429 """Writes a completed HTTP request to the logs.
01430
01431 By default writes to the python root logger. To change
01432 this behavior either subclass Application and override this method,
01433 or pass a function in the application settings dictionary as
01434 'log_function'.
01435 """
01436 if "log_function" in self.settings:
01437 self.settings["log_function"](handler)
01438 return
01439 if handler.get_status() < 400:
01440 log_method = logging.info
01441 elif handler.get_status() < 500:
01442 log_method = logging.warning
01443 else:
01444 log_method = logging.error
01445 request_time = 1000.0 * handler.request.request_time()
01446 log_method("%d %s %.2fms", handler.get_status(),
01447 handler._request_summary(), request_time)
01448
01449
01450 class HTTPError(Exception):
01451 """An exception that will turn into an HTTP error response."""
01452 def __init__(self, status_code, log_message=None, *args):
01453 self.status_code = status_code
01454 self.log_message = log_message
01455 self.args = args
01456
01457 def __str__(self):
01458 message = "HTTP %d: %s" % (
01459 self.status_code, httplib.responses[self.status_code])
01460 if self.log_message:
01461 return message + " (" + (self.log_message % self.args) + ")"
01462 else:
01463 return message
01464
01465
01466 class ErrorHandler(RequestHandler):
01467 """Generates an error response with status_code for all requests."""
01468 def initialize(self, status_code):
01469 self.set_status(status_code)
01470
01471 def prepare(self):
01472 raise HTTPError(self._status_code)
01473
01474
01475 class RedirectHandler(RequestHandler):
01476 """Redirects the client to the given URL for all GET requests.
01477
01478 You should provide the keyword argument "url" to the handler, e.g.::
01479
01480 application = web.Application([
01481 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
01482 ])
01483 """
01484 def initialize(self, url, permanent=True):
01485 self._url = url
01486 self._permanent = permanent
01487
01488 def get(self):
01489 self.redirect(self._url, permanent=self._permanent)
01490
01491
01492 class StaticFileHandler(RequestHandler):
01493 """A simple handler that can serve static content from a directory.
01494
01495 To map a path to this handler for a static data directory /var/www,
01496 you would add a line to your application like::
01497
01498 application = web.Application([
01499 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
01500 ])
01501
01502 The local root directory of the content should be passed as the "path"
01503 argument to the handler.
01504
01505 To support aggressive browser caching, if the argument "v" is given
01506 with the path, we set an infinite HTTP expiration header. So, if you
01507 want browsers to cache a file indefinitely, send them to, e.g.,
01508 /static/images/myimage.png?v=xxx. Override ``get_cache_time`` method for
01509 more fine-grained cache control.
01510 """
01511 CACHE_MAX_AGE = 86400 * 365 * 10
01512
01513 _static_hashes = {}
01514 _lock = threading.Lock()
01515
01516 def initialize(self, path, default_filename=None):
01517 self.root = os.path.abspath(path) + os.path.sep
01518 self.default_filename = default_filename
01519
01520 @classmethod
01521 def reset(cls):
01522 with cls._lock:
01523 cls._static_hashes = {}
01524
01525 def head(self, path):
01526 self.get(path, include_body=False)
01527
01528 def get(self, path, include_body=True):
01529 path = self.parse_url_path(path)
01530 abspath = os.path.abspath(os.path.join(self.root, path))
01531
01532
01533 if not (abspath + os.path.sep).startswith(self.root):
01534 raise HTTPError(403, "%s is not in root static directory", path)
01535 if os.path.isdir(abspath) and self.default_filename is not None:
01536
01537
01538
01539 if not self.request.path.endswith("/"):
01540 self.redirect(self.request.path + "/")
01541 return
01542 abspath = os.path.join(abspath, self.default_filename)
01543 if not os.path.exists(abspath):
01544 raise HTTPError(404)
01545 if not os.path.isfile(abspath):
01546 raise HTTPError(403, "%s is not a file", path)
01547
01548 stat_result = os.stat(abspath)
01549 modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
01550
01551 self.set_header("Last-Modified", modified)
01552
01553 mime_type, encoding = mimetypes.guess_type(abspath)
01554 if mime_type:
01555 self.set_header("Content-Type", mime_type)
01556
01557 cache_time = self.get_cache_time(path, modified, mime_type)
01558
01559 if cache_time > 0:
01560 self.set_header("Expires", datetime.datetime.utcnow() + \
01561 datetime.timedelta(seconds=cache_time))
01562 self.set_header("Cache-Control", "max-age=" + str(cache_time))
01563 else:
01564 self.set_header("Cache-Control", "public")
01565
01566 self.set_extra_headers(path)
01567
01568
01569
01570 ims_value = self.request.headers.get("If-Modified-Since")
01571 if ims_value is not None:
01572 date_tuple = email.utils.parsedate(ims_value)
01573 if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
01574 if if_since >= modified:
01575 self.set_status(304)
01576 return
01577
01578 with open(abspath, "rb") as file:
01579 data = file.read()
01580 hasher = hashlib.sha1()
01581 hasher.update(data)
01582 self.set_header("Etag", '"%s"' % hasher.hexdigest())
01583 if include_body:
01584 self.write(data)
01585 else:
01586 assert self.request.method == "HEAD"
01587 self.set_header("Content-Length", len(data))
01588
01589 def set_extra_headers(self, path):
01590 """For subclass to add extra headers to the response"""
01591 pass
01592
01593 def get_cache_time(self, path, modified, mime_type):
01594 """Override to customize cache control behavior.
01595
01596 Return a positive number of seconds to trigger aggressive caching or 0
01597 to mark resource as cacheable, only.
01598
01599 By default returns cache expiry of 10 years for resources requested
01600 with "v" argument.
01601 """
01602 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
01603
01604 @classmethod
01605 def make_static_url(cls, settings, path):
01606 """Constructs a versioned url for the given path.
01607
01608 This method may be overridden in subclasses (but note that it is
01609 a class method rather than an instance method).
01610
01611 ``settings`` is the `Application.settings` dictionary. ``path``
01612 is the static path being requested. The url returned should be
01613 relative to the current host.
01614 """
01615 static_url_prefix = settings.get('static_url_prefix', '/static/')
01616 version_hash = cls.get_version(settings, path)
01617 if version_hash:
01618 return static_url_prefix + path + "?v=" + version_hash
01619 return static_url_prefix + path
01620
01621 @classmethod
01622 def get_version(cls, settings, path):
01623 """Generate the version string to be used in static URLs.
01624
01625 This method may be overridden in subclasses (but note that it
01626 is a class method rather than a static method). The default
01627 implementation uses a hash of the file's contents.
01628
01629 ``settings`` is the `Application.settings` dictionary and ``path``
01630 is the relative location of the requested asset on the filesystem.
01631 The returned value should be a string, or ``None`` if no version
01632 could be determined.
01633 """
01634 abs_path = os.path.join(settings["static_path"], path)
01635 with cls._lock:
01636 hashes = cls._static_hashes
01637 if abs_path not in hashes:
01638 try:
01639 f = open(abs_path, "rb")
01640 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
01641 f.close()
01642 except Exception:
01643 logging.error("Could not open static file %r", path)
01644 hashes[abs_path] = None
01645 hsh = hashes.get(abs_path)
01646 if hsh:
01647 return hsh[:5]
01648 return None
01649
01650 def parse_url_path(self, url_path):
01651 """Converts a static URL path into a filesystem path.
01652
01653 ``url_path`` is the path component of the URL with
01654 ``static_url_prefix`` removed. The return value should be
01655 filesystem path relative to ``static_path``.
01656 """
01657 if os.path.sep != "/":
01658 url_path = url_path.replace("/", os.path.sep)
01659 return url_path
01660
01661
01662 class FallbackHandler(RequestHandler):
01663 """A RequestHandler that wraps another HTTP server callback.
01664
01665 The fallback is a callable object that accepts an HTTPRequest,
01666 such as an Application or tornado.wsgi.WSGIContainer. This is most
01667 useful to use both tornado RequestHandlers and WSGI in the same server.
01668 Typical usage::
01669
01670 wsgi_app = tornado.wsgi.WSGIContainer(
01671 django.core.handlers.wsgi.WSGIHandler())
01672 application = tornado.web.Application([
01673 (r"/foo", FooHandler),
01674 (r".*", FallbackHandler, dict(fallback=wsgi_app),
01675 ])
01676 """
01677 def initialize(self, fallback):
01678 self.fallback = fallback
01679
01680 def prepare(self):
01681 self.fallback(self.request)
01682 self._finished = True
01683
01684
01685 class OutputTransform(object):
01686 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
01687
01688 A new transform instance is created for every request. See the
01689 ChunkedTransferEncoding example below if you want to implement a
01690 new Transform.
01691 """
01692 def __init__(self, request):
01693 pass
01694
01695 def transform_first_chunk(self, status_code, headers, chunk, finishing):
01696 return status_code, headers, chunk
01697
01698 def transform_chunk(self, chunk, finishing):
01699 return chunk
01700
01701
01702 class GZipContentEncoding(OutputTransform):
01703 """Applies the gzip content encoding to the response.
01704
01705 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
01706 """
01707 CONTENT_TYPES = set([
01708 "text/plain", "text/html", "text/css", "text/xml", "application/javascript",
01709 "application/x-javascript", "application/xml", "application/atom+xml",
01710 "text/javascript", "application/json", "application/xhtml+xml"])
01711 MIN_LENGTH = 5
01712
01713 def __init__(self, request):
01714 self._gzipping = request.supports_http_1_1() and \
01715 "gzip" in request.headers.get("Accept-Encoding", "")
01716
01717 def transform_first_chunk(self, status_code, headers, chunk, finishing):
01718 if self._gzipping:
01719 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
01720 self._gzipping = (ctype in self.CONTENT_TYPES) and \
01721 (not finishing or len(chunk) >= self.MIN_LENGTH) and \
01722 (finishing or "Content-Length" not in headers) and \
01723 ("Content-Encoding" not in headers)
01724 if self._gzipping:
01725 headers["Content-Encoding"] = "gzip"
01726 self._gzip_value = BytesIO()
01727 self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
01728 chunk = self.transform_chunk(chunk, finishing)
01729 if "Content-Length" in headers:
01730 headers["Content-Length"] = str(len(chunk))
01731 return status_code, headers, chunk
01732
01733 def transform_chunk(self, chunk, finishing):
01734 if self._gzipping:
01735 self._gzip_file.write(chunk)
01736 if finishing:
01737 self._gzip_file.close()
01738 else:
01739 self._gzip_file.flush()
01740 chunk = self._gzip_value.getvalue()
01741 self._gzip_value.truncate(0)
01742 self._gzip_value.seek(0)
01743 return chunk
01744
01745
01746 class ChunkedTransferEncoding(OutputTransform):
01747 """Applies the chunked transfer encoding to the response.
01748
01749 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
01750 """
01751 def __init__(self, request):
01752 self._chunking = request.supports_http_1_1()
01753
01754 def transform_first_chunk(self, status_code, headers, chunk, finishing):
01755
01756
01757 if self._chunking and status_code != 304:
01758
01759 if "Content-Length" in headers or "Transfer-Encoding" in headers:
01760 self._chunking = False
01761 else:
01762 headers["Transfer-Encoding"] = "chunked"
01763 chunk = self.transform_chunk(chunk, finishing)
01764 return status_code, headers, chunk
01765
01766 def transform_chunk(self, block, finishing):
01767 if self._chunking:
01768
01769
01770 if block:
01771 block = utf8("%x" % len(block)) + b("\r\n") + block + b("\r\n")
01772 if finishing:
01773 block += b("0\r\n\r\n")
01774 return block
01775
01776
01777 def authenticated(method):
01778 """Decorate methods with this to require that the user be logged in."""
01779 @functools.wraps(method)
01780 def wrapper(self, *args, **kwargs):
01781 if not self.current_user:
01782 if self.request.method in ("GET", "HEAD"):
01783 url = self.get_login_url()
01784 if "?" not in url:
01785 if urlparse.urlsplit(url).scheme:
01786
01787 next_url = self.request.full_url()
01788 else:
01789 next_url = self.request.uri
01790 url += "?" + urllib.urlencode(dict(next=next_url))
01791 self.redirect(url)
01792 return
01793 raise HTTPError(403)
01794 return method(self, *args, **kwargs)
01795 return wrapper
01796
01797
01798 class UIModule(object):
01799 """A UI re-usable, modular unit on a page.
01800
01801 UI modules often execute additional queries, and they can include
01802 additional CSS and JavaScript that will be included in the output
01803 page, which is automatically inserted on page render.
01804 """
01805 def __init__(self, handler):
01806 self.handler = handler
01807 self.request = handler.request
01808 self.ui = handler.ui
01809 self.current_user = handler.current_user
01810 self.locale = handler.locale
01811
01812 def render(self, *args, **kwargs):
01813 """Overridden in subclasses to return this module's output."""
01814 raise NotImplementedError()
01815
01816 def embedded_javascript(self):
01817 """Returns a JavaScript string that will be embedded in the page."""
01818 return None
01819
01820 def javascript_files(self):
01821 """Returns a list of JavaScript files required by this module."""
01822 return None
01823
01824 def embedded_css(self):
01825 """Returns a CSS string that will be embedded in the page."""
01826 return None
01827
01828 def css_files(self):
01829 """Returns a list of CSS files required by this module."""
01830 return None
01831
01832 def html_head(self):
01833 """Returns a CSS string that will be put in the <head/> element"""
01834 return None
01835
01836 def html_body(self):
01837 """Returns an HTML string that will be put in the <body/> element"""
01838 return None
01839
01840 def render_string(self, path, **kwargs):
01841 """Renders a template and returns it as a string."""
01842 return self.handler.render_string(path, **kwargs)
01843
01844
01845 class _linkify(UIModule):
01846 def render(self, text, **kwargs):
01847 return escape.linkify(text, **kwargs)
01848
01849
01850 class _xsrf_form_html(UIModule):
01851 def render(self):
01852 return self.handler.xsrf_form_html()
01853
01854
01855 class TemplateModule(UIModule):
01856 """UIModule that simply renders the given template.
01857
01858 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
01859 but the module version gets its own namespace (with kwargs passed to
01860 Template()) instead of inheriting the outer template's namespace.
01861
01862 Templates rendered through this module also get access to UIModule's
01863 automatic javascript/css features. Simply call set_resources
01864 inside the template and give it keyword arguments corresponding to
01865 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
01866 Note that these resources are output once per template file, not once
01867 per instantiation of the template, so they must not depend on
01868 any arguments to the template.
01869 """
01870 def __init__(self, handler):
01871 super(TemplateModule, self).__init__(handler)
01872
01873 self._resource_list = []
01874 self._resource_dict = {}
01875
01876 def render(self, path, **kwargs):
01877 def set_resources(**kwargs):
01878 if path not in self._resource_dict:
01879 self._resource_list.append(kwargs)
01880 self._resource_dict[path] = kwargs
01881 else:
01882 if self._resource_dict[path] != kwargs:
01883 raise ValueError("set_resources called with different "
01884 "resources for the same template")
01885 return ""
01886 return self.render_string(path, set_resources=set_resources,
01887 **kwargs)
01888
01889 def _get_resources(self, key):
01890 return (r[key] for r in self._resource_list if key in r)
01891
01892 def embedded_javascript(self):
01893 return "\n".join(self._get_resources("embedded_javascript"))
01894
01895 def javascript_files(self):
01896 result = []
01897 for f in self._get_resources("javascript_files"):
01898 if isinstance(f, (unicode, bytes_type)):
01899 result.append(f)
01900 else:
01901 result.extend(f)
01902 return result
01903
01904 def embedded_css(self):
01905 return "\n".join(self._get_resources("embedded_css"))
01906
01907 def css_files(self):
01908 result = []
01909 for f in self._get_resources("css_files"):
01910 if isinstance(f, (unicode, bytes_type)):
01911 result.append(f)
01912 else:
01913 result.extend(f)
01914 return result
01915
01916 def html_head(self):
01917 return "".join(self._get_resources("html_head"))
01918
01919 def html_body(self):
01920 return "".join(self._get_resources("html_body"))
01921
01922
01923 class URLSpec(object):
01924 """Specifies mappings between URLs and handlers."""
01925 def __init__(self, pattern, handler_class, kwargs=None, name=None):
01926 """Creates a URLSpec.
01927
01928 Parameters:
01929
01930 pattern: Regular expression to be matched. Any groups in the regex
01931 will be passed in to the handler's get/post/etc methods as
01932 arguments.
01933
01934 handler_class: RequestHandler subclass to be invoked.
01935
01936 kwargs (optional): A dictionary of additional arguments to be passed
01937 to the handler's constructor.
01938
01939 name (optional): A name for this handler. Used by
01940 Application.reverse_url.
01941 """
01942 if not pattern.endswith('$'):
01943 pattern += '$'
01944 self.regex = re.compile(pattern)
01945 assert len(self.regex.groupindex) in (0, self.regex.groups), \
01946 ("groups in url regexes must either be all named or all "
01947 "positional: %r" % self.regex.pattern)
01948 self.handler_class = handler_class
01949 self.kwargs = kwargs or {}
01950 self.name = name
01951 self._path, self._group_count = self._find_groups()
01952
01953 def _find_groups(self):
01954 """Returns a tuple (reverse string, group count) for a url.
01955
01956 For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
01957 would return ('/%s/%s/', 2).
01958 """
01959 pattern = self.regex.pattern
01960 if pattern.startswith('^'):
01961 pattern = pattern[1:]
01962 if pattern.endswith('$'):
01963 pattern = pattern[:-1]
01964
01965 if self.regex.groups != pattern.count('('):
01966
01967
01968 return (None, None)
01969
01970 pieces = []
01971 for fragment in pattern.split('('):
01972 if ')' in fragment:
01973 paren_loc = fragment.index(')')
01974 if paren_loc >= 0:
01975 pieces.append('%s' + fragment[paren_loc + 1:])
01976 else:
01977 pieces.append(fragment)
01978
01979 return (''.join(pieces), self.regex.groups)
01980
01981 def reverse(self, *args):
01982 assert self._path is not None, \
01983 "Cannot reverse url regex " + self.regex.pattern
01984 assert len(args) == self._group_count, "required number of arguments "\
01985 "not found"
01986 if not len(args):
01987 return self._path
01988 converted_args = []
01989 for a in args:
01990 if not isinstance(a, (unicode, bytes_type)):
01991 a = str(a)
01992 converted_args.append(escape.url_escape(utf8(a)))
01993 return self._path % tuple(converted_args)
01994
01995 url = URLSpec
01996
01997
01998 def _time_independent_equals(a, b):
01999 if len(a) != len(b):
02000 return False
02001 result = 0
02002 if type(a[0]) is int:
02003 for x, y in zip(a, b):
02004 result |= x ^ y
02005 else:
02006 for x, y in zip(a, b):
02007 result |= ord(x) ^ ord(y)
02008 return result == 0
02009
02010
02011 def create_signed_value(secret, name, value):
02012 timestamp = utf8(str(int(time.time())))
02013 value = base64.b64encode(utf8(value))
02014 signature = _create_signature(secret, name, value, timestamp)
02015 value = b("|").join([value, timestamp, signature])
02016 return value
02017
02018
02019 def decode_signed_value(secret, name, value, max_age_days=31):
02020 if not value:
02021 return None
02022 parts = utf8(value).split(b("|"))
02023 if len(parts) != 3:
02024 return None
02025 signature = _create_signature(secret, name, parts[0], parts[1])
02026 if not _time_independent_equals(parts[2], signature):
02027 logging.warning("Invalid cookie signature %r", value)
02028 return None
02029 timestamp = int(parts[1])
02030 if timestamp < time.time() - max_age_days * 86400:
02031 logging.warning("Expired cookie %r", value)
02032 return None
02033 if timestamp > time.time() + 31 * 86400:
02034
02035
02036
02037
02038
02039 logging.warning("Cookie timestamp in future; possible tampering %r", value)
02040 return None
02041 if parts[1].startswith(b("0")):
02042 logging.warning("Tampered cookie %r", value)
02043 try:
02044 return base64.b64decode(parts[0])
02045 except Exception:
02046 return None
02047
02048
02049 def _create_signature(secret, *parts):
02050 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
02051 for part in parts:
02052 hash.update(utf8(part))
02053 return utf8(hash.hexdigest())