Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tornado/web.py: 22%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# Copyright 2009 Facebook
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""``tornado.web`` provides a simple web framework with asynchronous
17features that allow it to scale to large numbers of open connections,
18making it ideal for `long polling
19<http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_.
21Here is a simple "Hello, world" example app:
23.. testcode::
25 import asyncio
26 import tornado
28 class MainHandler(tornado.web.RequestHandler):
29 def get(self):
30 self.write("Hello, world")
32 async def main():
33 application = tornado.web.Application([
34 (r"/", MainHandler),
35 ])
36 application.listen(8888)
37 await asyncio.Event().wait()
39 if __name__ == "__main__":
40 asyncio.run(main())
42See the :doc:`guide` for additional information.
44Thread-safety notes
45-------------------
47In general, methods on `RequestHandler` and elsewhere in Tornado are
48not thread-safe. In particular, methods such as
49`~RequestHandler.write()`, `~RequestHandler.finish()`, and
50`~RequestHandler.flush()` must only be called from the main thread. If
51you use multiple threads it is important to use `.IOLoop.add_callback`
52to transfer control back to the main thread before finishing the
53request, or to limit your use of other threads to
54`.IOLoop.run_in_executor` and ensure that your callbacks running in
55the executor do not refer to Tornado objects.
57"""
59import base64
60import binascii
61import datetime
62import email.utils
63import functools
64import gzip
65import hashlib
66import hmac
67import http.cookies
68from inspect import isclass
69from io import BytesIO
70import mimetypes
71import numbers
72import os.path
73import re
74import socket
75import stat
76import sys
77import threading
78import time
79import warnings
80import tornado
81import traceback
82import types
83import urllib.parse
84from urllib.parse import urlencode
86from tornado.concurrent import Future, future_set_result_unless_cancelled
87from tornado import escape
88from tornado import gen
89from tornado.httpserver import HTTPServer
90from tornado import httputil
91from tornado import iostream
92from tornado import locale
93from tornado.log import access_log, app_log, gen_log
94from tornado import template
95from tornado.escape import utf8, _unicode
96from tornado.routing import (
97 AnyMatches,
98 DefaultHostMatches,
99 HostMatches,
100 ReversibleRouter,
101 Rule,
102 ReversibleRuleRouter,
103 URLSpec,
104 _RuleList,
105)
106from tornado.util import ObjectDict, unicode_type, _websocket_mask
108url = URLSpec
110from typing import (
111 Dict,
112 Any,
113 Union,
114 Optional,
115 Awaitable,
116 Tuple,
117 List,
118 Callable,
119 Iterable,
120 Generator,
121 Type,
122 TypeVar,
123 cast,
124 overload,
125)
126from types import TracebackType
127import typing
129if typing.TYPE_CHECKING:
130 from typing import Set # noqa: F401
133# The following types are accepted by RequestHandler.set_header
134# and related methods.
135_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime]
137_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]]
140MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
141"""The oldest signed value version supported by this version of Tornado.
143Signed values older than this version cannot be decoded.
145.. versionadded:: 3.2.1
146"""
148MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2
149"""The newest signed value version supported by this version of Tornado.
151Signed values newer than this version cannot be decoded.
153.. versionadded:: 3.2.1
154"""
156DEFAULT_SIGNED_VALUE_VERSION = 2
157"""The signed value version produced by `.RequestHandler.create_signed_value`.
159May be overridden by passing a ``version`` keyword argument.
161.. versionadded:: 3.2.1
162"""
164DEFAULT_SIGNED_VALUE_MIN_VERSION = 1
165"""The oldest signed value accepted by `.RequestHandler.get_signed_cookie`.
167May be overridden by passing a ``min_version`` keyword argument.
169.. versionadded:: 3.2.1
170"""
173class _ArgDefaultMarker:
174 pass
177_ARG_DEFAULT = _ArgDefaultMarker()
180class RequestHandler:
181 """Base class for HTTP request handlers.
183 Subclasses must define at least one of the methods defined in the
184 "Entry points" section below.
186 Applications should not construct `RequestHandler` objects
187 directly and subclasses should not override ``__init__`` (override
188 `~RequestHandler.initialize` instead).
190 """
192 SUPPORTED_METHODS: Tuple[str, ...] = (
193 "GET",
194 "HEAD",
195 "POST",
196 "DELETE",
197 "PATCH",
198 "PUT",
199 "OPTIONS",
200 )
202 _template_loaders = {} # type: Dict[str, template.BaseLoader]
203 _template_loader_lock = threading.Lock()
204 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
206 _stream_request_body = False
208 # Will be set in _execute.
209 _transforms = None # type: List[OutputTransform]
210 path_args = None # type: List[str]
211 path_kwargs = None # type: Dict[str, str]
213 def __init__(
214 self,
215 application: "Application",
216 request: httputil.HTTPServerRequest,
217 **kwargs: Any,
218 ) -> None:
219 super().__init__()
221 self.application = application
222 self.request = request
223 self._headers_written = False
224 self._finished = False
225 self._auto_finish = True
226 self._prepared_future = None
227 self.ui = ObjectDict(
228 (n, self._ui_method(m)) for n, m in application.ui_methods.items()
229 )
230 # UIModules are available as both `modules` and `_tt_modules` in the
231 # template namespace. Historically only `modules` was available
232 # but could be clobbered by user additions to the namespace.
233 # The template {% module %} directive looks in `_tt_modules` to avoid
234 # possible conflicts.
235 self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules)
236 self.ui["modules"] = self.ui["_tt_modules"]
237 self.clear()
238 assert self.request.connection is not None
239 # TODO: need to add set_close_callback to HTTPConnection interface
240 self.request.connection.set_close_callback( # type: ignore
241 self.on_connection_close
242 )
243 self.initialize(**kwargs) # type: ignore
245 def _initialize(self) -> None:
246 pass
248 initialize = _initialize # type: Callable[..., None]
249 """Hook for subclass initialization. Called for each request.
251 A dictionary passed as the third argument of a ``URLSpec`` will be
252 supplied as keyword arguments to ``initialize()``.
254 Example::
256 class ProfileHandler(RequestHandler):
257 def initialize(self, database):
258 self.database = database
260 def get(self, username):
261 ...
263 app = Application([
264 (r'/user/(.*)', ProfileHandler, dict(database=database)),
265 ])
266 """
268 @property
269 def settings(self) -> Dict[str, Any]:
270 """An alias for `self.application.settings <Application.settings>`."""
271 return self.application.settings
273 def _unimplemented_method(self, *args: str, **kwargs: str) -> None:
274 raise HTTPError(405)
276 head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
277 get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
278 post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
279 delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
280 patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
281 put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
282 options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
284 def prepare(self) -> Optional[Awaitable[None]]:
285 """Called at the beginning of a request before `get`/`post`/etc.
287 Override this method to perform common initialization regardless
288 of the request method. There is no guarantee that ``prepare`` will
289 be called if an error occurs that is handled by the framework.
291 Asynchronous support: Use ``async def`` or decorate this method with
292 `.gen.coroutine` to make it asynchronous.
293 If this method returns an ``Awaitable`` execution will not proceed
294 until the ``Awaitable`` is done.
296 .. versionadded:: 3.1
297 Asynchronous support.
298 """
299 pass
301 def on_finish(self) -> None:
302 """Called after the end of a request.
304 Override this method to perform cleanup, logging, etc. This method is primarily intended as
305 a counterpart to `prepare`. However, there are a few error cases where ``on_finish`` may be
306 called when ``prepare`` has not. (These are considered bugs and may be fixed in the future,
307 but for now you may need to check to see if the initialization work done in ``prepare`` has
308 occurred)
310 ``on_finish`` may not produce any output, as it is called after the response has been sent
311 to the client.
312 """
313 pass
315 def on_connection_close(self) -> None:
316 """Called in async handlers if the client closed the connection.
318 Override this to clean up resources associated with
319 long-lived connections. Note that this method is called only if
320 the connection was closed during asynchronous processing; if you
321 need to do cleanup after every request override `on_finish`
322 instead.
324 Proxies may keep a connection open for a time (perhaps
325 indefinitely) after the client has gone away, so this method
326 may not be called promptly after the end user closes their
327 connection.
328 """
329 if _has_stream_request_body(self.__class__):
330 if not self.request._body_future.done():
331 self.request._body_future.set_exception(iostream.StreamClosedError())
332 self.request._body_future.exception()
334 def clear(self) -> None:
335 """Resets all headers and content for this response."""
336 self._headers = httputil.HTTPHeaders(
337 {
338 "Server": "TornadoServer/%s" % tornado.version,
339 "Content-Type": "text/html; charset=UTF-8",
340 "Date": httputil.format_timestamp(time.time()),
341 }
342 )
343 self.set_default_headers()
344 self._write_buffer = [] # type: List[bytes]
345 self._status_code = 200
346 self._reason = httputil.responses[200]
348 def set_default_headers(self) -> None:
349 """Override this to set HTTP headers at the beginning of the request.
351 For example, this is the place to set a custom ``Server`` header.
352 Note that setting such headers in the normal flow of request
353 processing may not do what you want, since headers may be reset
354 during error handling.
355 """
356 pass
358 def set_status(self, status_code: int, reason: Optional[str] = None) -> None:
359 """Sets the status code for our response.
361 :arg int status_code: Response status code.
362 :arg str reason: Human-readable reason phrase describing the status
363 code (for example, the "Not Found" in ``HTTP/1.1 404 Not Found``).
364 Normally determined automatically from `http.client.responses`; this
365 argument should only be used if you need to use a non-standard
366 status code.
368 .. versionchanged:: 5.0
370 No longer validates that the response code is in
371 `http.client.responses`.
372 """
373 self._status_code = status_code
374 if reason is not None:
375 if "<" in reason or not httputil._ABNF.reason_phrase.fullmatch(reason):
376 # Logically this would be better as an exception, but this method
377 # is called on error-handling paths that would need some refactoring
378 # to tolerate internal errors cleanly.
379 #
380 # The check for "<" is a defense-in-depth against XSS attacks (we also
381 # escape the reason when rendering error pages).
382 reason = "Unknown"
383 self._reason = escape.native_str(reason)
384 else:
385 self._reason = httputil.responses.get(status_code, "Unknown")
387 def get_status(self) -> int:
388 """Returns the status code for our response."""
389 return self._status_code
391 def set_header(self, name: str, value: _HeaderTypes) -> None:
392 """Sets the given response header name and value.
394 All header values are converted to strings (`datetime` objects
395 are formatted according to the HTTP specification for the
396 ``Date`` header).
398 """
399 self._headers[name] = self._convert_header_value(value)
401 def add_header(self, name: str, value: _HeaderTypes) -> None:
402 """Adds the given response header and value.
404 Unlike `set_header`, `add_header` may be called multiple times
405 to return multiple values for the same header.
406 """
407 self._headers.add(name, self._convert_header_value(value))
409 def clear_header(self, name: str) -> None:
410 """Clears an outgoing header, undoing a previous `set_header` call.
412 Note that this method does not apply to multi-valued headers
413 set by `add_header`.
414 """
415 if name in self._headers:
416 del self._headers[name]
418 # https://www.rfc-editor.org/rfc/rfc9110#name-field-values
419 _VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*")
421 def _convert_header_value(self, value: _HeaderTypes) -> str:
422 # Convert the input value to a str. This type check is a bit
423 # subtle: The bytes case only executes on python 3, and the
424 # unicode case only executes on python 2, because the other
425 # cases are covered by the first match for str.
426 if isinstance(value, str):
427 retval = value
428 elif isinstance(value, bytes):
429 # Non-ascii characters in headers are not well supported,
430 # but if you pass bytes, use latin1 so they pass through as-is.
431 retval = value.decode("latin1")
432 elif isinstance(value, numbers.Integral):
433 # return immediately since we know the converted value will be safe
434 return str(value)
435 elif isinstance(value, datetime.datetime):
436 return httputil.format_timestamp(value)
437 else:
438 raise TypeError("Unsupported header value %r" % value)
439 # If \n is allowed into the header, it is possible to inject
440 # additional headers or split the request.
441 if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None:
442 raise ValueError("Unsafe header value %r", retval)
443 return retval
445 @overload
446 def get_argument(self, name: str, default: str, strip: bool = True) -> str:
447 pass
449 @overload
450 def get_argument( # noqa: F811
451 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
452 ) -> str:
453 pass
455 @overload
456 def get_argument( # noqa: F811
457 self, name: str, default: None, strip: bool = True
458 ) -> Optional[str]:
459 pass
461 def get_argument( # noqa: F811
462 self,
463 name: str,
464 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
465 strip: bool = True,
466 ) -> Optional[str]:
467 """Returns the value of the argument with the given name.
469 If default is not provided, the argument is considered to be
470 required, and we raise a `MissingArgumentError` if it is missing.
472 If the argument appears in the request more than once, we return the
473 last value.
475 This method searches both the query and body arguments.
476 """
477 return self._get_argument(name, default, self.request.arguments, strip)
479 def get_arguments(self, name: str, strip: bool = True) -> List[str]:
480 """Returns a list of the arguments with the given name.
482 If the argument is not present, returns an empty list.
484 This method searches both the query and body arguments.
485 """
487 # Make sure `get_arguments` isn't accidentally being called with a
488 # positional argument that's assumed to be a default (like in
489 # `get_argument`.)
490 assert isinstance(strip, bool)
492 return self._get_arguments(name, self.request.arguments, strip)
494 @overload
495 def get_body_argument(self, name: str, default: str, strip: bool = True) -> str:
496 pass
498 @overload
499 def get_body_argument( # noqa: F811
500 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
501 ) -> str:
502 pass
504 @overload
505 def get_body_argument( # noqa: F811
506 self, name: str, default: None, strip: bool = True
507 ) -> Optional[str]:
508 pass
510 def get_body_argument( # noqa: F811
511 self,
512 name: str,
513 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
514 strip: bool = True,
515 ) -> Optional[str]:
516 """Returns the value of the argument with the given name
517 from the request body.
519 If default is not provided, the argument is considered to be
520 required, and we raise a `MissingArgumentError` if it is missing.
522 If the argument appears in the url more than once, we return the
523 last value.
525 .. versionadded:: 3.2
526 """
527 return self._get_argument(name, default, self.request.body_arguments, strip)
529 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]:
530 """Returns a list of the body arguments with the given name.
532 If the argument is not present, returns an empty list.
534 .. versionadded:: 3.2
535 """
536 return self._get_arguments(name, self.request.body_arguments, strip)
538 @overload
539 def get_query_argument(self, name: str, default: str, strip: bool = True) -> str:
540 pass
542 @overload
543 def get_query_argument( # noqa: F811
544 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
545 ) -> str:
546 pass
548 @overload
549 def get_query_argument( # noqa: F811
550 self, name: str, default: None, strip: bool = True
551 ) -> Optional[str]:
552 pass
554 def get_query_argument( # noqa: F811
555 self,
556 name: str,
557 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
558 strip: bool = True,
559 ) -> Optional[str]:
560 """Returns the value of the argument with the given name
561 from the request query string.
563 If default is not provided, the argument is considered to be
564 required, and we raise a `MissingArgumentError` if it is missing.
566 If the argument appears in the url more than once, we return the
567 last value.
569 .. versionadded:: 3.2
570 """
571 return self._get_argument(name, default, self.request.query_arguments, strip)
573 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]:
574 """Returns a list of the query arguments with the given name.
576 If the argument is not present, returns an empty list.
578 .. versionadded:: 3.2
579 """
580 return self._get_arguments(name, self.request.query_arguments, strip)
582 def _get_argument(
583 self,
584 name: str,
585 default: Union[None, str, _ArgDefaultMarker],
586 source: Dict[str, List[bytes]],
587 strip: bool = True,
588 ) -> Optional[str]:
589 args = self._get_arguments(name, source, strip=strip)
590 if not args:
591 if isinstance(default, _ArgDefaultMarker):
592 raise MissingArgumentError(name)
593 return default
594 return args[-1]
596 def _get_arguments(
597 self, name: str, source: Dict[str, List[bytes]], strip: bool = True
598 ) -> List[str]:
599 values = []
600 for v in source.get(name, []):
601 s = self.decode_argument(v, name=name)
602 if isinstance(s, unicode_type):
603 # Get rid of any weird control chars (unless decoding gave
604 # us bytes, in which case leave it alone)
605 s = RequestHandler._remove_control_chars_regex.sub(" ", s)
606 if strip:
607 s = s.strip()
608 values.append(s)
609 return values
611 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str:
612 """Decodes an argument from the request.
614 The argument has been percent-decoded and is now a byte string.
615 By default, this method decodes the argument as utf-8 and returns
616 a unicode string, but this may be overridden in subclasses.
618 This method is used as a filter for both `get_argument()` and for
619 values extracted from the url and passed to `get()`/`post()`/etc.
621 The name of the argument is provided if known, but may be None
622 (e.g. for unnamed groups in the url regex).
623 """
624 try:
625 return _unicode(value)
626 except UnicodeDecodeError:
627 raise HTTPError(
628 400, "Invalid unicode in {}: {!r}".format(name or "url", value[:40])
629 )
631 @property
632 def cookies(self) -> Dict[str, http.cookies.Morsel]:
633 """An alias for
634 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`."""
635 return self.request.cookies
637 @overload
638 def get_cookie(self, name: str, default: str) -> str:
639 pass
641 @overload
642 def get_cookie(self, name: str, default: None = None) -> Optional[str]:
643 pass
645 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]:
646 """Returns the value of the request cookie with the given name.
648 If the named cookie is not present, returns ``default``.
650 This method only returns cookies that were present in the request.
651 It does not see the outgoing cookies set by `set_cookie` in this
652 handler.
653 """
654 if self.request.cookies is not None and name in self.request.cookies:
655 return self.request.cookies[name].value
656 return default
658 def set_cookie(
659 self,
660 name: str,
661 value: Union[str, bytes],
662 domain: Optional[str] = None,
663 expires: Optional[Union[float, Tuple, datetime.datetime]] = None,
664 path: str = "/",
665 expires_days: Optional[float] = None,
666 # Keyword-only args start here for historical reasons.
667 *,
668 max_age: Optional[int] = None,
669 httponly: bool = False,
670 secure: bool = False,
671 samesite: Optional[str] = None,
672 **kwargs: Any,
673 ) -> None:
674 """Sets an outgoing cookie name/value with the given options.
676 Newly-set cookies are not immediately visible via `get_cookie`;
677 they are not present until the next request.
679 Most arguments are passed directly to `http.cookies.Morsel` directly.
680 See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
681 for more information.
683 ``expires`` may be a numeric timestamp as returned by `time.time`,
684 a time tuple as returned by `time.gmtime`, or a
685 `datetime.datetime` object. ``expires_days`` is provided as a convenience
686 to set an expiration time in days from today (if both are set, ``expires``
687 is used).
689 .. deprecated:: 6.3
690 Keyword arguments are currently accepted case-insensitively.
691 In Tornado 7.0 this will be changed to only accept lowercase
692 arguments.
693 """
694 # The cookie library only accepts type str, in both python 2 and 3
695 name = escape.native_str(name)
696 value = escape.native_str(value)
697 if re.search(r"[\x00-\x20]", value):
698 # Legacy check for control characters in cookie values. This check is no longer needed
699 # since the cookie library escapes these characters correctly now. It will be removed
700 # in the next feature release.
701 raise ValueError(f"Invalid cookie {name!r}: {value!r}")
702 for attr_name, attr_value in [
703 ("name", name),
704 ("domain", domain),
705 ("path", path),
706 ("samesite", samesite),
707 ]:
708 # Cookie attributes may not contain control characters or semicolons (except when
709 # escaped in the value). A check for control characters was added to the http.cookies
710 # library in a Feb 2026 security release; as of March it still does not check for
711 # semicolons.
712 #
713 # When a semicolon check is added to the standard library (and the release has had time
714 # for adoption), this check may be removed, but be mindful of the fact that this may
715 # change the timing of the exception (to the generation of the Set-Cookie header in
716 # flush()). We m
717 if attr_value is not None and re.search(r"[\x00-\x20\x3b\x7f]", attr_value):
718 raise http.cookies.CookieError(
719 f"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}"
720 )
721 for k, v in kwargs.items():
722 # Also check for disallowed characters in deprecated kwargs.
723 if re.search(r"[\x00-\x20\x3b\x7f]", str(v)):
724 raise http.cookies.CookieError(
725 f"Invalid cookie attribute {k}={v!r} for cookie {name!r}"
726 )
727 if not hasattr(self, "_new_cookie"):
728 self._new_cookie = (
729 http.cookies.SimpleCookie()
730 ) # type: http.cookies.SimpleCookie
731 if name in self._new_cookie:
732 del self._new_cookie[name]
733 self._new_cookie[name] = value
734 morsel = self._new_cookie[name]
735 if domain:
736 morsel["domain"] = domain
737 if expires_days is not None and not expires:
738 expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
739 days=expires_days
740 )
741 if expires:
742 morsel["expires"] = httputil.format_timestamp(expires)
743 if path:
744 morsel["path"] = path
745 if max_age:
746 # Note change from _ to -.
747 morsel["max-age"] = str(max_age)
748 if httponly:
749 # Note that SimpleCookie ignores the value here. The presense of an
750 # httponly (or secure) key is treated as true.
751 morsel["httponly"] = True
752 if secure:
753 morsel["secure"] = True
754 if samesite:
755 morsel["samesite"] = samesite
756 if kwargs:
757 # The setitem interface is case-insensitive, so continue to support
758 # kwargs for backwards compatibility until we can remove deprecated
759 # features.
760 for k, v in kwargs.items():
761 morsel[k] = v
762 warnings.warn(
763 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} "
764 "(should be lowercase)",
765 DeprecationWarning,
766 )
768 def clear_cookie(self, name: str, **kwargs: Any) -> None:
769 """Deletes the cookie with the given name.
771 This method accepts the same arguments as `set_cookie`, except for
772 ``expires`` and ``max_age``. Clearing a cookie requires the same
773 ``domain`` and ``path`` arguments as when it was set. In some cases the
774 ``samesite`` and ``secure`` arguments are also required to match. Other
775 arguments are ignored.
777 Similar to `set_cookie`, the effect of this method will not be
778 seen until the following request.
780 .. versionchanged:: 6.3
782 Now accepts all keyword arguments that ``set_cookie`` does.
783 The ``samesite`` and ``secure`` flags have recently become
784 required for clearing ``samesite="none"`` cookies.
785 """
786 for excluded_arg in ["expires", "max_age"]:
787 if excluded_arg in kwargs:
788 raise TypeError(
789 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'"
790 )
791 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
792 days=365
793 )
794 self.set_cookie(name, value="", expires=expires, **kwargs)
796 def clear_all_cookies(self, **kwargs: Any) -> None:
797 """Attempt to delete all the cookies the user sent with this request.
799 See `clear_cookie` for more information on keyword arguments. Due to
800 limitations of the cookie protocol, it is impossible to determine on the
801 server side which values are necessary for the ``domain``, ``path``,
802 ``samesite``, or ``secure`` arguments, this method can only be
803 successful if you consistently use the same values for these arguments
804 when setting cookies.
806 Similar to `set_cookie`, the effect of this method will not be seen
807 until the following request.
809 .. versionchanged:: 3.2
811 Added the ``path`` and ``domain`` parameters.
813 .. versionchanged:: 6.3
815 Now accepts all keyword arguments that ``set_cookie`` does.
817 .. deprecated:: 6.3
819 The increasingly complex rules governing cookies have made it
820 impossible for a ``clear_all_cookies`` method to work reliably
821 since all we know about cookies are their names. Applications
822 should generally use ``clear_cookie`` one at a time instead.
823 """
824 for name in self.request.cookies:
825 self.clear_cookie(name, **kwargs)
827 def set_signed_cookie(
828 self,
829 name: str,
830 value: Union[str, bytes],
831 expires_days: Optional[float] = 30,
832 version: Optional[int] = None,
833 **kwargs: Any,
834 ) -> None:
835 """Signs and timestamps a cookie so it cannot be forged.
837 You must specify the ``cookie_secret`` setting in your Application
838 to use this method. It should be a long, random sequence of bytes
839 to be used as the HMAC secret for the signature.
841 To read a cookie set with this method, use `get_signed_cookie()`.
843 Note that the ``expires_days`` parameter sets the lifetime of the
844 cookie in the browser, but is independent of the ``max_age_days``
845 parameter to `get_signed_cookie`.
846 A value of None limits the lifetime to the current browser session.
848 Secure cookies may contain arbitrary byte values, not just unicode
849 strings (unlike regular cookies)
851 Similar to `set_cookie`, the effect of this method will not be
852 seen until the following request.
854 .. versionchanged:: 3.2.1
856 Added the ``version`` argument. Introduced cookie version 2
857 and made it the default.
859 .. versionchanged:: 6.3
861 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to
862 avoid confusion with other uses of "secure" in cookie attributes
863 and prefixes. The old name remains as an alias.
864 """
865 self.set_cookie(
866 name,
867 self.create_signed_value(name, value, version=version),
868 expires_days=expires_days,
869 **kwargs,
870 )
872 set_secure_cookie = set_signed_cookie
874 def create_signed_value(
875 self, name: str, value: Union[str, bytes], version: Optional[int] = None
876 ) -> bytes:
877 """Signs and timestamps a string so it cannot be forged.
879 Normally used via set_signed_cookie, but provided as a separate
880 method for non-cookie uses. To decode a value not stored
881 as a cookie use the optional value argument to get_signed_cookie.
883 .. versionchanged:: 3.2.1
885 Added the ``version`` argument. Introduced cookie version 2
886 and made it the default.
887 """
888 self.require_setting("cookie_secret", "secure cookies")
889 secret = self.application.settings["cookie_secret"]
890 key_version = None
891 if isinstance(secret, dict):
892 if self.application.settings.get("key_version") is None:
893 raise Exception("key_version setting must be used for secret_key dicts")
894 key_version = self.application.settings["key_version"]
896 return create_signed_value(
897 secret, name, value, version=version, key_version=key_version
898 )
900 def get_signed_cookie(
901 self,
902 name: str,
903 value: Optional[str] = None,
904 max_age_days: float = 31,
905 min_version: Optional[int] = None,
906 ) -> Optional[bytes]:
907 """Returns the given signed cookie if it validates, or None.
909 The decoded cookie value is returned as a byte string (unlike
910 `get_cookie`).
912 Similar to `get_cookie`, this method only returns cookies that
913 were present in the request. It does not see outgoing cookies set by
914 `set_signed_cookie` in this handler.
916 .. versionchanged:: 3.2.1
918 Added the ``min_version`` argument. Introduced cookie version 2;
919 both versions 1 and 2 are accepted by default.
921 .. versionchanged:: 6.3
923 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to
924 avoid confusion with other uses of "secure" in cookie attributes
925 and prefixes. The old name remains as an alias.
927 """
928 self.require_setting("cookie_secret", "secure cookies")
929 if value is None:
930 value = self.get_cookie(name)
931 return decode_signed_value(
932 self.application.settings["cookie_secret"],
933 name,
934 value,
935 max_age_days=max_age_days,
936 min_version=min_version,
937 )
939 get_secure_cookie = get_signed_cookie
941 def get_signed_cookie_key_version(
942 self, name: str, value: Optional[str] = None
943 ) -> Optional[int]:
944 """Returns the signing key version of the secure cookie.
946 The version is returned as int.
948 .. versionchanged:: 6.3
950 Renamed from ``get_secure_cookie_key_version`` to
951 ``set_signed_cookie_key_version`` to avoid confusion with other
952 uses of "secure" in cookie attributes and prefixes. The old name
953 remains as an alias.
955 """
956 self.require_setting("cookie_secret", "secure cookies")
957 if value is None:
958 value = self.get_cookie(name)
959 if value is None:
960 return None
961 return get_signature_key_version(value)
963 get_secure_cookie_key_version = get_signed_cookie_key_version
965 def redirect(
966 self, url: str, permanent: bool = False, status: Optional[int] = None
967 ) -> None:
968 """Sends a redirect to the given (optionally relative) URL.
970 If the ``status`` argument is specified, that value is used as the
971 HTTP status code; otherwise either 301 (permanent) or 302
972 (temporary) is chosen based on the ``permanent`` argument.
973 The default is 302 (temporary).
974 """
975 if self._headers_written:
976 raise Exception("Cannot redirect after headers have been written")
977 if status is None:
978 status = 301 if permanent else 302
979 else:
980 assert isinstance(status, int) and 300 <= status <= 399
981 self.set_status(status)
982 self.set_header("Location", utf8(url))
983 self.finish()
985 def write(self, chunk: Union[str, bytes, dict]) -> None:
986 """Writes the given chunk to the output buffer.
988 To write the output to the network, use the `flush()` method below.
990 If the given chunk is a dictionary, we write it as JSON and set
991 the Content-Type of the response to be ``application/json``.
992 (if you want to send JSON as a different ``Content-Type``, call
993 ``set_header`` *after* calling ``write()``).
995 Note that lists are not converted to JSON because of a potential
996 cross-site security vulnerability. All JSON output should be
997 wrapped in a dictionary. More details at
998 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
999 https://github.com/facebook/tornado/issues/1009
1000 """
1001 if self._finished:
1002 raise RuntimeError("Cannot write() after finish()")
1003 if not isinstance(chunk, (bytes, unicode_type, dict)):
1004 message = "write() only accepts bytes, unicode, and dict objects"
1005 if isinstance(chunk, list):
1006 message += (
1007 ". Lists not accepted for security reasons; see "
1008 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501
1009 )
1010 raise TypeError(message)
1011 if isinstance(chunk, dict):
1012 chunk = escape.json_encode(chunk)
1013 self.set_header("Content-Type", "application/json; charset=UTF-8")
1014 chunk = utf8(chunk)
1015 self._write_buffer.append(chunk)
1017 def render(self, template_name: str, **kwargs: Any) -> "Future[None]":
1018 """Renders the template with the given arguments as the response.
1020 ``render()`` calls ``finish()``, so no other output methods can be called
1021 after it.
1023 Returns a `.Future` with the same semantics as the one returned by `finish`.
1024 Awaiting this `.Future` is optional.
1026 .. versionchanged:: 5.1
1028 Now returns a `.Future` instead of ``None``.
1029 """
1030 if self._finished:
1031 raise RuntimeError("Cannot render() after finish()")
1032 html = self.render_string(template_name, **kwargs)
1034 # Insert the additional JS and CSS added by the modules on the page
1035 js_embed = []
1036 js_files = []
1037 css_embed = []
1038 css_files = []
1039 html_heads = []
1040 html_bodies = []
1041 for module in getattr(self, "_active_modules", {}).values():
1042 embed_part = module.embedded_javascript()
1043 if embed_part:
1044 js_embed.append(utf8(embed_part))
1045 file_part = module.javascript_files()
1046 if file_part:
1047 if isinstance(file_part, (unicode_type, bytes)):
1048 js_files.append(_unicode(file_part))
1049 else:
1050 js_files.extend(file_part)
1051 embed_part = module.embedded_css()
1052 if embed_part:
1053 css_embed.append(utf8(embed_part))
1054 file_part = module.css_files()
1055 if file_part:
1056 if isinstance(file_part, (unicode_type, bytes)):
1057 css_files.append(_unicode(file_part))
1058 else:
1059 css_files.extend(file_part)
1060 head_part = module.html_head()
1061 if head_part:
1062 html_heads.append(utf8(head_part))
1063 body_part = module.html_body()
1064 if body_part:
1065 html_bodies.append(utf8(body_part))
1067 if js_files:
1068 # Maintain order of JavaScript files given by modules
1069 js = self.render_linked_js(js_files)
1070 sloc = html.rindex(b"</body>")
1071 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:]
1072 if js_embed:
1073 js_bytes = self.render_embed_js(js_embed)
1074 sloc = html.rindex(b"</body>")
1075 html = html[:sloc] + js_bytes + b"\n" + html[sloc:]
1076 if css_files:
1077 css = self.render_linked_css(css_files)
1078 hloc = html.index(b"</head>")
1079 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:]
1080 if css_embed:
1081 css_bytes = self.render_embed_css(css_embed)
1082 hloc = html.index(b"</head>")
1083 html = html[:hloc] + css_bytes + b"\n" + html[hloc:]
1084 if html_heads:
1085 hloc = html.index(b"</head>")
1086 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:]
1087 if html_bodies:
1088 hloc = html.index(b"</body>")
1089 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:]
1090 return self.finish(html)
1092 def render_linked_js(self, js_files: Iterable[str]) -> str:
1093 """Default method used to render the final js links for the
1094 rendered webpage.
1096 Override this method in a sub-classed controller to change the output.
1097 """
1098 paths = []
1099 unique_paths = set() # type: Set[str]
1101 for path in js_files:
1102 if not is_absolute(path):
1103 path = self.static_url(path)
1104 if path not in unique_paths:
1105 paths.append(path)
1106 unique_paths.add(path)
1108 return "".join(
1109 '<script src="'
1110 + escape.xhtml_escape(p)
1111 + '" type="text/javascript"></script>'
1112 for p in paths
1113 )
1115 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
1116 """Default method used to render the final embedded js for the
1117 rendered webpage.
1119 Override this method in a sub-classed controller to change the output.
1120 """
1121 return (
1122 b'<script type="text/javascript">\n//<![CDATA[\n'
1123 + b"\n".join(js_embed)
1124 + b"\n//]]>\n</script>"
1125 )
1127 def render_linked_css(self, css_files: Iterable[str]) -> str:
1128 """Default method used to render the final css links for the
1129 rendered webpage.
1131 Override this method in a sub-classed controller to change the output.
1132 """
1133 paths = []
1134 unique_paths = set() # type: Set[str]
1136 for path in css_files:
1137 if not is_absolute(path):
1138 path = self.static_url(path)
1139 if path not in unique_paths:
1140 paths.append(path)
1141 unique_paths.add(path)
1143 return "".join(
1144 '<link href="' + escape.xhtml_escape(p) + '" '
1145 'type="text/css" rel="stylesheet"/>'
1146 for p in paths
1147 )
1149 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
1150 """Default method used to render the final embedded css for the
1151 rendered webpage.
1153 Override this method in a sub-classed controller to change the output.
1154 """
1155 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>"
1157 def render_string(self, template_name: str, **kwargs: Any) -> bytes:
1158 """Generate the given template with the given arguments.
1160 We return the generated byte string (in utf8). To generate and
1161 write a template as a response, use render() above.
1162 """
1163 # If no template_path is specified, use the path of the calling file
1164 template_path = self.get_template_path()
1165 if not template_path:
1166 frame = sys._getframe(0)
1167 web_file = frame.f_code.co_filename
1168 while frame.f_code.co_filename == web_file and frame.f_back is not None:
1169 frame = frame.f_back
1170 assert frame.f_code.co_filename is not None
1171 template_path = os.path.dirname(frame.f_code.co_filename)
1172 with RequestHandler._template_loader_lock:
1173 if template_path not in RequestHandler._template_loaders:
1174 loader = self.create_template_loader(template_path)
1175 RequestHandler._template_loaders[template_path] = loader
1176 else:
1177 loader = RequestHandler._template_loaders[template_path]
1178 t = loader.load(template_name)
1179 namespace = self.get_template_namespace()
1180 namespace.update(kwargs)
1181 return t.generate(**namespace)
1183 def get_template_namespace(self) -> Dict[str, Any]:
1184 """Returns a dictionary to be used as the default template namespace.
1186 May be overridden by subclasses to add or modify values.
1188 The results of this method will be combined with additional
1189 defaults in the `tornado.template` module and keyword arguments
1190 to `render` or `render_string`.
1191 """
1192 namespace = dict(
1193 handler=self,
1194 request=self.request,
1195 current_user=self.current_user,
1196 locale=self.locale,
1197 _=self.locale.translate,
1198 pgettext=self.locale.pgettext,
1199 static_url=self.static_url,
1200 xsrf_form_html=self.xsrf_form_html,
1201 reverse_url=self.reverse_url,
1202 )
1203 namespace.update(self.ui)
1204 return namespace
1206 def create_template_loader(self, template_path: str) -> template.BaseLoader:
1207 """Returns a new template loader for the given path.
1209 May be overridden by subclasses. By default returns a
1210 directory-based loader on the given path, using the
1211 ``autoescape`` and ``template_whitespace`` application
1212 settings. If a ``template_loader`` application setting is
1213 supplied, uses that instead.
1214 """
1215 settings = self.application.settings
1216 if "template_loader" in settings:
1217 return settings["template_loader"]
1218 kwargs = {}
1219 if "autoescape" in settings:
1220 # autoescape=None means "no escaping", so we have to be sure
1221 # to only pass this kwarg if the user asked for it.
1222 kwargs["autoescape"] = settings["autoescape"]
1223 if "template_whitespace" in settings:
1224 kwargs["whitespace"] = settings["template_whitespace"]
1225 return template.Loader(template_path, **kwargs)
1227 def flush(self, include_footers: bool = False) -> "Future[None]":
1228 """Flushes the current output buffer to the network.
1230 .. versionchanged:: 4.0
1231 Now returns a `.Future` if no callback is given.
1233 .. versionchanged:: 6.0
1235 The ``callback`` argument was removed.
1236 """
1237 assert self.request.connection is not None
1238 chunk = b"".join(self._write_buffer)
1239 self._write_buffer = []
1240 if not self._headers_written:
1241 self._headers_written = True
1242 for transform in self._transforms:
1243 assert chunk is not None
1244 (
1245 self._status_code,
1246 self._headers,
1247 chunk,
1248 ) = transform.transform_first_chunk(
1249 self._status_code, self._headers, chunk, include_footers
1250 )
1251 # Ignore the chunk and only write the headers for HEAD requests
1252 if self.request.method == "HEAD":
1253 chunk = b""
1255 # Finalize the cookie headers (which have been stored in a side
1256 # object so an outgoing cookie could be overwritten before it
1257 # is sent).
1258 if hasattr(self, "_new_cookie"):
1259 for cookie in self._new_cookie.values():
1260 self.add_header("Set-Cookie", cookie.OutputString(None))
1262 start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
1263 return self.request.connection.write_headers(
1264 start_line, self._headers, chunk
1265 )
1266 else:
1267 for transform in self._transforms:
1268 chunk = transform.transform_chunk(chunk, include_footers)
1269 # Ignore the chunk and only write the headers for HEAD requests
1270 if self.request.method != "HEAD":
1271 return self.request.connection.write(chunk)
1272 else:
1273 future = Future() # type: Future[None]
1274 future.set_result(None)
1275 return future
1277 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]":
1278 """Finishes this response, ending the HTTP request.
1280 Passing a ``chunk`` to ``finish()`` is equivalent to passing that
1281 chunk to ``write()`` and then calling ``finish()`` with no arguments.
1283 Returns a `.Future` which may optionally be awaited to track the sending
1284 of the response to the client. This `.Future` resolves when all the response
1285 data has been sent, and raises an error if the connection is closed before all
1286 data can be sent.
1288 .. versionchanged:: 5.1
1290 Now returns a `.Future` instead of ``None``.
1291 """
1292 if self._finished:
1293 raise RuntimeError("finish() called twice")
1295 if chunk is not None:
1296 self.write(chunk)
1298 # Automatically support ETags and add the Content-Length header if
1299 # we have not flushed any content yet.
1300 if not self._headers_written:
1301 if (
1302 self._status_code == 200
1303 and self.request.method in ("GET", "HEAD")
1304 and "Etag" not in self._headers
1305 ):
1306 self.set_etag_header()
1307 if self.check_etag_header():
1308 self._write_buffer = []
1309 self.set_status(304)
1310 if self._status_code in (204, 304) or (100 <= self._status_code < 200):
1311 assert not self._write_buffer, (
1312 "Cannot send body with %s" % self._status_code
1313 )
1314 self._clear_representation_headers()
1315 elif "Content-Length" not in self._headers:
1316 content_length = sum(len(part) for part in self._write_buffer)
1317 self.set_header("Content-Length", content_length)
1319 assert self.request.connection is not None
1320 # Now that the request is finished, clear the callback we
1321 # set on the HTTPConnection (which would otherwise prevent the
1322 # garbage collection of the RequestHandler when there
1323 # are keepalive connections)
1324 self.request.connection.set_close_callback(None) # type: ignore
1326 future = self.flush(include_footers=True)
1327 self.request.connection.finish()
1328 self._log()
1329 self._finished = True
1330 self.on_finish()
1331 self._break_cycles()
1332 return future
1334 def detach(self) -> iostream.IOStream:
1335 """Take control of the underlying stream.
1337 Returns the underlying `.IOStream` object and stops all
1338 further HTTP processing. Intended for implementing protocols
1339 like websockets that tunnel over an HTTP handshake.
1341 This method is only supported when HTTP/1.1 is used.
1343 .. versionadded:: 5.1
1344 """
1345 self._finished = True
1346 # TODO: add detach to HTTPConnection?
1347 return self.request.connection.detach() # type: ignore
1349 def _break_cycles(self) -> None:
1350 # Break up a reference cycle between this handler and the
1351 # _ui_module closures to allow for faster GC on CPython.
1352 self.ui = None # type: ignore
1354 def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
1355 """Sends the given HTTP error code to the browser.
1357 If `flush()` has already been called, it is not possible to send
1358 an error, so this method will simply terminate the response.
1359 If output has been written but not yet flushed, it will be discarded
1360 and replaced with the error page.
1362 Override `write_error()` to customize the error page that is returned.
1363 Additional keyword arguments are passed through to `write_error`.
1364 """
1365 if self._headers_written:
1366 gen_log.error("Cannot send error response after headers written")
1367 if not self._finished:
1368 # If we get an error between writing headers and finishing,
1369 # we are unlikely to be able to finish due to a
1370 # Content-Length mismatch. Try anyway to release the
1371 # socket.
1372 try:
1373 self.finish()
1374 except Exception:
1375 gen_log.error("Failed to flush partial response", exc_info=True)
1376 return
1377 self.clear()
1379 reason = kwargs.get("reason")
1380 if "exc_info" in kwargs:
1381 exception = kwargs["exc_info"][1]
1382 if isinstance(exception, HTTPError) and exception.reason:
1383 reason = exception.reason
1384 self.set_status(status_code, reason=reason)
1385 try:
1386 if status_code != 304:
1387 self.write_error(status_code, **kwargs)
1388 except Exception:
1389 app_log.error("Uncaught exception in write_error", exc_info=True)
1390 if not self._finished:
1391 self.finish()
1393 def write_error(self, status_code: int, **kwargs: Any) -> None:
1394 """Override to implement custom error pages.
1396 ``write_error`` may call `write`, `render`, `set_header`, etc
1397 to produce output as usual.
1399 If this error was caused by an uncaught exception (including
1400 HTTPError), an ``exc_info`` triple will be available as
1401 ``kwargs["exc_info"]``. Note that this exception may not be
1402 the "current" exception for purposes of methods like
1403 ``sys.exc_info()`` or ``traceback.format_exc``.
1404 """
1405 if self.settings.get("serve_traceback") and "exc_info" in kwargs:
1406 # in debug mode, try to send a traceback
1407 self.set_header("Content-Type", "text/plain")
1408 for line in traceback.format_exception(*kwargs["exc_info"]):
1409 self.write(line)
1410 self.finish()
1411 else:
1412 self.finish(
1413 "<html><title>%(code)d: %(message)s</title>"
1414 "<body>%(code)d: %(message)s</body></html>"
1415 % {"code": status_code, "message": escape.xhtml_escape(self._reason)}
1416 )
1418 @property
1419 def locale(self) -> tornado.locale.Locale:
1420 """The locale for the current session.
1422 Determined by either `get_user_locale`, which you can override to
1423 set the locale based on, e.g., a user preference stored in a
1424 database, or `get_browser_locale`, which uses the ``Accept-Language``
1425 header.
1427 .. versionchanged: 4.1
1428 Added a property setter.
1429 """
1430 if not hasattr(self, "_locale"):
1431 loc = self.get_user_locale()
1432 if loc is not None:
1433 self._locale = loc
1434 else:
1435 self._locale = self.get_browser_locale()
1436 assert self._locale
1437 return self._locale
1439 @locale.setter
1440 def locale(self, value: tornado.locale.Locale) -> None:
1441 self._locale = value
1443 def get_user_locale(self) -> Optional[tornado.locale.Locale]:
1444 """Override to determine the locale from the authenticated user.
1446 If None is returned, we fall back to `get_browser_locale()`.
1448 This method should return a `tornado.locale.Locale` object,
1449 most likely obtained via a call like ``tornado.locale.get("en")``
1450 """
1451 return None
1453 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
1454 """Determines the user's locale from ``Accept-Language`` header.
1456 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
1457 """
1458 if "Accept-Language" in self.request.headers:
1459 languages = self.request.headers["Accept-Language"].split(",")
1460 locales = []
1461 for language in languages:
1462 parts = language.strip().split(";")
1463 if len(parts) > 1 and parts[1].strip().startswith("q="):
1464 try:
1465 score = float(parts[1].strip()[2:])
1466 if score < 0:
1467 raise ValueError()
1468 except (ValueError, TypeError):
1469 score = 0.0
1470 else:
1471 score = 1.0
1472 if score > 0:
1473 locales.append((parts[0], score))
1474 if locales:
1475 locales.sort(key=lambda pair: pair[1], reverse=True)
1476 codes = [loc[0] for loc in locales]
1477 return locale.get(*codes)
1478 return locale.get(default)
1480 @property
1481 def current_user(self) -> Any:
1482 """The authenticated user for this request.
1484 This is set in one of two ways:
1486 * A subclass may override `get_current_user()`, which will be called
1487 automatically the first time ``self.current_user`` is accessed.
1488 `get_current_user()` will only be called once per request,
1489 and is cached for future access::
1491 def get_current_user(self):
1492 user_cookie = self.get_signed_cookie("user")
1493 if user_cookie:
1494 return json.loads(user_cookie)
1495 return None
1497 * It may be set as a normal variable, typically from an overridden
1498 `prepare()`::
1500 @gen.coroutine
1501 def prepare(self):
1502 user_id_cookie = self.get_signed_cookie("user_id")
1503 if user_id_cookie:
1504 self.current_user = yield load_user(user_id_cookie)
1506 Note that `prepare()` may be a coroutine while `get_current_user()`
1507 may not, so the latter form is necessary if loading the user requires
1508 asynchronous operations.
1510 The user object may be any type of the application's choosing.
1511 """
1512 if not hasattr(self, "_current_user"):
1513 self._current_user = self.get_current_user()
1514 return self._current_user
1516 @current_user.setter
1517 def current_user(self, value: Any) -> None:
1518 self._current_user = value
1520 def get_current_user(self) -> Any:
1521 """Override to determine the current user from, e.g., a cookie.
1523 This method may not be a coroutine.
1524 """
1525 return None
1527 def get_login_url(self) -> str:
1528 """Override to customize the login URL based on the request.
1530 By default, we use the ``login_url`` application setting.
1531 """
1532 self.require_setting("login_url", "@tornado.web.authenticated")
1533 return self.application.settings["login_url"]
1535 def get_template_path(self) -> Optional[str]:
1536 """Override to customize template path for each handler.
1538 By default, we use the ``template_path`` application setting.
1539 Return None to load templates relative to the calling file.
1540 """
1541 return self.application.settings.get("template_path")
1543 @property
1544 def xsrf_token(self) -> bytes:
1545 """The XSRF-prevention token for the current user/session.
1547 To prevent cross-site request forgery, we set an '_xsrf' cookie
1548 and include the same '_xsrf' value as an argument with all POST
1549 requests. If the two do not match, we reject the form submission
1550 as a potential forgery.
1552 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1554 This property is of type `bytes`, but it contains only ASCII
1555 characters. If a character string is required, there is no
1556 need to base64-encode it; just decode the byte string as
1557 UTF-8.
1559 .. versionchanged:: 3.2.2
1560 The xsrf token will now be have a random mask applied in every
1561 request, which makes it safe to include the token in pages
1562 that are compressed. See http://breachattack.com for more
1563 information on the issue fixed by this change. Old (version 1)
1564 cookies will be converted to version 2 when this method is called
1565 unless the ``xsrf_cookie_version`` `Application` setting is
1566 set to 1.
1568 .. versionchanged:: 4.3
1569 The ``xsrf_cookie_kwargs`` `Application` setting may be
1570 used to supply additional cookie options (which will be
1571 passed directly to `set_cookie`). For example,
1572 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
1573 will set the ``secure`` and ``httponly`` flags on the
1574 ``_xsrf`` cookie.
1575 """
1576 if not hasattr(self, "_xsrf_token"):
1577 version, token, timestamp = self._get_raw_xsrf_token()
1578 output_version = self.settings.get("xsrf_cookie_version", 2)
1579 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
1580 if output_version == 1:
1581 self._xsrf_token = binascii.b2a_hex(token)
1582 elif output_version == 2:
1583 mask = os.urandom(4)
1584 self._xsrf_token = b"|".join(
1585 [
1586 b"2",
1587 binascii.b2a_hex(mask),
1588 binascii.b2a_hex(_websocket_mask(mask, token)),
1589 utf8(str(int(timestamp))),
1590 ]
1591 )
1592 else:
1593 raise ValueError("unknown xsrf cookie version %d", output_version)
1594 if version is None:
1595 if self.current_user and "expires_days" not in cookie_kwargs:
1596 cookie_kwargs["expires_days"] = 30
1597 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1598 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)
1599 return self._xsrf_token
1601 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
1602 """Read or generate the xsrf token in its raw form.
1604 The raw_xsrf_token is a tuple containing:
1606 * version: the version of the cookie from which this token was read,
1607 or None if we generated a new token in this request.
1608 * token: the raw token data; random (non-ascii) bytes.
1609 * timestamp: the time this token was generated (will not be accurate
1610 for version 1 cookies)
1611 """
1612 if not hasattr(self, "_raw_xsrf_token"):
1613 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1614 cookie = self.get_cookie(cookie_name)
1615 if cookie:
1616 version, token, timestamp = self._decode_xsrf_token(cookie)
1617 else:
1618 version, token, timestamp = None, None, None
1619 if token is None:
1620 version = None
1621 token = os.urandom(16)
1622 timestamp = time.time()
1623 assert token is not None
1624 assert timestamp is not None
1625 self._raw_xsrf_token = (version, token, timestamp)
1626 return self._raw_xsrf_token
1628 def _decode_xsrf_token(
1629 self, cookie: str
1630 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
1631 """Convert a cookie string into a the tuple form returned by
1632 _get_raw_xsrf_token.
1633 """
1635 try:
1636 m = _signed_value_version_re.match(utf8(cookie))
1638 if m:
1639 version = int(m.group(1))
1640 if version == 2:
1641 _, mask_str, masked_token, timestamp_str = cookie.split("|")
1643 mask = binascii.a2b_hex(utf8(mask_str))
1644 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
1645 timestamp = int(timestamp_str)
1646 return version, token, timestamp
1647 else:
1648 # Treat unknown versions as not present instead of failing.
1649 raise Exception("Unknown xsrf cookie version")
1650 else:
1651 version = 1
1652 try:
1653 token = binascii.a2b_hex(utf8(cookie))
1654 except (binascii.Error, TypeError):
1655 token = utf8(cookie)
1656 # We don't have a usable timestamp in older versions.
1657 timestamp = int(time.time())
1658 return (version, token, timestamp)
1659 except Exception:
1660 # Catch exceptions and return nothing instead of failing.
1661 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
1662 return None, None, None
1664 def check_xsrf_cookie(self) -> None:
1665 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
1667 To prevent cross-site request forgery, we set an ``_xsrf``
1668 cookie and include the same value as a non-cookie
1669 field with all ``POST`` requests. If the two do not match, we
1670 reject the form submission as a potential forgery.
1672 The ``_xsrf`` value may be set as either a form field named ``_xsrf``
1673 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
1674 (the latter is accepted for compatibility with Django).
1676 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1678 .. versionchanged:: 3.2.2
1679 Added support for cookie version 2. Both versions 1 and 2 are
1680 supported.
1681 """
1682 # Prior to release 1.1.1, this check was ignored if the HTTP header
1683 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception
1684 # has been shown to be insecure and has been removed. For more
1685 # information please see
1686 # http://www.djangoproject.com/weblog/2011/feb/08/security/
1687 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
1688 input_token = (
1689 self.get_argument("_xsrf", None)
1690 or self.request.headers.get("X-Xsrftoken")
1691 or self.request.headers.get("X-Csrftoken")
1692 )
1693 if not input_token:
1694 raise HTTPError(403, "'_xsrf' argument missing from POST")
1695 _, token, _ = self._decode_xsrf_token(input_token)
1696 _, expected_token, _ = self._get_raw_xsrf_token()
1697 if not token:
1698 raise HTTPError(403, "'_xsrf' argument has invalid format")
1699 if not hmac.compare_digest(utf8(token), utf8(expected_token)):
1700 raise HTTPError(403, "XSRF cookie does not match POST argument")
1702 def xsrf_form_html(self) -> str:
1703 """An HTML ``<input/>`` element to be included with all POST forms.
1705 It defines the ``_xsrf`` input value, which we check on all POST
1706 requests to prevent cross-site request forgery. If you have set
1707 the ``xsrf_cookies`` application setting, you must include this
1708 HTML within all of your HTML forms.
1710 In a template, this method should be called with ``{% module
1711 xsrf_form_html() %}``
1713 See `check_xsrf_cookie()` above for more information.
1714 """
1715 return (
1716 '<input type="hidden" name="_xsrf" value="'
1717 + escape.xhtml_escape(self.xsrf_token)
1718 + '"/>'
1719 )
1721 def static_url(
1722 self, path: str, include_host: Optional[bool] = None, **kwargs: Any
1723 ) -> str:
1724 """Returns a static URL for the given relative static file path.
1726 This method requires you set the ``static_path`` setting in your
1727 application (which specifies the root directory of your static
1728 files).
1730 This method returns a versioned url (by default appending
1731 ``?v=<signature>``), which allows the static files to be
1732 cached indefinitely. This can be disabled by passing
1733 ``include_version=False`` (in the default implementation;
1734 other static file implementations are not required to support
1735 this, but they may support other options).
1737 By default this method returns URLs relative to the current
1738 host, but if ``include_host`` is true the URL returned will be
1739 absolute. If this handler has an ``include_host`` attribute,
1740 that value will be used as the default for all `static_url`
1741 calls that do not pass ``include_host`` as a keyword argument.
1743 """
1744 self.require_setting("static_path", "static_url")
1745 get_url = self.settings.get(
1746 "static_handler_class", StaticFileHandler
1747 ).make_static_url
1749 if include_host is None:
1750 include_host = getattr(self, "include_host", False)
1752 if include_host:
1753 base = self.request.protocol + "://" + self.request.host
1754 else:
1755 base = ""
1757 return base + get_url(self.settings, path, **kwargs)
1759 def require_setting(self, name: str, feature: str = "this feature") -> None:
1760 """Raises an exception if the given app setting is not defined."""
1761 if not self.application.settings.get(name):
1762 raise Exception(
1763 "You must define the '%s' setting in your "
1764 "application to use %s" % (name, feature)
1765 )
1767 def reverse_url(self, name: str, *args: Any) -> str:
1768 """Alias for `Application.reverse_url`."""
1769 return self.application.reverse_url(name, *args)
1771 def compute_etag(self) -> Optional[str]:
1772 """Computes the etag header to be used for this request.
1774 By default uses a hash of the content written so far.
1776 May be overridden to provide custom etag implementations,
1777 or may return None to disable tornado's default etag support.
1778 """
1779 hasher = hashlib.sha1()
1780 for part in self._write_buffer:
1781 hasher.update(part)
1782 return '"%s"' % hasher.hexdigest()
1784 def set_etag_header(self) -> None:
1785 """Sets the response's Etag header using ``self.compute_etag()``.
1787 Note: no header will be set if ``compute_etag()`` returns ``None``.
1789 This method is called automatically when the request is finished.
1790 """
1791 etag = self.compute_etag()
1792 if etag is not None:
1793 self.set_header("Etag", etag)
1795 def check_etag_header(self) -> bool:
1796 """Checks the ``Etag`` header against requests's ``If-None-Match``.
1798 Returns ``True`` if the request's Etag matches and a 304 should be
1799 returned. For example::
1801 self.set_etag_header()
1802 if self.check_etag_header():
1803 self.set_status(304)
1804 return
1806 This method is called automatically when the request is finished,
1807 but may be called earlier for applications that override
1808 `compute_etag` and want to do an early check for ``If-None-Match``
1809 before completing the request. The ``Etag`` header should be set
1810 (perhaps with `set_etag_header`) before calling this method.
1811 """
1812 computed_etag = utf8(self._headers.get("Etag", ""))
1813 # Find all weak and strong etag values from If-None-Match header
1814 # because RFC 7232 allows multiple etag values in a single header.
1815 etags = re.findall(
1816 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
1817 )
1818 if not computed_etag or not etags:
1819 return False
1821 match = False
1822 if etags[0] == b"*":
1823 match = True
1824 else:
1825 # Use a weak comparison when comparing entity-tags.
1826 def val(x: bytes) -> bytes:
1827 return x[2:] if x.startswith(b"W/") else x
1829 for etag in etags:
1830 if val(etag) == val(computed_etag):
1831 match = True
1832 break
1833 return match
1835 async def _execute(
1836 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
1837 ) -> None:
1838 """Executes this request with the given output transforms."""
1839 self._transforms = transforms
1840 try:
1841 if self.request.method not in self.SUPPORTED_METHODS:
1842 raise HTTPError(405)
1844 # If we're not in stream_request_body mode, this is the place where we parse the body.
1845 if not _has_stream_request_body(self.__class__):
1846 try:
1847 self.request._parse_body()
1848 except httputil.HTTPInputError as e:
1849 raise HTTPError(400, "Invalid body: %s" % e) from e
1851 self.path_args = [self.decode_argument(arg) for arg in args]
1852 self.path_kwargs = {
1853 k: self.decode_argument(v, name=k) for (k, v) in kwargs.items()
1854 }
1855 # If XSRF cookies are turned on, reject form submissions without
1856 # the proper cookie
1857 if self.request.method not in (
1858 "GET",
1859 "HEAD",
1860 "OPTIONS",
1861 ) and self.application.settings.get("xsrf_cookies"):
1862 self.check_xsrf_cookie()
1864 result = self.prepare()
1865 if result is not None:
1866 result = await result # type: ignore
1867 if self._prepared_future is not None:
1868 # Tell the Application we've finished with prepare()
1869 # and are ready for the body to arrive.
1870 future_set_result_unless_cancelled(self._prepared_future, None)
1871 if self._finished:
1872 return
1874 if _has_stream_request_body(self.__class__):
1875 # In streaming mode request.body is a Future that signals
1876 # the body has been completely received. The Future has no
1877 # result; the data has been passed to self.data_received
1878 # instead.
1879 try:
1880 await self.request._body_future
1881 except iostream.StreamClosedError:
1882 return
1884 method = getattr(self, self.request.method.lower())
1885 result = method(*self.path_args, **self.path_kwargs)
1886 if result is not None:
1887 result = await result
1888 if self._auto_finish and not self._finished:
1889 self.finish()
1890 except Exception as e:
1891 try:
1892 self._handle_request_exception(e)
1893 except Exception:
1894 app_log.error("Exception in exception handler", exc_info=True)
1895 finally:
1896 # Unset result to avoid circular references
1897 result = None
1898 if self._prepared_future is not None and not self._prepared_future.done():
1899 # In case we failed before setting _prepared_future, do it
1900 # now (to unblock the HTTP server). Note that this is not
1901 # in a finally block to avoid GC issues prior to Python 3.4.
1902 self._prepared_future.set_result(None)
1904 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
1905 """Implement this method to handle streamed request data.
1907 Requires the `.stream_request_body` decorator.
1909 May be a coroutine for flow control.
1910 """
1911 raise NotImplementedError()
1913 def _log(self) -> None:
1914 """Logs the current request.
1916 Sort of deprecated since this functionality was moved to the
1917 Application, but left in place for the benefit of existing apps
1918 that have overridden this method.
1919 """
1920 self.application.log_request(self)
1922 def _request_summary(self) -> str:
1923 return "{} {} ({})".format(
1924 self.request.method,
1925 self.request.uri,
1926 self.request.remote_ip,
1927 )
1929 def _handle_request_exception(self, e: BaseException) -> None:
1930 if isinstance(e, Finish):
1931 # Not an error; just finish the request without logging.
1932 if not self._finished:
1933 self.finish(*e.args)
1934 return
1935 try:
1936 self.log_exception(*sys.exc_info())
1937 except Exception:
1938 # An error here should still get a best-effort send_error()
1939 # to avoid leaking the connection.
1940 app_log.error("Error in exception logger", exc_info=True)
1941 if self._finished:
1942 # Extra errors after the request has been finished should
1943 # be logged, but there is no reason to continue to try and
1944 # send a response.
1945 return
1946 if isinstance(e, HTTPError):
1947 self.send_error(e.status_code, exc_info=sys.exc_info())
1948 else:
1949 self.send_error(500, exc_info=sys.exc_info())
1951 def log_exception(
1952 self,
1953 typ: "Optional[Type[BaseException]]",
1954 value: Optional[BaseException],
1955 tb: Optional[TracebackType],
1956 ) -> None:
1957 """Override to customize logging of uncaught exceptions.
1959 By default logs instances of `HTTPError` as warnings without
1960 stack traces (on the ``tornado.general`` logger), and all
1961 other exceptions as errors with stack traces (on the
1962 ``tornado.application`` logger).
1964 .. versionadded:: 3.1
1965 """
1966 if isinstance(value, HTTPError):
1967 log_message = value.get_message()
1968 if log_message:
1969 format = "%d %s: %s"
1970 args = [value.status_code, self._request_summary(), log_message]
1971 gen_log.warning(format, *args)
1972 else:
1973 app_log.error(
1974 "Uncaught exception %s\n%r",
1975 self._request_summary(),
1976 self.request,
1977 exc_info=(typ, value, tb), # type: ignore
1978 )
1980 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
1981 def render(*args, **kwargs) -> str: # type: ignore
1982 if not hasattr(self, "_active_modules"):
1983 self._active_modules = {} # type: Dict[str, UIModule]
1984 if name not in self._active_modules:
1985 self._active_modules[name] = module(self)
1986 rendered = self._active_modules[name].render(*args, **kwargs)
1987 return _unicode(rendered)
1989 return render
1991 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
1992 return lambda *args, **kwargs: method(self, *args, **kwargs)
1994 def _clear_representation_headers(self) -> None:
1995 # 304 responses should not contain representation metadata
1996 # headers (defined in
1997 # https://tools.ietf.org/html/rfc7231#section-3.1)
1998 # not explicitly allowed by
1999 # https://tools.ietf.org/html/rfc7232#section-4.1
2000 headers = ["Content-Encoding", "Content-Language", "Content-Type"]
2001 for h in headers:
2002 self.clear_header(h)
2005_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler)
2008def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]:
2009 """Apply to `RequestHandler` subclasses to enable streaming body support.
2011 This decorator implies the following changes:
2013 * `.HTTPServerRequest.body` is undefined, and body arguments will not
2014 be included in `RequestHandler.get_argument`.
2015 * `RequestHandler.prepare` is called when the request headers have been
2016 read instead of after the entire body has been read.
2017 * The subclass must define a method ``data_received(self, data):``, which
2018 will be called zero or more times as data is available. Note that
2019 if the request has an empty body, ``data_received`` may not be called.
2020 * ``prepare`` and ``data_received`` may return Futures (such as via
2021 ``@gen.coroutine``, in which case the next method will not be called
2022 until those futures have completed.
2023 * The regular HTTP method (``post``, ``put``, etc) will be called after
2024 the entire body has been read.
2026 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_
2027 for example usage.
2028 """ # noqa: E501
2029 if not issubclass(cls, RequestHandler):
2030 raise TypeError("expected subclass of RequestHandler, got %r", cls)
2031 cls._stream_request_body = True
2032 return cls
2035def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
2036 if not issubclass(cls, RequestHandler):
2037 raise TypeError("expected subclass of RequestHandler, got %r", cls)
2038 return cls._stream_request_body
2041def removeslash(
2042 method: Callable[..., Optional[Awaitable[None]]],
2043) -> Callable[..., Optional[Awaitable[None]]]:
2044 """Use this decorator to remove trailing slashes from the request path.
2046 For example, a request to ``/foo/`` would redirect to ``/foo`` with this
2047 decorator. Your request handler mapping should use a regular expression
2048 like ``r'/foo/*'`` in conjunction with using the decorator.
2049 """
2051 @functools.wraps(method)
2052 def wrapper( # type: ignore
2053 self: RequestHandler, *args, **kwargs
2054 ) -> Optional[Awaitable[None]]:
2055 if self.request.path.endswith("/"):
2056 if self.request.method in ("GET", "HEAD"):
2057 uri = self.request.path.rstrip("/")
2058 if uri: # don't try to redirect '/' to ''
2059 if self.request.query:
2060 uri += "?" + self.request.query
2061 self.redirect(uri, permanent=True)
2062 return None
2063 else:
2064 raise HTTPError(404)
2065 return method(self, *args, **kwargs)
2067 return wrapper
2070def addslash(
2071 method: Callable[..., Optional[Awaitable[None]]],
2072) -> Callable[..., Optional[Awaitable[None]]]:
2073 """Use this decorator to add a missing trailing slash to the request path.
2075 For example, a request to ``/foo`` would redirect to ``/foo/`` with this
2076 decorator. Your request handler mapping should use a regular expression
2077 like ``r'/foo/?'`` in conjunction with using the decorator.
2078 """
2080 @functools.wraps(method)
2081 def wrapper( # type: ignore
2082 self: RequestHandler, *args, **kwargs
2083 ) -> Optional[Awaitable[None]]:
2084 if not self.request.path.endswith("/"):
2085 if self.request.method in ("GET", "HEAD"):
2086 uri = self.request.path + "/"
2087 if self.request.query:
2088 uri += "?" + self.request.query
2089 self.redirect(uri, permanent=True)
2090 return None
2091 raise HTTPError(404)
2092 return method(self, *args, **kwargs)
2094 return wrapper
2097class _ApplicationRouter(ReversibleRuleRouter):
2098 """Routing implementation used internally by `Application`.
2100 Provides a binding between `Application` and `RequestHandler`.
2101 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
2102 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
2103 * it allows to use a list/tuple of rules as `~.routing.Rule` target.
2104 ``process_rule`` implementation will substitute this list with an appropriate
2105 `_ApplicationRouter` instance.
2106 """
2108 def __init__(
2109 self, application: "Application", rules: Optional[_RuleList] = None
2110 ) -> None:
2111 assert isinstance(application, Application)
2112 self.application = application
2113 super().__init__(rules)
2115 def process_rule(self, rule: Rule) -> Rule:
2116 rule = super().process_rule(rule)
2118 if isinstance(rule.target, (list, tuple)):
2119 rule.target = _ApplicationRouter(
2120 self.application, rule.target # type: ignore
2121 )
2123 return rule
2125 def get_target_delegate(
2126 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
2127 ) -> Optional[httputil.HTTPMessageDelegate]:
2128 if isclass(target) and issubclass(target, RequestHandler):
2129 return self.application.get_handler_delegate(
2130 request, target, **target_params
2131 )
2133 return super().get_target_delegate(target, request, **target_params)
2136class Application(ReversibleRouter):
2137 r"""A collection of request handlers that make up a web application.
2139 Instances of this class are callable and can be passed directly to
2140 HTTPServer to serve the application::
2142 application = web.Application([
2143 (r"/", MainPageHandler),
2144 ])
2145 http_server = httpserver.HTTPServer(application)
2146 http_server.listen(8080)
2148 The constructor for this class takes in a list of `~.routing.Rule`
2149 objects or tuples of values corresponding to the arguments of
2150 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
2151 the values in square brackets being optional. The default matcher is
2152 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
2153 instead of ``(PathMatches(regexp), target)``.
2155 A common routing target is a `RequestHandler` subclass, but you can also
2156 use lists of rules as a target, which create a nested routing configuration::
2158 application = web.Application([
2159 (HostMatches("example.com"), [
2160 (r"/", MainPageHandler),
2161 (r"/feed", FeedHandler),
2162 ]),
2163 ])
2165 In addition to this you can use nested `~.routing.Router` instances,
2166 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
2167 (see `~.routing` module docs for more information).
2169 When we receive requests, we iterate over the list in order and
2170 instantiate an instance of the first request class whose regexp
2171 matches the request path. The request class can be specified as
2172 either a class object or a (fully-qualified) name.
2174 A dictionary may be passed as the third element (``target_kwargs``)
2175 of the tuple, which will be used as keyword arguments to the handler's
2176 constructor and `~RequestHandler.initialize` method. This pattern
2177 is used for the `StaticFileHandler` in this example (note that a
2178 `StaticFileHandler` can be installed automatically with the
2179 static_path setting described below)::
2181 application = web.Application([
2182 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2183 ])
2185 We support virtual hosts with the `add_handlers` method, which takes in
2186 a host regular expression as the first argument::
2188 application.add_handlers(r"www\.myhost\.com", [
2189 (r"/article/([0-9]+)", ArticleHandler),
2190 ])
2192 If there's no match for the current request's host, then ``default_host``
2193 parameter value is matched against host regular expressions.
2196 .. warning::
2198 Applications that do not use TLS may be vulnerable to :ref:`DNS
2199 rebinding <dnsrebinding>` attacks. This attack is especially
2200 relevant to applications that only listen on ``127.0.0.1`` or
2201 other private networks. Appropriate host patterns must be used
2202 (instead of the default of ``r'.*'``) to prevent this risk. The
2203 ``default_host`` argument must not be used in applications that
2204 may be vulnerable to DNS rebinding.
2206 You can serve static files by sending the ``static_path`` setting
2207 as a keyword argument. We will serve those files from the
2208 ``/static/`` URI (this is configurable with the
2209 ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
2210 and ``/robots.txt`` from the same directory. A custom subclass of
2211 `StaticFileHandler` can be specified with the
2212 ``static_handler_class`` setting.
2214 .. versionchanged:: 4.5
2215 Integration with the new `tornado.routing` module.
2217 """
2219 def __init__(
2220 self,
2221 handlers: Optional[_RuleList] = None,
2222 default_host: Optional[str] = None,
2223 transforms: Optional[List[Type["OutputTransform"]]] = None,
2224 **settings: Any,
2225 ) -> None:
2226 if transforms is None:
2227 self.transforms = [] # type: List[Type[OutputTransform]]
2228 if settings.get("compress_response") or settings.get("gzip"):
2229 self.transforms.append(GZipContentEncoding)
2230 else:
2231 self.transforms = transforms
2232 self.default_host = default_host
2233 self.settings = settings
2234 self.ui_modules = {
2235 "linkify": _linkify,
2236 "xsrf_form_html": _xsrf_form_html,
2237 "Template": TemplateModule,
2238 }
2239 self.ui_methods = {} # type: Dict[str, Callable[..., str]]
2240 self._load_ui_modules(settings.get("ui_modules", {}))
2241 self._load_ui_methods(settings.get("ui_methods", {}))
2242 if self.settings.get("static_path"):
2243 path = self.settings["static_path"]
2244 handlers = list(handlers or [])
2245 static_url_prefix = settings.get("static_url_prefix", "/static/")
2246 static_handler_class = settings.get(
2247 "static_handler_class", StaticFileHandler
2248 )
2249 static_handler_args = settings.get("static_handler_args", {})
2250 static_handler_args["path"] = path
2251 for pattern in [
2252 re.escape(static_url_prefix) + r"(.*)",
2253 r"/(favicon\.ico)",
2254 r"/(robots\.txt)",
2255 ]:
2256 handlers.insert(0, (pattern, static_handler_class, static_handler_args))
2258 if self.settings.get("debug"):
2259 self.settings.setdefault("autoreload", True)
2260 self.settings.setdefault("compiled_template_cache", False)
2261 self.settings.setdefault("static_hash_cache", False)
2262 self.settings.setdefault("serve_traceback", True)
2264 self.wildcard_router = _ApplicationRouter(self, handlers)
2265 self.default_router = _ApplicationRouter(
2266 self, [Rule(AnyMatches(), self.wildcard_router)]
2267 )
2269 # Automatically reload modified modules
2270 if self.settings.get("autoreload"):
2271 from tornado import autoreload
2273 autoreload.start()
2275 def listen(
2276 self,
2277 port: int,
2278 address: Optional[str] = None,
2279 *,
2280 family: socket.AddressFamily = socket.AF_UNSPEC,
2281 backlog: int = tornado.netutil._DEFAULT_BACKLOG,
2282 flags: Optional[int] = None,
2283 reuse_port: bool = False,
2284 **kwargs: Any,
2285 ) -> HTTPServer:
2286 """Starts an HTTP server for this application on the given port.
2288 This is a convenience alias for creating an `.HTTPServer` object and
2289 calling its listen method. Keyword arguments not supported by
2290 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`
2291 constructor. For advanced uses (e.g. multi-process mode), do not use
2292 this method; create an `.HTTPServer` and call its
2293 `.TCPServer.bind`/`.TCPServer.start` methods directly.
2295 Note that after calling this method you still need to call
2296 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start
2297 the server.
2299 Returns the `.HTTPServer` object.
2301 .. versionchanged:: 4.3
2302 Now returns the `.HTTPServer` object.
2304 .. versionchanged:: 6.2
2305 Added support for new keyword arguments in `.TCPServer.listen`,
2306 including ``reuse_port``.
2307 """
2308 server = HTTPServer(self, **kwargs)
2309 server.listen(
2310 port,
2311 address=address,
2312 family=family,
2313 backlog=backlog,
2314 flags=flags,
2315 reuse_port=reuse_port,
2316 )
2317 return server
2319 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
2320 """Appends the given handlers to our handler list.
2322 Host patterns are processed sequentially in the order they were
2323 added. All matching patterns will be considered.
2324 """
2325 host_matcher = HostMatches(host_pattern)
2326 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
2328 self.default_router.rules.insert(-1, rule)
2330 if self.default_host is not None:
2331 self.wildcard_router.add_rules(
2332 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
2333 )
2335 def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
2336 self.transforms.append(transform_class)
2338 def _load_ui_methods(self, methods: Any) -> None:
2339 if isinstance(methods, types.ModuleType):
2340 self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)})
2341 elif isinstance(methods, list):
2342 for m in methods:
2343 self._load_ui_methods(m)
2344 else:
2345 for name, fn in methods.items():
2346 if (
2347 not name.startswith("_")
2348 and hasattr(fn, "__call__")
2349 and name[0].lower() == name[0]
2350 ):
2351 self.ui_methods[name] = fn
2353 def _load_ui_modules(self, modules: Any) -> None:
2354 if isinstance(modules, types.ModuleType):
2355 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)})
2356 elif isinstance(modules, list):
2357 for m in modules:
2358 self._load_ui_modules(m)
2359 else:
2360 assert isinstance(modules, dict)
2361 for name, cls in modules.items():
2362 try:
2363 if issubclass(cls, UIModule):
2364 self.ui_modules[name] = cls
2365 except TypeError:
2366 pass
2368 def __call__(
2369 self, request: httputil.HTTPServerRequest
2370 ) -> Optional[Awaitable[None]]:
2371 # Legacy HTTPServer interface
2372 dispatcher = self.find_handler(request)
2373 return dispatcher.execute()
2375 def find_handler(
2376 self, request: httputil.HTTPServerRequest, **kwargs: Any
2377 ) -> "_HandlerDelegate":
2378 route = self.default_router.find_handler(request)
2379 if route is not None:
2380 return cast("_HandlerDelegate", route)
2382 if self.settings.get("default_handler_class"):
2383 return self.get_handler_delegate(
2384 request,
2385 self.settings["default_handler_class"],
2386 self.settings.get("default_handler_args", {}),
2387 )
2389 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
2391 def get_handler_delegate(
2392 self,
2393 request: httputil.HTTPServerRequest,
2394 target_class: Type[RequestHandler],
2395 target_kwargs: Optional[Dict[str, Any]] = None,
2396 path_args: Optional[List[bytes]] = None,
2397 path_kwargs: Optional[Dict[str, bytes]] = None,
2398 ) -> "_HandlerDelegate":
2399 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
2400 for application and `RequestHandler` subclass.
2402 :arg httputil.HTTPServerRequest request: current HTTP request.
2403 :arg RequestHandler target_class: a `RequestHandler` class.
2404 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
2405 :arg list path_args: positional arguments for ``target_class`` HTTP method that
2406 will be executed while handling a request (``get``, ``post`` or any other).
2407 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
2408 """
2409 return _HandlerDelegate(
2410 self, request, target_class, target_kwargs, path_args, path_kwargs
2411 )
2413 def reverse_url(self, name: str, *args: Any) -> str:
2414 """Returns a URL path for handler named ``name``
2416 The handler must be added to the application as a named `URLSpec`.
2418 Args will be substituted for capturing groups in the `URLSpec` regex.
2419 They will be converted to strings if necessary, encoded as utf8,
2420 and url-escaped.
2421 """
2422 reversed_url = self.default_router.reverse_url(name, *args)
2423 if reversed_url is not None:
2424 return reversed_url
2426 raise KeyError("%s not found in named urls" % name)
2428 def log_request(self, handler: RequestHandler) -> None:
2429 """Writes a completed HTTP request to the logs.
2431 By default writes to the python root logger. To change
2432 this behavior either subclass Application and override this method,
2433 or pass a function in the application settings dictionary as
2434 ``log_function``.
2435 """
2436 if "log_function" in self.settings:
2437 self.settings["log_function"](handler)
2438 return
2439 if handler.get_status() < 400:
2440 log_method = access_log.info
2441 elif handler.get_status() < 500:
2442 log_method = access_log.warning
2443 else:
2444 log_method = access_log.error
2445 request_time = 1000.0 * handler.request.request_time()
2446 log_method(
2447 "%d %s %.2fms",
2448 handler.get_status(),
2449 handler._request_summary(),
2450 request_time,
2451 )
2454class _HandlerDelegate(httputil.HTTPMessageDelegate):
2455 def __init__(
2456 self,
2457 application: Application,
2458 request: httputil.HTTPServerRequest,
2459 handler_class: Type[RequestHandler],
2460 handler_kwargs: Optional[Dict[str, Any]],
2461 path_args: Optional[List[bytes]],
2462 path_kwargs: Optional[Dict[str, bytes]],
2463 ) -> None:
2464 self.application = application
2465 self.connection = request.connection
2466 self.request = request
2467 self.handler_class = handler_class
2468 self.handler_kwargs = handler_kwargs or {}
2469 self.path_args = path_args or []
2470 self.path_kwargs = path_kwargs or {}
2471 self.chunks = [] # type: List[bytes]
2472 self.stream_request_body = _has_stream_request_body(self.handler_class)
2474 def headers_received(
2475 self,
2476 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
2477 headers: httputil.HTTPHeaders,
2478 ) -> Optional[Awaitable[None]]:
2479 if self.stream_request_body:
2480 self.request._body_future = Future()
2481 return self.execute()
2482 return None
2484 def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
2485 if self.stream_request_body:
2486 return self.handler.data_received(data)
2487 else:
2488 self.chunks.append(data)
2489 return None
2491 def finish(self) -> None:
2492 if self.stream_request_body:
2493 future_set_result_unless_cancelled(self.request._body_future, None)
2494 else:
2495 # Note that the body gets parsed in RequestHandler._execute so it can be in
2496 # the right exception handler scope.
2497 self.request.body = b"".join(self.chunks)
2498 self.execute()
2500 def on_connection_close(self) -> None:
2501 if self.stream_request_body:
2502 self.handler.on_connection_close()
2503 else:
2504 self.chunks = None # type: ignore
2506 def execute(self) -> Optional[Awaitable[None]]:
2507 # If template cache is disabled (usually in the debug mode),
2508 # re-compile templates and reload static files on every
2509 # request so you don't need to restart to see changes
2510 if not self.application.settings.get("compiled_template_cache", True):
2511 with RequestHandler._template_loader_lock:
2512 for loader in RequestHandler._template_loaders.values():
2513 loader.reset()
2514 if not self.application.settings.get("static_hash_cache", True):
2515 static_handler_class = self.application.settings.get(
2516 "static_handler_class", StaticFileHandler
2517 )
2518 static_handler_class.reset()
2520 self.handler = self.handler_class(
2521 self.application, self.request, **self.handler_kwargs
2522 )
2523 transforms = [t(self.request) for t in self.application.transforms]
2525 if self.stream_request_body:
2526 self.handler._prepared_future = Future()
2527 # Note that if an exception escapes handler._execute it will be
2528 # trapped in the Future it returns (which we are ignoring here,
2529 # leaving it to be logged when the Future is GC'd).
2530 # However, that shouldn't happen because _execute has a blanket
2531 # except handler, and we cannot easily access the IOLoop here to
2532 # call add_future (because of the requirement to remain compatible
2533 # with WSGI)
2534 fut = gen.convert_yielded(
2535 self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
2536 )
2537 fut.add_done_callback(lambda f: f.result())
2538 # If we are streaming the request body, then execute() is finished
2539 # when the handler has prepared to receive the body. If not,
2540 # it doesn't matter when execute() finishes (so we return None)
2541 return self.handler._prepared_future
2544class HTTPError(Exception):
2545 """An exception that will turn into an HTTP error response.
2547 Raising an `HTTPError` is a convenient alternative to calling
2548 `RequestHandler.send_error` since it automatically ends the
2549 current function.
2551 To customize the response sent with an `HTTPError`, override
2552 `RequestHandler.write_error`.
2554 :arg int status_code: HTTP status code. Must be listed in
2555 `httplib.responses <http.client.responses>` unless the ``reason``
2556 keyword argument is given.
2557 :arg str log_message: Message to be written to the log for this error
2558 (will not be shown to the user unless the `Application` is in debug
2559 mode). May contain ``%s``-style placeholders, which will be filled
2560 in with remaining positional parameters.
2561 :arg str reason: Keyword-only argument. The HTTP "reason" phrase
2562 to pass in the status line along with ``status_code`` (for example,
2563 the "Not Found" in ``HTTP/1.1 404 Not Found``). Normally
2564 determined automatically from ``status_code``, but can be used
2565 to use a non-standard numeric code. This is not a general-purpose
2566 error message.
2567 """
2569 def __init__(
2570 self,
2571 status_code: int = 500,
2572 log_message: Optional[str] = None,
2573 *args: Any,
2574 **kwargs: Any,
2575 ) -> None:
2576 self.status_code = status_code
2577 self._log_message = log_message
2578 self.args = args
2579 self.reason = kwargs.get("reason", None)
2581 @property
2582 def log_message(self) -> Optional[str]:
2583 """
2584 A backwards compatible way of accessing log_message.
2585 """
2586 if self._log_message and not self.args:
2587 return self._log_message.replace("%", "%%")
2588 return self._log_message
2590 def get_message(self) -> Optional[str]:
2591 if self._log_message and self.args:
2592 return self._log_message % self.args
2593 return self._log_message
2595 def __str__(self) -> str:
2596 message = "HTTP %d: %s" % (
2597 self.status_code,
2598 self.reason or httputil.responses.get(self.status_code, "Unknown"),
2599 )
2600 log_message = self.get_message()
2601 if log_message:
2602 return message + " (" + log_message + ")"
2603 else:
2604 return message
2607class Finish(Exception):
2608 """An exception that ends the request without producing an error response.
2610 When `Finish` is raised in a `RequestHandler`, the request will
2611 end (calling `RequestHandler.finish` if it hasn't already been
2612 called), but the error-handling methods (including
2613 `RequestHandler.write_error`) will not be called.
2615 If `Finish()` was created with no arguments, the pending response
2616 will be sent as-is. If `Finish()` was given an argument, that
2617 argument will be passed to `RequestHandler.finish()`.
2619 This can be a more convenient way to implement custom error pages
2620 than overriding ``write_error`` (especially in library code)::
2622 if self.current_user is None:
2623 self.set_status(401)
2624 self.set_header('WWW-Authenticate', 'Basic realm="something"')
2625 raise Finish()
2627 .. versionchanged:: 4.3
2628 Arguments passed to ``Finish()`` will be passed on to
2629 `RequestHandler.finish`.
2630 """
2632 pass
2635class MissingArgumentError(HTTPError):
2636 """Exception raised by `RequestHandler.get_argument`.
2638 This is a subclass of `HTTPError`, so if it is uncaught a 400 response
2639 code will be used instead of 500 (and a stack trace will not be logged).
2641 .. versionadded:: 3.1
2642 """
2644 def __init__(self, arg_name: str) -> None:
2645 super().__init__(400, "Missing argument %s" % arg_name)
2646 self.arg_name = arg_name
2649class ErrorHandler(RequestHandler):
2650 """Generates an error response with ``status_code`` for all requests."""
2652 def initialize(self, status_code: int) -> None:
2653 self.set_status(status_code)
2655 def prepare(self) -> None:
2656 raise HTTPError(self._status_code)
2658 def check_xsrf_cookie(self) -> None:
2659 # POSTs to an ErrorHandler don't actually have side effects,
2660 # so we don't need to check the xsrf token. This allows POSTs
2661 # to the wrong url to return a 404 instead of 403.
2662 pass
2665class RedirectHandler(RequestHandler):
2666 """Redirects the client to the given URL for all GET requests.
2668 You should provide the keyword argument ``url`` to the handler, e.g.::
2670 application = web.Application([
2671 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
2672 ])
2674 `RedirectHandler` supports regular expression substitutions. E.g., to
2675 swap the first and second parts of a path while preserving the remainder::
2677 application = web.Application([
2678 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
2679 ])
2681 The final URL is formatted with `str.format` and the substrings that match
2682 the capturing groups. In the above example, a request to "/a/b/c" would be
2683 formatted like::
2685 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c"
2687 Use Python's :ref:`format string syntax <formatstrings>` to customize how
2688 values are substituted.
2690 .. versionchanged:: 4.5
2691 Added support for substitutions into the destination URL.
2693 .. versionchanged:: 5.0
2694 If any query arguments are present, they will be copied to the
2695 destination URL.
2696 """
2698 def initialize(self, url: str, permanent: bool = True) -> None:
2699 self._url = url
2700 self._permanent = permanent
2702 def get(self, *args: Any, **kwargs: Any) -> None:
2703 to_url = self._url.format(*args, **kwargs)
2704 if self.request.query_arguments:
2705 # TODO: figure out typing for the next line.
2706 to_url = httputil.url_concat(
2707 to_url,
2708 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore
2709 )
2710 self.redirect(to_url, permanent=self._permanent)
2713class StaticFileHandler(RequestHandler):
2714 """A simple handler that can serve static content from a directory.
2716 A `StaticFileHandler` is configured automatically if you pass the
2717 ``static_path`` keyword argument to `Application`. This handler
2718 can be customized with the ``static_url_prefix``, ``static_handler_class``,
2719 and ``static_handler_args`` settings.
2721 To map an additional path to this handler for a static data directory
2722 you would add a line to your application like::
2724 application = web.Application([
2725 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2726 ])
2728 The handler constructor requires a ``path`` argument, which specifies the
2729 local root directory of the content to be served.
2731 Note that a capture group in the regex is required to parse the value for
2732 the ``path`` argument to the get() method (different than the constructor
2733 argument above); see `URLSpec` for details.
2735 To serve a file like ``index.html`` automatically when a directory is
2736 requested, set ``static_handler_args=dict(default_filename="index.html")``
2737 in your application settings, or add ``default_filename`` as an initializer
2738 argument for your ``StaticFileHandler``.
2740 Symlinks are not followed out of the directory being served: if a path
2741 resolves to a location outside it, the request is rejected with a 403
2742 error. Deployments that deliberately serve content through symlinks
2743 pointing elsewhere can set the ``allowed_symlink_directory`` initializer
2744 argument to a directory containing all of the intended targets, or to a
2745 list of such directories. It defaults to the ``path`` argument.
2747 To maximize the effectiveness of browser caching, this class supports
2748 versioned urls (by default using the argument ``?v=``). If a version
2749 is given, we instruct the browser to cache this file indefinitely.
2750 `make_static_url` (also available as `RequestHandler.static_url`) can
2751 be used to construct a versioned url.
2753 This handler is intended primarily for use in development and light-duty
2754 file serving; for heavy traffic it will be more efficient to use
2755 a dedicated static file server (such as nginx or Apache). We support
2756 the HTTP ``Accept-Ranges`` mechanism to return partial content (because
2757 some browsers require this functionality to be present to seek in
2758 HTML5 audio or video).
2760 **Subclassing notes**
2762 This class is designed to be extensible by subclassing, but because
2763 of the way static urls are generated with class methods rather than
2764 instance methods, the inheritance patterns are somewhat unusual.
2765 Be sure to use the ``@classmethod`` decorator when overriding a
2766 class method. Instance methods may use the attributes ``self.path``
2767 ``self.absolute_path``, and ``self.modified``.
2769 Subclasses should only override methods discussed in this section;
2770 overriding other methods is error-prone. Overriding
2771 ``StaticFileHandler.get`` is particularly problematic due to the
2772 tight coupling with ``compute_etag`` and other methods.
2774 To change the way static urls are generated (e.g. to match the behavior
2775 of another server or CDN), override `make_static_url`, `parse_url_path`,
2776 `get_cache_time`, and/or `get_version`.
2778 To replace all interaction with the filesystem (e.g. to serve
2779 static content from a database), override `get_content`,
2780 `get_content_size`, `get_modified_time`, `get_absolute_path`, and
2781 `validate_absolute_path`.
2783 .. versionchanged:: 3.1
2784 Many of the methods for subclasses were added in Tornado 3.1.
2786 .. versionchanged:: 6.5.9
2787 Symlinks pointing outside the served directory are no longer
2788 followed, and the ``allowed_symlink_directory`` argument was added
2789 to configure this.
2791 .. versionchanged:: 6.5.10
2792 ``allowed_symlink_directory`` may now be a list of directories.
2793 """
2795 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
2797 _static_hashes = {} # type: Dict[str, Optional[str]]
2798 _lock = threading.Lock() # protects _static_hashes
2800 # A single directory or a list of them. None means "not set", in which
2801 # case the symlink check falls back to the directory being served. The
2802 # default lives on the class, not in initialize(), because this attribute
2803 # was introduced in a security patch and some projects (notably Jupyter)
2804 # subclass StaticFileHandler with their own initialize() that does not
2805 # call ours.
2806 allowed_symlink_directory = None # type: Optional[Union[str, List[str]]]
2808 def initialize(
2809 self,
2810 path: str,
2811 default_filename: Optional[str] = None,
2812 allowed_symlink_directory: Optional[Union[str, List[str]]] = None,
2813 ) -> None:
2814 self.root = path
2815 self.default_filename = default_filename
2816 self.allowed_symlink_directory = allowed_symlink_directory
2818 @classmethod
2819 def reset(cls) -> None:
2820 with cls._lock:
2821 cls._static_hashes = {}
2823 def head(self, path: str) -> Awaitable[None]:
2824 return self.get(path, include_body=False)
2826 async def get(self, path: str, include_body: bool = True) -> None:
2827 # Set up our path instance variables.
2828 self.path = self.parse_url_path(path)
2829 del path # make sure we don't refer to path instead of self.path again
2830 absolute_path = self.get_absolute_path(self.root, self.path)
2831 self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
2832 if self.absolute_path is None:
2833 return
2835 self.modified = self.get_modified_time()
2836 self.set_headers()
2838 if self.should_return_304():
2839 self.set_status(304)
2840 return
2842 request_range = None
2843 range_header = self.request.headers.get("Range")
2844 if range_header:
2845 # As per RFC 2616 14.16, if an invalid Range header is specified,
2846 # the request will be treated as if the header didn't exist.
2847 request_range = httputil._parse_request_range(range_header)
2849 size = self.get_content_size()
2850 if request_range:
2851 start, end = request_range
2852 if start is not None and start < 0:
2853 start += size
2854 if start < 0:
2855 start = 0
2856 if (
2857 start is not None
2858 and (start >= size or (end is not None and start >= end))
2859 ) or end == 0:
2860 # As per RFC 2616 14.35.1, a range is not satisfiable only: if
2861 # the first requested byte is equal to or greater than the
2862 # content, or when a suffix with length 0 is specified.
2863 # https://tools.ietf.org/html/rfc7233#section-2.1
2864 # A byte-range-spec is invalid if the last-byte-pos value is present
2865 # and less than the first-byte-pos.
2866 self.set_status(416) # Range Not Satisfiable
2867 self.set_header("Content-Type", "text/plain")
2868 self.set_header("Content-Range", f"bytes */{size}")
2869 return
2870 if end is not None and end > size:
2871 # Clients sometimes blindly use a large range to limit their
2872 # download size; cap the endpoint at the actual file size.
2873 end = size
2874 # Note: only return HTTP 206 if less than the entire range has been
2875 # requested. Not only is this semantically correct, but Chrome
2876 # refuses to play audio if it gets an HTTP 206 in response to
2877 # ``Range: bytes=0-``.
2878 if size != (end or size) - (start or 0):
2879 self.set_status(206) # Partial Content
2880 self.set_header(
2881 "Content-Range", httputil._get_content_range(start, end, size)
2882 )
2883 else:
2884 start = end = None
2886 if start is not None and end is not None:
2887 content_length = end - start
2888 elif end is not None:
2889 content_length = end
2890 elif start is not None:
2891 content_length = size - start
2892 else:
2893 content_length = size
2894 self.set_header("Content-Length", content_length)
2896 if include_body:
2897 content = self.get_content(self.absolute_path, start, end)
2898 if isinstance(content, bytes):
2899 content = [content]
2900 for chunk in content:
2901 try:
2902 self.write(chunk)
2903 await self.flush()
2904 except iostream.StreamClosedError:
2905 return
2906 else:
2907 assert self.request.method == "HEAD"
2909 def compute_etag(self) -> Optional[str]:
2910 """Sets the ``Etag`` header based on static url version.
2912 This allows efficient ``If-None-Match`` checks against cached
2913 versions, and sends the correct ``Etag`` for a partial response
2914 (i.e. the same ``Etag`` as the full file).
2916 .. versionadded:: 3.1
2917 """
2918 assert self.absolute_path is not None
2919 version_hash = self._get_cached_version(self.absolute_path)
2920 if not version_hash:
2921 return None
2922 return f'"{version_hash}"'
2924 def set_headers(self) -> None:
2925 """Sets the content and caching headers on the response.
2927 .. versionadded:: 3.1
2928 """
2929 self.set_header("Accept-Ranges", "bytes")
2930 self.set_etag_header()
2932 if self.modified is not None:
2933 self.set_header("Last-Modified", self.modified)
2935 content_type = self.get_content_type()
2936 if content_type:
2937 self.set_header("Content-Type", content_type)
2939 cache_time = self.get_cache_time(self.path, self.modified, content_type)
2940 if cache_time > 0:
2941 self.set_header(
2942 "Expires",
2943 datetime.datetime.now(datetime.timezone.utc)
2944 + datetime.timedelta(seconds=cache_time),
2945 )
2946 self.set_header("Cache-Control", "max-age=" + str(cache_time))
2948 self.set_extra_headers(self.path)
2950 def should_return_304(self) -> bool:
2951 """Returns True if the headers indicate that we should return 304.
2953 .. versionadded:: 3.1
2954 """
2955 # If client sent If-None-Match, use it, ignore If-Modified-Since
2956 if self.request.headers.get("If-None-Match"):
2957 return self.check_etag_header()
2959 # Check the If-Modified-Since, and don't send the result if the
2960 # content has not been modified
2961 ims_value = self.request.headers.get("If-Modified-Since")
2962 if ims_value is not None:
2963 try:
2964 if_since = email.utils.parsedate_to_datetime(ims_value)
2965 except Exception:
2966 return False
2967 if if_since.tzinfo is None:
2968 if_since = if_since.replace(tzinfo=datetime.timezone.utc)
2969 assert self.modified is not None
2970 if if_since >= self.modified:
2971 return True
2973 return False
2975 @classmethod
2976 def get_absolute_path(cls, root: str, path: str) -> str:
2977 """Returns the absolute location of ``path`` relative to ``root``.
2979 ``root`` is the path configured for this `StaticFileHandler`
2980 (in most cases the ``static_path`` `Application` setting).
2982 This class method may be overridden in subclasses. By default
2983 it returns a filesystem path, but other strings may be used
2984 as long as they are unique and understood by the subclass's
2985 overridden `get_content`.
2987 .. versionadded:: 3.1
2988 """
2989 abspath = os.path.abspath(os.path.join(root, path))
2990 return abspath
2992 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
2993 """Validate and return the absolute path.
2995 ``root`` is the configured path for the `StaticFileHandler`,
2996 and ``path`` is the result of `get_absolute_path`
2998 This is an instance method called during request processing,
2999 so it may raise `HTTPError` or use methods like
3000 `RequestHandler.redirect` (return None after redirecting to
3001 halt further processing). This is where 404 errors for missing files
3002 are generated.
3004 This method may modify the path before returning it, but note that
3005 any such modifications will not be understood by `make_static_url`.
3007 In instance methods, this method's result is available as
3008 ``self.absolute_path``.
3010 .. versionadded:: 3.1
3011 """
3012 # os.path.abspath strips a trailing /.
3013 # We must add it back to `root` so that we only match files
3014 # in a directory named `root` instead of files starting with
3015 # that prefix.
3016 root = os.path.abspath(root)
3017 if not root.endswith(os.path.sep):
3018 # abspath always removes a trailing slash, except when
3019 # root is '/'. This is an unusual case, but several projects
3020 # have independently discovered this technique to disable
3021 # Tornado's path validation and (hopefully) do their own,
3022 # so we need to support it.
3023 root += os.path.sep
3024 # The trailing slash also needs to be temporarily added back
3025 # the requested path so a request to root/ will match.
3026 if not (absolute_path + os.path.sep).startswith(root):
3027 raise HTTPError(403, "%s is not in root static directory", self.path)
3028 # Symlinks may point anywhere inside allowed_symlink_directory, which
3029 # defaults to the directory being served. Resolve that default here,
3030 # against the same root the checks above used, and normalize the one
3031 # directory that is allowed by default (or configured as a plain
3032 # string) to a list, so that _resolve_symlink_target has only one
3033 # shape to deal with.
3034 allowed_symlink_directories = self.allowed_symlink_directory or root
3035 if isinstance(allowed_symlink_directories, str):
3036 allowed_symlink_directories = [allowed_symlink_directories]
3037 # The check above is on the path as written, which is cheap and
3038 # rejects traversal without touching the filesystem. Resolve
3039 # symlinks only once it has passed, so that the remaining checks -
3040 # and the path this method returns - refer to the file that will
3041 # actually be opened rather than to the link. The two checks
3042 # cannot be merged: this one must stay on the unresolved path,
3043 # because allowed_symlink_directory may be wider than the root and
3044 # would then let a ../ traversal through.
3045 absolute_path = self._resolve_symlink_target(
3046 absolute_path, allowed_symlink_directories, self.path
3047 )
3048 if os.path.isdir(absolute_path) and self.default_filename is not None:
3049 # need to look at the request.path here for when path is empty
3050 # but there is some prefix to the path that was already
3051 # trimmed by the routing
3052 if not self.request.path.endswith("/"):
3053 if self.request.path.startswith("//"):
3054 # A redirect with two initial slashes is a "protocol-relative" URL.
3055 # This means the next path segment is treated as a hostname instead
3056 # of a part of the path, making this effectively an open redirect.
3057 # Reject paths starting with two slashes to prevent this.
3058 # This is only reachable under certain configurations.
3059 raise HTTPError(
3060 403, "cannot redirect path with two initial slashes"
3061 )
3062 self.redirect(self.request.path + "/", permanent=True)
3063 return None
3064 absolute_path = os.path.join(absolute_path, self.default_filename)
3065 # The default filename may be a symlink even when the directory
3066 # containing it is not. Resolving here rather than deferring a
3067 # single resolution until after this block is deliberate: the
3068 # isdir() above, and the redirect it can trigger, must not run
3069 # on a path that has already escaped, or the difference between
3070 # a redirect and a 403 would reveal whether an out-of-bounds
3071 # directory exists.
3072 absolute_path = self._resolve_symlink_target(
3073 absolute_path, allowed_symlink_directories, self.path
3074 )
3075 # Stat the resolved path once and keep the result, instead of
3076 # letting os.path.exists, os.path.isfile and the later header
3077 # generation each resolve the path again. Every one of those is a
3078 # separate trip through the filesystem that could observe a
3079 # different file from the one validated here.
3080 try:
3081 stat_result = os.stat(absolute_path)
3082 except OSError:
3083 # Matches the previous os.path.exists() check, which is also
3084 # false when the path cannot be stat'ed at all.
3085 raise HTTPError(404)
3086 if not stat.S_ISREG(stat_result.st_mode):
3087 raise HTTPError(403, "%s is not a file", self.path)
3088 self._stat_result = stat_result
3089 return absolute_path
3091 @staticmethod
3092 def _resolve_symlink_target(
3093 absolute_path: str,
3094 allowed_symlink_directories: List[str],
3095 path_message: str,
3096 ) -> str:
3097 """Resolves symlinks in ``absolute_path`` and validates the result.
3099 The path checks in `validate_absolute_path` operate on the path as
3100 written, which says nothing about where a symlink points: a symlink
3101 inside the static directory can name any file on the system. The
3102 resolved path must therefore stay inside one of
3103 ``allowed_symlink_directories``.
3105 This is a static method so that it can only see the values its
3106 caller checked against: ``allowed_symlink_directories`` are the
3107 directories `validate_absolute_path` settled on, which are not
3108 necessarily ``self.allowed_symlink_directory`` or ``self.root``.
3109 It is always a list, so that the single-directory case does not
3110 need a second code path here. ``path_message`` is used only to
3111 build the error message.
3113 Raises `HTTPError` (403) if the resolved path escapes all of those
3114 directories.
3116 Note that this is a check against the state of the filesystem at
3117 one moment: a path that is replaced by a symlink after this returns
3118 but before the file is opened would not be caught. Closing that
3119 race entirely would require holding file descriptors across the
3120 whole operation. This check is not a substitute for filesystem
3121 permissions on a directory that untrusted users can write to.
3122 """
3123 # strict=True is deliberately not used here: it is unavailable on
3124 # Python 3.9, and a path that does not exist resolves to itself,
3125 # which the caller then reports as a 404.
3126 resolved_path = os.path.realpath(absolute_path)
3127 for allowed_symlink_directory in allowed_symlink_directories:
3128 # realpath() is applied to the allowed directory as well, because
3129 # it may itself be reached through a symlink (a static directory
3130 # of /var/www that links to /srv/www, say). Comparing a resolved
3131 # path against an unresolved directory would reject every request.
3132 allowed_directory = os.path.realpath(allowed_symlink_directory)
3133 if not allowed_directory.endswith(os.path.sep):
3134 # As in validate_absolute_path, the separator must not be
3135 # doubled when the directory is the filesystem root.
3136 allowed_directory += os.path.sep
3137 # The trailing separator lets the allowed directory itself match,
3138 # as well as anything beneath it. This is reachable because the
3139 # path may still be a directory at this point (when
3140 # default_filename is set).
3141 if (resolved_path + os.path.sep).startswith(allowed_directory):
3142 return resolved_path
3143 # Deliberately the same error as the check on the path as written,
3144 # so that a caller cannot tell which way a path left the served
3145 # directory.
3146 raise HTTPError(403, "%s is not in root static directory", path_message)
3148 @classmethod
3149 def get_content(
3150 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None
3151 ) -> Generator[bytes, None, None]:
3152 """Retrieve the content of the requested resource which is located
3153 at the given absolute path.
3155 This class method may be overridden by subclasses. Note that its
3156 signature is different from other overridable class methods
3157 (no ``settings`` argument); this is deliberate to ensure that
3158 ``abspath`` is able to stand on its own as a cache key.
3160 This method should either return a byte string or an iterator
3161 of byte strings. The latter is preferred for large files
3162 as it helps reduce memory fragmentation.
3164 .. versionadded:: 3.1
3165 """
3166 with open(abspath, "rb") as file:
3167 if start is not None:
3168 file.seek(start)
3169 if end is not None:
3170 remaining = end - (start or 0) # type: Optional[int]
3171 else:
3172 remaining = None
3173 while True:
3174 chunk_size = 64 * 1024
3175 if remaining is not None and remaining < chunk_size:
3176 chunk_size = remaining
3177 chunk = file.read(chunk_size)
3178 if chunk:
3179 if remaining is not None:
3180 remaining -= len(chunk)
3181 yield chunk
3182 else:
3183 if remaining is not None:
3184 assert remaining == 0
3185 return
3187 @classmethod
3188 def get_content_version(cls, abspath: str) -> str:
3189 """Returns a version string for the resource at the given path.
3191 This class method may be overridden by subclasses. The
3192 default implementation is a SHA-512 hash of the file's contents.
3194 .. versionadded:: 3.1
3195 """
3196 data = cls.get_content(abspath)
3197 hasher = hashlib.sha512()
3198 if isinstance(data, bytes):
3199 hasher.update(data)
3200 else:
3201 for chunk in data:
3202 hasher.update(chunk)
3203 return hasher.hexdigest()
3205 def _stat(self) -> os.stat_result:
3206 assert self.absolute_path is not None
3207 # validate_absolute_path normally populates this, so that the size
3208 # and modification time reported here describe the same file that
3209 # was validated. Subclasses that override validate_absolute_path
3210 # without calling super() fall back to stat'ing here.
3211 if not hasattr(self, "_stat_result"):
3212 self._stat_result = os.stat(self.absolute_path)
3213 return self._stat_result
3215 def get_content_size(self) -> int:
3216 """Retrieve the total size of the resource at the given path.
3218 This method may be overridden by subclasses.
3220 .. versionadded:: 3.1
3222 .. versionchanged:: 4.0
3223 This method is now always called, instead of only when
3224 partial results are requested.
3225 """
3226 stat_result = self._stat()
3227 return stat_result.st_size
3229 def get_modified_time(self) -> Optional[datetime.datetime]:
3230 """Returns the time that ``self.absolute_path`` was last modified.
3232 May be overridden in subclasses. Should return a `~datetime.datetime`
3233 object or None.
3235 .. versionadded:: 3.1
3237 .. versionchanged:: 6.4
3238 Now returns an aware datetime object instead of a naive one.
3239 Subclasses that override this method may return either kind.
3240 """
3241 stat_result = self._stat()
3242 # NOTE: Historically, this used stat_result[stat.ST_MTIME],
3243 # which truncates the fractional portion of the timestamp. It
3244 # was changed from that form to stat_result.st_mtime to
3245 # satisfy mypy (which disallows the bracket operator), but the
3246 # latter form returns a float instead of an int. For
3247 # consistency with the past (and because we have a unit test
3248 # that relies on this), we truncate the float here, although
3249 # I'm not sure that's the right thing to do.
3250 modified = datetime.datetime.fromtimestamp(
3251 int(stat_result.st_mtime), datetime.timezone.utc
3252 )
3253 return modified
3255 def get_content_type(self) -> str:
3256 """Returns the ``Content-Type`` header to be used for this request.
3258 .. versionadded:: 3.1
3259 """
3260 assert self.absolute_path is not None
3261 mime_type, encoding = mimetypes.guess_type(self.absolute_path)
3262 # per RFC 6713, use the appropriate type for a gzip compressed file
3263 if encoding == "gzip":
3264 return "application/gzip"
3265 # As of 2015-07-21 there is no bzip2 encoding defined at
3266 # http://www.iana.org/assignments/media-types/media-types.xhtml
3267 # So for that (and any other encoding), use octet-stream.
3268 elif encoding is not None:
3269 return "application/octet-stream"
3270 elif mime_type is not None:
3271 return mime_type
3272 # if mime_type not detected, use application/octet-stream
3273 else:
3274 return "application/octet-stream"
3276 def set_extra_headers(self, path: str) -> None:
3277 """For subclass to add extra headers to the response"""
3278 pass
3280 def get_cache_time(
3281 self, path: str, modified: Optional[datetime.datetime], mime_type: str
3282 ) -> int:
3283 """Override to customize cache control behavior.
3285 Return a positive number of seconds to make the result
3286 cacheable for that amount of time or 0 to mark resource as
3287 cacheable for an unspecified amount of time (subject to
3288 browser heuristics).
3290 By default returns cache expiry of 10 years for resources requested
3291 with ``v`` argument.
3292 """
3293 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
3295 @classmethod
3296 def make_static_url(
3297 cls, settings: Dict[str, Any], path: str, include_version: bool = True
3298 ) -> str:
3299 """Constructs a versioned url for the given path.
3301 This method may be overridden in subclasses (but note that it
3302 is a class method rather than an instance method). Subclasses
3303 are only required to implement the signature
3304 ``make_static_url(cls, settings, path)``; other keyword
3305 arguments may be passed through `~RequestHandler.static_url`
3306 but are not standard.
3308 ``settings`` is the `Application.settings` dictionary. ``path``
3309 is the static path being requested. The url returned should be
3310 relative to the current host.
3312 ``include_version`` determines whether the generated URL should
3313 include the query string containing the version hash of the
3314 file corresponding to the given ``path``.
3316 """
3317 url = settings.get("static_url_prefix", "/static/") + path
3318 if not include_version:
3319 return url
3321 version_hash = cls.get_version(settings, path)
3322 if not version_hash:
3323 return url
3325 return f"{url}?v={version_hash}"
3327 def parse_url_path(self, url_path: str) -> str:
3328 """Converts a static URL path into a filesystem path.
3330 ``url_path`` is the path component of the URL with
3331 ``static_url_prefix`` removed. The return value should be
3332 filesystem path relative to ``static_path``.
3334 This is the inverse of `make_static_url`.
3335 """
3336 if os.path.sep != "/":
3337 url_path = url_path.replace("/", os.path.sep)
3338 return url_path
3340 @classmethod
3341 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
3342 """Generate the version string to be used in static URLs.
3344 ``settings`` is the `Application.settings` dictionary and ``path``
3345 is the relative location of the requested asset on the filesystem.
3346 The returned value should be a string, or ``None`` if no version
3347 could be determined.
3349 .. versionchanged:: 3.1
3350 This method was previously recommended for subclasses to override;
3351 `get_content_version` is now preferred as it allows the base
3352 class to handle caching of the result.
3353 """
3354 abs_path = cls.get_absolute_path(settings["static_path"], path)
3355 return cls._get_cached_version(abs_path)
3357 @classmethod
3358 def _get_cached_version(cls, abs_path: str) -> Optional[str]:
3359 with cls._lock:
3360 hashes = cls._static_hashes
3361 if abs_path not in hashes:
3362 try:
3363 hashes[abs_path] = cls.get_content_version(abs_path)
3364 except Exception:
3365 gen_log.error("Could not open static file %r", abs_path)
3366 hashes[abs_path] = None
3367 hsh = hashes.get(abs_path)
3368 if hsh:
3369 return hsh
3370 return None
3373class FallbackHandler(RequestHandler):
3374 """A `RequestHandler` that wraps another HTTP server callback.
3376 The fallback is a callable object that accepts an
3377 `~.httputil.HTTPServerRequest`, such as an `Application` or
3378 `tornado.wsgi.WSGIContainer`. This is most useful to use both
3379 Tornado ``RequestHandlers`` and WSGI in the same server. Typical
3380 usage::
3382 wsgi_app = tornado.wsgi.WSGIContainer(
3383 django.core.handlers.wsgi.WSGIHandler())
3384 application = tornado.web.Application([
3385 (r"/foo", FooHandler),
3386 (r".*", FallbackHandler, dict(fallback=wsgi_app)),
3387 ])
3388 """
3390 def initialize(
3391 self, fallback: Callable[[httputil.HTTPServerRequest], None]
3392 ) -> None:
3393 self.fallback = fallback
3395 def prepare(self) -> None:
3396 self.fallback(self.request)
3397 self._finished = True
3398 self.on_finish()
3401class OutputTransform:
3402 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
3404 Applications are not expected to create their own OutputTransforms
3405 or interact with them directly; the framework chooses which transforms
3406 (if any) to apply.
3407 """
3409 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3410 pass
3412 def transform_first_chunk(
3413 self,
3414 status_code: int,
3415 headers: httputil.HTTPHeaders,
3416 chunk: bytes,
3417 finishing: bool,
3418 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3419 return status_code, headers, chunk
3421 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3422 return chunk
3425class GZipContentEncoding(OutputTransform):
3426 """Applies the gzip content encoding to the response.
3428 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
3430 .. versionchanged:: 4.0
3431 Now compresses all mime types beginning with ``text/``, instead
3432 of just a whitelist. (the whitelist is still used for certain
3433 non-text mime types).
3434 """
3436 # Whitelist of compressible mime types (in addition to any types
3437 # beginning with "text/").
3438 CONTENT_TYPES = {
3439 "application/javascript",
3440 "application/x-javascript",
3441 "application/xml",
3442 "application/atom+xml",
3443 "application/json",
3444 "application/xhtml+xml",
3445 "image/svg+xml",
3446 }
3447 # Python's GzipFile defaults to level 9, while most other gzip
3448 # tools (including gzip itself) default to 6, which is probably a
3449 # better CPU/size tradeoff.
3450 GZIP_LEVEL = 6
3451 # Responses that are too short are unlikely to benefit from gzipping
3452 # after considering the "Content-Encoding: gzip" header and the header
3453 # inside the gzip encoding.
3454 # Note that responses written in multiple chunks will be compressed
3455 # regardless of size.
3456 MIN_LENGTH = 1024
3458 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3459 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
3461 def _compressible_type(self, ctype: str) -> bool:
3462 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
3464 def transform_first_chunk(
3465 self,
3466 status_code: int,
3467 headers: httputil.HTTPHeaders,
3468 chunk: bytes,
3469 finishing: bool,
3470 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3471 # TODO: can/should this type be inherited from the superclass?
3472 if "Vary" in headers:
3473 headers["Vary"] += ", Accept-Encoding"
3474 else:
3475 headers["Vary"] = "Accept-Encoding"
3476 if self._gzipping:
3477 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
3478 self._gzipping = (
3479 self._compressible_type(ctype)
3480 and (not finishing or len(chunk) >= self.MIN_LENGTH)
3481 and ("Content-Encoding" not in headers)
3482 )
3483 if self._gzipping:
3484 headers["Content-Encoding"] = "gzip"
3485 self._gzip_value = BytesIO()
3486 self._gzip_file = gzip.GzipFile(
3487 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
3488 )
3489 chunk = self.transform_chunk(chunk, finishing)
3490 if "Content-Length" in headers:
3491 # The original content length is no longer correct.
3492 # If this is the last (and only) chunk, we can set the new
3493 # content-length; otherwise we remove it and fall back to
3494 # chunked encoding.
3495 if finishing:
3496 headers["Content-Length"] = str(len(chunk))
3497 else:
3498 del headers["Content-Length"]
3499 return status_code, headers, chunk
3501 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3502 if self._gzipping:
3503 self._gzip_file.write(chunk)
3504 if finishing:
3505 self._gzip_file.close()
3506 else:
3507 self._gzip_file.flush()
3508 chunk = self._gzip_value.getvalue()
3509 self._gzip_value.truncate(0)
3510 self._gzip_value.seek(0)
3511 return chunk
3514def authenticated(
3515 method: Callable[..., Optional[Awaitable[None]]],
3516) -> Callable[..., Optional[Awaitable[None]]]:
3517 """Decorate methods with this to require that the user be logged in.
3519 If the user is not logged in, they will be redirected to the configured
3520 `login url <RequestHandler.get_login_url>`.
3522 If you configure a login url with a query parameter, Tornado will
3523 assume you know what you're doing and use it as-is. If not, it
3524 will add a `next` parameter so the login page knows where to send
3525 you once you're logged in.
3526 """
3528 @functools.wraps(method)
3529 def wrapper( # type: ignore
3530 self: RequestHandler, *args, **kwargs
3531 ) -> Optional[Awaitable[None]]:
3532 if not self.current_user:
3533 if self.request.method in ("GET", "HEAD"):
3534 url = self.get_login_url()
3535 if "?" not in url:
3536 if urllib.parse.urlsplit(url).scheme:
3537 # if login url is absolute, make next absolute too
3538 next_url = self.request.full_url()
3539 else:
3540 assert self.request.uri is not None
3541 next_url = self.request.uri
3542 url += "?" + urlencode(dict(next=next_url))
3543 self.redirect(url)
3544 return None
3545 raise HTTPError(403)
3546 return method(self, *args, **kwargs)
3548 return wrapper
3551class UIModule:
3552 """A re-usable, modular UI unit on a page.
3554 UI modules often execute additional queries, and they can include
3555 additional CSS and JavaScript that will be included in the output
3556 page, which is automatically inserted on page render.
3558 Subclasses of UIModule must override the `render` method.
3559 """
3561 def __init__(self, handler: RequestHandler) -> None:
3562 self.handler = handler
3563 self.request = handler.request
3564 self.ui = handler.ui
3565 self.locale = handler.locale
3567 @property
3568 def current_user(self) -> Any:
3569 return self.handler.current_user
3571 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]:
3572 """Override in subclasses to return this module's output."""
3573 raise NotImplementedError()
3575 def embedded_javascript(self) -> Optional[str]:
3576 """Override to return a JavaScript string
3577 to be embedded in the page."""
3578 return None
3580 def javascript_files(self) -> Optional[Iterable[str]]:
3581 """Override to return a list of JavaScript files needed by this module.
3583 If the return values are relative paths, they will be passed to
3584 `RequestHandler.static_url`; otherwise they will be used as-is.
3585 """
3586 return None
3588 def embedded_css(self) -> Optional[str]:
3589 """Override to return a CSS string
3590 that will be embedded in the page."""
3591 return None
3593 def css_files(self) -> Optional[Iterable[str]]:
3594 """Override to returns a list of CSS files required by this module.
3596 If the return values are relative paths, they will be passed to
3597 `RequestHandler.static_url`; otherwise they will be used as-is.
3598 """
3599 return None
3601 def html_head(self) -> Optional[str]:
3602 """Override to return an HTML string that will be put in the <head/>
3603 element.
3604 """
3605 return None
3607 def html_body(self) -> Optional[str]:
3608 """Override to return an HTML string that will be put at the end of
3609 the <body/> element.
3610 """
3611 return None
3613 def render_string(self, path: str, **kwargs: Any) -> bytes:
3614 """Renders a template and returns it as a string."""
3615 return self.handler.render_string(path, **kwargs)
3618class _linkify(UIModule):
3619 def render(self, text: str, **kwargs: Any) -> str:
3620 return escape.linkify(text, **kwargs)
3623class _xsrf_form_html(UIModule):
3624 def render(self) -> str:
3625 return self.handler.xsrf_form_html()
3628class TemplateModule(UIModule):
3629 """UIModule that simply renders the given template.
3631 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
3632 but the module version gets its own namespace (with kwargs passed to
3633 Template()) instead of inheriting the outer template's namespace.
3635 Templates rendered through this module also get access to UIModule's
3636 automatic JavaScript/CSS features. Simply call set_resources
3637 inside the template and give it keyword arguments corresponding to
3638 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
3639 Note that these resources are output once per template file, not once
3640 per instantiation of the template, so they must not depend on
3641 any arguments to the template.
3642 """
3644 def __init__(self, handler: RequestHandler) -> None:
3645 super().__init__(handler)
3646 # keep resources in both a list and a dict to preserve order
3647 self._resource_list = [] # type: List[Dict[str, Any]]
3648 self._resource_dict = {} # type: Dict[str, Dict[str, Any]]
3650 def render(self, path: str, **kwargs: Any) -> bytes:
3651 def set_resources(**kwargs) -> str: # type: ignore
3652 if path not in self._resource_dict:
3653 self._resource_list.append(kwargs)
3654 self._resource_dict[path] = kwargs
3655 else:
3656 if self._resource_dict[path] != kwargs:
3657 raise ValueError(
3658 "set_resources called with different "
3659 "resources for the same template"
3660 )
3661 return ""
3663 return self.render_string(path, set_resources=set_resources, **kwargs)
3665 def _get_resources(self, key: str) -> Iterable[str]:
3666 return (r[key] for r in self._resource_list if key in r)
3668 def embedded_javascript(self) -> str:
3669 return "\n".join(self._get_resources("embedded_javascript"))
3671 def javascript_files(self) -> Iterable[str]:
3672 result = []
3673 for f in self._get_resources("javascript_files"):
3674 if isinstance(f, (unicode_type, bytes)):
3675 result.append(f)
3676 else:
3677 result.extend(f)
3678 return result
3680 def embedded_css(self) -> str:
3681 return "\n".join(self._get_resources("embedded_css"))
3683 def css_files(self) -> Iterable[str]:
3684 result = []
3685 for f in self._get_resources("css_files"):
3686 if isinstance(f, (unicode_type, bytes)):
3687 result.append(f)
3688 else:
3689 result.extend(f)
3690 return result
3692 def html_head(self) -> str:
3693 return "".join(self._get_resources("html_head"))
3695 def html_body(self) -> str:
3696 return "".join(self._get_resources("html_body"))
3699class _UIModuleNamespace:
3700 """Lazy namespace which creates UIModule proxies bound to a handler."""
3702 def __init__(
3703 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
3704 ) -> None:
3705 self.handler = handler
3706 self.ui_modules = ui_modules
3708 def __getitem__(self, key: str) -> Callable[..., str]:
3709 return self.handler._ui_module(key, self.ui_modules[key])
3711 def __getattr__(self, key: str) -> Callable[..., str]:
3712 try:
3713 return self[key]
3714 except KeyError as e:
3715 raise AttributeError(str(e))
3718def create_signed_value(
3719 secret: _CookieSecretTypes,
3720 name: str,
3721 value: Union[str, bytes],
3722 version: Optional[int] = None,
3723 clock: Optional[Callable[[], float]] = None,
3724 key_version: Optional[int] = None,
3725) -> bytes:
3726 if version is None:
3727 version = DEFAULT_SIGNED_VALUE_VERSION
3728 if clock is None:
3729 clock = time.time
3731 timestamp = utf8(str(int(clock())))
3732 value = base64.b64encode(utf8(value))
3733 if version == 1:
3734 assert not isinstance(secret, dict)
3735 signature = _create_signature_v1(secret, name, value, timestamp)
3736 value = b"|".join([value, timestamp, signature])
3737 return value
3738 elif version == 2:
3739 # The v2 format consists of a version number and a series of
3740 # length-prefixed fields "%d:%s", the last of which is a
3741 # signature, all separated by pipes. All numbers are in
3742 # decimal format with no leading zeros. The signature is an
3743 # HMAC-SHA256 of the whole string up to that point, including
3744 # the final pipe.
3745 #
3746 # The fields are:
3747 # - format version (i.e. 2; no length prefix)
3748 # - key version (integer, default is 0)
3749 # - timestamp (integer seconds since epoch)
3750 # - name (not encoded; assumed to be ~alphanumeric)
3751 # - value (base64-encoded)
3752 # - signature (hex-encoded; no length prefix)
3753 def format_field(s: Union[str, bytes]) -> bytes:
3754 return utf8("%d:" % len(s)) + utf8(s)
3756 to_sign = b"|".join(
3757 [
3758 b"2",
3759 format_field(str(key_version or 0)),
3760 format_field(timestamp),
3761 format_field(name),
3762 format_field(value),
3763 b"",
3764 ]
3765 )
3767 if isinstance(secret, dict):
3768 assert (
3769 key_version is not None
3770 ), "Key version must be set when sign key dict is used"
3771 assert version >= 2, "Version must be at least 2 for key version support"
3772 secret = secret[key_version]
3774 signature = _create_signature_v2(secret, to_sign)
3775 return to_sign + signature
3776 else:
3777 raise ValueError("Unsupported version %d" % version)
3780# A leading version number in decimal
3781# with no leading zeros, followed by a pipe.
3782_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$")
3785def _get_version(value: bytes) -> int:
3786 # Figures out what version value is. Version 1 did not include an
3787 # explicit version field and started with arbitrary base64 data,
3788 # which makes this tricky.
3789 m = _signed_value_version_re.match(value)
3790 if m is None:
3791 version = 1
3792 else:
3793 try:
3794 version = int(m.group(1))
3795 if version > 999:
3796 # Certain payloads from the version-less v1 format may
3797 # be parsed as valid integers. Due to base64 padding
3798 # restrictions, this can only happen for numbers whose
3799 # length is a multiple of 4, so we can treat all
3800 # numbers up to 999 as versions, and for the rest we
3801 # fall back to v1 format.
3802 version = 1
3803 except ValueError:
3804 version = 1
3805 return version
3808def decode_signed_value(
3809 secret: _CookieSecretTypes,
3810 name: str,
3811 value: Union[None, str, bytes],
3812 max_age_days: float = 31,
3813 clock: Optional[Callable[[], float]] = None,
3814 min_version: Optional[int] = None,
3815) -> Optional[bytes]:
3816 if clock is None:
3817 clock = time.time
3818 if min_version is None:
3819 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
3820 if min_version > 2:
3821 raise ValueError("Unsupported min_version %d" % min_version)
3822 if not value:
3823 return None
3825 value = utf8(value)
3826 version = _get_version(value)
3828 if version < min_version:
3829 return None
3830 if version == 1:
3831 assert not isinstance(secret, dict)
3832 return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
3833 elif version == 2:
3834 return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
3835 else:
3836 return None
3839def _decode_signed_value_v1(
3840 secret: Union[str, bytes],
3841 name: str,
3842 value: bytes,
3843 max_age_days: float,
3844 clock: Callable[[], float],
3845) -> Optional[bytes]:
3846 parts = utf8(value).split(b"|")
3847 if len(parts) != 3:
3848 return None
3849 signature = _create_signature_v1(secret, name, parts[0], parts[1])
3850 if not hmac.compare_digest(parts[2], signature):
3851 gen_log.warning("Invalid cookie signature %r", value)
3852 return None
3853 timestamp = int(parts[1])
3854 if timestamp < clock() - max_age_days * 86400:
3855 gen_log.warning("Expired cookie %r", value)
3856 return None
3857 if timestamp > clock() + 31 * 86400:
3858 # _cookie_signature does not hash a delimiter between the
3859 # parts of the cookie, so an attacker could transfer trailing
3860 # digits from the payload to the timestamp without altering the
3861 # signature. For backwards compatibility, sanity-check timestamp
3862 # here instead of modifying _cookie_signature.
3863 gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
3864 return None
3865 if parts[1].startswith(b"0"):
3866 gen_log.warning("Tampered cookie %r", value)
3867 return None
3868 try:
3869 return base64.b64decode(parts[0])
3870 except Exception:
3871 return None
3874def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
3875 def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
3876 length, _, rest = s.partition(b":")
3877 n = int(length)
3878 field_value = rest[:n]
3879 # In python 3, indexing bytes returns small integers; we must
3880 # use a slice to get a byte string as in python 2.
3881 if rest[n : n + 1] != b"|":
3882 raise ValueError("malformed v2 signed value field")
3883 rest = rest[n + 1 :]
3884 return field_value, rest
3886 rest = value[2:] # remove version number
3887 key_version, rest = _consume_field(rest)
3888 timestamp, rest = _consume_field(rest)
3889 name_field, rest = _consume_field(rest)
3890 value_field, passed_sig = _consume_field(rest)
3891 return int(key_version), timestamp, name_field, value_field, passed_sig
3894def _decode_signed_value_v2(
3895 secret: _CookieSecretTypes,
3896 name: str,
3897 value: bytes,
3898 max_age_days: float,
3899 clock: Callable[[], float],
3900) -> Optional[bytes]:
3901 try:
3902 (
3903 key_version,
3904 timestamp_bytes,
3905 name_field,
3906 value_field,
3907 passed_sig,
3908 ) = _decode_fields_v2(value)
3909 except ValueError:
3910 return None
3911 signed_string = value[: -len(passed_sig)]
3913 if isinstance(secret, dict):
3914 try:
3915 secret = secret[key_version]
3916 except KeyError:
3917 return None
3919 expected_sig = _create_signature_v2(secret, signed_string)
3920 if not hmac.compare_digest(passed_sig, expected_sig):
3921 return None
3922 if name_field != utf8(name):
3923 return None
3924 timestamp = int(timestamp_bytes)
3925 if timestamp < clock() - max_age_days * 86400:
3926 # The signature has expired.
3927 return None
3928 try:
3929 return base64.b64decode(value_field)
3930 except Exception:
3931 return None
3934def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
3935 value = utf8(value)
3936 version = _get_version(value)
3937 if version < 2:
3938 return None
3939 try:
3940 key_version, _, _, _, _ = _decode_fields_v2(value)
3941 except ValueError:
3942 return None
3944 return key_version
3947def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
3948 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
3949 for part in parts:
3950 hash.update(utf8(part))
3951 return utf8(hash.hexdigest())
3954def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
3955 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
3956 hash.update(utf8(s))
3957 return utf8(hash.hexdigest())
3960def is_absolute(path: str) -> bool:
3961 return any(path.startswith(x) for x in ["/", "http:", "https:"])