3 # Copyright 2009 Facebook
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
17 """The Tornado web framework.
19 The Tornado web framework looks a bit like web.py (http://webpy.org/) or
20 Google's webapp (http://code.google.com/appengine/docs/python/tools/webapp/),
21 but with additional tools and optimizations to take advantage of the
22 Tornado non-blocking web server and tools.
24 Here is the canonical "Hello, world" example app:
26 import tornado.httpserver
30 class MainHandler(tornado.web.RequestHandler):
32 self.write("Hello, world")
34 if __name__ == "__main__":
35 application = tornado.web.Application([
38 http_server = tornado.httpserver.HTTPServer(application)
39 http_server.listen(8888)
40 tornado.ioloop.IOLoop.instance().start()
42 See the Tornado walkthrough on GitHub for more details and a good
43 getting started guide.
73 class RequestHandler(object):
74 """Subclass this class and define get() or post() to make a handler.
76 If you want to support more methods than the standard GET/HEAD/POST, you
77 should override the class variable SUPPORTED_METHODS in your
80 SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PUT")
82 def __init__(self, application, request, transforms=None):
83 self.application = application
84 self.request = request
85 self._headers_written = False
86 self._finished = False
87 self._auto_finish = True
88 self._transforms = transforms or []
89 self.ui = _O((n, self._ui_method(m)) for n, m in
90 application.ui_methods.iteritems())
91 self.ui["modules"] = _O((n, self._ui_module(n, m)) for n, m in
92 application.ui_modules.iteritems())
94 # Check since connection is not available in WSGI
95 if hasattr(self.request, "connection"):
96 self.request.connection.stream.set_close_callback(
97 self.on_connection_close)
101 return self.application.settings
103 def head(self, *args, **kwargs):
106 def get(self, *args, **kwargs):
109 def post(self, *args, **kwargs):
112 def delete(self, *args, **kwargs):
115 def put(self, *args, **kwargs):
119 """Called before the actual handler method.
121 Useful to override in a handler if you want a common bottleneck for
122 all of your requests.
126 def on_connection_close(self):
127 """Called in async handlers if the client closed the connection.
129 You may override this to clean up resources associated with
130 long-lived connections.
132 Note that the select()-based implementation of IOLoop does not detect
133 closed connections and so this method will not be called until
134 you try (and fail) to produce some output. The epoll- and kqueue-
135 based implementations should detect closed connections even while
141 """Resets all headers and content for this response."""
143 "Server": "TornadoServer/0.1",
144 "Content-Type": "text/html; charset=UTF-8",
146 if not self.request.supports_http_1_1():
147 if self.request.headers.get("Connection") == "Keep-Alive":
148 self.set_header("Connection", "Keep-Alive")
149 self._write_buffer = []
150 self._status_code = 200
152 def set_status(self, status_code):
153 """Sets the status code for our response."""
154 assert status_code in httplib.responses
155 self._status_code = status_code
157 def set_header(self, name, value):
158 """Sets the given response header name and value.
160 If a datetime is given, we automatically format it according to the
161 HTTP specification. If the value is not a string, we convert it to
162 a string. All header values are then encoded as UTF-8.
164 if isinstance(value, datetime.datetime):
165 t = calendar.timegm(value.utctimetuple())
166 value = email.utils.formatdate(t, localtime=False, usegmt=True)
167 elif isinstance(value, int) or isinstance(value, long):
171 # If \n is allowed into the header, it is possible to inject
172 # additional headers or split the request. Also cap length to
173 # prevent obviously erroneous values.
174 safe_value = re.sub(r"[\x00-\x1f]", " ", value)[:4000]
175 if safe_value != value:
176 raise ValueError("Unsafe header value %r", value)
177 self._headers[name] = value
180 def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
181 """Returns the value of the argument with the given name.
183 If default is not provided, the argument is considered to be
184 required, and we throw an HTTP 404 exception if it is missing.
186 If the argument appears in the url more than once, we return the
189 The returned value is always unicode.
191 args = self.get_arguments(name, strip=strip)
193 if default is self._ARG_DEFAULT:
194 raise HTTPError(404, "Missing argument %s" % name)
198 def get_arguments(self, name, strip=True):
199 """Returns a list of the arguments with the given name.
201 If the argument is not present, returns an empty list.
203 The returned values are always unicode.
205 values = self.request.arguments.get(name, [])
206 # Get rid of any weird control chars
207 values = [re.sub(r"[\x00-\x08\x0e-\x1f]", " ", x) for x in values]
208 values = [_unicode(x) for x in values]
210 values = [x.strip() for x in values]
216 """A dictionary of Cookie.Morsel objects."""
217 if not hasattr(self, "_cookies"):
218 self._cookies = Cookie.BaseCookie()
219 if "Cookie" in self.request.headers:
221 self._cookies.load(self.request.headers["Cookie"])
223 self.clear_all_cookies()
226 def get_cookie(self, name, default=None):
227 """Gets the value of the cookie with the given name, else default."""
228 if name in self.cookies:
229 return self.cookies[name].value
232 def set_cookie(self, name, value, domain=None, expires=None, path="/",
233 expires_days=None, **kwargs):
234 """Sets the given cookie name/value with the given options.
236 Additional keyword arguments are set on the Cookie.Morsel
238 See http://docs.python.org/library/cookie.html#morsel-objects
239 for available attributes.
243 if re.search(r"[\x00-\x20]", name + value):
244 # Don't let us accidentally inject bad stuff
245 raise ValueError("Invalid cookie %r: %r" % (name, value))
246 if not hasattr(self, "_new_cookies"):
247 self._new_cookies = []
248 new_cookie = Cookie.BaseCookie()
249 self._new_cookies.append(new_cookie)
250 new_cookie[name] = value
252 new_cookie[name]["domain"] = domain
253 if expires_days is not None and not expires:
254 expires = datetime.datetime.utcnow() + datetime.timedelta(
257 timestamp = calendar.timegm(expires.utctimetuple())
258 new_cookie[name]["expires"] = email.utils.formatdate(
259 timestamp, localtime=False, usegmt=True)
261 new_cookie[name]["path"] = path
262 for k, v in kwargs.iteritems():
263 new_cookie[name][k] = v
265 def clear_cookie(self, name, path="/", domain=None):
266 """Deletes the cookie with the given name."""
267 expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
268 self.set_cookie(name, value="", path=path, expires=expires,
271 def clear_all_cookies(self):
272 """Deletes all the cookies the user sent with this request."""
273 for name in self.cookies.iterkeys():
274 self.clear_cookie(name)
276 def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
277 """Signs and timestamps a cookie so it cannot be forged.
279 You must specify the 'cookie_secret' setting in your Application
280 to use this method. It should be a long, random sequence of bytes
281 to be used as the HMAC secret for the signature.
283 To read a cookie set with this method, use get_secure_cookie().
285 timestamp = str(int(time.time()))
286 value = base64.b64encode(value)
287 signature = self._cookie_signature(name, value, timestamp)
288 value = "|".join([value, timestamp, signature])
289 self.set_cookie(name, value, expires_days=expires_days, **kwargs)
291 def get_secure_cookie(self, name, include_name=True, value=None):
292 """Returns the given signed cookie if it validates, or None.
294 In older versions of Tornado (0.1 and 0.2), we did not include the
295 name of the cookie in the cookie signature. To read these old-style
296 cookies, pass include_name=False to this method. Otherwise, all
297 attempts to read old-style cookies will fail (and you may log all
298 your users out whose cookies were written with a previous Tornado
301 if value is None: value = self.get_cookie(name)
302 if not value: return None
303 parts = value.split("|")
304 if len(parts) != 3: return None
306 signature = self._cookie_signature(name, parts[0], parts[1])
308 signature = self._cookie_signature(parts[0], parts[1])
309 if not _time_independent_equals(parts[2], signature):
310 logging.warning("Invalid cookie signature %r", value)
312 timestamp = int(parts[1])
313 if timestamp < time.time() - 31 * 86400:
314 logging.warning("Expired cookie %r", value)
317 return base64.b64decode(parts[0])
321 def _cookie_signature(self, *parts):
322 self.require_setting("cookie_secret", "secure cookies")
323 hash = hmac.new(self.application.settings["cookie_secret"],
324 digestmod=hashlib.sha1)
325 for part in parts: hash.update(part)
326 return hash.hexdigest()
328 def redirect(self, url, permanent=False):
329 """Sends a redirect to the given (optionally relative) URL."""
330 if self._headers_written:
331 raise Exception("Cannot redirect after headers have been written")
332 self.set_status(301 if permanent else 302)
334 url = re.sub(r"[\x00-\x20]+", "", _utf8(url))
335 self.set_header("Location", urlparse.urljoin(self.request.uri, url))
338 def write(self, chunk):
339 """Writes the given chunk to the output buffer.
341 To write the output to the network, use the flush() method below.
343 If the given chunk is a dictionary, we write it as JSON and set
344 the Content-Type of the response to be text/javascript.
346 assert not self._finished
347 if isinstance(chunk, dict):
348 chunk = escape.json_encode(chunk)
349 self.set_header("Content-Type", "text/javascript; charset=UTF-8")
351 self._write_buffer.append(chunk)
353 def render(self, template_name, **kwargs):
354 """Renders the template with the given arguments as the response."""
355 html = self.render_string(template_name, **kwargs)
357 # Insert the additional JS and CSS added by the modules on the page
364 for module in getattr(self, "_active_modules", {}).itervalues():
365 embed_part = module.embedded_javascript()
366 if embed_part: js_embed.append(_utf8(embed_part))
367 file_part = module.javascript_files()
369 if isinstance(file_part, basestring):
370 js_files.append(file_part)
372 js_files.extend(file_part)
373 embed_part = module.embedded_css()
374 if embed_part: css_embed.append(_utf8(embed_part))
375 file_part = module.css_files()
377 if isinstance(file_part, basestring):
378 css_files.append(file_part)
380 css_files.extend(file_part)
381 head_part = module.html_head()
382 if head_part: html_heads.append(_utf8(head_part))
383 body_part = module.html_body()
384 if body_part: html_bodies.append(_utf8(body_part))
386 # Maintain order of JavaScript files given by modules
389 for path in js_files:
390 if not path.startswith("/") and not path.startswith("http:"):
391 path = self.static_url(path)
392 if path not in unique_paths:
394 unique_paths.add(path)
395 js = ''.join('<script src="' + escape.xhtml_escape(p) +
396 '" type="text/javascript"></script>'
398 sloc = html.rindex('</body>')
399 html = html[:sloc] + js + '\n' + html[sloc:]
401 js = '<script type="text/javascript">\n//<![CDATA[\n' + \
402 '\n'.join(js_embed) + '\n//]]>\n</script>'
403 sloc = html.rindex('</body>')
404 html = html[:sloc] + js + '\n' + html[sloc:]
407 for path in css_files:
408 if not path.startswith("/") and not path.startswith("http:"):
409 paths.add(self.static_url(path))
412 css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
413 'type="text/css" rel="stylesheet"/>'
415 hloc = html.index('</head>')
416 html = html[:hloc] + css + '\n' + html[hloc:]
418 css = '<style type="text/css">\n' + '\n'.join(css_embed) + \
420 hloc = html.index('</head>')
421 html = html[:hloc] + css + '\n' + html[hloc:]
423 hloc = html.index('</head>')
424 html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]
426 hloc = html.index('</body>')
427 html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]
430 def render_string(self, template_name, **kwargs):
431 """Generate the given template with the given arguments.
433 We return the generated string. To generate and write a template
434 as a response, use render() above.
436 # If no template_path is specified, use the path of the calling file
437 template_path = self.get_template_path()
438 if not template_path:
439 frame = sys._getframe(0)
440 web_file = frame.f_code.co_filename
441 while frame.f_code.co_filename == web_file:
443 template_path = os.path.dirname(frame.f_code.co_filename)
444 if not getattr(RequestHandler, "_templates", None):
445 RequestHandler._templates = {}
446 if template_path not in RequestHandler._templates:
447 loader = self.application.settings.get("template_loader") or\
448 template.Loader(template_path)
449 RequestHandler._templates[template_path] = loader
450 t = RequestHandler._templates[template_path].load(template_name)
453 request=self.request,
454 current_user=self.current_user,
456 _=self.locale.translate,
457 static_url=self.static_url,
458 xsrf_form_html=self.xsrf_form_html,
459 reverse_url=self.application.reverse_url
463 return t.generate(**args)
465 def flush(self, include_footers=False):
466 """Flushes the current output buffer to the nextwork."""
467 if self.application._wsgi:
468 raise Exception("WSGI applications do not support flush()")
470 chunk = "".join(self._write_buffer)
471 self._write_buffer = []
472 if not self._headers_written:
473 self._headers_written = True
474 for transform in self._transforms:
475 self._headers, chunk = transform.transform_first_chunk(
476 self._headers, chunk, include_footers)
477 headers = self._generate_headers()
479 for transform in self._transforms:
480 chunk = transform.transform_chunk(chunk, include_footers)
483 # Ignore the chunk and only write the headers for HEAD requests
484 if self.request.method == "HEAD":
485 if headers: self.request.write(headers)
489 self.request.write(headers + chunk)
491 def finish(self, chunk=None):
492 """Finishes this response, ending the HTTP request."""
493 assert not self._finished
494 if chunk is not None: self.write(chunk)
496 # Automatically support ETags and add the Content-Length header if
497 # we have not flushed any content yet.
498 if not self._headers_written:
499 if (self._status_code == 200 and self.request.method == "GET" and
500 "Etag" not in self._headers):
501 hasher = hashlib.sha1()
502 for part in self._write_buffer:
504 etag = '"%s"' % hasher.hexdigest()
505 inm = self.request.headers.get("If-None-Match")
506 if inm and inm.find(etag) != -1:
507 self._write_buffer = []
510 self.set_header("Etag", etag)
511 if "Content-Length" not in self._headers:
512 content_length = sum(len(part) for part in self._write_buffer)
513 self.set_header("Content-Length", content_length)
515 if hasattr(self.request, "connection"):
516 # Now that the request is finished, clear the callback we
517 # set on the IOStream (which would otherwise prevent the
518 # garbage collection of the RequestHandler when there
519 # are keepalive connections)
520 self.request.connection.stream.set_close_callback(None)
522 if not self.application._wsgi:
523 self.flush(include_footers=True)
524 self.request.finish()
526 self._finished = True
528 def send_error(self, status_code=500, **kwargs):
529 """Sends the given HTTP error code to the browser.
531 We also send the error HTML for the given error code as returned by
532 get_error_html. Override that method if you want custom error pages
533 for your application.
535 if self._headers_written:
536 logging.error("Cannot send error response after headers written")
537 if not self._finished:
541 self.set_status(status_code)
542 message = self.get_error_html(status_code, **kwargs)
545 def get_error_html(self, status_code, **kwargs):
546 """Override to implement custom error pages.
548 If this error was caused by an uncaught exception, the
549 exception object can be found in kwargs e.g. kwargs['exception']
551 return "<html><title>%(code)d: %(message)s</title>" \
552 "<body>%(code)d: %(message)s</body></html>" % {
554 "message": httplib.responses[status_code],
559 """The local for the current session.
561 Determined by either get_user_locale, which you can override to
562 set the locale based on, e.g., a user preference stored in a
563 database, or get_browser_locale, which uses the Accept-Language
566 if not hasattr(self, "_locale"):
567 self._locale = self.get_user_locale()
569 self._locale = self.get_browser_locale()
573 def get_user_locale(self):
574 """Override to determine the locale from the authenticated user.
576 If None is returned, we use the Accept-Language header.
580 def get_browser_locale(self, default="en_US"):
581 """Determines the user's locale from Accept-Language header.
583 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
585 if "Accept-Language" in self.request.headers:
586 languages = self.request.headers["Accept-Language"].split(",")
588 for language in languages:
589 parts = language.strip().split(";")
590 if len(parts) > 1 and parts[1].startswith("q="):
592 score = float(parts[1][2:])
593 except (ValueError, TypeError):
597 locales.append((parts[0], score))
599 locales.sort(key=lambda (l, s): s, reverse=True)
600 codes = [l[0] for l in locales]
601 return locale.get(*codes)
602 return locale.get(default)
605 def current_user(self):
606 """The authenticated user for this request.
608 Determined by either get_current_user, which you can override to
609 set the user based on, e.g., a cookie. If that method is not
610 overridden, this method always returns None.
612 We lazy-load the current user the first time this method is called
613 and cache the result after that.
615 if not hasattr(self, "_current_user"):
616 self._current_user = self.get_current_user()
617 return self._current_user
619 def get_current_user(self):
620 """Override to determine the current user from, e.g., a cookie."""
623 def get_login_url(self):
624 """Override to customize the login URL based on the request.
626 By default, we use the 'login_url' application setting.
628 self.require_setting("login_url", "@tornado.web.authenticated")
629 return self.application.settings["login_url"]
631 def get_template_path(self):
632 """Override to customize template path for each handler.
634 By default, we use the 'template_path' application setting.
635 Return None to load templates relative to the calling file.
637 return self.application.settings.get("template_path")
640 def xsrf_token(self):
641 """The XSRF-prevention token for the current user/session.
643 To prevent cross-site request forgery, we set an '_xsrf' cookie
644 and include the same '_xsrf' value as an argument with all POST
645 requests. If the two do not match, we reject the form submission
646 as a potential forgery.
648 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
650 if not hasattr(self, "_xsrf_token"):
651 token = self.get_cookie("_xsrf")
653 token = binascii.b2a_hex(uuid.uuid4().bytes)
654 expires_days = 30 if self.current_user else None
655 self.set_cookie("_xsrf", token, expires_days=expires_days)
656 self._xsrf_token = token
657 return self._xsrf_token
659 def check_xsrf_cookie(self):
660 """Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
662 To prevent cross-site request forgery, we set an '_xsrf' cookie
663 and include the same '_xsrf' value as an argument with all POST
664 requests. If the two do not match, we reject the form submission
665 as a potential forgery.
667 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
669 if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
671 token = self.get_argument("_xsrf", None)
673 raise HTTPError(403, "'_xsrf' argument missing from POST")
674 if self.xsrf_token != token:
675 raise HTTPError(403, "XSRF cookie does not match POST argument")
677 def xsrf_form_html(self):
678 """An HTML <input/> element to be included with all POST forms.
680 It defines the _xsrf input value, which we check on all POST
681 requests to prevent cross-site request forgery. If you have set
682 the 'xsrf_cookies' application setting, you must include this
683 HTML within all of your HTML forms.
685 See check_xsrf_cookie() above for more information.
687 return '<input type="hidden" name="_xsrf" value="' + \
688 escape.xhtml_escape(self.xsrf_token) + '"/>'
690 def static_url(self, path):
691 """Returns a static URL for the given relative static file path.
693 This method requires you set the 'static_path' setting in your
694 application (which specifies the root directory of your static
697 We append ?v=<signature> to the returned URL, which makes our
698 static file handler set an infinite expiration header on the
699 returned content. The signature is based on the content of the
702 If this handler has a "include_host" attribute, we include the
703 full host for every static URL, including the "http://". Set
704 this attribute for handlers whose output needs non-relative static
707 self.require_setting("static_path", "static_url")
708 if not hasattr(RequestHandler, "_static_hashes"):
709 RequestHandler._static_hashes = {}
710 hashes = RequestHandler._static_hashes
711 if path not in hashes:
713 f = open(os.path.join(
714 self.application.settings["static_path"], path))
715 hashes[path] = hashlib.md5(f.read()).hexdigest()
718 logging.error("Could not open static file %r", path)
720 base = self.request.protocol + "://" + self.request.host \
721 if getattr(self, "include_host", False) else ""
722 static_url_prefix = self.settings.get('static_url_prefix', '/static/')
724 return base + static_url_prefix + path + "?v=" + hashes[path][:5]
726 return base + static_url_prefix + path
728 def async_callback(self, callback, *args, **kwargs):
729 """Wrap callbacks with this if they are used on asynchronous requests.
731 Catches exceptions and properly finishes the request.
736 callback = functools.partial(callback, *args, **kwargs)
737 def wrapper(*args, **kwargs):
739 return callback(*args, **kwargs)
741 if self._headers_written:
742 logging.error("Exception after headers written",
745 self._handle_request_exception(e)
748 def require_setting(self, name, feature="this feature"):
749 """Raises an exception if the given app setting is not defined."""
750 if not self.application.settings.get(name):
751 raise Exception("You must define the '%s' setting in your "
752 "application to use %s" % (name, feature))
754 def reverse_url(self, name, *args):
755 return self.application.reverse_url(name, *args)
757 def _execute(self, transforms, *args, **kwargs):
758 """Executes this request with the given output transforms."""
759 self._transforms = transforms
761 if self.request.method not in self.SUPPORTED_METHODS:
763 # If XSRF cookies are turned on, reject form submissions without
765 if self.request.method == "POST" and \
766 self.application.settings.get("xsrf_cookies"):
767 self.check_xsrf_cookie()
769 if not self._finished:
770 getattr(self, self.request.method.lower())(*args, **kwargs)
771 if self._auto_finish and not self._finished:
774 self._handle_request_exception(e)
776 def _generate_headers(self):
777 lines = [self.request.version + " " + str(self._status_code) + " " +
778 httplib.responses[self._status_code]]
779 lines.extend(["%s: %s" % (n, v) for n, v in self._headers.iteritems()])
780 for cookie_dict in getattr(self, "_new_cookies", []):
781 for cookie in cookie_dict.values():
782 lines.append("Set-Cookie: " + cookie.OutputString(None))
783 return "\r\n".join(lines) + "\r\n\r\n"
786 if self._status_code < 400:
787 log_method = logging.info
788 elif self._status_code < 500:
789 log_method = logging.warning
791 log_method = logging.error
792 request_time = 1000.0 * self.request.request_time()
793 log_method("%d %s %.2fms", self._status_code,
794 self._request_summary(), request_time)
796 def _request_summary(self):
797 return self.request.method + " " + self.request.uri + " (" + \
798 self.request.remote_ip + ")"
800 def _handle_request_exception(self, e):
801 if isinstance(e, HTTPError):
803 format = "%d %s: " + e.log_message
804 args = [e.status_code, self._request_summary()] + list(e.args)
805 logging.warning(format, *args)
806 if e.status_code not in httplib.responses:
807 logging.error("Bad HTTP status code: %d", e.status_code)
808 self.send_error(500, exception=e)
810 self.send_error(e.status_code, exception=e)
812 logging.error("Uncaught exception %s\n%r", self._request_summary(),
813 self.request, exc_info=e)
814 self.send_error(500, exception=e)
816 def _ui_module(self, name, module):
817 def render(*args, **kwargs):
818 if not hasattr(self, "_active_modules"):
819 self._active_modules = {}
820 if name not in self._active_modules:
821 self._active_modules[name] = module(self)
822 rendered = self._active_modules[name].render(*args, **kwargs)
826 def _ui_method(self, method):
827 return lambda *args, **kwargs: method(self, *args, **kwargs)
830 def asynchronous(method):
831 """Wrap request handler methods with this if they are asynchronous.
833 If this decorator is given, the response is not finished when the
834 method returns. It is up to the request handler to call self.finish()
835 to finish the HTTP request. Without this decorator, the request is
836 automatically finished when the get() or post() method returns.
838 class MyRequestHandler(web.RequestHandler):
841 http = httpclient.AsyncHTTPClient()
842 http.fetch("http://friendfeed.com/", self._on_download)
844 def _on_download(self, response):
845 self.write("Downloaded!")
849 @functools.wraps(method)
850 def wrapper(self, *args, **kwargs):
851 if self.application._wsgi:
852 raise Exception("@asynchronous is not supported for WSGI apps")
853 self._auto_finish = False
854 return method(self, *args, **kwargs)
858 def removeslash(method):
859 """Use this decorator to remove trailing slashes from the request path.
861 For example, a request to '/foo/' would redirect to '/foo' with this
862 decorator. Your request handler mapping should use a regular expression
863 like r'/foo/*' in conjunction with using the decorator.
865 @functools.wraps(method)
866 def wrapper(self, *args, **kwargs):
867 if self.request.path.endswith("/"):
868 if self.request.method == "GET":
869 uri = self.request.path.rstrip("/")
870 if self.request.query: uri += "?" + self.request.query
874 return method(self, *args, **kwargs)
878 def addslash(method):
879 """Use this decorator to add a missing trailing slash to the request path.
881 For example, a request to '/foo' would redirect to '/foo/' with this
882 decorator. Your request handler mapping should use a regular expression
883 like r'/foo/?' in conjunction with using the decorator.
885 @functools.wraps(method)
886 def wrapper(self, *args, **kwargs):
887 if not self.request.path.endswith("/"):
888 if self.request.method == "GET":
889 uri = self.request.path + "/"
890 if self.request.query: uri += "?" + self.request.query
894 return method(self, *args, **kwargs)
898 class Application(object):
899 """A collection of request handlers that make up a web application.
901 Instances of this class are callable and can be passed directly to
902 HTTPServer to serve the application:
904 application = web.Application([
905 (r"/", MainPageHandler),
907 http_server = httpserver.HTTPServer(application)
908 http_server.listen(8080)
909 ioloop.IOLoop.instance().start()
911 The constructor for this class takes in a list of URLSpec objects
912 or (regexp, request_class) tuples. When we receive requests, we
913 iterate over the list in order and instantiate an instance of the
914 first request class whose regexp matches the request path.
916 Each tuple can contain an optional third element, which should be a
917 dictionary if it is present. That dictionary is passed as keyword
918 arguments to the contructor of the handler. This pattern is used
919 for the StaticFileHandler below:
921 application = web.Application([
922 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
925 We support virtual hosts with the add_handlers method, which takes in
926 a host regular expression as the first argument:
928 application.add_handlers(r"www\.myhost\.com", [
929 (r"/article/([0-9]+)", ArticleHandler),
932 You can serve static files by sending the static_path setting as a
933 keyword argument. We will serve those files from the /static/ URI
934 (this is configurable with the static_url_prefix setting),
935 and we will serve /favicon.ico and /robots.txt from the same directory.
937 def __init__(self, handlers=None, default_host="", transforms=None,
938 wsgi=False, **settings):
939 if transforms is None:
941 if settings.get("gzip"):
942 self.transforms.append(GZipContentEncoding)
943 self.transforms.append(ChunkedTransferEncoding)
945 self.transforms = transforms
947 self.named_handlers = {}
948 self.default_host = default_host
949 self.settings = settings
953 self._load_ui_modules(settings.get("ui_modules", {}))
954 self._load_ui_methods(settings.get("ui_methods", {}))
955 if self.settings.get("static_path"):
956 path = self.settings["static_path"]
957 handlers = list(handlers or [])
958 static_url_prefix = settings.get("static_url_prefix",
961 (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
963 (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
964 (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
966 if handlers: self.add_handlers(".*$", handlers)
968 # Automatically reload modified modules
969 if self.settings.get("debug") and not wsgi:
973 def add_handlers(self, host_pattern, host_handlers):
974 """Appends the given handlers to our handler list."""
975 if not host_pattern.endswith("$"):
978 # The handlers with the wildcard host_pattern are a special
979 # case - they're added in the constructor but should have lower
980 # precedence than the more-precise handlers added later.
981 # If a wildcard handler group exists, it should always be last
982 # in the list, so insert new groups just before it.
983 if self.handlers and self.handlers[-1][0].pattern == '.*$':
984 self.handlers.insert(-1, (re.compile(host_pattern), handlers))
986 self.handlers.append((re.compile(host_pattern), handlers))
988 for spec in host_handlers:
989 if type(spec) is type(()):
990 assert len(spec) in (2, 3)
997 spec = URLSpec(pattern, handler, kwargs)
998 handlers.append(spec)
1000 if spec.name in self.named_handlers:
1002 "Multiple handlers named %s; replacing previous value",
1004 self.named_handlers[spec.name] = spec
1006 def add_transform(self, transform_class):
1007 """Adds the given OutputTransform to our transform list."""
1008 self.transforms.append(transform_class)
1010 def _get_host_handlers(self, request):
1011 host = request.host.lower().split(':')[0]
1012 for pattern, handlers in self.handlers:
1013 if pattern.match(host):
1015 # Look for default host if not behind load balancer (for debugging)
1016 if "X-Real-Ip" not in request.headers:
1017 for pattern, handlers in self.handlers:
1018 if pattern.match(self.default_host):
1022 def _load_ui_methods(self, methods):
1023 if type(methods) is types.ModuleType:
1024 self._load_ui_methods(dict((n, getattr(methods, n))
1025 for n in dir(methods)))
1026 elif isinstance(methods, list):
1027 for m in methods: self._load_ui_methods(m)
1029 for name, fn in methods.iteritems():
1030 if not name.startswith("_") and hasattr(fn, "__call__") \
1031 and name[0].lower() == name[0]:
1032 self.ui_methods[name] = fn
1034 def _load_ui_modules(self, modules):
1035 if type(modules) is types.ModuleType:
1036 self._load_ui_modules(dict((n, getattr(modules, n))
1037 for n in dir(modules)))
1038 elif isinstance(modules, list):
1039 for m in modules: self._load_ui_modules(m)
1041 assert isinstance(modules, dict)
1042 for name, cls in modules.iteritems():
1044 if issubclass(cls, UIModule):
1045 self.ui_modules[name] = cls
1049 def __call__(self, request):
1050 """Called by HTTPServer to execute the request."""
1051 transforms = [t(request) for t in self.transforms]
1055 handlers = self._get_host_handlers(request)
1057 handler = RedirectHandler(
1058 request, "http://" + self.default_host + "/")
1060 for spec in handlers:
1061 match = spec.regex.match(request.path)
1063 handler = spec.handler_class(self, request, **spec.kwargs)
1064 # Pass matched groups to the handler. Since
1065 # match.groups() includes both named and unnamed groups,
1066 # we want to use either groups or groupdict but not both.
1067 kwargs = dict((k, urllib.unquote(v))
1068 for (k, v) in match.groupdict().iteritems())
1072 args = [urllib.unquote(s) for s in match.groups()]
1075 handler = ErrorHandler(self, request, 404)
1077 # In debug mode, re-compile templates and reload static files on every
1078 # request so you don't need to restart to see changes
1079 if self.settings.get("debug"):
1080 if getattr(RequestHandler, "_templates", None):
1081 map(lambda loader: loader.reset(),
1082 RequestHandler._templates.values())
1083 RequestHandler._static_hashes = {}
1085 handler._execute(transforms, *args, **kwargs)
1088 def reverse_url(self, name, *args):
1089 """Returns a URL path for handler named `name`
1091 The handler must be added to the application as a named URLSpec
1093 if name in self.named_handlers:
1094 return self.named_handlers[name].reverse(*args)
1095 raise KeyError("%s not found in named urls" % name)
1098 class HTTPError(Exception):
1099 """An exception that will turn into an HTTP error response."""
1100 def __init__(self, status_code, log_message=None, *args):
1101 self.status_code = status_code
1102 self.log_message = log_message
1106 message = "HTTP %d: %s" % (
1107 self.status_code, httplib.responses[self.status_code])
1108 if self.log_message:
1109 return message + " (" + (self.log_message % self.args) + ")"
1114 class ErrorHandler(RequestHandler):
1115 """Generates an error response with status_code for all requests."""
1116 def __init__(self, application, request, status_code):
1117 RequestHandler.__init__(self, application, request)
1118 self.set_status(status_code)
1121 raise HTTPError(self._status_code)
1124 class RedirectHandler(RequestHandler):
1125 """Redirects the client to the given URL for all GET requests.
1127 You should provide the keyword argument "url" to the handler, e.g.:
1129 application = web.Application([
1130 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
1133 def __init__(self, application, request, url, permanent=True):
1134 RequestHandler.__init__(self, application, request)
1136 self._permanent = permanent
1139 self.redirect(self._url, permanent=self._permanent)
1142 class StaticFileHandler(RequestHandler):
1143 """A simple handler that can serve static content from a directory.
1145 To map a path to this handler for a static data directory /var/www,
1146 you would add a line to your application like:
1148 application = web.Application([
1149 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
1152 The local root directory of the content should be passed as the "path"
1153 argument to the handler.
1155 To support aggressive browser caching, if the argument "v" is given
1156 with the path, we set an infinite HTTP expiration header. So, if you
1157 want browsers to cache a file indefinitely, send them to, e.g.,
1158 /static/images/myimage.png?v=xxx.
1160 def __init__(self, application, request, path):
1161 RequestHandler.__init__(self, application, request)
1162 self.root = os.path.abspath(path) + os.path.sep
1164 def head(self, path):
1165 self.get(path, include_body=False)
1167 def get(self, path, include_body=True):
1168 abspath = os.path.abspath(os.path.join(self.root, path))
1169 if not abspath.startswith(self.root):
1170 raise HTTPError(403, "%s is not in root static directory", path)
1171 if not os.path.exists(abspath):
1172 raise HTTPError(404)
1173 if not os.path.isfile(abspath):
1174 raise HTTPError(403, "%s is not a file", path)
1176 stat_result = os.stat(abspath)
1177 modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
1179 self.set_header("Last-Modified", modified)
1180 if "v" in self.request.arguments:
1181 self.set_header("Expires", datetime.datetime.utcnow() + \
1182 datetime.timedelta(days=365*10))
1183 self.set_header("Cache-Control", "max-age=" + str(86400*365*10))
1185 self.set_header("Cache-Control", "public")
1186 mime_type, encoding = mimetypes.guess_type(abspath)
1188 self.set_header("Content-Type", mime_type)
1190 self.set_extra_headers(path)
1192 # Check the If-Modified-Since, and don't send the result if the
1193 # content has not been modified
1194 ims_value = self.request.headers.get("If-Modified-Since")
1195 if ims_value is not None:
1196 date_tuple = email.utils.parsedate(ims_value)
1197 if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
1198 if if_since >= modified:
1199 self.set_status(304)
1202 if not include_body:
1204 self.set_header("Content-Length", stat_result[stat.ST_SIZE])
1205 file = open(abspath, "rb")
1207 self.write(file.read())
1211 def set_extra_headers(self, path):
1212 """For subclass to add extra headers to the response"""
1216 class FallbackHandler(RequestHandler):
1217 """A RequestHandler that wraps another HTTP server callback.
1219 The fallback is a callable object that accepts an HTTPRequest,
1220 such as an Application or tornado.wsgi.WSGIContainer. This is most
1221 useful to use both tornado RequestHandlers and WSGI in the same server.
1223 wsgi_app = tornado.wsgi.WSGIContainer(
1224 django.core.handlers.wsgi.WSGIHandler())
1225 application = tornado.web.Application([
1226 (r"/foo", FooHandler),
1227 (r".*", FallbackHandler, dict(fallback=wsgi_app),
1230 def __init__(self, app, request, fallback):
1231 RequestHandler.__init__(self, app, request)
1232 self.fallback = fallback
1235 self.fallback(self.request)
1236 self._finished = True
1239 class OutputTransform(object):
1240 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
1242 A new transform instance is created for every request. See the
1243 ChunkedTransferEncoding example below if you want to implement a
1246 def __init__(self, request):
1249 def transform_first_chunk(self, headers, chunk, finishing):
1250 return headers, chunk
1252 def transform_chunk(self, chunk, finishing):
1256 class GZipContentEncoding(OutputTransform):
1257 """Applies the gzip content encoding to the response.
1259 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
1261 CONTENT_TYPES = set([
1262 "text/plain", "text/html", "text/css", "text/xml",
1263 "application/x-javascript", "application/xml", "application/atom+xml",
1264 "text/javascript", "application/json", "application/xhtml+xml"])
1267 def __init__(self, request):
1268 self._gzipping = request.supports_http_1_1() and \
1269 "gzip" in request.headers.get("Accept-Encoding", "")
1271 def transform_first_chunk(self, headers, chunk, finishing):
1273 ctype = headers.get("Content-Type", "").split(";")[0]
1274 self._gzipping = (ctype in self.CONTENT_TYPES) and \
1275 (not finishing or len(chunk) >= self.MIN_LENGTH) and \
1276 (finishing or "Content-Length" not in headers) and \
1277 ("Content-Encoding" not in headers)
1279 headers["Content-Encoding"] = "gzip"
1280 self._gzip_value = cStringIO.StringIO()
1281 self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
1283 chunk = self.transform_chunk(chunk, finishing)
1284 if "Content-Length" in headers:
1285 headers["Content-Length"] = str(len(chunk))
1286 return headers, chunk
1288 def transform_chunk(self, chunk, finishing):
1290 self._gzip_file.write(chunk)
1292 self._gzip_file.close()
1294 self._gzip_file.flush()
1295 chunk = self._gzip_value.getvalue()
1296 if self._gzip_pos > 0:
1297 chunk = chunk[self._gzip_pos:]
1298 self._gzip_pos += len(chunk)
1302 class ChunkedTransferEncoding(OutputTransform):
1303 """Applies the chunked transfer encoding to the response.
1305 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
1307 def __init__(self, request):
1308 self._chunking = request.supports_http_1_1()
1310 def transform_first_chunk(self, headers, chunk, finishing):
1312 # No need to chunk the output if a Content-Length is specified
1313 if "Content-Length" in headers or "Transfer-Encoding" in headers:
1314 self._chunking = False
1316 headers["Transfer-Encoding"] = "chunked"
1317 chunk = self.transform_chunk(chunk, finishing)
1318 return headers, chunk
1320 def transform_chunk(self, block, finishing):
1322 # Don't write out empty chunks because that means END-OF-STREAM
1323 # with chunked encoding
1325 block = ("%x" % len(block)) + "\r\n" + block + "\r\n"
1327 block += "0\r\n\r\n"
1331 def authenticated(method):
1332 """Decorate methods with this to require that the user be logged in."""
1333 @functools.wraps(method)
1334 def wrapper(self, *args, **kwargs):
1335 if not self.current_user:
1336 if self.request.method == "GET":
1337 url = self.get_login_url()
1339 url += "?" + urllib.urlencode(dict(next=self.request.uri))
1342 raise HTTPError(403)
1343 return method(self, *args, **kwargs)
1347 class UIModule(object):
1348 """A UI re-usable, modular unit on a page.
1350 UI modules often execute additional queries, and they can include
1351 additional CSS and JavaScript that will be included in the output
1352 page, which is automatically inserted on page render.
1354 def __init__(self, handler):
1355 self.handler = handler
1356 self.request = handler.request
1357 self.ui = handler.ui
1358 self.current_user = handler.current_user
1359 self.locale = handler.locale
1361 def render(self, *args, **kwargs):
1362 raise NotImplementedError()
1364 def embedded_javascript(self):
1365 """Returns a JavaScript string that will be embedded in the page."""
1368 def javascript_files(self):
1369 """Returns a list of JavaScript files required by this module."""
1372 def embedded_css(self):
1373 """Returns a CSS string that will be embedded in the page."""
1376 def css_files(self):
1377 """Returns a list of CSS files required by this module."""
1380 def html_head(self):
1381 """Returns a CSS string that will be put in the <head/> element"""
1384 def html_body(self):
1385 """Returns an HTML string that will be put in the <body/> element"""
1388 def render_string(self, path, **kwargs):
1389 return self.handler.render_string(path, **kwargs)
1391 class URLSpec(object):
1392 """Specifies mappings between URLs and handlers."""
1393 def __init__(self, pattern, handler_class, kwargs={}, name=None):
1394 """Creates a URLSpec.
1397 pattern: Regular expression to be matched. Any groups in the regex
1398 will be passed in to the handler's get/post/etc methods as
1400 handler_class: RequestHandler subclass to be invoked.
1401 kwargs (optional): A dictionary of additional arguments to be passed
1402 to the handler's constructor.
1403 name (optional): A name for this handler. Used by
1404 Application.reverse_url.
1406 if not pattern.endswith('$'):
1408 self.regex = re.compile(pattern)
1409 self.handler_class = handler_class
1410 self.kwargs = kwargs
1412 self._path, self._group_count = self._find_groups()
1414 def _find_groups(self):
1415 """Returns a tuple (reverse string, group count) for a url.
1417 For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
1418 would return ('/%s/%s/', 2).
1420 pattern = self.regex.pattern
1421 if pattern.startswith('^'):
1422 pattern = pattern[1:]
1423 if pattern.endswith('$'):
1424 pattern = pattern[:-1]
1426 if self.regex.groups != pattern.count('('):
1427 # The pattern is too complicated for our simplistic matching,
1428 # so we can't support reversing it.
1432 for fragment in pattern.split('('):
1434 paren_loc = fragment.index(')')
1436 pieces.append('%s' + fragment[paren_loc + 1:])
1438 pieces.append(fragment)
1440 return (''.join(pieces), self.regex.groups)
1442 def reverse(self, *args):
1443 assert self._path is not None, \
1444 "Cannot reverse url regex " + self.regex.pattern
1445 assert len(args) == self._group_count, "required number of arguments "\
1449 return self._path % tuple([str(a) for a in args])
1454 if isinstance(s, unicode):
1455 return s.encode("utf-8")
1456 assert isinstance(s, str)
1461 if isinstance(s, str):
1463 return s.decode("utf-8")
1464 except UnicodeDecodeError:
1465 raise HTTPError(400, "Non-utf8 argument")
1466 assert isinstance(s, unicode)
1470 def _time_independent_equals(a, b):
1471 if len(a) != len(b):
1474 for x, y in zip(a, b):
1475 result |= ord(x) ^ ord(y)
1480 """Makes a dictionary behave like an object."""
1481 def __getattr__(self, name):
1485 raise AttributeError(name)
1487 def __setattr__(self, name, value):