web.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
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  # python 3
00090 except ImportError:
00091     from cStringIO import StringIO as BytesIO  # python 2
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 = {}  # {path: template.BaseLoader}
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  # will be set in _execute
00114         self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
00115                      application.ui_methods.iteritems())
00116         # UIModules are available as both `modules` and `_modules` in the
00117         # template namespace.  Historically only `modules` was available
00118         # but could be clobbered by user additions to the namespace.
00119         # The template {% module %} directive looks in `_modules` to avoid
00120         # possible conflicts.
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         # Check since connection is not available in WSGI
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         # The performance cost of tornado.httputil.HTTPHeaders is significant
00215         # (slowing down a benchmark with a trivial handler by more than 10%),
00216         # and its case-normalization is not generally necessary for
00217         # headers we generate on the server side, so use a plain dict
00218         # and list instead.
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             # return immediately since we know the converted value will be safe
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         # If \n is allowed into the header, it is possible to inject
00290         # additional headers or split the request. Also cap length to
00291         # prevent obviously erroneous values.
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                 # Get rid of any weird control chars (unless decoding gave
00328                 # us bytes, in which case leave it alone)
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         # The cookie library only accepts type str, in both python 2 and 3
00370         name = escape.native_str(name)
00371         value = escape.native_str(value)
00372         if re.search(r"[\x00-\x20]", name + value):
00373             # Don't let us accidentally inject bad stuff
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         # Remove whitespace
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         # Insert the additional JS and CSS added by the modules on the page
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             # Maintain order of JavaScript files given by modules
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         # If no template_path is specified, use the path of the calling file
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             # autoescape=None means "no escaping", so we have to be sure
00624             # to only pass this kwarg if the user asked for it.
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         # Ignore the chunk and only write the headers for HEAD requests
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         # Automatically support ETags and add the Content-Length header if
00673         # we have not flushed any content yet.
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             # Now that the request is finished, clear the callback we
00694             # set on the IOStream (which would otherwise prevent the
00695             # garbage collection of the RequestHandler when there
00696             # are keepalive connections)
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                     # Put the traceback into sys.exc_info()
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             # in debug mode, try to send a traceback
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             # For historical reasons _handle_request_exception only takes
00997             # the exception value instead of the full triple,
00998             # so re-raise the exception to ensure that it's in
00999             # sys.exc_info()
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             # If XSRF cookies are turned on, reject form submissions without
01012             # the proper cookie
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         # 304 responses should not contain entity headers (defined in
01082         # http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1)
01083         # not explicitly allowed by
01084         # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
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:  # don't try to redirect '/' to ''
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         # Automatically reload modified modules
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         # import is here rather than top level because HTTPServer
01271         # is not importable on appengine
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         # The handlers with the wildcard host_pattern are a special
01288         # case - they're added in the constructor but should have lower
01289         # precedence than the more-precise handlers added later.
01290         # If a wildcard handler group exists, it should always be last
01291         # in the list, so insert new groups just before it.
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                     # import the Module and instantiate the class
01305                     # Must be a fully qualified name (module.ClassName)
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         # Look for default host if not behind load balancer (for debugging)
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                         # None-safe wrapper around url_unescape to handle
01383                         # unmatched optional groups correctly
01384                         def unquote(s):
01385                             if s is None:
01386                                 return s
01387                             return escape.url_unescape(s, encoding=None)
01388                         # Pass matched groups to the handler.  Since
01389                         # match.groups() includes both named and unnamed groups,
01390                         # we want to use either groups or groupdict but not both.
01391                         # Note that args are passed as bytes so the handler can
01392                         # decide what encoding to use.
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         # In debug mode, re-compile templates and reload static files on every
01405         # request so you don't need to restart to see changes
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  # 10 years
01512 
01513     _static_hashes = {}
01514     _lock = threading.Lock()  # protects _static_hashes
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         # os.path.abspath strips a trailing /
01532         # it needs to be temporarily added back for requests to root/
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             # need to look at the request.path here for when path is empty
01537             # but there is some prefix to the path that was already
01538             # trimmed by the routing
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         # Check the If-Modified-Since, and don't send the result if the
01569         # content has not been modified
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         # 304 responses have no body (not even a zero-length body), and so
01756         # should not have either Content-Length or Transfer-Encoding headers.
01757         if self._chunking and status_code != 304:
01758             # No need to chunk the output if a Content-Length is specified
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             # Don't write out empty chunks because that means END-OF-STREAM
01769             # with chunked encoding
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                         # if login url is absolute, make next absolute too
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         # keep resources in both a list and a dict to preserve order
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             # The pattern is too complicated for our simplistic matching,
01967             # so we can't support reversing it.
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:  # python3 byte strings
02003         for x, y in zip(a, b):
02004             result |= x ^ y
02005     else:  # python2
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         # _cookie_signature does not hash a delimiter between the
02035         # parts of the cookie, so an attacker could transfer trailing
02036         # digits from the payload to the timestamp without altering the
02037         # signature.  For backwards compatibility, sanity-check timestamp
02038         # here instead of modifying _cookie_signature.
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())


roswww
Author(s): Jonathan Mace
autogenerated on Thu Jan 2 2014 11:53:30