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 sys
76import threading
77import time
78import warnings
79import tornado
80import traceback
81import types
82import urllib.parse
83from urllib.parse import urlencode
85from tornado.concurrent import Future, future_set_result_unless_cancelled
86from tornado import escape
87from tornado import gen
88from tornado.httpserver import HTTPServer
89from tornado import httputil
90from tornado import iostream
91from tornado import locale
92from tornado.log import access_log, app_log, gen_log
93from tornado import template
94from tornado.escape import utf8, _unicode
95from tornado.routing import (
96 AnyMatches,
97 DefaultHostMatches,
98 HostMatches,
99 ReversibleRouter,
100 Rule,
101 ReversibleRuleRouter,
102 URLSpec,
103 _RuleList,
104)
105from tornado.util import ObjectDict, unicode_type, _websocket_mask
107url = URLSpec
109from typing import (
110 Dict,
111 Any,
112 Union,
113 Optional,
114 Awaitable,
115 Tuple,
116 List,
117 Callable,
118 Iterable,
119 Generator,
120 Type,
121 TypeVar,
122 cast,
123 overload,
124)
125from types import TracebackType
126import typing
128if typing.TYPE_CHECKING:
129 from typing import Set # noqa: F401
132# The following types are accepted by RequestHandler.set_header
133# and related methods.
134_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime]
136_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]]
139MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
140"""The oldest signed value version supported by this version of Tornado.
142Signed values older than this version cannot be decoded.
144.. versionadded:: 3.2.1
145"""
147MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2
148"""The newest signed value version supported by this version of Tornado.
150Signed values newer than this version cannot be decoded.
152.. versionadded:: 3.2.1
153"""
155DEFAULT_SIGNED_VALUE_VERSION = 2
156"""The signed value version produced by `.RequestHandler.create_signed_value`.
158May be overridden by passing a ``version`` keyword argument.
160.. versionadded:: 3.2.1
161"""
163DEFAULT_SIGNED_VALUE_MIN_VERSION = 1
164"""The oldest signed value accepted by `.RequestHandler.get_signed_cookie`.
166May be overridden by passing a ``min_version`` keyword argument.
168.. versionadded:: 3.2.1
169"""
172class _ArgDefaultMarker:
173 pass
176_ARG_DEFAULT = _ArgDefaultMarker()
179class RequestHandler:
180 """Base class for HTTP request handlers.
182 Subclasses must define at least one of the methods defined in the
183 "Entry points" section below.
185 Applications should not construct `RequestHandler` objects
186 directly and subclasses should not override ``__init__`` (override
187 `~RequestHandler.initialize` instead).
189 """
191 SUPPORTED_METHODS: Tuple[str, ...] = (
192 "GET",
193 "HEAD",
194 "POST",
195 "DELETE",
196 "PATCH",
197 "PUT",
198 "OPTIONS",
199 )
201 _template_loaders = {} # type: Dict[str, template.BaseLoader]
202 _template_loader_lock = threading.Lock()
203 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
205 _stream_request_body = False
207 # Will be set in _execute.
208 _transforms = None # type: List[OutputTransform]
209 path_args = None # type: List[str]
210 path_kwargs = None # type: Dict[str, str]
212 def __init__(
213 self,
214 application: "Application",
215 request: httputil.HTTPServerRequest,
216 **kwargs: Any,
217 ) -> None:
218 super().__init__()
220 self.application = application
221 self.request = request
222 self._headers_written = False
223 self._finished = False
224 self._auto_finish = True
225 self._prepared_future = None
226 self.ui = ObjectDict(
227 (n, self._ui_method(m)) for n, m in application.ui_methods.items()
228 )
229 # UIModules are available as both `modules` and `_tt_modules` in the
230 # template namespace. Historically only `modules` was available
231 # but could be clobbered by user additions to the namespace.
232 # The template {% module %} directive looks in `_tt_modules` to avoid
233 # possible conflicts.
234 self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules)
235 self.ui["modules"] = self.ui["_tt_modules"]
236 self.clear()
237 assert self.request.connection is not None
238 # TODO: need to add set_close_callback to HTTPConnection interface
239 self.request.connection.set_close_callback( # type: ignore
240 self.on_connection_close
241 )
242 self.initialize(**kwargs) # type: ignore
244 def _initialize(self) -> None:
245 pass
247 initialize = _initialize # type: Callable[..., None]
248 """Hook for subclass initialization. Called for each request.
250 A dictionary passed as the third argument of a ``URLSpec`` will be
251 supplied as keyword arguments to ``initialize()``.
253 Example::
255 class ProfileHandler(RequestHandler):
256 def initialize(self, database):
257 self.database = database
259 def get(self, username):
260 ...
262 app = Application([
263 (r'/user/(.*)', ProfileHandler, dict(database=database)),
264 ])
265 """
267 @property
268 def settings(self) -> Dict[str, Any]:
269 """An alias for `self.application.settings <Application.settings>`."""
270 return self.application.settings
272 def _unimplemented_method(self, *args: str, **kwargs: str) -> None:
273 raise HTTPError(405)
275 head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
276 get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
277 post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
278 delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
279 patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
280 put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
281 options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]]
283 def prepare(self) -> Optional[Awaitable[None]]:
284 """Called at the beginning of a request before `get`/`post`/etc.
286 Override this method to perform common initialization regardless
287 of the request method. There is no guarantee that ``prepare`` will
288 be called if an error occurs that is handled by the framework.
290 Asynchronous support: Use ``async def`` or decorate this method with
291 `.gen.coroutine` to make it asynchronous.
292 If this method returns an ``Awaitable`` execution will not proceed
293 until the ``Awaitable`` is done.
295 .. versionadded:: 3.1
296 Asynchronous support.
297 """
298 pass
300 def on_finish(self) -> None:
301 """Called after the end of a request.
303 Override this method to perform cleanup, logging, etc. This method is primarily intended as
304 a counterpart to `prepare`. However, there are a few error cases where ``on_finish`` may be
305 called when ``prepare`` has not. (These are considered bugs and may be fixed in the future,
306 but for now you may need to check to see if the initialization work done in ``prepare`` has
307 occurred)
309 ``on_finish`` may not produce any output, as it is called after the response has been sent
310 to the client.
311 """
312 pass
314 def on_connection_close(self) -> None:
315 """Called in async handlers if the client closed the connection.
317 Override this to clean up resources associated with
318 long-lived connections. Note that this method is called only if
319 the connection was closed during asynchronous processing; if you
320 need to do cleanup after every request override `on_finish`
321 instead.
323 Proxies may keep a connection open for a time (perhaps
324 indefinitely) after the client has gone away, so this method
325 may not be called promptly after the end user closes their
326 connection.
327 """
328 if _has_stream_request_body(self.__class__):
329 if not self.request._body_future.done():
330 self.request._body_future.set_exception(iostream.StreamClosedError())
331 self.request._body_future.exception()
333 def clear(self) -> None:
334 """Resets all headers and content for this response."""
335 self._headers = httputil.HTTPHeaders(
336 {
337 "Server": "TornadoServer/%s" % tornado.version,
338 "Content-Type": "text/html; charset=UTF-8",
339 "Date": httputil.format_timestamp(time.time()),
340 }
341 )
342 self.set_default_headers()
343 self._write_buffer = [] # type: List[bytes]
344 self._status_code = 200
345 self._reason = httputil.responses[200]
347 def set_default_headers(self) -> None:
348 """Override this to set HTTP headers at the beginning of the request.
350 For example, this is the place to set a custom ``Server`` header.
351 Note that setting such headers in the normal flow of request
352 processing may not do what you want, since headers may be reset
353 during error handling.
354 """
355 pass
357 def set_status(self, status_code: int, reason: Optional[str] = None) -> None:
358 """Sets the status code for our response.
360 :arg int status_code: Response status code.
361 :arg str reason: Human-readable reason phrase describing the status
362 code (for example, the "Not Found" in ``HTTP/1.1 404 Not Found``).
363 Normally determined automatically from `http.client.responses`; this
364 argument should only be used if you need to use a non-standard
365 status code.
367 .. versionchanged:: 5.0
369 No longer validates that the response code is in
370 `http.client.responses`.
371 """
372 self._status_code = status_code
373 if reason is not None:
374 if "<" in reason or not httputil._ABNF.reason_phrase.fullmatch(reason):
375 # Logically this would be better as an exception, but this method
376 # is called on error-handling paths that would need some refactoring
377 # to tolerate internal errors cleanly.
378 #
379 # The check for "<" is a defense-in-depth against XSS attacks (we also
380 # escape the reason when rendering error pages).
381 reason = "Unknown"
382 self._reason = escape.native_str(reason)
383 else:
384 self._reason = httputil.responses.get(status_code, "Unknown")
386 def get_status(self) -> int:
387 """Returns the status code for our response."""
388 return self._status_code
390 def set_header(self, name: str, value: _HeaderTypes) -> None:
391 """Sets the given response header name and value.
393 All header values are converted to strings (`datetime` objects
394 are formatted according to the HTTP specification for the
395 ``Date`` header).
397 """
398 self._headers[name] = self._convert_header_value(value)
400 def add_header(self, name: str, value: _HeaderTypes) -> None:
401 """Adds the given response header and value.
403 Unlike `set_header`, `add_header` may be called multiple times
404 to return multiple values for the same header.
405 """
406 self._headers.add(name, self._convert_header_value(value))
408 def clear_header(self, name: str) -> None:
409 """Clears an outgoing header, undoing a previous `set_header` call.
411 Note that this method does not apply to multi-valued headers
412 set by `add_header`.
413 """
414 if name in self._headers:
415 del self._headers[name]
417 # https://www.rfc-editor.org/rfc/rfc9110#name-field-values
418 _VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*")
420 def _convert_header_value(self, value: _HeaderTypes) -> str:
421 # Convert the input value to a str. This type check is a bit
422 # subtle: The bytes case only executes on python 3, and the
423 # unicode case only executes on python 2, because the other
424 # cases are covered by the first match for str.
425 if isinstance(value, str):
426 retval = value
427 elif isinstance(value, bytes):
428 # Non-ascii characters in headers are not well supported,
429 # but if you pass bytes, use latin1 so they pass through as-is.
430 retval = value.decode("latin1")
431 elif isinstance(value, numbers.Integral):
432 # return immediately since we know the converted value will be safe
433 return str(value)
434 elif isinstance(value, datetime.datetime):
435 return httputil.format_timestamp(value)
436 else:
437 raise TypeError("Unsupported header value %r" % value)
438 # If \n is allowed into the header, it is possible to inject
439 # additional headers or split the request.
440 if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None:
441 raise ValueError("Unsafe header value %r", retval)
442 return retval
444 @overload
445 def get_argument(self, name: str, default: str, strip: bool = True) -> str:
446 pass
448 @overload
449 def get_argument( # noqa: F811
450 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
451 ) -> str:
452 pass
454 @overload
455 def get_argument( # noqa: F811
456 self, name: str, default: None, strip: bool = True
457 ) -> Optional[str]:
458 pass
460 def get_argument( # noqa: F811
461 self,
462 name: str,
463 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
464 strip: bool = True,
465 ) -> Optional[str]:
466 """Returns the value of the argument with the given name.
468 If default is not provided, the argument is considered to be
469 required, and we raise a `MissingArgumentError` if it is missing.
471 If the argument appears in the request more than once, we return the
472 last value.
474 This method searches both the query and body arguments.
475 """
476 return self._get_argument(name, default, self.request.arguments, strip)
478 def get_arguments(self, name: str, strip: bool = True) -> List[str]:
479 """Returns a list of the arguments with the given name.
481 If the argument is not present, returns an empty list.
483 This method searches both the query and body arguments.
484 """
486 # Make sure `get_arguments` isn't accidentally being called with a
487 # positional argument that's assumed to be a default (like in
488 # `get_argument`.)
489 assert isinstance(strip, bool)
491 return self._get_arguments(name, self.request.arguments, strip)
493 @overload
494 def get_body_argument(self, name: str, default: str, strip: bool = True) -> str:
495 pass
497 @overload
498 def get_body_argument( # noqa: F811
499 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
500 ) -> str:
501 pass
503 @overload
504 def get_body_argument( # noqa: F811
505 self, name: str, default: None, strip: bool = True
506 ) -> Optional[str]:
507 pass
509 def get_body_argument( # noqa: F811
510 self,
511 name: str,
512 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
513 strip: bool = True,
514 ) -> Optional[str]:
515 """Returns the value of the argument with the given name
516 from the request body.
518 If default is not provided, the argument is considered to be
519 required, and we raise a `MissingArgumentError` if it is missing.
521 If the argument appears in the url more than once, we return the
522 last value.
524 .. versionadded:: 3.2
525 """
526 return self._get_argument(name, default, self.request.body_arguments, strip)
528 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]:
529 """Returns a list of the body arguments with the given name.
531 If the argument is not present, returns an empty list.
533 .. versionadded:: 3.2
534 """
535 return self._get_arguments(name, self.request.body_arguments, strip)
537 @overload
538 def get_query_argument(self, name: str, default: str, strip: bool = True) -> str:
539 pass
541 @overload
542 def get_query_argument( # noqa: F811
543 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
544 ) -> str:
545 pass
547 @overload
548 def get_query_argument( # noqa: F811
549 self, name: str, default: None, strip: bool = True
550 ) -> Optional[str]:
551 pass
553 def get_query_argument( # noqa: F811
554 self,
555 name: str,
556 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
557 strip: bool = True,
558 ) -> Optional[str]:
559 """Returns the value of the argument with the given name
560 from the request query string.
562 If default is not provided, the argument is considered to be
563 required, and we raise a `MissingArgumentError` if it is missing.
565 If the argument appears in the url more than once, we return the
566 last value.
568 .. versionadded:: 3.2
569 """
570 return self._get_argument(name, default, self.request.query_arguments, strip)
572 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]:
573 """Returns a list of the query arguments with the given name.
575 If the argument is not present, returns an empty list.
577 .. versionadded:: 3.2
578 """
579 return self._get_arguments(name, self.request.query_arguments, strip)
581 def _get_argument(
582 self,
583 name: str,
584 default: Union[None, str, _ArgDefaultMarker],
585 source: Dict[str, List[bytes]],
586 strip: bool = True,
587 ) -> Optional[str]:
588 args = self._get_arguments(name, source, strip=strip)
589 if not args:
590 if isinstance(default, _ArgDefaultMarker):
591 raise MissingArgumentError(name)
592 return default
593 return args[-1]
595 def _get_arguments(
596 self, name: str, source: Dict[str, List[bytes]], strip: bool = True
597 ) -> List[str]:
598 values = []
599 for v in source.get(name, []):
600 s = self.decode_argument(v, name=name)
601 if isinstance(s, unicode_type):
602 # Get rid of any weird control chars (unless decoding gave
603 # us bytes, in which case leave it alone)
604 s = RequestHandler._remove_control_chars_regex.sub(" ", s)
605 if strip:
606 s = s.strip()
607 values.append(s)
608 return values
610 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str:
611 """Decodes an argument from the request.
613 The argument has been percent-decoded and is now a byte string.
614 By default, this method decodes the argument as utf-8 and returns
615 a unicode string, but this may be overridden in subclasses.
617 This method is used as a filter for both `get_argument()` and for
618 values extracted from the url and passed to `get()`/`post()`/etc.
620 The name of the argument is provided if known, but may be None
621 (e.g. for unnamed groups in the url regex).
622 """
623 try:
624 return _unicode(value)
625 except UnicodeDecodeError:
626 raise HTTPError(
627 400, "Invalid unicode in {}: {!r}".format(name or "url", value[:40])
628 )
630 @property
631 def cookies(self) -> Dict[str, http.cookies.Morsel]:
632 """An alias for
633 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`."""
634 return self.request.cookies
636 @overload
637 def get_cookie(self, name: str, default: str) -> str:
638 pass
640 @overload
641 def get_cookie(self, name: str, default: None = None) -> Optional[str]:
642 pass
644 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]:
645 """Returns the value of the request cookie with the given name.
647 If the named cookie is not present, returns ``default``.
649 This method only returns cookies that were present in the request.
650 It does not see the outgoing cookies set by `set_cookie` in this
651 handler.
652 """
653 if self.request.cookies is not None and name in self.request.cookies:
654 return self.request.cookies[name].value
655 return default
657 def set_cookie(
658 self,
659 name: str,
660 value: Union[str, bytes],
661 domain: Optional[str] = None,
662 expires: Optional[Union[float, Tuple, datetime.datetime]] = None,
663 path: str = "/",
664 expires_days: Optional[float] = None,
665 # Keyword-only args start here for historical reasons.
666 *,
667 max_age: Optional[int] = None,
668 httponly: bool = False,
669 secure: bool = False,
670 samesite: Optional[str] = None,
671 **kwargs: Any,
672 ) -> None:
673 """Sets an outgoing cookie name/value with the given options.
675 Newly-set cookies are not immediately visible via `get_cookie`;
676 they are not present until the next request.
678 Most arguments are passed directly to `http.cookies.Morsel` directly.
679 See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
680 for more information.
682 ``expires`` may be a numeric timestamp as returned by `time.time`,
683 a time tuple as returned by `time.gmtime`, or a
684 `datetime.datetime` object. ``expires_days`` is provided as a convenience
685 to set an expiration time in days from today (if both are set, ``expires``
686 is used).
688 .. deprecated:: 6.3
689 Keyword arguments are currently accepted case-insensitively.
690 In Tornado 7.0 this will be changed to only accept lowercase
691 arguments.
692 """
693 # The cookie library only accepts type str, in both python 2 and 3
694 name = escape.native_str(name)
695 value = escape.native_str(value)
696 if re.search(r"[\x00-\x20]", value):
697 # Legacy check for control characters in cookie values. This check is no longer needed
698 # since the cookie library escapes these characters correctly now. It will be removed
699 # in the next feature release.
700 raise ValueError(f"Invalid cookie {name!r}: {value!r}")
701 for attr_name, attr_value in [
702 ("name", name),
703 ("domain", domain),
704 ("path", path),
705 ("samesite", samesite),
706 ]:
707 # Cookie attributes may not contain control characters or semicolons (except when
708 # escaped in the value). A check for control characters was added to the http.cookies
709 # library in a Feb 2026 security release; as of March it still does not check for
710 # semicolons.
711 #
712 # When a semicolon check is added to the standard library (and the release has had time
713 # for adoption), this check may be removed, but be mindful of the fact that this may
714 # change the timing of the exception (to the generation of the Set-Cookie header in
715 # flush()). We m
716 if attr_value is not None and re.search(r"[\x00-\x20\x3b\x7f]", attr_value):
717 raise http.cookies.CookieError(
718 f"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}"
719 )
720 if not hasattr(self, "_new_cookie"):
721 self._new_cookie = (
722 http.cookies.SimpleCookie()
723 ) # type: http.cookies.SimpleCookie
724 if name in self._new_cookie:
725 del self._new_cookie[name]
726 self._new_cookie[name] = value
727 morsel = self._new_cookie[name]
728 if domain:
729 morsel["domain"] = domain
730 if expires_days is not None and not expires:
731 expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
732 days=expires_days
733 )
734 if expires:
735 morsel["expires"] = httputil.format_timestamp(expires)
736 if path:
737 morsel["path"] = path
738 if max_age:
739 # Note change from _ to -.
740 morsel["max-age"] = str(max_age)
741 if httponly:
742 # Note that SimpleCookie ignores the value here. The presense of an
743 # httponly (or secure) key is treated as true.
744 morsel["httponly"] = True
745 if secure:
746 morsel["secure"] = True
747 if samesite:
748 morsel["samesite"] = samesite
749 if kwargs:
750 # The setitem interface is case-insensitive, so continue to support
751 # kwargs for backwards compatibility until we can remove deprecated
752 # features.
753 for k, v in kwargs.items():
754 morsel[k] = v
755 warnings.warn(
756 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} "
757 "(should be lowercase)",
758 DeprecationWarning,
759 )
761 def clear_cookie(self, name: str, **kwargs: Any) -> None:
762 """Deletes the cookie with the given name.
764 This method accepts the same arguments as `set_cookie`, except for
765 ``expires`` and ``max_age``. Clearing a cookie requires the same
766 ``domain`` and ``path`` arguments as when it was set. In some cases the
767 ``samesite`` and ``secure`` arguments are also required to match. Other
768 arguments are ignored.
770 Similar to `set_cookie`, the effect of this method will not be
771 seen until the following request.
773 .. versionchanged:: 6.3
775 Now accepts all keyword arguments that ``set_cookie`` does.
776 The ``samesite`` and ``secure`` flags have recently become
777 required for clearing ``samesite="none"`` cookies.
778 """
779 for excluded_arg in ["expires", "max_age"]:
780 if excluded_arg in kwargs:
781 raise TypeError(
782 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'"
783 )
784 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
785 days=365
786 )
787 self.set_cookie(name, value="", expires=expires, **kwargs)
789 def clear_all_cookies(self, **kwargs: Any) -> None:
790 """Attempt to delete all the cookies the user sent with this request.
792 See `clear_cookie` for more information on keyword arguments. Due to
793 limitations of the cookie protocol, it is impossible to determine on the
794 server side which values are necessary for the ``domain``, ``path``,
795 ``samesite``, or ``secure`` arguments, this method can only be
796 successful if you consistently use the same values for these arguments
797 when setting cookies.
799 Similar to `set_cookie`, the effect of this method will not be seen
800 until the following request.
802 .. versionchanged:: 3.2
804 Added the ``path`` and ``domain`` parameters.
806 .. versionchanged:: 6.3
808 Now accepts all keyword arguments that ``set_cookie`` does.
810 .. deprecated:: 6.3
812 The increasingly complex rules governing cookies have made it
813 impossible for a ``clear_all_cookies`` method to work reliably
814 since all we know about cookies are their names. Applications
815 should generally use ``clear_cookie`` one at a time instead.
816 """
817 for name in self.request.cookies:
818 self.clear_cookie(name, **kwargs)
820 def set_signed_cookie(
821 self,
822 name: str,
823 value: Union[str, bytes],
824 expires_days: Optional[float] = 30,
825 version: Optional[int] = None,
826 **kwargs: Any,
827 ) -> None:
828 """Signs and timestamps a cookie so it cannot be forged.
830 You must specify the ``cookie_secret`` setting in your Application
831 to use this method. It should be a long, random sequence of bytes
832 to be used as the HMAC secret for the signature.
834 To read a cookie set with this method, use `get_signed_cookie()`.
836 Note that the ``expires_days`` parameter sets the lifetime of the
837 cookie in the browser, but is independent of the ``max_age_days``
838 parameter to `get_signed_cookie`.
839 A value of None limits the lifetime to the current browser session.
841 Secure cookies may contain arbitrary byte values, not just unicode
842 strings (unlike regular cookies)
844 Similar to `set_cookie`, the effect of this method will not be
845 seen until the following request.
847 .. versionchanged:: 3.2.1
849 Added the ``version`` argument. Introduced cookie version 2
850 and made it the default.
852 .. versionchanged:: 6.3
854 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to
855 avoid confusion with other uses of "secure" in cookie attributes
856 and prefixes. The old name remains as an alias.
857 """
858 self.set_cookie(
859 name,
860 self.create_signed_value(name, value, version=version),
861 expires_days=expires_days,
862 **kwargs,
863 )
865 set_secure_cookie = set_signed_cookie
867 def create_signed_value(
868 self, name: str, value: Union[str, bytes], version: Optional[int] = None
869 ) -> bytes:
870 """Signs and timestamps a string so it cannot be forged.
872 Normally used via set_signed_cookie, but provided as a separate
873 method for non-cookie uses. To decode a value not stored
874 as a cookie use the optional value argument to get_signed_cookie.
876 .. versionchanged:: 3.2.1
878 Added the ``version`` argument. Introduced cookie version 2
879 and made it the default.
880 """
881 self.require_setting("cookie_secret", "secure cookies")
882 secret = self.application.settings["cookie_secret"]
883 key_version = None
884 if isinstance(secret, dict):
885 if self.application.settings.get("key_version") is None:
886 raise Exception("key_version setting must be used for secret_key dicts")
887 key_version = self.application.settings["key_version"]
889 return create_signed_value(
890 secret, name, value, version=version, key_version=key_version
891 )
893 def get_signed_cookie(
894 self,
895 name: str,
896 value: Optional[str] = None,
897 max_age_days: float = 31,
898 min_version: Optional[int] = None,
899 ) -> Optional[bytes]:
900 """Returns the given signed cookie if it validates, or None.
902 The decoded cookie value is returned as a byte string (unlike
903 `get_cookie`).
905 Similar to `get_cookie`, this method only returns cookies that
906 were present in the request. It does not see outgoing cookies set by
907 `set_signed_cookie` in this handler.
909 .. versionchanged:: 3.2.1
911 Added the ``min_version`` argument. Introduced cookie version 2;
912 both versions 1 and 2 are accepted by default.
914 .. versionchanged:: 6.3
916 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to
917 avoid confusion with other uses of "secure" in cookie attributes
918 and prefixes. The old name remains as an alias.
920 """
921 self.require_setting("cookie_secret", "secure cookies")
922 if value is None:
923 value = self.get_cookie(name)
924 return decode_signed_value(
925 self.application.settings["cookie_secret"],
926 name,
927 value,
928 max_age_days=max_age_days,
929 min_version=min_version,
930 )
932 get_secure_cookie = get_signed_cookie
934 def get_signed_cookie_key_version(
935 self, name: str, value: Optional[str] = None
936 ) -> Optional[int]:
937 """Returns the signing key version of the secure cookie.
939 The version is returned as int.
941 .. versionchanged:: 6.3
943 Renamed from ``get_secure_cookie_key_version`` to
944 ``set_signed_cookie_key_version`` to avoid confusion with other
945 uses of "secure" in cookie attributes and prefixes. The old name
946 remains as an alias.
948 """
949 self.require_setting("cookie_secret", "secure cookies")
950 if value is None:
951 value = self.get_cookie(name)
952 if value is None:
953 return None
954 return get_signature_key_version(value)
956 get_secure_cookie_key_version = get_signed_cookie_key_version
958 def redirect(
959 self, url: str, permanent: bool = False, status: Optional[int] = None
960 ) -> None:
961 """Sends a redirect to the given (optionally relative) URL.
963 If the ``status`` argument is specified, that value is used as the
964 HTTP status code; otherwise either 301 (permanent) or 302
965 (temporary) is chosen based on the ``permanent`` argument.
966 The default is 302 (temporary).
967 """
968 if self._headers_written:
969 raise Exception("Cannot redirect after headers have been written")
970 if status is None:
971 status = 301 if permanent else 302
972 else:
973 assert isinstance(status, int) and 300 <= status <= 399
974 self.set_status(status)
975 self.set_header("Location", utf8(url))
976 self.finish()
978 def write(self, chunk: Union[str, bytes, dict]) -> None:
979 """Writes the given chunk to the output buffer.
981 To write the output to the network, use the `flush()` method below.
983 If the given chunk is a dictionary, we write it as JSON and set
984 the Content-Type of the response to be ``application/json``.
985 (if you want to send JSON as a different ``Content-Type``, call
986 ``set_header`` *after* calling ``write()``).
988 Note that lists are not converted to JSON because of a potential
989 cross-site security vulnerability. All JSON output should be
990 wrapped in a dictionary. More details at
991 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
992 https://github.com/facebook/tornado/issues/1009
993 """
994 if self._finished:
995 raise RuntimeError("Cannot write() after finish()")
996 if not isinstance(chunk, (bytes, unicode_type, dict)):
997 message = "write() only accepts bytes, unicode, and dict objects"
998 if isinstance(chunk, list):
999 message += (
1000 ". Lists not accepted for security reasons; see "
1001 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501
1002 )
1003 raise TypeError(message)
1004 if isinstance(chunk, dict):
1005 chunk = escape.json_encode(chunk)
1006 self.set_header("Content-Type", "application/json; charset=UTF-8")
1007 chunk = utf8(chunk)
1008 self._write_buffer.append(chunk)
1010 def render(self, template_name: str, **kwargs: Any) -> "Future[None]":
1011 """Renders the template with the given arguments as the response.
1013 ``render()`` calls ``finish()``, so no other output methods can be called
1014 after it.
1016 Returns a `.Future` with the same semantics as the one returned by `finish`.
1017 Awaiting this `.Future` is optional.
1019 .. versionchanged:: 5.1
1021 Now returns a `.Future` instead of ``None``.
1022 """
1023 if self._finished:
1024 raise RuntimeError("Cannot render() after finish()")
1025 html = self.render_string(template_name, **kwargs)
1027 # Insert the additional JS and CSS added by the modules on the page
1028 js_embed = []
1029 js_files = []
1030 css_embed = []
1031 css_files = []
1032 html_heads = []
1033 html_bodies = []
1034 for module in getattr(self, "_active_modules", {}).values():
1035 embed_part = module.embedded_javascript()
1036 if embed_part:
1037 js_embed.append(utf8(embed_part))
1038 file_part = module.javascript_files()
1039 if file_part:
1040 if isinstance(file_part, (unicode_type, bytes)):
1041 js_files.append(_unicode(file_part))
1042 else:
1043 js_files.extend(file_part)
1044 embed_part = module.embedded_css()
1045 if embed_part:
1046 css_embed.append(utf8(embed_part))
1047 file_part = module.css_files()
1048 if file_part:
1049 if isinstance(file_part, (unicode_type, bytes)):
1050 css_files.append(_unicode(file_part))
1051 else:
1052 css_files.extend(file_part)
1053 head_part = module.html_head()
1054 if head_part:
1055 html_heads.append(utf8(head_part))
1056 body_part = module.html_body()
1057 if body_part:
1058 html_bodies.append(utf8(body_part))
1060 if js_files:
1061 # Maintain order of JavaScript files given by modules
1062 js = self.render_linked_js(js_files)
1063 sloc = html.rindex(b"</body>")
1064 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:]
1065 if js_embed:
1066 js_bytes = self.render_embed_js(js_embed)
1067 sloc = html.rindex(b"</body>")
1068 html = html[:sloc] + js_bytes + b"\n" + html[sloc:]
1069 if css_files:
1070 css = self.render_linked_css(css_files)
1071 hloc = html.index(b"</head>")
1072 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:]
1073 if css_embed:
1074 css_bytes = self.render_embed_css(css_embed)
1075 hloc = html.index(b"</head>")
1076 html = html[:hloc] + css_bytes + b"\n" + html[hloc:]
1077 if html_heads:
1078 hloc = html.index(b"</head>")
1079 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:]
1080 if html_bodies:
1081 hloc = html.index(b"</body>")
1082 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:]
1083 return self.finish(html)
1085 def render_linked_js(self, js_files: Iterable[str]) -> str:
1086 """Default method used to render the final js links for the
1087 rendered webpage.
1089 Override this method in a sub-classed controller to change the output.
1090 """
1091 paths = []
1092 unique_paths = set() # type: Set[str]
1094 for path in js_files:
1095 if not is_absolute(path):
1096 path = self.static_url(path)
1097 if path not in unique_paths:
1098 paths.append(path)
1099 unique_paths.add(path)
1101 return "".join(
1102 '<script src="'
1103 + escape.xhtml_escape(p)
1104 + '" type="text/javascript"></script>'
1105 for p in paths
1106 )
1108 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
1109 """Default method used to render the final embedded js for the
1110 rendered webpage.
1112 Override this method in a sub-classed controller to change the output.
1113 """
1114 return (
1115 b'<script type="text/javascript">\n//<![CDATA[\n'
1116 + b"\n".join(js_embed)
1117 + b"\n//]]>\n</script>"
1118 )
1120 def render_linked_css(self, css_files: Iterable[str]) -> str:
1121 """Default method used to render the final css links for the
1122 rendered webpage.
1124 Override this method in a sub-classed controller to change the output.
1125 """
1126 paths = []
1127 unique_paths = set() # type: Set[str]
1129 for path in css_files:
1130 if not is_absolute(path):
1131 path = self.static_url(path)
1132 if path not in unique_paths:
1133 paths.append(path)
1134 unique_paths.add(path)
1136 return "".join(
1137 '<link href="' + escape.xhtml_escape(p) + '" '
1138 'type="text/css" rel="stylesheet"/>'
1139 for p in paths
1140 )
1142 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
1143 """Default method used to render the final embedded css for the
1144 rendered webpage.
1146 Override this method in a sub-classed controller to change the output.
1147 """
1148 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>"
1150 def render_string(self, template_name: str, **kwargs: Any) -> bytes:
1151 """Generate the given template with the given arguments.
1153 We return the generated byte string (in utf8). To generate and
1154 write a template as a response, use render() above.
1155 """
1156 # If no template_path is specified, use the path of the calling file
1157 template_path = self.get_template_path()
1158 if not template_path:
1159 frame = sys._getframe(0)
1160 web_file = frame.f_code.co_filename
1161 while frame.f_code.co_filename == web_file and frame.f_back is not None:
1162 frame = frame.f_back
1163 assert frame.f_code.co_filename is not None
1164 template_path = os.path.dirname(frame.f_code.co_filename)
1165 with RequestHandler._template_loader_lock:
1166 if template_path not in RequestHandler._template_loaders:
1167 loader = self.create_template_loader(template_path)
1168 RequestHandler._template_loaders[template_path] = loader
1169 else:
1170 loader = RequestHandler._template_loaders[template_path]
1171 t = loader.load(template_name)
1172 namespace = self.get_template_namespace()
1173 namespace.update(kwargs)
1174 return t.generate(**namespace)
1176 def get_template_namespace(self) -> Dict[str, Any]:
1177 """Returns a dictionary to be used as the default template namespace.
1179 May be overridden by subclasses to add or modify values.
1181 The results of this method will be combined with additional
1182 defaults in the `tornado.template` module and keyword arguments
1183 to `render` or `render_string`.
1184 """
1185 namespace = dict(
1186 handler=self,
1187 request=self.request,
1188 current_user=self.current_user,
1189 locale=self.locale,
1190 _=self.locale.translate,
1191 pgettext=self.locale.pgettext,
1192 static_url=self.static_url,
1193 xsrf_form_html=self.xsrf_form_html,
1194 reverse_url=self.reverse_url,
1195 )
1196 namespace.update(self.ui)
1197 return namespace
1199 def create_template_loader(self, template_path: str) -> template.BaseLoader:
1200 """Returns a new template loader for the given path.
1202 May be overridden by subclasses. By default returns a
1203 directory-based loader on the given path, using the
1204 ``autoescape`` and ``template_whitespace`` application
1205 settings. If a ``template_loader`` application setting is
1206 supplied, uses that instead.
1207 """
1208 settings = self.application.settings
1209 if "template_loader" in settings:
1210 return settings["template_loader"]
1211 kwargs = {}
1212 if "autoescape" in settings:
1213 # autoescape=None means "no escaping", so we have to be sure
1214 # to only pass this kwarg if the user asked for it.
1215 kwargs["autoescape"] = settings["autoescape"]
1216 if "template_whitespace" in settings:
1217 kwargs["whitespace"] = settings["template_whitespace"]
1218 return template.Loader(template_path, **kwargs)
1220 def flush(self, include_footers: bool = False) -> "Future[None]":
1221 """Flushes the current output buffer to the network.
1223 .. versionchanged:: 4.0
1224 Now returns a `.Future` if no callback is given.
1226 .. versionchanged:: 6.0
1228 The ``callback`` argument was removed.
1229 """
1230 assert self.request.connection is not None
1231 chunk = b"".join(self._write_buffer)
1232 self._write_buffer = []
1233 if not self._headers_written:
1234 self._headers_written = True
1235 for transform in self._transforms:
1236 assert chunk is not None
1237 (
1238 self._status_code,
1239 self._headers,
1240 chunk,
1241 ) = transform.transform_first_chunk(
1242 self._status_code, self._headers, chunk, include_footers
1243 )
1244 # Ignore the chunk and only write the headers for HEAD requests
1245 if self.request.method == "HEAD":
1246 chunk = b""
1248 # Finalize the cookie headers (which have been stored in a side
1249 # object so an outgoing cookie could be overwritten before it
1250 # is sent).
1251 if hasattr(self, "_new_cookie"):
1252 for cookie in self._new_cookie.values():
1253 self.add_header("Set-Cookie", cookie.OutputString(None))
1255 start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
1256 return self.request.connection.write_headers(
1257 start_line, self._headers, chunk
1258 )
1259 else:
1260 for transform in self._transforms:
1261 chunk = transform.transform_chunk(chunk, include_footers)
1262 # Ignore the chunk and only write the headers for HEAD requests
1263 if self.request.method != "HEAD":
1264 return self.request.connection.write(chunk)
1265 else:
1266 future = Future() # type: Future[None]
1267 future.set_result(None)
1268 return future
1270 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]":
1271 """Finishes this response, ending the HTTP request.
1273 Passing a ``chunk`` to ``finish()`` is equivalent to passing that
1274 chunk to ``write()`` and then calling ``finish()`` with no arguments.
1276 Returns a `.Future` which may optionally be awaited to track the sending
1277 of the response to the client. This `.Future` resolves when all the response
1278 data has been sent, and raises an error if the connection is closed before all
1279 data can be sent.
1281 .. versionchanged:: 5.1
1283 Now returns a `.Future` instead of ``None``.
1284 """
1285 if self._finished:
1286 raise RuntimeError("finish() called twice")
1288 if chunk is not None:
1289 self.write(chunk)
1291 # Automatically support ETags and add the Content-Length header if
1292 # we have not flushed any content yet.
1293 if not self._headers_written:
1294 if (
1295 self._status_code == 200
1296 and self.request.method in ("GET", "HEAD")
1297 and "Etag" not in self._headers
1298 ):
1299 self.set_etag_header()
1300 if self.check_etag_header():
1301 self._write_buffer = []
1302 self.set_status(304)
1303 if self._status_code in (204, 304) or (100 <= self._status_code < 200):
1304 assert not self._write_buffer, (
1305 "Cannot send body with %s" % self._status_code
1306 )
1307 self._clear_representation_headers()
1308 elif "Content-Length" not in self._headers:
1309 content_length = sum(len(part) for part in self._write_buffer)
1310 self.set_header("Content-Length", content_length)
1312 assert self.request.connection is not None
1313 # Now that the request is finished, clear the callback we
1314 # set on the HTTPConnection (which would otherwise prevent the
1315 # garbage collection of the RequestHandler when there
1316 # are keepalive connections)
1317 self.request.connection.set_close_callback(None) # type: ignore
1319 future = self.flush(include_footers=True)
1320 self.request.connection.finish()
1321 self._log()
1322 self._finished = True
1323 self.on_finish()
1324 self._break_cycles()
1325 return future
1327 def detach(self) -> iostream.IOStream:
1328 """Take control of the underlying stream.
1330 Returns the underlying `.IOStream` object and stops all
1331 further HTTP processing. Intended for implementing protocols
1332 like websockets that tunnel over an HTTP handshake.
1334 This method is only supported when HTTP/1.1 is used.
1336 .. versionadded:: 5.1
1337 """
1338 self._finished = True
1339 # TODO: add detach to HTTPConnection?
1340 return self.request.connection.detach() # type: ignore
1342 def _break_cycles(self) -> None:
1343 # Break up a reference cycle between this handler and the
1344 # _ui_module closures to allow for faster GC on CPython.
1345 self.ui = None # type: ignore
1347 def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
1348 """Sends the given HTTP error code to the browser.
1350 If `flush()` has already been called, it is not possible to send
1351 an error, so this method will simply terminate the response.
1352 If output has been written but not yet flushed, it will be discarded
1353 and replaced with the error page.
1355 Override `write_error()` to customize the error page that is returned.
1356 Additional keyword arguments are passed through to `write_error`.
1357 """
1358 if self._headers_written:
1359 gen_log.error("Cannot send error response after headers written")
1360 if not self._finished:
1361 # If we get an error between writing headers and finishing,
1362 # we are unlikely to be able to finish due to a
1363 # Content-Length mismatch. Try anyway to release the
1364 # socket.
1365 try:
1366 self.finish()
1367 except Exception:
1368 gen_log.error("Failed to flush partial response", exc_info=True)
1369 return
1370 self.clear()
1372 reason = kwargs.get("reason")
1373 if "exc_info" in kwargs:
1374 exception = kwargs["exc_info"][1]
1375 if isinstance(exception, HTTPError) and exception.reason:
1376 reason = exception.reason
1377 self.set_status(status_code, reason=reason)
1378 try:
1379 if status_code != 304:
1380 self.write_error(status_code, **kwargs)
1381 except Exception:
1382 app_log.error("Uncaught exception in write_error", exc_info=True)
1383 if not self._finished:
1384 self.finish()
1386 def write_error(self, status_code: int, **kwargs: Any) -> None:
1387 """Override to implement custom error pages.
1389 ``write_error`` may call `write`, `render`, `set_header`, etc
1390 to produce output as usual.
1392 If this error was caused by an uncaught exception (including
1393 HTTPError), an ``exc_info`` triple will be available as
1394 ``kwargs["exc_info"]``. Note that this exception may not be
1395 the "current" exception for purposes of methods like
1396 ``sys.exc_info()`` or ``traceback.format_exc``.
1397 """
1398 if self.settings.get("serve_traceback") and "exc_info" in kwargs:
1399 # in debug mode, try to send a traceback
1400 self.set_header("Content-Type", "text/plain")
1401 for line in traceback.format_exception(*kwargs["exc_info"]):
1402 self.write(line)
1403 self.finish()
1404 else:
1405 self.finish(
1406 "<html><title>%(code)d: %(message)s</title>"
1407 "<body>%(code)d: %(message)s</body></html>"
1408 % {"code": status_code, "message": escape.xhtml_escape(self._reason)}
1409 )
1411 @property
1412 def locale(self) -> tornado.locale.Locale:
1413 """The locale for the current session.
1415 Determined by either `get_user_locale`, which you can override to
1416 set the locale based on, e.g., a user preference stored in a
1417 database, or `get_browser_locale`, which uses the ``Accept-Language``
1418 header.
1420 .. versionchanged: 4.1
1421 Added a property setter.
1422 """
1423 if not hasattr(self, "_locale"):
1424 loc = self.get_user_locale()
1425 if loc is not None:
1426 self._locale = loc
1427 else:
1428 self._locale = self.get_browser_locale()
1429 assert self._locale
1430 return self._locale
1432 @locale.setter
1433 def locale(self, value: tornado.locale.Locale) -> None:
1434 self._locale = value
1436 def get_user_locale(self) -> Optional[tornado.locale.Locale]:
1437 """Override to determine the locale from the authenticated user.
1439 If None is returned, we fall back to `get_browser_locale()`.
1441 This method should return a `tornado.locale.Locale` object,
1442 most likely obtained via a call like ``tornado.locale.get("en")``
1443 """
1444 return None
1446 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
1447 """Determines the user's locale from ``Accept-Language`` header.
1449 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
1450 """
1451 if "Accept-Language" in self.request.headers:
1452 languages = self.request.headers["Accept-Language"].split(",")
1453 locales = []
1454 for language in languages:
1455 parts = language.strip().split(";")
1456 if len(parts) > 1 and parts[1].strip().startswith("q="):
1457 try:
1458 score = float(parts[1].strip()[2:])
1459 if score < 0:
1460 raise ValueError()
1461 except (ValueError, TypeError):
1462 score = 0.0
1463 else:
1464 score = 1.0
1465 if score > 0:
1466 locales.append((parts[0], score))
1467 if locales:
1468 locales.sort(key=lambda pair: pair[1], reverse=True)
1469 codes = [loc[0] for loc in locales]
1470 return locale.get(*codes)
1471 return locale.get(default)
1473 @property
1474 def current_user(self) -> Any:
1475 """The authenticated user for this request.
1477 This is set in one of two ways:
1479 * A subclass may override `get_current_user()`, which will be called
1480 automatically the first time ``self.current_user`` is accessed.
1481 `get_current_user()` will only be called once per request,
1482 and is cached for future access::
1484 def get_current_user(self):
1485 user_cookie = self.get_signed_cookie("user")
1486 if user_cookie:
1487 return json.loads(user_cookie)
1488 return None
1490 * It may be set as a normal variable, typically from an overridden
1491 `prepare()`::
1493 @gen.coroutine
1494 def prepare(self):
1495 user_id_cookie = self.get_signed_cookie("user_id")
1496 if user_id_cookie:
1497 self.current_user = yield load_user(user_id_cookie)
1499 Note that `prepare()` may be a coroutine while `get_current_user()`
1500 may not, so the latter form is necessary if loading the user requires
1501 asynchronous operations.
1503 The user object may be any type of the application's choosing.
1504 """
1505 if not hasattr(self, "_current_user"):
1506 self._current_user = self.get_current_user()
1507 return self._current_user
1509 @current_user.setter
1510 def current_user(self, value: Any) -> None:
1511 self._current_user = value
1513 def get_current_user(self) -> Any:
1514 """Override to determine the current user from, e.g., a cookie.
1516 This method may not be a coroutine.
1517 """
1518 return None
1520 def get_login_url(self) -> str:
1521 """Override to customize the login URL based on the request.
1523 By default, we use the ``login_url`` application setting.
1524 """
1525 self.require_setting("login_url", "@tornado.web.authenticated")
1526 return self.application.settings["login_url"]
1528 def get_template_path(self) -> Optional[str]:
1529 """Override to customize template path for each handler.
1531 By default, we use the ``template_path`` application setting.
1532 Return None to load templates relative to the calling file.
1533 """
1534 return self.application.settings.get("template_path")
1536 @property
1537 def xsrf_token(self) -> bytes:
1538 """The XSRF-prevention token for the current user/session.
1540 To prevent cross-site request forgery, we set an '_xsrf' cookie
1541 and include the same '_xsrf' value as an argument with all POST
1542 requests. If the two do not match, we reject the form submission
1543 as a potential forgery.
1545 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1547 This property is of type `bytes`, but it contains only ASCII
1548 characters. If a character string is required, there is no
1549 need to base64-encode it; just decode the byte string as
1550 UTF-8.
1552 .. versionchanged:: 3.2.2
1553 The xsrf token will now be have a random mask applied in every
1554 request, which makes it safe to include the token in pages
1555 that are compressed. See http://breachattack.com for more
1556 information on the issue fixed by this change. Old (version 1)
1557 cookies will be converted to version 2 when this method is called
1558 unless the ``xsrf_cookie_version`` `Application` setting is
1559 set to 1.
1561 .. versionchanged:: 4.3
1562 The ``xsrf_cookie_kwargs`` `Application` setting may be
1563 used to supply additional cookie options (which will be
1564 passed directly to `set_cookie`). For example,
1565 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
1566 will set the ``secure`` and ``httponly`` flags on the
1567 ``_xsrf`` cookie.
1568 """
1569 if not hasattr(self, "_xsrf_token"):
1570 version, token, timestamp = self._get_raw_xsrf_token()
1571 output_version = self.settings.get("xsrf_cookie_version", 2)
1572 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
1573 if output_version == 1:
1574 self._xsrf_token = binascii.b2a_hex(token)
1575 elif output_version == 2:
1576 mask = os.urandom(4)
1577 self._xsrf_token = b"|".join(
1578 [
1579 b"2",
1580 binascii.b2a_hex(mask),
1581 binascii.b2a_hex(_websocket_mask(mask, token)),
1582 utf8(str(int(timestamp))),
1583 ]
1584 )
1585 else:
1586 raise ValueError("unknown xsrf cookie version %d", output_version)
1587 if version is None:
1588 if self.current_user and "expires_days" not in cookie_kwargs:
1589 cookie_kwargs["expires_days"] = 30
1590 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1591 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)
1592 return self._xsrf_token
1594 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
1595 """Read or generate the xsrf token in its raw form.
1597 The raw_xsrf_token is a tuple containing:
1599 * version: the version of the cookie from which this token was read,
1600 or None if we generated a new token in this request.
1601 * token: the raw token data; random (non-ascii) bytes.
1602 * timestamp: the time this token was generated (will not be accurate
1603 for version 1 cookies)
1604 """
1605 if not hasattr(self, "_raw_xsrf_token"):
1606 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1607 cookie = self.get_cookie(cookie_name)
1608 if cookie:
1609 version, token, timestamp = self._decode_xsrf_token(cookie)
1610 else:
1611 version, token, timestamp = None, None, None
1612 if token is None:
1613 version = None
1614 token = os.urandom(16)
1615 timestamp = time.time()
1616 assert token is not None
1617 assert timestamp is not None
1618 self._raw_xsrf_token = (version, token, timestamp)
1619 return self._raw_xsrf_token
1621 def _decode_xsrf_token(
1622 self, cookie: str
1623 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
1624 """Convert a cookie string into a the tuple form returned by
1625 _get_raw_xsrf_token.
1626 """
1628 try:
1629 m = _signed_value_version_re.match(utf8(cookie))
1631 if m:
1632 version = int(m.group(1))
1633 if version == 2:
1634 _, mask_str, masked_token, timestamp_str = cookie.split("|")
1636 mask = binascii.a2b_hex(utf8(mask_str))
1637 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
1638 timestamp = int(timestamp_str)
1639 return version, token, timestamp
1640 else:
1641 # Treat unknown versions as not present instead of failing.
1642 raise Exception("Unknown xsrf cookie version")
1643 else:
1644 version = 1
1645 try:
1646 token = binascii.a2b_hex(utf8(cookie))
1647 except (binascii.Error, TypeError):
1648 token = utf8(cookie)
1649 # We don't have a usable timestamp in older versions.
1650 timestamp = int(time.time())
1651 return (version, token, timestamp)
1652 except Exception:
1653 # Catch exceptions and return nothing instead of failing.
1654 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
1655 return None, None, None
1657 def check_xsrf_cookie(self) -> None:
1658 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
1660 To prevent cross-site request forgery, we set an ``_xsrf``
1661 cookie and include the same value as a non-cookie
1662 field with all ``POST`` requests. If the two do not match, we
1663 reject the form submission as a potential forgery.
1665 The ``_xsrf`` value may be set as either a form field named ``_xsrf``
1666 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
1667 (the latter is accepted for compatibility with Django).
1669 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1671 .. versionchanged:: 3.2.2
1672 Added support for cookie version 2. Both versions 1 and 2 are
1673 supported.
1674 """
1675 # Prior to release 1.1.1, this check was ignored if the HTTP header
1676 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception
1677 # has been shown to be insecure and has been removed. For more
1678 # information please see
1679 # http://www.djangoproject.com/weblog/2011/feb/08/security/
1680 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
1681 input_token = (
1682 self.get_argument("_xsrf", None)
1683 or self.request.headers.get("X-Xsrftoken")
1684 or self.request.headers.get("X-Csrftoken")
1685 )
1686 if not input_token:
1687 raise HTTPError(403, "'_xsrf' argument missing from POST")
1688 _, token, _ = self._decode_xsrf_token(input_token)
1689 _, expected_token, _ = self._get_raw_xsrf_token()
1690 if not token:
1691 raise HTTPError(403, "'_xsrf' argument has invalid format")
1692 if not hmac.compare_digest(utf8(token), utf8(expected_token)):
1693 raise HTTPError(403, "XSRF cookie does not match POST argument")
1695 def xsrf_form_html(self) -> str:
1696 """An HTML ``<input/>`` element to be included with all POST forms.
1698 It defines the ``_xsrf`` input value, which we check on all POST
1699 requests to prevent cross-site request forgery. If you have set
1700 the ``xsrf_cookies`` application setting, you must include this
1701 HTML within all of your HTML forms.
1703 In a template, this method should be called with ``{% module
1704 xsrf_form_html() %}``
1706 See `check_xsrf_cookie()` above for more information.
1707 """
1708 return (
1709 '<input type="hidden" name="_xsrf" value="'
1710 + escape.xhtml_escape(self.xsrf_token)
1711 + '"/>'
1712 )
1714 def static_url(
1715 self, path: str, include_host: Optional[bool] = None, **kwargs: Any
1716 ) -> str:
1717 """Returns a static URL for the given relative static file path.
1719 This method requires you set the ``static_path`` setting in your
1720 application (which specifies the root directory of your static
1721 files).
1723 This method returns a versioned url (by default appending
1724 ``?v=<signature>``), which allows the static files to be
1725 cached indefinitely. This can be disabled by passing
1726 ``include_version=False`` (in the default implementation;
1727 other static file implementations are not required to support
1728 this, but they may support other options).
1730 By default this method returns URLs relative to the current
1731 host, but if ``include_host`` is true the URL returned will be
1732 absolute. If this handler has an ``include_host`` attribute,
1733 that value will be used as the default for all `static_url`
1734 calls that do not pass ``include_host`` as a keyword argument.
1736 """
1737 self.require_setting("static_path", "static_url")
1738 get_url = self.settings.get(
1739 "static_handler_class", StaticFileHandler
1740 ).make_static_url
1742 if include_host is None:
1743 include_host = getattr(self, "include_host", False)
1745 if include_host:
1746 base = self.request.protocol + "://" + self.request.host
1747 else:
1748 base = ""
1750 return base + get_url(self.settings, path, **kwargs)
1752 def require_setting(self, name: str, feature: str = "this feature") -> None:
1753 """Raises an exception if the given app setting is not defined."""
1754 if not self.application.settings.get(name):
1755 raise Exception(
1756 "You must define the '%s' setting in your "
1757 "application to use %s" % (name, feature)
1758 )
1760 def reverse_url(self, name: str, *args: Any) -> str:
1761 """Alias for `Application.reverse_url`."""
1762 return self.application.reverse_url(name, *args)
1764 def compute_etag(self) -> Optional[str]:
1765 """Computes the etag header to be used for this request.
1767 By default uses a hash of the content written so far.
1769 May be overridden to provide custom etag implementations,
1770 or may return None to disable tornado's default etag support.
1771 """
1772 hasher = hashlib.sha1()
1773 for part in self._write_buffer:
1774 hasher.update(part)
1775 return '"%s"' % hasher.hexdigest()
1777 def set_etag_header(self) -> None:
1778 """Sets the response's Etag header using ``self.compute_etag()``.
1780 Note: no header will be set if ``compute_etag()`` returns ``None``.
1782 This method is called automatically when the request is finished.
1783 """
1784 etag = self.compute_etag()
1785 if etag is not None:
1786 self.set_header("Etag", etag)
1788 def check_etag_header(self) -> bool:
1789 """Checks the ``Etag`` header against requests's ``If-None-Match``.
1791 Returns ``True`` if the request's Etag matches and a 304 should be
1792 returned. For example::
1794 self.set_etag_header()
1795 if self.check_etag_header():
1796 self.set_status(304)
1797 return
1799 This method is called automatically when the request is finished,
1800 but may be called earlier for applications that override
1801 `compute_etag` and want to do an early check for ``If-None-Match``
1802 before completing the request. The ``Etag`` header should be set
1803 (perhaps with `set_etag_header`) before calling this method.
1804 """
1805 computed_etag = utf8(self._headers.get("Etag", ""))
1806 # Find all weak and strong etag values from If-None-Match header
1807 # because RFC 7232 allows multiple etag values in a single header.
1808 etags = re.findall(
1809 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
1810 )
1811 if not computed_etag or not etags:
1812 return False
1814 match = False
1815 if etags[0] == b"*":
1816 match = True
1817 else:
1818 # Use a weak comparison when comparing entity-tags.
1819 def val(x: bytes) -> bytes:
1820 return x[2:] if x.startswith(b"W/") else x
1822 for etag in etags:
1823 if val(etag) == val(computed_etag):
1824 match = True
1825 break
1826 return match
1828 async def _execute(
1829 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
1830 ) -> None:
1831 """Executes this request with the given output transforms."""
1832 self._transforms = transforms
1833 try:
1834 if self.request.method not in self.SUPPORTED_METHODS:
1835 raise HTTPError(405)
1837 # If we're not in stream_request_body mode, this is the place where we parse the body.
1838 if not _has_stream_request_body(self.__class__):
1839 try:
1840 self.request._parse_body()
1841 except httputil.HTTPInputError as e:
1842 raise HTTPError(400, "Invalid body: %s" % e) from e
1844 self.path_args = [self.decode_argument(arg) for arg in args]
1845 self.path_kwargs = {
1846 k: self.decode_argument(v, name=k) for (k, v) in kwargs.items()
1847 }
1848 # If XSRF cookies are turned on, reject form submissions without
1849 # the proper cookie
1850 if self.request.method not in (
1851 "GET",
1852 "HEAD",
1853 "OPTIONS",
1854 ) and self.application.settings.get("xsrf_cookies"):
1855 self.check_xsrf_cookie()
1857 result = self.prepare()
1858 if result is not None:
1859 result = await result # type: ignore
1860 if self._prepared_future is not None:
1861 # Tell the Application we've finished with prepare()
1862 # and are ready for the body to arrive.
1863 future_set_result_unless_cancelled(self._prepared_future, None)
1864 if self._finished:
1865 return
1867 if _has_stream_request_body(self.__class__):
1868 # In streaming mode request.body is a Future that signals
1869 # the body has been completely received. The Future has no
1870 # result; the data has been passed to self.data_received
1871 # instead.
1872 try:
1873 await self.request._body_future
1874 except iostream.StreamClosedError:
1875 return
1877 method = getattr(self, self.request.method.lower())
1878 result = method(*self.path_args, **self.path_kwargs)
1879 if result is not None:
1880 result = await result
1881 if self._auto_finish and not self._finished:
1882 self.finish()
1883 except Exception as e:
1884 try:
1885 self._handle_request_exception(e)
1886 except Exception:
1887 app_log.error("Exception in exception handler", exc_info=True)
1888 finally:
1889 # Unset result to avoid circular references
1890 result = None
1891 if self._prepared_future is not None and not self._prepared_future.done():
1892 # In case we failed before setting _prepared_future, do it
1893 # now (to unblock the HTTP server). Note that this is not
1894 # in a finally block to avoid GC issues prior to Python 3.4.
1895 self._prepared_future.set_result(None)
1897 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
1898 """Implement this method to handle streamed request data.
1900 Requires the `.stream_request_body` decorator.
1902 May be a coroutine for flow control.
1903 """
1904 raise NotImplementedError()
1906 def _log(self) -> None:
1907 """Logs the current request.
1909 Sort of deprecated since this functionality was moved to the
1910 Application, but left in place for the benefit of existing apps
1911 that have overridden this method.
1912 """
1913 self.application.log_request(self)
1915 def _request_summary(self) -> str:
1916 return "{} {} ({})".format(
1917 self.request.method,
1918 self.request.uri,
1919 self.request.remote_ip,
1920 )
1922 def _handle_request_exception(self, e: BaseException) -> None:
1923 if isinstance(e, Finish):
1924 # Not an error; just finish the request without logging.
1925 if not self._finished:
1926 self.finish(*e.args)
1927 return
1928 try:
1929 self.log_exception(*sys.exc_info())
1930 except Exception:
1931 # An error here should still get a best-effort send_error()
1932 # to avoid leaking the connection.
1933 app_log.error("Error in exception logger", exc_info=True)
1934 if self._finished:
1935 # Extra errors after the request has been finished should
1936 # be logged, but there is no reason to continue to try and
1937 # send a response.
1938 return
1939 if isinstance(e, HTTPError):
1940 self.send_error(e.status_code, exc_info=sys.exc_info())
1941 else:
1942 self.send_error(500, exc_info=sys.exc_info())
1944 def log_exception(
1945 self,
1946 typ: "Optional[Type[BaseException]]",
1947 value: Optional[BaseException],
1948 tb: Optional[TracebackType],
1949 ) -> None:
1950 """Override to customize logging of uncaught exceptions.
1952 By default logs instances of `HTTPError` as warnings without
1953 stack traces (on the ``tornado.general`` logger), and all
1954 other exceptions as errors with stack traces (on the
1955 ``tornado.application`` logger).
1957 .. versionadded:: 3.1
1958 """
1959 if isinstance(value, HTTPError):
1960 log_message = value.get_message()
1961 if log_message:
1962 format = "%d %s: %s"
1963 args = [value.status_code, self._request_summary(), log_message]
1964 gen_log.warning(format, *args)
1965 else:
1966 app_log.error(
1967 "Uncaught exception %s\n%r",
1968 self._request_summary(),
1969 self.request,
1970 exc_info=(typ, value, tb), # type: ignore
1971 )
1973 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
1974 def render(*args, **kwargs) -> str: # type: ignore
1975 if not hasattr(self, "_active_modules"):
1976 self._active_modules = {} # type: Dict[str, UIModule]
1977 if name not in self._active_modules:
1978 self._active_modules[name] = module(self)
1979 rendered = self._active_modules[name].render(*args, **kwargs)
1980 return _unicode(rendered)
1982 return render
1984 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
1985 return lambda *args, **kwargs: method(self, *args, **kwargs)
1987 def _clear_representation_headers(self) -> None:
1988 # 304 responses should not contain representation metadata
1989 # headers (defined in
1990 # https://tools.ietf.org/html/rfc7231#section-3.1)
1991 # not explicitly allowed by
1992 # https://tools.ietf.org/html/rfc7232#section-4.1
1993 headers = ["Content-Encoding", "Content-Language", "Content-Type"]
1994 for h in headers:
1995 self.clear_header(h)
1998_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler)
2001def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]:
2002 """Apply to `RequestHandler` subclasses to enable streaming body support.
2004 This decorator implies the following changes:
2006 * `.HTTPServerRequest.body` is undefined, and body arguments will not
2007 be included in `RequestHandler.get_argument`.
2008 * `RequestHandler.prepare` is called when the request headers have been
2009 read instead of after the entire body has been read.
2010 * The subclass must define a method ``data_received(self, data):``, which
2011 will be called zero or more times as data is available. Note that
2012 if the request has an empty body, ``data_received`` may not be called.
2013 * ``prepare`` and ``data_received`` may return Futures (such as via
2014 ``@gen.coroutine``, in which case the next method will not be called
2015 until those futures have completed.
2016 * The regular HTTP method (``post``, ``put``, etc) will be called after
2017 the entire body has been read.
2019 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_
2020 for example usage.
2021 """ # noqa: E501
2022 if not issubclass(cls, RequestHandler):
2023 raise TypeError("expected subclass of RequestHandler, got %r", cls)
2024 cls._stream_request_body = True
2025 return cls
2028def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
2029 if not issubclass(cls, RequestHandler):
2030 raise TypeError("expected subclass of RequestHandler, got %r", cls)
2031 return cls._stream_request_body
2034def removeslash(
2035 method: Callable[..., Optional[Awaitable[None]]],
2036) -> Callable[..., Optional[Awaitable[None]]]:
2037 """Use this decorator to remove trailing slashes from the request path.
2039 For example, a request to ``/foo/`` would redirect to ``/foo`` with this
2040 decorator. Your request handler mapping should use a regular expression
2041 like ``r'/foo/*'`` in conjunction with using the decorator.
2042 """
2044 @functools.wraps(method)
2045 def wrapper( # type: ignore
2046 self: RequestHandler, *args, **kwargs
2047 ) -> Optional[Awaitable[None]]:
2048 if self.request.path.endswith("/"):
2049 if self.request.method in ("GET", "HEAD"):
2050 uri = self.request.path.rstrip("/")
2051 if uri: # don't try to redirect '/' to ''
2052 if self.request.query:
2053 uri += "?" + self.request.query
2054 self.redirect(uri, permanent=True)
2055 return None
2056 else:
2057 raise HTTPError(404)
2058 return method(self, *args, **kwargs)
2060 return wrapper
2063def addslash(
2064 method: Callable[..., Optional[Awaitable[None]]],
2065) -> Callable[..., Optional[Awaitable[None]]]:
2066 """Use this decorator to add a missing trailing slash to the request path.
2068 For example, a request to ``/foo`` would redirect to ``/foo/`` with this
2069 decorator. Your request handler mapping should use a regular expression
2070 like ``r'/foo/?'`` in conjunction with using the decorator.
2071 """
2073 @functools.wraps(method)
2074 def wrapper( # type: ignore
2075 self: RequestHandler, *args, **kwargs
2076 ) -> Optional[Awaitable[None]]:
2077 if not self.request.path.endswith("/"):
2078 if self.request.method in ("GET", "HEAD"):
2079 uri = self.request.path + "/"
2080 if self.request.query:
2081 uri += "?" + self.request.query
2082 self.redirect(uri, permanent=True)
2083 return None
2084 raise HTTPError(404)
2085 return method(self, *args, **kwargs)
2087 return wrapper
2090class _ApplicationRouter(ReversibleRuleRouter):
2091 """Routing implementation used internally by `Application`.
2093 Provides a binding between `Application` and `RequestHandler`.
2094 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
2095 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
2096 * it allows to use a list/tuple of rules as `~.routing.Rule` target.
2097 ``process_rule`` implementation will substitute this list with an appropriate
2098 `_ApplicationRouter` instance.
2099 """
2101 def __init__(
2102 self, application: "Application", rules: Optional[_RuleList] = None
2103 ) -> None:
2104 assert isinstance(application, Application)
2105 self.application = application
2106 super().__init__(rules)
2108 def process_rule(self, rule: Rule) -> Rule:
2109 rule = super().process_rule(rule)
2111 if isinstance(rule.target, (list, tuple)):
2112 rule.target = _ApplicationRouter(
2113 self.application, rule.target # type: ignore
2114 )
2116 return rule
2118 def get_target_delegate(
2119 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
2120 ) -> Optional[httputil.HTTPMessageDelegate]:
2121 if isclass(target) and issubclass(target, RequestHandler):
2122 return self.application.get_handler_delegate(
2123 request, target, **target_params
2124 )
2126 return super().get_target_delegate(target, request, **target_params)
2129class Application(ReversibleRouter):
2130 r"""A collection of request handlers that make up a web application.
2132 Instances of this class are callable and can be passed directly to
2133 HTTPServer to serve the application::
2135 application = web.Application([
2136 (r"/", MainPageHandler),
2137 ])
2138 http_server = httpserver.HTTPServer(application)
2139 http_server.listen(8080)
2141 The constructor for this class takes in a list of `~.routing.Rule`
2142 objects or tuples of values corresponding to the arguments of
2143 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
2144 the values in square brackets being optional. The default matcher is
2145 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
2146 instead of ``(PathMatches(regexp), target)``.
2148 A common routing target is a `RequestHandler` subclass, but you can also
2149 use lists of rules as a target, which create a nested routing configuration::
2151 application = web.Application([
2152 (HostMatches("example.com"), [
2153 (r"/", MainPageHandler),
2154 (r"/feed", FeedHandler),
2155 ]),
2156 ])
2158 In addition to this you can use nested `~.routing.Router` instances,
2159 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
2160 (see `~.routing` module docs for more information).
2162 When we receive requests, we iterate over the list in order and
2163 instantiate an instance of the first request class whose regexp
2164 matches the request path. The request class can be specified as
2165 either a class object or a (fully-qualified) name.
2167 A dictionary may be passed as the third element (``target_kwargs``)
2168 of the tuple, which will be used as keyword arguments to the handler's
2169 constructor and `~RequestHandler.initialize` method. This pattern
2170 is used for the `StaticFileHandler` in this example (note that a
2171 `StaticFileHandler` can be installed automatically with the
2172 static_path setting described below)::
2174 application = web.Application([
2175 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2176 ])
2178 We support virtual hosts with the `add_handlers` method, which takes in
2179 a host regular expression as the first argument::
2181 application.add_handlers(r"www\.myhost\.com", [
2182 (r"/article/([0-9]+)", ArticleHandler),
2183 ])
2185 If there's no match for the current request's host, then ``default_host``
2186 parameter value is matched against host regular expressions.
2189 .. warning::
2191 Applications that do not use TLS may be vulnerable to :ref:`DNS
2192 rebinding <dnsrebinding>` attacks. This attack is especially
2193 relevant to applications that only listen on ``127.0.0.1`` or
2194 other private networks. Appropriate host patterns must be used
2195 (instead of the default of ``r'.*'``) to prevent this risk. The
2196 ``default_host`` argument must not be used in applications that
2197 may be vulnerable to DNS rebinding.
2199 You can serve static files by sending the ``static_path`` setting
2200 as a keyword argument. We will serve those files from the
2201 ``/static/`` URI (this is configurable with the
2202 ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
2203 and ``/robots.txt`` from the same directory. A custom subclass of
2204 `StaticFileHandler` can be specified with the
2205 ``static_handler_class`` setting.
2207 .. versionchanged:: 4.5
2208 Integration with the new `tornado.routing` module.
2210 """
2212 def __init__(
2213 self,
2214 handlers: Optional[_RuleList] = None,
2215 default_host: Optional[str] = None,
2216 transforms: Optional[List[Type["OutputTransform"]]] = None,
2217 **settings: Any,
2218 ) -> None:
2219 if transforms is None:
2220 self.transforms = [] # type: List[Type[OutputTransform]]
2221 if settings.get("compress_response") or settings.get("gzip"):
2222 self.transforms.append(GZipContentEncoding)
2223 else:
2224 self.transforms = transforms
2225 self.default_host = default_host
2226 self.settings = settings
2227 self.ui_modules = {
2228 "linkify": _linkify,
2229 "xsrf_form_html": _xsrf_form_html,
2230 "Template": TemplateModule,
2231 }
2232 self.ui_methods = {} # type: Dict[str, Callable[..., str]]
2233 self._load_ui_modules(settings.get("ui_modules", {}))
2234 self._load_ui_methods(settings.get("ui_methods", {}))
2235 if self.settings.get("static_path"):
2236 path = self.settings["static_path"]
2237 handlers = list(handlers or [])
2238 static_url_prefix = settings.get("static_url_prefix", "/static/")
2239 static_handler_class = settings.get(
2240 "static_handler_class", StaticFileHandler
2241 )
2242 static_handler_args = settings.get("static_handler_args", {})
2243 static_handler_args["path"] = path
2244 for pattern in [
2245 re.escape(static_url_prefix) + r"(.*)",
2246 r"/(favicon\.ico)",
2247 r"/(robots\.txt)",
2248 ]:
2249 handlers.insert(0, (pattern, static_handler_class, static_handler_args))
2251 if self.settings.get("debug"):
2252 self.settings.setdefault("autoreload", True)
2253 self.settings.setdefault("compiled_template_cache", False)
2254 self.settings.setdefault("static_hash_cache", False)
2255 self.settings.setdefault("serve_traceback", True)
2257 self.wildcard_router = _ApplicationRouter(self, handlers)
2258 self.default_router = _ApplicationRouter(
2259 self, [Rule(AnyMatches(), self.wildcard_router)]
2260 )
2262 # Automatically reload modified modules
2263 if self.settings.get("autoreload"):
2264 from tornado import autoreload
2266 autoreload.start()
2268 def listen(
2269 self,
2270 port: int,
2271 address: Optional[str] = None,
2272 *,
2273 family: socket.AddressFamily = socket.AF_UNSPEC,
2274 backlog: int = tornado.netutil._DEFAULT_BACKLOG,
2275 flags: Optional[int] = None,
2276 reuse_port: bool = False,
2277 **kwargs: Any,
2278 ) -> HTTPServer:
2279 """Starts an HTTP server for this application on the given port.
2281 This is a convenience alias for creating an `.HTTPServer` object and
2282 calling its listen method. Keyword arguments not supported by
2283 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`
2284 constructor. For advanced uses (e.g. multi-process mode), do not use
2285 this method; create an `.HTTPServer` and call its
2286 `.TCPServer.bind`/`.TCPServer.start` methods directly.
2288 Note that after calling this method you still need to call
2289 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start
2290 the server.
2292 Returns the `.HTTPServer` object.
2294 .. versionchanged:: 4.3
2295 Now returns the `.HTTPServer` object.
2297 .. versionchanged:: 6.2
2298 Added support for new keyword arguments in `.TCPServer.listen`,
2299 including ``reuse_port``.
2300 """
2301 server = HTTPServer(self, **kwargs)
2302 server.listen(
2303 port,
2304 address=address,
2305 family=family,
2306 backlog=backlog,
2307 flags=flags,
2308 reuse_port=reuse_port,
2309 )
2310 return server
2312 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
2313 """Appends the given handlers to our handler list.
2315 Host patterns are processed sequentially in the order they were
2316 added. All matching patterns will be considered.
2317 """
2318 host_matcher = HostMatches(host_pattern)
2319 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
2321 self.default_router.rules.insert(-1, rule)
2323 if self.default_host is not None:
2324 self.wildcard_router.add_rules(
2325 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
2326 )
2328 def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
2329 self.transforms.append(transform_class)
2331 def _load_ui_methods(self, methods: Any) -> None:
2332 if isinstance(methods, types.ModuleType):
2333 self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)})
2334 elif isinstance(methods, list):
2335 for m in methods:
2336 self._load_ui_methods(m)
2337 else:
2338 for name, fn in methods.items():
2339 if (
2340 not name.startswith("_")
2341 and hasattr(fn, "__call__")
2342 and name[0].lower() == name[0]
2343 ):
2344 self.ui_methods[name] = fn
2346 def _load_ui_modules(self, modules: Any) -> None:
2347 if isinstance(modules, types.ModuleType):
2348 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)})
2349 elif isinstance(modules, list):
2350 for m in modules:
2351 self._load_ui_modules(m)
2352 else:
2353 assert isinstance(modules, dict)
2354 for name, cls in modules.items():
2355 try:
2356 if issubclass(cls, UIModule):
2357 self.ui_modules[name] = cls
2358 except TypeError:
2359 pass
2361 def __call__(
2362 self, request: httputil.HTTPServerRequest
2363 ) -> Optional[Awaitable[None]]:
2364 # Legacy HTTPServer interface
2365 dispatcher = self.find_handler(request)
2366 return dispatcher.execute()
2368 def find_handler(
2369 self, request: httputil.HTTPServerRequest, **kwargs: Any
2370 ) -> "_HandlerDelegate":
2371 route = self.default_router.find_handler(request)
2372 if route is not None:
2373 return cast("_HandlerDelegate", route)
2375 if self.settings.get("default_handler_class"):
2376 return self.get_handler_delegate(
2377 request,
2378 self.settings["default_handler_class"],
2379 self.settings.get("default_handler_args", {}),
2380 )
2382 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
2384 def get_handler_delegate(
2385 self,
2386 request: httputil.HTTPServerRequest,
2387 target_class: Type[RequestHandler],
2388 target_kwargs: Optional[Dict[str, Any]] = None,
2389 path_args: Optional[List[bytes]] = None,
2390 path_kwargs: Optional[Dict[str, bytes]] = None,
2391 ) -> "_HandlerDelegate":
2392 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
2393 for application and `RequestHandler` subclass.
2395 :arg httputil.HTTPServerRequest request: current HTTP request.
2396 :arg RequestHandler target_class: a `RequestHandler` class.
2397 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
2398 :arg list path_args: positional arguments for ``target_class`` HTTP method that
2399 will be executed while handling a request (``get``, ``post`` or any other).
2400 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
2401 """
2402 return _HandlerDelegate(
2403 self, request, target_class, target_kwargs, path_args, path_kwargs
2404 )
2406 def reverse_url(self, name: str, *args: Any) -> str:
2407 """Returns a URL path for handler named ``name``
2409 The handler must be added to the application as a named `URLSpec`.
2411 Args will be substituted for capturing groups in the `URLSpec` regex.
2412 They will be converted to strings if necessary, encoded as utf8,
2413 and url-escaped.
2414 """
2415 reversed_url = self.default_router.reverse_url(name, *args)
2416 if reversed_url is not None:
2417 return reversed_url
2419 raise KeyError("%s not found in named urls" % name)
2421 def log_request(self, handler: RequestHandler) -> None:
2422 """Writes a completed HTTP request to the logs.
2424 By default writes to the python root logger. To change
2425 this behavior either subclass Application and override this method,
2426 or pass a function in the application settings dictionary as
2427 ``log_function``.
2428 """
2429 if "log_function" in self.settings:
2430 self.settings["log_function"](handler)
2431 return
2432 if handler.get_status() < 400:
2433 log_method = access_log.info
2434 elif handler.get_status() < 500:
2435 log_method = access_log.warning
2436 else:
2437 log_method = access_log.error
2438 request_time = 1000.0 * handler.request.request_time()
2439 log_method(
2440 "%d %s %.2fms",
2441 handler.get_status(),
2442 handler._request_summary(),
2443 request_time,
2444 )
2447class _HandlerDelegate(httputil.HTTPMessageDelegate):
2448 def __init__(
2449 self,
2450 application: Application,
2451 request: httputil.HTTPServerRequest,
2452 handler_class: Type[RequestHandler],
2453 handler_kwargs: Optional[Dict[str, Any]],
2454 path_args: Optional[List[bytes]],
2455 path_kwargs: Optional[Dict[str, bytes]],
2456 ) -> None:
2457 self.application = application
2458 self.connection = request.connection
2459 self.request = request
2460 self.handler_class = handler_class
2461 self.handler_kwargs = handler_kwargs or {}
2462 self.path_args = path_args or []
2463 self.path_kwargs = path_kwargs or {}
2464 self.chunks = [] # type: List[bytes]
2465 self.stream_request_body = _has_stream_request_body(self.handler_class)
2467 def headers_received(
2468 self,
2469 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
2470 headers: httputil.HTTPHeaders,
2471 ) -> Optional[Awaitable[None]]:
2472 if self.stream_request_body:
2473 self.request._body_future = Future()
2474 return self.execute()
2475 return None
2477 def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
2478 if self.stream_request_body:
2479 return self.handler.data_received(data)
2480 else:
2481 self.chunks.append(data)
2482 return None
2484 def finish(self) -> None:
2485 if self.stream_request_body:
2486 future_set_result_unless_cancelled(self.request._body_future, None)
2487 else:
2488 # Note that the body gets parsed in RequestHandler._execute so it can be in
2489 # the right exception handler scope.
2490 self.request.body = b"".join(self.chunks)
2491 self.execute()
2493 def on_connection_close(self) -> None:
2494 if self.stream_request_body:
2495 self.handler.on_connection_close()
2496 else:
2497 self.chunks = None # type: ignore
2499 def execute(self) -> Optional[Awaitable[None]]:
2500 # If template cache is disabled (usually in the debug mode),
2501 # re-compile templates and reload static files on every
2502 # request so you don't need to restart to see changes
2503 if not self.application.settings.get("compiled_template_cache", True):
2504 with RequestHandler._template_loader_lock:
2505 for loader in RequestHandler._template_loaders.values():
2506 loader.reset()
2507 if not self.application.settings.get("static_hash_cache", True):
2508 static_handler_class = self.application.settings.get(
2509 "static_handler_class", StaticFileHandler
2510 )
2511 static_handler_class.reset()
2513 self.handler = self.handler_class(
2514 self.application, self.request, **self.handler_kwargs
2515 )
2516 transforms = [t(self.request) for t in self.application.transforms]
2518 if self.stream_request_body:
2519 self.handler._prepared_future = Future()
2520 # Note that if an exception escapes handler._execute it will be
2521 # trapped in the Future it returns (which we are ignoring here,
2522 # leaving it to be logged when the Future is GC'd).
2523 # However, that shouldn't happen because _execute has a blanket
2524 # except handler, and we cannot easily access the IOLoop here to
2525 # call add_future (because of the requirement to remain compatible
2526 # with WSGI)
2527 fut = gen.convert_yielded(
2528 self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
2529 )
2530 fut.add_done_callback(lambda f: f.result())
2531 # If we are streaming the request body, then execute() is finished
2532 # when the handler has prepared to receive the body. If not,
2533 # it doesn't matter when execute() finishes (so we return None)
2534 return self.handler._prepared_future
2537class HTTPError(Exception):
2538 """An exception that will turn into an HTTP error response.
2540 Raising an `HTTPError` is a convenient alternative to calling
2541 `RequestHandler.send_error` since it automatically ends the
2542 current function.
2544 To customize the response sent with an `HTTPError`, override
2545 `RequestHandler.write_error`.
2547 :arg int status_code: HTTP status code. Must be listed in
2548 `httplib.responses <http.client.responses>` unless the ``reason``
2549 keyword argument is given.
2550 :arg str log_message: Message to be written to the log for this error
2551 (will not be shown to the user unless the `Application` is in debug
2552 mode). May contain ``%s``-style placeholders, which will be filled
2553 in with remaining positional parameters.
2554 :arg str reason: Keyword-only argument. The HTTP "reason" phrase
2555 to pass in the status line along with ``status_code`` (for example,
2556 the "Not Found" in ``HTTP/1.1 404 Not Found``). Normally
2557 determined automatically from ``status_code``, but can be used
2558 to use a non-standard numeric code. This is not a general-purpose
2559 error message.
2560 """
2562 def __init__(
2563 self,
2564 status_code: int = 500,
2565 log_message: Optional[str] = None,
2566 *args: Any,
2567 **kwargs: Any,
2568 ) -> None:
2569 self.status_code = status_code
2570 self._log_message = log_message
2571 self.args = args
2572 self.reason = kwargs.get("reason", None)
2574 @property
2575 def log_message(self) -> Optional[str]:
2576 """
2577 A backwards compatible way of accessing log_message.
2578 """
2579 if self._log_message and not self.args:
2580 return self._log_message.replace("%", "%%")
2581 return self._log_message
2583 def get_message(self) -> Optional[str]:
2584 if self._log_message and self.args:
2585 return self._log_message % self.args
2586 return self._log_message
2588 def __str__(self) -> str:
2589 message = "HTTP %d: %s" % (
2590 self.status_code,
2591 self.reason or httputil.responses.get(self.status_code, "Unknown"),
2592 )
2593 log_message = self.get_message()
2594 if log_message:
2595 return message + " (" + log_message + ")"
2596 else:
2597 return message
2600class Finish(Exception):
2601 """An exception that ends the request without producing an error response.
2603 When `Finish` is raised in a `RequestHandler`, the request will
2604 end (calling `RequestHandler.finish` if it hasn't already been
2605 called), but the error-handling methods (including
2606 `RequestHandler.write_error`) will not be called.
2608 If `Finish()` was created with no arguments, the pending response
2609 will be sent as-is. If `Finish()` was given an argument, that
2610 argument will be passed to `RequestHandler.finish()`.
2612 This can be a more convenient way to implement custom error pages
2613 than overriding ``write_error`` (especially in library code)::
2615 if self.current_user is None:
2616 self.set_status(401)
2617 self.set_header('WWW-Authenticate', 'Basic realm="something"')
2618 raise Finish()
2620 .. versionchanged:: 4.3
2621 Arguments passed to ``Finish()`` will be passed on to
2622 `RequestHandler.finish`.
2623 """
2625 pass
2628class MissingArgumentError(HTTPError):
2629 """Exception raised by `RequestHandler.get_argument`.
2631 This is a subclass of `HTTPError`, so if it is uncaught a 400 response
2632 code will be used instead of 500 (and a stack trace will not be logged).
2634 .. versionadded:: 3.1
2635 """
2637 def __init__(self, arg_name: str) -> None:
2638 super().__init__(400, "Missing argument %s" % arg_name)
2639 self.arg_name = arg_name
2642class ErrorHandler(RequestHandler):
2643 """Generates an error response with ``status_code`` for all requests."""
2645 def initialize(self, status_code: int) -> None:
2646 self.set_status(status_code)
2648 def prepare(self) -> None:
2649 raise HTTPError(self._status_code)
2651 def check_xsrf_cookie(self) -> None:
2652 # POSTs to an ErrorHandler don't actually have side effects,
2653 # so we don't need to check the xsrf token. This allows POSTs
2654 # to the wrong url to return a 404 instead of 403.
2655 pass
2658class RedirectHandler(RequestHandler):
2659 """Redirects the client to the given URL for all GET requests.
2661 You should provide the keyword argument ``url`` to the handler, e.g.::
2663 application = web.Application([
2664 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
2665 ])
2667 `RedirectHandler` supports regular expression substitutions. E.g., to
2668 swap the first and second parts of a path while preserving the remainder::
2670 application = web.Application([
2671 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
2672 ])
2674 The final URL is formatted with `str.format` and the substrings that match
2675 the capturing groups. In the above example, a request to "/a/b/c" would be
2676 formatted like::
2678 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c"
2680 Use Python's :ref:`format string syntax <formatstrings>` to customize how
2681 values are substituted.
2683 .. versionchanged:: 4.5
2684 Added support for substitutions into the destination URL.
2686 .. versionchanged:: 5.0
2687 If any query arguments are present, they will be copied to the
2688 destination URL.
2689 """
2691 def initialize(self, url: str, permanent: bool = True) -> None:
2692 self._url = url
2693 self._permanent = permanent
2695 def get(self, *args: Any, **kwargs: Any) -> None:
2696 to_url = self._url.format(*args, **kwargs)
2697 if self.request.query_arguments:
2698 # TODO: figure out typing for the next line.
2699 to_url = httputil.url_concat(
2700 to_url,
2701 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore
2702 )
2703 self.redirect(to_url, permanent=self._permanent)
2706class StaticFileHandler(RequestHandler):
2707 """A simple handler that can serve static content from a directory.
2709 A `StaticFileHandler` is configured automatically if you pass the
2710 ``static_path`` keyword argument to `Application`. This handler
2711 can be customized with the ``static_url_prefix``, ``static_handler_class``,
2712 and ``static_handler_args`` settings.
2714 To map an additional path to this handler for a static data directory
2715 you would add a line to your application like::
2717 application = web.Application([
2718 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2719 ])
2721 The handler constructor requires a ``path`` argument, which specifies the
2722 local root directory of the content to be served.
2724 Note that a capture group in the regex is required to parse the value for
2725 the ``path`` argument to the get() method (different than the constructor
2726 argument above); see `URLSpec` for details.
2728 To serve a file like ``index.html`` automatically when a directory is
2729 requested, set ``static_handler_args=dict(default_filename="index.html")``
2730 in your application settings, or add ``default_filename`` as an initializer
2731 argument for your ``StaticFileHandler``.
2733 To maximize the effectiveness of browser caching, this class supports
2734 versioned urls (by default using the argument ``?v=``). If a version
2735 is given, we instruct the browser to cache this file indefinitely.
2736 `make_static_url` (also available as `RequestHandler.static_url`) can
2737 be used to construct a versioned url.
2739 This handler is intended primarily for use in development and light-duty
2740 file serving; for heavy traffic it will be more efficient to use
2741 a dedicated static file server (such as nginx or Apache). We support
2742 the HTTP ``Accept-Ranges`` mechanism to return partial content (because
2743 some browsers require this functionality to be present to seek in
2744 HTML5 audio or video).
2746 **Subclassing notes**
2748 This class is designed to be extensible by subclassing, but because
2749 of the way static urls are generated with class methods rather than
2750 instance methods, the inheritance patterns are somewhat unusual.
2751 Be sure to use the ``@classmethod`` decorator when overriding a
2752 class method. Instance methods may use the attributes ``self.path``
2753 ``self.absolute_path``, and ``self.modified``.
2755 Subclasses should only override methods discussed in this section;
2756 overriding other methods is error-prone. Overriding
2757 ``StaticFileHandler.get`` is particularly problematic due to the
2758 tight coupling with ``compute_etag`` and other methods.
2760 To change the way static urls are generated (e.g. to match the behavior
2761 of another server or CDN), override `make_static_url`, `parse_url_path`,
2762 `get_cache_time`, and/or `get_version`.
2764 To replace all interaction with the filesystem (e.g. to serve
2765 static content from a database), override `get_content`,
2766 `get_content_size`, `get_modified_time`, `get_absolute_path`, and
2767 `validate_absolute_path`.
2769 .. versionchanged:: 3.1
2770 Many of the methods for subclasses were added in Tornado 3.1.
2771 """
2773 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
2775 _static_hashes = {} # type: Dict[str, Optional[str]]
2776 _lock = threading.Lock() # protects _static_hashes
2778 def initialize(self, path: str, default_filename: Optional[str] = None) -> None:
2779 self.root = path
2780 self.default_filename = default_filename
2782 @classmethod
2783 def reset(cls) -> None:
2784 with cls._lock:
2785 cls._static_hashes = {}
2787 def head(self, path: str) -> Awaitable[None]:
2788 return self.get(path, include_body=False)
2790 async def get(self, path: str, include_body: bool = True) -> None:
2791 # Set up our path instance variables.
2792 self.path = self.parse_url_path(path)
2793 del path # make sure we don't refer to path instead of self.path again
2794 absolute_path = self.get_absolute_path(self.root, self.path)
2795 self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
2796 if self.absolute_path is None:
2797 return
2799 self.modified = self.get_modified_time()
2800 self.set_headers()
2802 if self.should_return_304():
2803 self.set_status(304)
2804 return
2806 request_range = None
2807 range_header = self.request.headers.get("Range")
2808 if range_header:
2809 # As per RFC 2616 14.16, if an invalid Range header is specified,
2810 # the request will be treated as if the header didn't exist.
2811 request_range = httputil._parse_request_range(range_header)
2813 size = self.get_content_size()
2814 if request_range:
2815 start, end = request_range
2816 if start is not None and start < 0:
2817 start += size
2818 if start < 0:
2819 start = 0
2820 if (
2821 start is not None
2822 and (start >= size or (end is not None and start >= end))
2823 ) or end == 0:
2824 # As per RFC 2616 14.35.1, a range is not satisfiable only: if
2825 # the first requested byte is equal to or greater than the
2826 # content, or when a suffix with length 0 is specified.
2827 # https://tools.ietf.org/html/rfc7233#section-2.1
2828 # A byte-range-spec is invalid if the last-byte-pos value is present
2829 # and less than the first-byte-pos.
2830 self.set_status(416) # Range Not Satisfiable
2831 self.set_header("Content-Type", "text/plain")
2832 self.set_header("Content-Range", f"bytes */{size}")
2833 return
2834 if end is not None and end > size:
2835 # Clients sometimes blindly use a large range to limit their
2836 # download size; cap the endpoint at the actual file size.
2837 end = size
2838 # Note: only return HTTP 206 if less than the entire range has been
2839 # requested. Not only is this semantically correct, but Chrome
2840 # refuses to play audio if it gets an HTTP 206 in response to
2841 # ``Range: bytes=0-``.
2842 if size != (end or size) - (start or 0):
2843 self.set_status(206) # Partial Content
2844 self.set_header(
2845 "Content-Range", httputil._get_content_range(start, end, size)
2846 )
2847 else:
2848 start = end = None
2850 if start is not None and end is not None:
2851 content_length = end - start
2852 elif end is not None:
2853 content_length = end
2854 elif start is not None:
2855 content_length = size - start
2856 else:
2857 content_length = size
2858 self.set_header("Content-Length", content_length)
2860 if include_body:
2861 content = self.get_content(self.absolute_path, start, end)
2862 if isinstance(content, bytes):
2863 content = [content]
2864 for chunk in content:
2865 try:
2866 self.write(chunk)
2867 await self.flush()
2868 except iostream.StreamClosedError:
2869 return
2870 else:
2871 assert self.request.method == "HEAD"
2873 def compute_etag(self) -> Optional[str]:
2874 """Sets the ``Etag`` header based on static url version.
2876 This allows efficient ``If-None-Match`` checks against cached
2877 versions, and sends the correct ``Etag`` for a partial response
2878 (i.e. the same ``Etag`` as the full file).
2880 .. versionadded:: 3.1
2881 """
2882 assert self.absolute_path is not None
2883 version_hash = self._get_cached_version(self.absolute_path)
2884 if not version_hash:
2885 return None
2886 return f'"{version_hash}"'
2888 def set_headers(self) -> None:
2889 """Sets the content and caching headers on the response.
2891 .. versionadded:: 3.1
2892 """
2893 self.set_header("Accept-Ranges", "bytes")
2894 self.set_etag_header()
2896 if self.modified is not None:
2897 self.set_header("Last-Modified", self.modified)
2899 content_type = self.get_content_type()
2900 if content_type:
2901 self.set_header("Content-Type", content_type)
2903 cache_time = self.get_cache_time(self.path, self.modified, content_type)
2904 if cache_time > 0:
2905 self.set_header(
2906 "Expires",
2907 datetime.datetime.now(datetime.timezone.utc)
2908 + datetime.timedelta(seconds=cache_time),
2909 )
2910 self.set_header("Cache-Control", "max-age=" + str(cache_time))
2912 self.set_extra_headers(self.path)
2914 def should_return_304(self) -> bool:
2915 """Returns True if the headers indicate that we should return 304.
2917 .. versionadded:: 3.1
2918 """
2919 # If client sent If-None-Match, use it, ignore If-Modified-Since
2920 if self.request.headers.get("If-None-Match"):
2921 return self.check_etag_header()
2923 # Check the If-Modified-Since, and don't send the result if the
2924 # content has not been modified
2925 ims_value = self.request.headers.get("If-Modified-Since")
2926 if ims_value is not None:
2927 try:
2928 if_since = email.utils.parsedate_to_datetime(ims_value)
2929 except Exception:
2930 return False
2931 if if_since.tzinfo is None:
2932 if_since = if_since.replace(tzinfo=datetime.timezone.utc)
2933 assert self.modified is not None
2934 if if_since >= self.modified:
2935 return True
2937 return False
2939 @classmethod
2940 def get_absolute_path(cls, root: str, path: str) -> str:
2941 """Returns the absolute location of ``path`` relative to ``root``.
2943 ``root`` is the path configured for this `StaticFileHandler`
2944 (in most cases the ``static_path`` `Application` setting).
2946 This class method may be overridden in subclasses. By default
2947 it returns a filesystem path, but other strings may be used
2948 as long as they are unique and understood by the subclass's
2949 overridden `get_content`.
2951 .. versionadded:: 3.1
2952 """
2953 abspath = os.path.abspath(os.path.join(root, path))
2954 return abspath
2956 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
2957 """Validate and return the absolute path.
2959 ``root`` is the configured path for the `StaticFileHandler`,
2960 and ``path`` is the result of `get_absolute_path`
2962 This is an instance method called during request processing,
2963 so it may raise `HTTPError` or use methods like
2964 `RequestHandler.redirect` (return None after redirecting to
2965 halt further processing). This is where 404 errors for missing files
2966 are generated.
2968 This method may modify the path before returning it, but note that
2969 any such modifications will not be understood by `make_static_url`.
2971 In instance methods, this method's result is available as
2972 ``self.absolute_path``.
2974 .. versionadded:: 3.1
2975 """
2976 # os.path.abspath strips a trailing /.
2977 # We must add it back to `root` so that we only match files
2978 # in a directory named `root` instead of files starting with
2979 # that prefix.
2980 root = os.path.abspath(root)
2981 if not root.endswith(os.path.sep):
2982 # abspath always removes a trailing slash, except when
2983 # root is '/'. This is an unusual case, but several projects
2984 # have independently discovered this technique to disable
2985 # Tornado's path validation and (hopefully) do their own,
2986 # so we need to support it.
2987 root += os.path.sep
2988 # The trailing slash also needs to be temporarily added back
2989 # the requested path so a request to root/ will match.
2990 if not (absolute_path + os.path.sep).startswith(root):
2991 raise HTTPError(403, "%s is not in root static directory", self.path)
2992 if os.path.isdir(absolute_path) and self.default_filename is not None:
2993 # need to look at the request.path here for when path is empty
2994 # but there is some prefix to the path that was already
2995 # trimmed by the routing
2996 if not self.request.path.endswith("/"):
2997 if self.request.path.startswith("//"):
2998 # A redirect with two initial slashes is a "protocol-relative" URL.
2999 # This means the next path segment is treated as a hostname instead
3000 # of a part of the path, making this effectively an open redirect.
3001 # Reject paths starting with two slashes to prevent this.
3002 # This is only reachable under certain configurations.
3003 raise HTTPError(
3004 403, "cannot redirect path with two initial slashes"
3005 )
3006 self.redirect(self.request.path + "/", permanent=True)
3007 return None
3008 absolute_path = os.path.join(absolute_path, self.default_filename)
3009 if not os.path.exists(absolute_path):
3010 raise HTTPError(404)
3011 if not os.path.isfile(absolute_path):
3012 raise HTTPError(403, "%s is not a file", self.path)
3013 return absolute_path
3015 @classmethod
3016 def get_content(
3017 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None
3018 ) -> Generator[bytes, None, None]:
3019 """Retrieve the content of the requested resource which is located
3020 at the given absolute path.
3022 This class method may be overridden by subclasses. Note that its
3023 signature is different from other overridable class methods
3024 (no ``settings`` argument); this is deliberate to ensure that
3025 ``abspath`` is able to stand on its own as a cache key.
3027 This method should either return a byte string or an iterator
3028 of byte strings. The latter is preferred for large files
3029 as it helps reduce memory fragmentation.
3031 .. versionadded:: 3.1
3032 """
3033 with open(abspath, "rb") as file:
3034 if start is not None:
3035 file.seek(start)
3036 if end is not None:
3037 remaining = end - (start or 0) # type: Optional[int]
3038 else:
3039 remaining = None
3040 while True:
3041 chunk_size = 64 * 1024
3042 if remaining is not None and remaining < chunk_size:
3043 chunk_size = remaining
3044 chunk = file.read(chunk_size)
3045 if chunk:
3046 if remaining is not None:
3047 remaining -= len(chunk)
3048 yield chunk
3049 else:
3050 if remaining is not None:
3051 assert remaining == 0
3052 return
3054 @classmethod
3055 def get_content_version(cls, abspath: str) -> str:
3056 """Returns a version string for the resource at the given path.
3058 This class method may be overridden by subclasses. The
3059 default implementation is a SHA-512 hash of the file's contents.
3061 .. versionadded:: 3.1
3062 """
3063 data = cls.get_content(abspath)
3064 hasher = hashlib.sha512()
3065 if isinstance(data, bytes):
3066 hasher.update(data)
3067 else:
3068 for chunk in data:
3069 hasher.update(chunk)
3070 return hasher.hexdigest()
3072 def _stat(self) -> os.stat_result:
3073 assert self.absolute_path is not None
3074 if not hasattr(self, "_stat_result"):
3075 self._stat_result = os.stat(self.absolute_path)
3076 return self._stat_result
3078 def get_content_size(self) -> int:
3079 """Retrieve the total size of the resource at the given path.
3081 This method may be overridden by subclasses.
3083 .. versionadded:: 3.1
3085 .. versionchanged:: 4.0
3086 This method is now always called, instead of only when
3087 partial results are requested.
3088 """
3089 stat_result = self._stat()
3090 return stat_result.st_size
3092 def get_modified_time(self) -> Optional[datetime.datetime]:
3093 """Returns the time that ``self.absolute_path`` was last modified.
3095 May be overridden in subclasses. Should return a `~datetime.datetime`
3096 object or None.
3098 .. versionadded:: 3.1
3100 .. versionchanged:: 6.4
3101 Now returns an aware datetime object instead of a naive one.
3102 Subclasses that override this method may return either kind.
3103 """
3104 stat_result = self._stat()
3105 # NOTE: Historically, this used stat_result[stat.ST_MTIME],
3106 # which truncates the fractional portion of the timestamp. It
3107 # was changed from that form to stat_result.st_mtime to
3108 # satisfy mypy (which disallows the bracket operator), but the
3109 # latter form returns a float instead of an int. For
3110 # consistency with the past (and because we have a unit test
3111 # that relies on this), we truncate the float here, although
3112 # I'm not sure that's the right thing to do.
3113 modified = datetime.datetime.fromtimestamp(
3114 int(stat_result.st_mtime), datetime.timezone.utc
3115 )
3116 return modified
3118 def get_content_type(self) -> str:
3119 """Returns the ``Content-Type`` header to be used for this request.
3121 .. versionadded:: 3.1
3122 """
3123 assert self.absolute_path is not None
3124 mime_type, encoding = mimetypes.guess_type(self.absolute_path)
3125 # per RFC 6713, use the appropriate type for a gzip compressed file
3126 if encoding == "gzip":
3127 return "application/gzip"
3128 # As of 2015-07-21 there is no bzip2 encoding defined at
3129 # http://www.iana.org/assignments/media-types/media-types.xhtml
3130 # So for that (and any other encoding), use octet-stream.
3131 elif encoding is not None:
3132 return "application/octet-stream"
3133 elif mime_type is not None:
3134 return mime_type
3135 # if mime_type not detected, use application/octet-stream
3136 else:
3137 return "application/octet-stream"
3139 def set_extra_headers(self, path: str) -> None:
3140 """For subclass to add extra headers to the response"""
3141 pass
3143 def get_cache_time(
3144 self, path: str, modified: Optional[datetime.datetime], mime_type: str
3145 ) -> int:
3146 """Override to customize cache control behavior.
3148 Return a positive number of seconds to make the result
3149 cacheable for that amount of time or 0 to mark resource as
3150 cacheable for an unspecified amount of time (subject to
3151 browser heuristics).
3153 By default returns cache expiry of 10 years for resources requested
3154 with ``v`` argument.
3155 """
3156 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
3158 @classmethod
3159 def make_static_url(
3160 cls, settings: Dict[str, Any], path: str, include_version: bool = True
3161 ) -> str:
3162 """Constructs a versioned url for the given path.
3164 This method may be overridden in subclasses (but note that it
3165 is a class method rather than an instance method). Subclasses
3166 are only required to implement the signature
3167 ``make_static_url(cls, settings, path)``; other keyword
3168 arguments may be passed through `~RequestHandler.static_url`
3169 but are not standard.
3171 ``settings`` is the `Application.settings` dictionary. ``path``
3172 is the static path being requested. The url returned should be
3173 relative to the current host.
3175 ``include_version`` determines whether the generated URL should
3176 include the query string containing the version hash of the
3177 file corresponding to the given ``path``.
3179 """
3180 url = settings.get("static_url_prefix", "/static/") + path
3181 if not include_version:
3182 return url
3184 version_hash = cls.get_version(settings, path)
3185 if not version_hash:
3186 return url
3188 return f"{url}?v={version_hash}"
3190 def parse_url_path(self, url_path: str) -> str:
3191 """Converts a static URL path into a filesystem path.
3193 ``url_path`` is the path component of the URL with
3194 ``static_url_prefix`` removed. The return value should be
3195 filesystem path relative to ``static_path``.
3197 This is the inverse of `make_static_url`.
3198 """
3199 if os.path.sep != "/":
3200 url_path = url_path.replace("/", os.path.sep)
3201 return url_path
3203 @classmethod
3204 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
3205 """Generate the version string to be used in static URLs.
3207 ``settings`` is the `Application.settings` dictionary and ``path``
3208 is the relative location of the requested asset on the filesystem.
3209 The returned value should be a string, or ``None`` if no version
3210 could be determined.
3212 .. versionchanged:: 3.1
3213 This method was previously recommended for subclasses to override;
3214 `get_content_version` is now preferred as it allows the base
3215 class to handle caching of the result.
3216 """
3217 abs_path = cls.get_absolute_path(settings["static_path"], path)
3218 return cls._get_cached_version(abs_path)
3220 @classmethod
3221 def _get_cached_version(cls, abs_path: str) -> Optional[str]:
3222 with cls._lock:
3223 hashes = cls._static_hashes
3224 if abs_path not in hashes:
3225 try:
3226 hashes[abs_path] = cls.get_content_version(abs_path)
3227 except Exception:
3228 gen_log.error("Could not open static file %r", abs_path)
3229 hashes[abs_path] = None
3230 hsh = hashes.get(abs_path)
3231 if hsh:
3232 return hsh
3233 return None
3236class FallbackHandler(RequestHandler):
3237 """A `RequestHandler` that wraps another HTTP server callback.
3239 The fallback is a callable object that accepts an
3240 `~.httputil.HTTPServerRequest`, such as an `Application` or
3241 `tornado.wsgi.WSGIContainer`. This is most useful to use both
3242 Tornado ``RequestHandlers`` and WSGI in the same server. Typical
3243 usage::
3245 wsgi_app = tornado.wsgi.WSGIContainer(
3246 django.core.handlers.wsgi.WSGIHandler())
3247 application = tornado.web.Application([
3248 (r"/foo", FooHandler),
3249 (r".*", FallbackHandler, dict(fallback=wsgi_app)),
3250 ])
3251 """
3253 def initialize(
3254 self, fallback: Callable[[httputil.HTTPServerRequest], None]
3255 ) -> None:
3256 self.fallback = fallback
3258 def prepare(self) -> None:
3259 self.fallback(self.request)
3260 self._finished = True
3261 self.on_finish()
3264class OutputTransform:
3265 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
3267 Applications are not expected to create their own OutputTransforms
3268 or interact with them directly; the framework chooses which transforms
3269 (if any) to apply.
3270 """
3272 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3273 pass
3275 def transform_first_chunk(
3276 self,
3277 status_code: int,
3278 headers: httputil.HTTPHeaders,
3279 chunk: bytes,
3280 finishing: bool,
3281 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3282 return status_code, headers, chunk
3284 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3285 return chunk
3288class GZipContentEncoding(OutputTransform):
3289 """Applies the gzip content encoding to the response.
3291 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
3293 .. versionchanged:: 4.0
3294 Now compresses all mime types beginning with ``text/``, instead
3295 of just a whitelist. (the whitelist is still used for certain
3296 non-text mime types).
3297 """
3299 # Whitelist of compressible mime types (in addition to any types
3300 # beginning with "text/").
3301 CONTENT_TYPES = {
3302 "application/javascript",
3303 "application/x-javascript",
3304 "application/xml",
3305 "application/atom+xml",
3306 "application/json",
3307 "application/xhtml+xml",
3308 "image/svg+xml",
3309 }
3310 # Python's GzipFile defaults to level 9, while most other gzip
3311 # tools (including gzip itself) default to 6, which is probably a
3312 # better CPU/size tradeoff.
3313 GZIP_LEVEL = 6
3314 # Responses that are too short are unlikely to benefit from gzipping
3315 # after considering the "Content-Encoding: gzip" header and the header
3316 # inside the gzip encoding.
3317 # Note that responses written in multiple chunks will be compressed
3318 # regardless of size.
3319 MIN_LENGTH = 1024
3321 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3322 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
3324 def _compressible_type(self, ctype: str) -> bool:
3325 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
3327 def transform_first_chunk(
3328 self,
3329 status_code: int,
3330 headers: httputil.HTTPHeaders,
3331 chunk: bytes,
3332 finishing: bool,
3333 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3334 # TODO: can/should this type be inherited from the superclass?
3335 if "Vary" in headers:
3336 headers["Vary"] += ", Accept-Encoding"
3337 else:
3338 headers["Vary"] = "Accept-Encoding"
3339 if self._gzipping:
3340 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
3341 self._gzipping = (
3342 self._compressible_type(ctype)
3343 and (not finishing or len(chunk) >= self.MIN_LENGTH)
3344 and ("Content-Encoding" not in headers)
3345 )
3346 if self._gzipping:
3347 headers["Content-Encoding"] = "gzip"
3348 self._gzip_value = BytesIO()
3349 self._gzip_file = gzip.GzipFile(
3350 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
3351 )
3352 chunk = self.transform_chunk(chunk, finishing)
3353 if "Content-Length" in headers:
3354 # The original content length is no longer correct.
3355 # If this is the last (and only) chunk, we can set the new
3356 # content-length; otherwise we remove it and fall back to
3357 # chunked encoding.
3358 if finishing:
3359 headers["Content-Length"] = str(len(chunk))
3360 else:
3361 del headers["Content-Length"]
3362 return status_code, headers, chunk
3364 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3365 if self._gzipping:
3366 self._gzip_file.write(chunk)
3367 if finishing:
3368 self._gzip_file.close()
3369 else:
3370 self._gzip_file.flush()
3371 chunk = self._gzip_value.getvalue()
3372 self._gzip_value.truncate(0)
3373 self._gzip_value.seek(0)
3374 return chunk
3377def authenticated(
3378 method: Callable[..., Optional[Awaitable[None]]],
3379) -> Callable[..., Optional[Awaitable[None]]]:
3380 """Decorate methods with this to require that the user be logged in.
3382 If the user is not logged in, they will be redirected to the configured
3383 `login url <RequestHandler.get_login_url>`.
3385 If you configure a login url with a query parameter, Tornado will
3386 assume you know what you're doing and use it as-is. If not, it
3387 will add a `next` parameter so the login page knows where to send
3388 you once you're logged in.
3389 """
3391 @functools.wraps(method)
3392 def wrapper( # type: ignore
3393 self: RequestHandler, *args, **kwargs
3394 ) -> Optional[Awaitable[None]]:
3395 if not self.current_user:
3396 if self.request.method in ("GET", "HEAD"):
3397 url = self.get_login_url()
3398 if "?" not in url:
3399 if urllib.parse.urlsplit(url).scheme:
3400 # if login url is absolute, make next absolute too
3401 next_url = self.request.full_url()
3402 else:
3403 assert self.request.uri is not None
3404 next_url = self.request.uri
3405 url += "?" + urlencode(dict(next=next_url))
3406 self.redirect(url)
3407 return None
3408 raise HTTPError(403)
3409 return method(self, *args, **kwargs)
3411 return wrapper
3414class UIModule:
3415 """A re-usable, modular UI unit on a page.
3417 UI modules often execute additional queries, and they can include
3418 additional CSS and JavaScript that will be included in the output
3419 page, which is automatically inserted on page render.
3421 Subclasses of UIModule must override the `render` method.
3422 """
3424 def __init__(self, handler: RequestHandler) -> None:
3425 self.handler = handler
3426 self.request = handler.request
3427 self.ui = handler.ui
3428 self.locale = handler.locale
3430 @property
3431 def current_user(self) -> Any:
3432 return self.handler.current_user
3434 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]:
3435 """Override in subclasses to return this module's output."""
3436 raise NotImplementedError()
3438 def embedded_javascript(self) -> Optional[str]:
3439 """Override to return a JavaScript string
3440 to be embedded in the page."""
3441 return None
3443 def javascript_files(self) -> Optional[Iterable[str]]:
3444 """Override to return a list of JavaScript files needed by this module.
3446 If the return values are relative paths, they will be passed to
3447 `RequestHandler.static_url`; otherwise they will be used as-is.
3448 """
3449 return None
3451 def embedded_css(self) -> Optional[str]:
3452 """Override to return a CSS string
3453 that will be embedded in the page."""
3454 return None
3456 def css_files(self) -> Optional[Iterable[str]]:
3457 """Override to returns a list of CSS files required by this module.
3459 If the return values are relative paths, they will be passed to
3460 `RequestHandler.static_url`; otherwise they will be used as-is.
3461 """
3462 return None
3464 def html_head(self) -> Optional[str]:
3465 """Override to return an HTML string that will be put in the <head/>
3466 element.
3467 """
3468 return None
3470 def html_body(self) -> Optional[str]:
3471 """Override to return an HTML string that will be put at the end of
3472 the <body/> element.
3473 """
3474 return None
3476 def render_string(self, path: str, **kwargs: Any) -> bytes:
3477 """Renders a template and returns it as a string."""
3478 return self.handler.render_string(path, **kwargs)
3481class _linkify(UIModule):
3482 def render(self, text: str, **kwargs: Any) -> str:
3483 return escape.linkify(text, **kwargs)
3486class _xsrf_form_html(UIModule):
3487 def render(self) -> str:
3488 return self.handler.xsrf_form_html()
3491class TemplateModule(UIModule):
3492 """UIModule that simply renders the given template.
3494 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
3495 but the module version gets its own namespace (with kwargs passed to
3496 Template()) instead of inheriting the outer template's namespace.
3498 Templates rendered through this module also get access to UIModule's
3499 automatic JavaScript/CSS features. Simply call set_resources
3500 inside the template and give it keyword arguments corresponding to
3501 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
3502 Note that these resources are output once per template file, not once
3503 per instantiation of the template, so they must not depend on
3504 any arguments to the template.
3505 """
3507 def __init__(self, handler: RequestHandler) -> None:
3508 super().__init__(handler)
3509 # keep resources in both a list and a dict to preserve order
3510 self._resource_list = [] # type: List[Dict[str, Any]]
3511 self._resource_dict = {} # type: Dict[str, Dict[str, Any]]
3513 def render(self, path: str, **kwargs: Any) -> bytes:
3514 def set_resources(**kwargs) -> str: # type: ignore
3515 if path not in self._resource_dict:
3516 self._resource_list.append(kwargs)
3517 self._resource_dict[path] = kwargs
3518 else:
3519 if self._resource_dict[path] != kwargs:
3520 raise ValueError(
3521 "set_resources called with different "
3522 "resources for the same template"
3523 )
3524 return ""
3526 return self.render_string(path, set_resources=set_resources, **kwargs)
3528 def _get_resources(self, key: str) -> Iterable[str]:
3529 return (r[key] for r in self._resource_list if key in r)
3531 def embedded_javascript(self) -> str:
3532 return "\n".join(self._get_resources("embedded_javascript"))
3534 def javascript_files(self) -> Iterable[str]:
3535 result = []
3536 for f in self._get_resources("javascript_files"):
3537 if isinstance(f, (unicode_type, bytes)):
3538 result.append(f)
3539 else:
3540 result.extend(f)
3541 return result
3543 def embedded_css(self) -> str:
3544 return "\n".join(self._get_resources("embedded_css"))
3546 def css_files(self) -> Iterable[str]:
3547 result = []
3548 for f in self._get_resources("css_files"):
3549 if isinstance(f, (unicode_type, bytes)):
3550 result.append(f)
3551 else:
3552 result.extend(f)
3553 return result
3555 def html_head(self) -> str:
3556 return "".join(self._get_resources("html_head"))
3558 def html_body(self) -> str:
3559 return "".join(self._get_resources("html_body"))
3562class _UIModuleNamespace:
3563 """Lazy namespace which creates UIModule proxies bound to a handler."""
3565 def __init__(
3566 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
3567 ) -> None:
3568 self.handler = handler
3569 self.ui_modules = ui_modules
3571 def __getitem__(self, key: str) -> Callable[..., str]:
3572 return self.handler._ui_module(key, self.ui_modules[key])
3574 def __getattr__(self, key: str) -> Callable[..., str]:
3575 try:
3576 return self[key]
3577 except KeyError as e:
3578 raise AttributeError(str(e))
3581def create_signed_value(
3582 secret: _CookieSecretTypes,
3583 name: str,
3584 value: Union[str, bytes],
3585 version: Optional[int] = None,
3586 clock: Optional[Callable[[], float]] = None,
3587 key_version: Optional[int] = None,
3588) -> bytes:
3589 if version is None:
3590 version = DEFAULT_SIGNED_VALUE_VERSION
3591 if clock is None:
3592 clock = time.time
3594 timestamp = utf8(str(int(clock())))
3595 value = base64.b64encode(utf8(value))
3596 if version == 1:
3597 assert not isinstance(secret, dict)
3598 signature = _create_signature_v1(secret, name, value, timestamp)
3599 value = b"|".join([value, timestamp, signature])
3600 return value
3601 elif version == 2:
3602 # The v2 format consists of a version number and a series of
3603 # length-prefixed fields "%d:%s", the last of which is a
3604 # signature, all separated by pipes. All numbers are in
3605 # decimal format with no leading zeros. The signature is an
3606 # HMAC-SHA256 of the whole string up to that point, including
3607 # the final pipe.
3608 #
3609 # The fields are:
3610 # - format version (i.e. 2; no length prefix)
3611 # - key version (integer, default is 0)
3612 # - timestamp (integer seconds since epoch)
3613 # - name (not encoded; assumed to be ~alphanumeric)
3614 # - value (base64-encoded)
3615 # - signature (hex-encoded; no length prefix)
3616 def format_field(s: Union[str, bytes]) -> bytes:
3617 return utf8("%d:" % len(s)) + utf8(s)
3619 to_sign = b"|".join(
3620 [
3621 b"2",
3622 format_field(str(key_version or 0)),
3623 format_field(timestamp),
3624 format_field(name),
3625 format_field(value),
3626 b"",
3627 ]
3628 )
3630 if isinstance(secret, dict):
3631 assert (
3632 key_version is not None
3633 ), "Key version must be set when sign key dict is used"
3634 assert version >= 2, "Version must be at least 2 for key version support"
3635 secret = secret[key_version]
3637 signature = _create_signature_v2(secret, to_sign)
3638 return to_sign + signature
3639 else:
3640 raise ValueError("Unsupported version %d" % version)
3643# A leading version number in decimal
3644# with no leading zeros, followed by a pipe.
3645_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$")
3648def _get_version(value: bytes) -> int:
3649 # Figures out what version value is. Version 1 did not include an
3650 # explicit version field and started with arbitrary base64 data,
3651 # which makes this tricky.
3652 m = _signed_value_version_re.match(value)
3653 if m is None:
3654 version = 1
3655 else:
3656 try:
3657 version = int(m.group(1))
3658 if version > 999:
3659 # Certain payloads from the version-less v1 format may
3660 # be parsed as valid integers. Due to base64 padding
3661 # restrictions, this can only happen for numbers whose
3662 # length is a multiple of 4, so we can treat all
3663 # numbers up to 999 as versions, and for the rest we
3664 # fall back to v1 format.
3665 version = 1
3666 except ValueError:
3667 version = 1
3668 return version
3671def decode_signed_value(
3672 secret: _CookieSecretTypes,
3673 name: str,
3674 value: Union[None, str, bytes],
3675 max_age_days: float = 31,
3676 clock: Optional[Callable[[], float]] = None,
3677 min_version: Optional[int] = None,
3678) -> Optional[bytes]:
3679 if clock is None:
3680 clock = time.time
3681 if min_version is None:
3682 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
3683 if min_version > 2:
3684 raise ValueError("Unsupported min_version %d" % min_version)
3685 if not value:
3686 return None
3688 value = utf8(value)
3689 version = _get_version(value)
3691 if version < min_version:
3692 return None
3693 if version == 1:
3694 assert not isinstance(secret, dict)
3695 return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
3696 elif version == 2:
3697 return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
3698 else:
3699 return None
3702def _decode_signed_value_v1(
3703 secret: Union[str, bytes],
3704 name: str,
3705 value: bytes,
3706 max_age_days: float,
3707 clock: Callable[[], float],
3708) -> Optional[bytes]:
3709 parts = utf8(value).split(b"|")
3710 if len(parts) != 3:
3711 return None
3712 signature = _create_signature_v1(secret, name, parts[0], parts[1])
3713 if not hmac.compare_digest(parts[2], signature):
3714 gen_log.warning("Invalid cookie signature %r", value)
3715 return None
3716 timestamp = int(parts[1])
3717 if timestamp < clock() - max_age_days * 86400:
3718 gen_log.warning("Expired cookie %r", value)
3719 return None
3720 if timestamp > clock() + 31 * 86400:
3721 # _cookie_signature does not hash a delimiter between the
3722 # parts of the cookie, so an attacker could transfer trailing
3723 # digits from the payload to the timestamp without altering the
3724 # signature. For backwards compatibility, sanity-check timestamp
3725 # here instead of modifying _cookie_signature.
3726 gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
3727 return None
3728 if parts[1].startswith(b"0"):
3729 gen_log.warning("Tampered cookie %r", value)
3730 return None
3731 try:
3732 return base64.b64decode(parts[0])
3733 except Exception:
3734 return None
3737def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
3738 def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
3739 length, _, rest = s.partition(b":")
3740 n = int(length)
3741 field_value = rest[:n]
3742 # In python 3, indexing bytes returns small integers; we must
3743 # use a slice to get a byte string as in python 2.
3744 if rest[n : n + 1] != b"|":
3745 raise ValueError("malformed v2 signed value field")
3746 rest = rest[n + 1 :]
3747 return field_value, rest
3749 rest = value[2:] # remove version number
3750 key_version, rest = _consume_field(rest)
3751 timestamp, rest = _consume_field(rest)
3752 name_field, rest = _consume_field(rest)
3753 value_field, passed_sig = _consume_field(rest)
3754 return int(key_version), timestamp, name_field, value_field, passed_sig
3757def _decode_signed_value_v2(
3758 secret: _CookieSecretTypes,
3759 name: str,
3760 value: bytes,
3761 max_age_days: float,
3762 clock: Callable[[], float],
3763) -> Optional[bytes]:
3764 try:
3765 (
3766 key_version,
3767 timestamp_bytes,
3768 name_field,
3769 value_field,
3770 passed_sig,
3771 ) = _decode_fields_v2(value)
3772 except ValueError:
3773 return None
3774 signed_string = value[: -len(passed_sig)]
3776 if isinstance(secret, dict):
3777 try:
3778 secret = secret[key_version]
3779 except KeyError:
3780 return None
3782 expected_sig = _create_signature_v2(secret, signed_string)
3783 if not hmac.compare_digest(passed_sig, expected_sig):
3784 return None
3785 if name_field != utf8(name):
3786 return None
3787 timestamp = int(timestamp_bytes)
3788 if timestamp < clock() - max_age_days * 86400:
3789 # The signature has expired.
3790 return None
3791 try:
3792 return base64.b64decode(value_field)
3793 except Exception:
3794 return None
3797def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
3798 value = utf8(value)
3799 version = _get_version(value)
3800 if version < 2:
3801 return None
3802 try:
3803 key_version, _, _, _, _ = _decode_fields_v2(value)
3804 except ValueError:
3805 return None
3807 return key_version
3810def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
3811 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
3812 for part in parts:
3813 hash.update(utf8(part))
3814 return utf8(hash.hexdigest())
3817def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
3818 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
3819 hash.update(utf8(s))
3820 return utf8(hash.hexdigest())
3823def is_absolute(path: str) -> bool:
3824 return any(path.startswith(x) for x in ["/", "http:", "https:"])