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. If ``None``, it will be filled in from
363 `http.client.responses` or "Unknown".
365 .. versionchanged:: 5.0
367 No longer validates that the response code is in
368 `http.client.responses`.
369 """
370 self._status_code = status_code
371 if reason is not None:
372 self._reason = escape.native_str(reason)
373 else:
374 self._reason = httputil.responses.get(status_code, "Unknown")
376 def get_status(self) -> int:
377 """Returns the status code for our response."""
378 return self._status_code
380 def set_header(self, name: str, value: _HeaderTypes) -> None:
381 """Sets the given response header name and value.
383 All header values are converted to strings (`datetime` objects
384 are formatted according to the HTTP specification for the
385 ``Date`` header).
387 """
388 self._headers[name] = self._convert_header_value(value)
390 def add_header(self, name: str, value: _HeaderTypes) -> None:
391 """Adds the given response header and value.
393 Unlike `set_header`, `add_header` may be called multiple times
394 to return multiple values for the same header.
395 """
396 self._headers.add(name, self._convert_header_value(value))
398 def clear_header(self, name: str) -> None:
399 """Clears an outgoing header, undoing a previous `set_header` call.
401 Note that this method does not apply to multi-valued headers
402 set by `add_header`.
403 """
404 if name in self._headers:
405 del self._headers[name]
407 # https://www.rfc-editor.org/rfc/rfc9110#name-field-values
408 _VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*")
410 def _convert_header_value(self, value: _HeaderTypes) -> str:
411 # Convert the input value to a str. This type check is a bit
412 # subtle: The bytes case only executes on python 3, and the
413 # unicode case only executes on python 2, because the other
414 # cases are covered by the first match for str.
415 if isinstance(value, str):
416 retval = value
417 elif isinstance(value, bytes):
418 # Non-ascii characters in headers are not well supported,
419 # but if you pass bytes, use latin1 so they pass through as-is.
420 retval = value.decode("latin1")
421 elif isinstance(value, numbers.Integral):
422 # return immediately since we know the converted value will be safe
423 return str(value)
424 elif isinstance(value, datetime.datetime):
425 return httputil.format_timestamp(value)
426 else:
427 raise TypeError("Unsupported header value %r" % value)
428 # If \n is allowed into the header, it is possible to inject
429 # additional headers or split the request.
430 if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None:
431 raise ValueError("Unsafe header value %r", retval)
432 return retval
434 @overload
435 def get_argument(self, name: str, default: str, strip: bool = True) -> str:
436 pass
438 @overload
439 def get_argument( # noqa: F811
440 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
441 ) -> str:
442 pass
444 @overload
445 def get_argument( # noqa: F811
446 self, name: str, default: None, strip: bool = True
447 ) -> Optional[str]:
448 pass
450 def get_argument( # noqa: F811
451 self,
452 name: str,
453 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
454 strip: bool = True,
455 ) -> Optional[str]:
456 """Returns the value of the argument with the given name.
458 If default is not provided, the argument is considered to be
459 required, and we raise a `MissingArgumentError` if it is missing.
461 If the argument appears in the request more than once, we return the
462 last value.
464 This method searches both the query and body arguments.
465 """
466 return self._get_argument(name, default, self.request.arguments, strip)
468 def get_arguments(self, name: str, strip: bool = True) -> List[str]:
469 """Returns a list of the arguments with the given name.
471 If the argument is not present, returns an empty list.
473 This method searches both the query and body arguments.
474 """
476 # Make sure `get_arguments` isn't accidentally being called with a
477 # positional argument that's assumed to be a default (like in
478 # `get_argument`.)
479 assert isinstance(strip, bool)
481 return self._get_arguments(name, self.request.arguments, strip)
483 @overload
484 def get_body_argument(self, name: str, default: str, strip: bool = True) -> str:
485 pass
487 @overload
488 def get_body_argument( # noqa: F811
489 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
490 ) -> str:
491 pass
493 @overload
494 def get_body_argument( # noqa: F811
495 self, name: str, default: None, strip: bool = True
496 ) -> Optional[str]:
497 pass
499 def get_body_argument( # noqa: F811
500 self,
501 name: str,
502 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
503 strip: bool = True,
504 ) -> Optional[str]:
505 """Returns the value of the argument with the given name
506 from the request body.
508 If default is not provided, the argument is considered to be
509 required, and we raise a `MissingArgumentError` if it is missing.
511 If the argument appears in the url more than once, we return the
512 last value.
514 .. versionadded:: 3.2
515 """
516 return self._get_argument(name, default, self.request.body_arguments, strip)
518 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]:
519 """Returns a list of the body arguments with the given name.
521 If the argument is not present, returns an empty list.
523 .. versionadded:: 3.2
524 """
525 return self._get_arguments(name, self.request.body_arguments, strip)
527 @overload
528 def get_query_argument(self, name: str, default: str, strip: bool = True) -> str:
529 pass
531 @overload
532 def get_query_argument( # noqa: F811
533 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
534 ) -> str:
535 pass
537 @overload
538 def get_query_argument( # noqa: F811
539 self, name: str, default: None, strip: bool = True
540 ) -> Optional[str]:
541 pass
543 def get_query_argument( # noqa: F811
544 self,
545 name: str,
546 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
547 strip: bool = True,
548 ) -> Optional[str]:
549 """Returns the value of the argument with the given name
550 from the request query string.
552 If default is not provided, the argument is considered to be
553 required, and we raise a `MissingArgumentError` if it is missing.
555 If the argument appears in the url more than once, we return the
556 last value.
558 .. versionadded:: 3.2
559 """
560 return self._get_argument(name, default, self.request.query_arguments, strip)
562 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]:
563 """Returns a list of the query arguments with the given name.
565 If the argument is not present, returns an empty list.
567 .. versionadded:: 3.2
568 """
569 return self._get_arguments(name, self.request.query_arguments, strip)
571 def _get_argument(
572 self,
573 name: str,
574 default: Union[None, str, _ArgDefaultMarker],
575 source: Dict[str, List[bytes]],
576 strip: bool = True,
577 ) -> Optional[str]:
578 args = self._get_arguments(name, source, strip=strip)
579 if not args:
580 if isinstance(default, _ArgDefaultMarker):
581 raise MissingArgumentError(name)
582 return default
583 return args[-1]
585 def _get_arguments(
586 self, name: str, source: Dict[str, List[bytes]], strip: bool = True
587 ) -> List[str]:
588 values = []
589 for v in source.get(name, []):
590 s = self.decode_argument(v, name=name)
591 if isinstance(s, unicode_type):
592 # Get rid of any weird control chars (unless decoding gave
593 # us bytes, in which case leave it alone)
594 s = RequestHandler._remove_control_chars_regex.sub(" ", s)
595 if strip:
596 s = s.strip()
597 values.append(s)
598 return values
600 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str:
601 """Decodes an argument from the request.
603 The argument has been percent-decoded and is now a byte string.
604 By default, this method decodes the argument as utf-8 and returns
605 a unicode string, but this may be overridden in subclasses.
607 This method is used as a filter for both `get_argument()` and for
608 values extracted from the url and passed to `get()`/`post()`/etc.
610 The name of the argument is provided if known, but may be None
611 (e.g. for unnamed groups in the url regex).
612 """
613 try:
614 return _unicode(value)
615 except UnicodeDecodeError:
616 raise HTTPError(
617 400, "Invalid unicode in {}: {!r}".format(name or "url", value[:40])
618 )
620 @property
621 def cookies(self) -> Dict[str, http.cookies.Morsel]:
622 """An alias for
623 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`."""
624 return self.request.cookies
626 @overload
627 def get_cookie(self, name: str, default: str) -> str:
628 pass
630 @overload
631 def get_cookie(self, name: str, default: None = None) -> Optional[str]:
632 pass
634 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]:
635 """Returns the value of the request cookie with the given name.
637 If the named cookie is not present, returns ``default``.
639 This method only returns cookies that were present in the request.
640 It does not see the outgoing cookies set by `set_cookie` in this
641 handler.
642 """
643 if self.request.cookies is not None and name in self.request.cookies:
644 return self.request.cookies[name].value
645 return default
647 def set_cookie(
648 self,
649 name: str,
650 value: Union[str, bytes],
651 domain: Optional[str] = None,
652 expires: Optional[Union[float, Tuple, datetime.datetime]] = None,
653 path: str = "/",
654 expires_days: Optional[float] = None,
655 # Keyword-only args start here for historical reasons.
656 *,
657 max_age: Optional[int] = None,
658 httponly: bool = False,
659 secure: bool = False,
660 samesite: Optional[str] = None,
661 **kwargs: Any,
662 ) -> None:
663 """Sets an outgoing cookie name/value with the given options.
665 Newly-set cookies are not immediately visible via `get_cookie`;
666 they are not present until the next request.
668 Most arguments are passed directly to `http.cookies.Morsel` directly.
669 See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
670 for more information.
672 ``expires`` may be a numeric timestamp as returned by `time.time`,
673 a time tuple as returned by `time.gmtime`, or a
674 `datetime.datetime` object. ``expires_days`` is provided as a convenience
675 to set an expiration time in days from today (if both are set, ``expires``
676 is used).
678 .. deprecated:: 6.3
679 Keyword arguments are currently accepted case-insensitively.
680 In Tornado 7.0 this will be changed to only accept lowercase
681 arguments.
682 """
683 # The cookie library only accepts type str, in both python 2 and 3
684 name = escape.native_str(name)
685 value = escape.native_str(value)
686 if re.search(r"[\x00-\x20]", name + value):
687 # Don't let us accidentally inject bad stuff
688 raise ValueError(f"Invalid cookie {name!r}: {value!r}")
689 if not hasattr(self, "_new_cookie"):
690 self._new_cookie = (
691 http.cookies.SimpleCookie()
692 ) # type: http.cookies.SimpleCookie
693 if name in self._new_cookie:
694 del self._new_cookie[name]
695 self._new_cookie[name] = value
696 morsel = self._new_cookie[name]
697 if domain:
698 morsel["domain"] = domain
699 if expires_days is not None and not expires:
700 expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
701 days=expires_days
702 )
703 if expires:
704 morsel["expires"] = httputil.format_timestamp(expires)
705 if path:
706 morsel["path"] = path
707 if max_age:
708 # Note change from _ to -.
709 morsel["max-age"] = str(max_age)
710 if httponly:
711 # Note that SimpleCookie ignores the value here. The presense of an
712 # httponly (or secure) key is treated as true.
713 morsel["httponly"] = True
714 if secure:
715 morsel["secure"] = True
716 if samesite:
717 morsel["samesite"] = samesite
718 if kwargs:
719 # The setitem interface is case-insensitive, so continue to support
720 # kwargs for backwards compatibility until we can remove deprecated
721 # features.
722 for k, v in kwargs.items():
723 morsel[k] = v
724 warnings.warn(
725 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} "
726 "(should be lowercase)",
727 DeprecationWarning,
728 )
730 def clear_cookie(self, name: str, **kwargs: Any) -> None:
731 """Deletes the cookie with the given name.
733 This method accepts the same arguments as `set_cookie`, except for
734 ``expires`` and ``max_age``. Clearing a cookie requires the same
735 ``domain`` and ``path`` arguments as when it was set. In some cases the
736 ``samesite`` and ``secure`` arguments are also required to match. Other
737 arguments are ignored.
739 Similar to `set_cookie`, the effect of this method will not be
740 seen until the following request.
742 .. versionchanged:: 6.3
744 Now accepts all keyword arguments that ``set_cookie`` does.
745 The ``samesite`` and ``secure`` flags have recently become
746 required for clearing ``samesite="none"`` cookies.
747 """
748 for excluded_arg in ["expires", "max_age"]:
749 if excluded_arg in kwargs:
750 raise TypeError(
751 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'"
752 )
753 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
754 days=365
755 )
756 self.set_cookie(name, value="", expires=expires, **kwargs)
758 def clear_all_cookies(self, **kwargs: Any) -> None:
759 """Attempt to delete all the cookies the user sent with this request.
761 See `clear_cookie` for more information on keyword arguments. Due to
762 limitations of the cookie protocol, it is impossible to determine on the
763 server side which values are necessary for the ``domain``, ``path``,
764 ``samesite``, or ``secure`` arguments, this method can only be
765 successful if you consistently use the same values for these arguments
766 when setting cookies.
768 Similar to `set_cookie`, the effect of this method will not be seen
769 until the following request.
771 .. versionchanged:: 3.2
773 Added the ``path`` and ``domain`` parameters.
775 .. versionchanged:: 6.3
777 Now accepts all keyword arguments that ``set_cookie`` does.
779 .. deprecated:: 6.3
781 The increasingly complex rules governing cookies have made it
782 impossible for a ``clear_all_cookies`` method to work reliably
783 since all we know about cookies are their names. Applications
784 should generally use ``clear_cookie`` one at a time instead.
785 """
786 for name in self.request.cookies:
787 self.clear_cookie(name, **kwargs)
789 def set_signed_cookie(
790 self,
791 name: str,
792 value: Union[str, bytes],
793 expires_days: Optional[float] = 30,
794 version: Optional[int] = None,
795 **kwargs: Any,
796 ) -> None:
797 """Signs and timestamps a cookie so it cannot be forged.
799 You must specify the ``cookie_secret`` setting in your Application
800 to use this method. It should be a long, random sequence of bytes
801 to be used as the HMAC secret for the signature.
803 To read a cookie set with this method, use `get_signed_cookie()`.
805 Note that the ``expires_days`` parameter sets the lifetime of the
806 cookie in the browser, but is independent of the ``max_age_days``
807 parameter to `get_signed_cookie`.
808 A value of None limits the lifetime to the current browser session.
810 Secure cookies may contain arbitrary byte values, not just unicode
811 strings (unlike regular cookies)
813 Similar to `set_cookie`, the effect of this method will not be
814 seen until the following request.
816 .. versionchanged:: 3.2.1
818 Added the ``version`` argument. Introduced cookie version 2
819 and made it the default.
821 .. versionchanged:: 6.3
823 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to
824 avoid confusion with other uses of "secure" in cookie attributes
825 and prefixes. The old name remains as an alias.
826 """
827 self.set_cookie(
828 name,
829 self.create_signed_value(name, value, version=version),
830 expires_days=expires_days,
831 **kwargs,
832 )
834 set_secure_cookie = set_signed_cookie
836 def create_signed_value(
837 self, name: str, value: Union[str, bytes], version: Optional[int] = None
838 ) -> bytes:
839 """Signs and timestamps a string so it cannot be forged.
841 Normally used via set_signed_cookie, but provided as a separate
842 method for non-cookie uses. To decode a value not stored
843 as a cookie use the optional value argument to get_signed_cookie.
845 .. versionchanged:: 3.2.1
847 Added the ``version`` argument. Introduced cookie version 2
848 and made it the default.
849 """
850 self.require_setting("cookie_secret", "secure cookies")
851 secret = self.application.settings["cookie_secret"]
852 key_version = None
853 if isinstance(secret, dict):
854 if self.application.settings.get("key_version") is None:
855 raise Exception("key_version setting must be used for secret_key dicts")
856 key_version = self.application.settings["key_version"]
858 return create_signed_value(
859 secret, name, value, version=version, key_version=key_version
860 )
862 def get_signed_cookie(
863 self,
864 name: str,
865 value: Optional[str] = None,
866 max_age_days: float = 31,
867 min_version: Optional[int] = None,
868 ) -> Optional[bytes]:
869 """Returns the given signed cookie if it validates, or None.
871 The decoded cookie value is returned as a byte string (unlike
872 `get_cookie`).
874 Similar to `get_cookie`, this method only returns cookies that
875 were present in the request. It does not see outgoing cookies set by
876 `set_signed_cookie` in this handler.
878 .. versionchanged:: 3.2.1
880 Added the ``min_version`` argument. Introduced cookie version 2;
881 both versions 1 and 2 are accepted by default.
883 .. versionchanged:: 6.3
885 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to
886 avoid confusion with other uses of "secure" in cookie attributes
887 and prefixes. The old name remains as an alias.
889 """
890 self.require_setting("cookie_secret", "secure cookies")
891 if value is None:
892 value = self.get_cookie(name)
893 return decode_signed_value(
894 self.application.settings["cookie_secret"],
895 name,
896 value,
897 max_age_days=max_age_days,
898 min_version=min_version,
899 )
901 get_secure_cookie = get_signed_cookie
903 def get_signed_cookie_key_version(
904 self, name: str, value: Optional[str] = None
905 ) -> Optional[int]:
906 """Returns the signing key version of the secure cookie.
908 The version is returned as int.
910 .. versionchanged:: 6.3
912 Renamed from ``get_secure_cookie_key_version`` to
913 ``set_signed_cookie_key_version`` to avoid confusion with other
914 uses of "secure" in cookie attributes and prefixes. The old name
915 remains as an alias.
917 """
918 self.require_setting("cookie_secret", "secure cookies")
919 if value is None:
920 value = self.get_cookie(name)
921 if value is None:
922 return None
923 return get_signature_key_version(value)
925 get_secure_cookie_key_version = get_signed_cookie_key_version
927 def redirect(
928 self, url: str, permanent: bool = False, status: Optional[int] = None
929 ) -> None:
930 """Sends a redirect to the given (optionally relative) URL.
932 If the ``status`` argument is specified, that value is used as the
933 HTTP status code; otherwise either 301 (permanent) or 302
934 (temporary) is chosen based on the ``permanent`` argument.
935 The default is 302 (temporary).
936 """
937 if self._headers_written:
938 raise Exception("Cannot redirect after headers have been written")
939 if status is None:
940 status = 301 if permanent else 302
941 else:
942 assert isinstance(status, int) and 300 <= status <= 399
943 self.set_status(status)
944 self.set_header("Location", utf8(url))
945 self.finish()
947 def write(self, chunk: Union[str, bytes, dict]) -> None:
948 """Writes the given chunk to the output buffer.
950 To write the output to the network, use the `flush()` method below.
952 If the given chunk is a dictionary, we write it as JSON and set
953 the Content-Type of the response to be ``application/json``.
954 (if you want to send JSON as a different ``Content-Type``, call
955 ``set_header`` *after* calling ``write()``).
957 Note that lists are not converted to JSON because of a potential
958 cross-site security vulnerability. All JSON output should be
959 wrapped in a dictionary. More details at
960 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
961 https://github.com/facebook/tornado/issues/1009
962 """
963 if self._finished:
964 raise RuntimeError("Cannot write() after finish()")
965 if not isinstance(chunk, (bytes, unicode_type, dict)):
966 message = "write() only accepts bytes, unicode, and dict objects"
967 if isinstance(chunk, list):
968 message += (
969 ". Lists not accepted for security reasons; see "
970 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501
971 )
972 raise TypeError(message)
973 if isinstance(chunk, dict):
974 chunk = escape.json_encode(chunk)
975 self.set_header("Content-Type", "application/json; charset=UTF-8")
976 chunk = utf8(chunk)
977 self._write_buffer.append(chunk)
979 def render(self, template_name: str, **kwargs: Any) -> "Future[None]":
980 """Renders the template with the given arguments as the response.
982 ``render()`` calls ``finish()``, so no other output methods can be called
983 after it.
985 Returns a `.Future` with the same semantics as the one returned by `finish`.
986 Awaiting this `.Future` is optional.
988 .. versionchanged:: 5.1
990 Now returns a `.Future` instead of ``None``.
991 """
992 if self._finished:
993 raise RuntimeError("Cannot render() after finish()")
994 html = self.render_string(template_name, **kwargs)
996 # Insert the additional JS and CSS added by the modules on the page
997 js_embed = []
998 js_files = []
999 css_embed = []
1000 css_files = []
1001 html_heads = []
1002 html_bodies = []
1003 for module in getattr(self, "_active_modules", {}).values():
1004 embed_part = module.embedded_javascript()
1005 if embed_part:
1006 js_embed.append(utf8(embed_part))
1007 file_part = module.javascript_files()
1008 if file_part:
1009 if isinstance(file_part, (unicode_type, bytes)):
1010 js_files.append(_unicode(file_part))
1011 else:
1012 js_files.extend(file_part)
1013 embed_part = module.embedded_css()
1014 if embed_part:
1015 css_embed.append(utf8(embed_part))
1016 file_part = module.css_files()
1017 if file_part:
1018 if isinstance(file_part, (unicode_type, bytes)):
1019 css_files.append(_unicode(file_part))
1020 else:
1021 css_files.extend(file_part)
1022 head_part = module.html_head()
1023 if head_part:
1024 html_heads.append(utf8(head_part))
1025 body_part = module.html_body()
1026 if body_part:
1027 html_bodies.append(utf8(body_part))
1029 if js_files:
1030 # Maintain order of JavaScript files given by modules
1031 js = self.render_linked_js(js_files)
1032 sloc = html.rindex(b"</body>")
1033 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:]
1034 if js_embed:
1035 js_bytes = self.render_embed_js(js_embed)
1036 sloc = html.rindex(b"</body>")
1037 html = html[:sloc] + js_bytes + b"\n" + html[sloc:]
1038 if css_files:
1039 css = self.render_linked_css(css_files)
1040 hloc = html.index(b"</head>")
1041 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:]
1042 if css_embed:
1043 css_bytes = self.render_embed_css(css_embed)
1044 hloc = html.index(b"</head>")
1045 html = html[:hloc] + css_bytes + b"\n" + html[hloc:]
1046 if html_heads:
1047 hloc = html.index(b"</head>")
1048 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:]
1049 if html_bodies:
1050 hloc = html.index(b"</body>")
1051 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:]
1052 return self.finish(html)
1054 def render_linked_js(self, js_files: Iterable[str]) -> str:
1055 """Default method used to render the final js links for the
1056 rendered webpage.
1058 Override this method in a sub-classed controller to change the output.
1059 """
1060 paths = []
1061 unique_paths = set() # type: Set[str]
1063 for path in js_files:
1064 if not is_absolute(path):
1065 path = self.static_url(path)
1066 if path not in unique_paths:
1067 paths.append(path)
1068 unique_paths.add(path)
1070 return "".join(
1071 '<script src="'
1072 + escape.xhtml_escape(p)
1073 + '" type="text/javascript"></script>'
1074 for p in paths
1075 )
1077 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
1078 """Default method used to render the final embedded js for the
1079 rendered webpage.
1081 Override this method in a sub-classed controller to change the output.
1082 """
1083 return (
1084 b'<script type="text/javascript">\n//<![CDATA[\n'
1085 + b"\n".join(js_embed)
1086 + b"\n//]]>\n</script>"
1087 )
1089 def render_linked_css(self, css_files: Iterable[str]) -> str:
1090 """Default method used to render the final css links for the
1091 rendered webpage.
1093 Override this method in a sub-classed controller to change the output.
1094 """
1095 paths = []
1096 unique_paths = set() # type: Set[str]
1098 for path in css_files:
1099 if not is_absolute(path):
1100 path = self.static_url(path)
1101 if path not in unique_paths:
1102 paths.append(path)
1103 unique_paths.add(path)
1105 return "".join(
1106 '<link href="' + escape.xhtml_escape(p) + '" '
1107 'type="text/css" rel="stylesheet"/>'
1108 for p in paths
1109 )
1111 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
1112 """Default method used to render the final embedded css for the
1113 rendered webpage.
1115 Override this method in a sub-classed controller to change the output.
1116 """
1117 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>"
1119 def render_string(self, template_name: str, **kwargs: Any) -> bytes:
1120 """Generate the given template with the given arguments.
1122 We return the generated byte string (in utf8). To generate and
1123 write a template as a response, use render() above.
1124 """
1125 # If no template_path is specified, use the path of the calling file
1126 template_path = self.get_template_path()
1127 if not template_path:
1128 frame = sys._getframe(0)
1129 web_file = frame.f_code.co_filename
1130 while frame.f_code.co_filename == web_file and frame.f_back is not None:
1131 frame = frame.f_back
1132 assert frame.f_code.co_filename is not None
1133 template_path = os.path.dirname(frame.f_code.co_filename)
1134 with RequestHandler._template_loader_lock:
1135 if template_path not in RequestHandler._template_loaders:
1136 loader = self.create_template_loader(template_path)
1137 RequestHandler._template_loaders[template_path] = loader
1138 else:
1139 loader = RequestHandler._template_loaders[template_path]
1140 t = loader.load(template_name)
1141 namespace = self.get_template_namespace()
1142 namespace.update(kwargs)
1143 return t.generate(**namespace)
1145 def get_template_namespace(self) -> Dict[str, Any]:
1146 """Returns a dictionary to be used as the default template namespace.
1148 May be overridden by subclasses to add or modify values.
1150 The results of this method will be combined with additional
1151 defaults in the `tornado.template` module and keyword arguments
1152 to `render` or `render_string`.
1153 """
1154 namespace = dict(
1155 handler=self,
1156 request=self.request,
1157 current_user=self.current_user,
1158 locale=self.locale,
1159 _=self.locale.translate,
1160 pgettext=self.locale.pgettext,
1161 static_url=self.static_url,
1162 xsrf_form_html=self.xsrf_form_html,
1163 reverse_url=self.reverse_url,
1164 )
1165 namespace.update(self.ui)
1166 return namespace
1168 def create_template_loader(self, template_path: str) -> template.BaseLoader:
1169 """Returns a new template loader for the given path.
1171 May be overridden by subclasses. By default returns a
1172 directory-based loader on the given path, using the
1173 ``autoescape`` and ``template_whitespace`` application
1174 settings. If a ``template_loader`` application setting is
1175 supplied, uses that instead.
1176 """
1177 settings = self.application.settings
1178 if "template_loader" in settings:
1179 return settings["template_loader"]
1180 kwargs = {}
1181 if "autoescape" in settings:
1182 # autoescape=None means "no escaping", so we have to be sure
1183 # to only pass this kwarg if the user asked for it.
1184 kwargs["autoescape"] = settings["autoescape"]
1185 if "template_whitespace" in settings:
1186 kwargs["whitespace"] = settings["template_whitespace"]
1187 return template.Loader(template_path, **kwargs)
1189 def flush(self, include_footers: bool = False) -> "Future[None]":
1190 """Flushes the current output buffer to the network.
1192 .. versionchanged:: 4.0
1193 Now returns a `.Future` if no callback is given.
1195 .. versionchanged:: 6.0
1197 The ``callback`` argument was removed.
1198 """
1199 assert self.request.connection is not None
1200 chunk = b"".join(self._write_buffer)
1201 self._write_buffer = []
1202 if not self._headers_written:
1203 self._headers_written = True
1204 for transform in self._transforms:
1205 assert chunk is not None
1206 (
1207 self._status_code,
1208 self._headers,
1209 chunk,
1210 ) = transform.transform_first_chunk(
1211 self._status_code, self._headers, chunk, include_footers
1212 )
1213 # Ignore the chunk and only write the headers for HEAD requests
1214 if self.request.method == "HEAD":
1215 chunk = b""
1217 # Finalize the cookie headers (which have been stored in a side
1218 # object so an outgoing cookie could be overwritten before it
1219 # is sent).
1220 if hasattr(self, "_new_cookie"):
1221 for cookie in self._new_cookie.values():
1222 self.add_header("Set-Cookie", cookie.OutputString(None))
1224 start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
1225 return self.request.connection.write_headers(
1226 start_line, self._headers, chunk
1227 )
1228 else:
1229 for transform in self._transforms:
1230 chunk = transform.transform_chunk(chunk, include_footers)
1231 # Ignore the chunk and only write the headers for HEAD requests
1232 if self.request.method != "HEAD":
1233 return self.request.connection.write(chunk)
1234 else:
1235 future = Future() # type: Future[None]
1236 future.set_result(None)
1237 return future
1239 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]":
1240 """Finishes this response, ending the HTTP request.
1242 Passing a ``chunk`` to ``finish()`` is equivalent to passing that
1243 chunk to ``write()`` and then calling ``finish()`` with no arguments.
1245 Returns a `.Future` which may optionally be awaited to track the sending
1246 of the response to the client. This `.Future` resolves when all the response
1247 data has been sent, and raises an error if the connection is closed before all
1248 data can be sent.
1250 .. versionchanged:: 5.1
1252 Now returns a `.Future` instead of ``None``.
1253 """
1254 if self._finished:
1255 raise RuntimeError("finish() called twice")
1257 if chunk is not None:
1258 self.write(chunk)
1260 # Automatically support ETags and add the Content-Length header if
1261 # we have not flushed any content yet.
1262 if not self._headers_written:
1263 if (
1264 self._status_code == 200
1265 and self.request.method in ("GET", "HEAD")
1266 and "Etag" not in self._headers
1267 ):
1268 self.set_etag_header()
1269 if self.check_etag_header():
1270 self._write_buffer = []
1271 self.set_status(304)
1272 if self._status_code in (204, 304) or (100 <= self._status_code < 200):
1273 assert not self._write_buffer, (
1274 "Cannot send body with %s" % self._status_code
1275 )
1276 self._clear_representation_headers()
1277 elif "Content-Length" not in self._headers:
1278 content_length = sum(len(part) for part in self._write_buffer)
1279 self.set_header("Content-Length", content_length)
1281 assert self.request.connection is not None
1282 # Now that the request is finished, clear the callback we
1283 # set on the HTTPConnection (which would otherwise prevent the
1284 # garbage collection of the RequestHandler when there
1285 # are keepalive connections)
1286 self.request.connection.set_close_callback(None) # type: ignore
1288 future = self.flush(include_footers=True)
1289 self.request.connection.finish()
1290 self._log()
1291 self._finished = True
1292 self.on_finish()
1293 self._break_cycles()
1294 return future
1296 def detach(self) -> iostream.IOStream:
1297 """Take control of the underlying stream.
1299 Returns the underlying `.IOStream` object and stops all
1300 further HTTP processing. Intended for implementing protocols
1301 like websockets that tunnel over an HTTP handshake.
1303 This method is only supported when HTTP/1.1 is used.
1305 .. versionadded:: 5.1
1306 """
1307 self._finished = True
1308 # TODO: add detach to HTTPConnection?
1309 return self.request.connection.detach() # type: ignore
1311 def _break_cycles(self) -> None:
1312 # Break up a reference cycle between this handler and the
1313 # _ui_module closures to allow for faster GC on CPython.
1314 self.ui = None # type: ignore
1316 def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
1317 """Sends the given HTTP error code to the browser.
1319 If `flush()` has already been called, it is not possible to send
1320 an error, so this method will simply terminate the response.
1321 If output has been written but not yet flushed, it will be discarded
1322 and replaced with the error page.
1324 Override `write_error()` to customize the error page that is returned.
1325 Additional keyword arguments are passed through to `write_error`.
1326 """
1327 if self._headers_written:
1328 gen_log.error("Cannot send error response after headers written")
1329 if not self._finished:
1330 # If we get an error between writing headers and finishing,
1331 # we are unlikely to be able to finish due to a
1332 # Content-Length mismatch. Try anyway to release the
1333 # socket.
1334 try:
1335 self.finish()
1336 except Exception:
1337 gen_log.error("Failed to flush partial response", exc_info=True)
1338 return
1339 self.clear()
1341 reason = kwargs.get("reason")
1342 if "exc_info" in kwargs:
1343 exception = kwargs["exc_info"][1]
1344 if isinstance(exception, HTTPError) and exception.reason:
1345 reason = exception.reason
1346 self.set_status(status_code, reason=reason)
1347 try:
1348 self.write_error(status_code, **kwargs)
1349 except Exception:
1350 app_log.error("Uncaught exception in write_error", exc_info=True)
1351 if not self._finished:
1352 self.finish()
1354 def write_error(self, status_code: int, **kwargs: Any) -> None:
1355 """Override to implement custom error pages.
1357 ``write_error`` may call `write`, `render`, `set_header`, etc
1358 to produce output as usual.
1360 If this error was caused by an uncaught exception (including
1361 HTTPError), an ``exc_info`` triple will be available as
1362 ``kwargs["exc_info"]``. Note that this exception may not be
1363 the "current" exception for purposes of methods like
1364 ``sys.exc_info()`` or ``traceback.format_exc``.
1365 """
1366 if self.settings.get("serve_traceback") and "exc_info" in kwargs:
1367 # in debug mode, try to send a traceback
1368 self.set_header("Content-Type", "text/plain")
1369 for line in traceback.format_exception(*kwargs["exc_info"]):
1370 self.write(line)
1371 self.finish()
1372 else:
1373 self.finish(
1374 "<html><title>%(code)d: %(message)s</title>"
1375 "<body>%(code)d: %(message)s</body></html>"
1376 % {"code": status_code, "message": self._reason}
1377 )
1379 @property
1380 def locale(self) -> tornado.locale.Locale:
1381 """The locale for the current session.
1383 Determined by either `get_user_locale`, which you can override to
1384 set the locale based on, e.g., a user preference stored in a
1385 database, or `get_browser_locale`, which uses the ``Accept-Language``
1386 header.
1388 .. versionchanged: 4.1
1389 Added a property setter.
1390 """
1391 if not hasattr(self, "_locale"):
1392 loc = self.get_user_locale()
1393 if loc is not None:
1394 self._locale = loc
1395 else:
1396 self._locale = self.get_browser_locale()
1397 assert self._locale
1398 return self._locale
1400 @locale.setter
1401 def locale(self, value: tornado.locale.Locale) -> None:
1402 self._locale = value
1404 def get_user_locale(self) -> Optional[tornado.locale.Locale]:
1405 """Override to determine the locale from the authenticated user.
1407 If None is returned, we fall back to `get_browser_locale()`.
1409 This method should return a `tornado.locale.Locale` object,
1410 most likely obtained via a call like ``tornado.locale.get("en")``
1411 """
1412 return None
1414 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
1415 """Determines the user's locale from ``Accept-Language`` header.
1417 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
1418 """
1419 if "Accept-Language" in self.request.headers:
1420 languages = self.request.headers["Accept-Language"].split(",")
1421 locales = []
1422 for language in languages:
1423 parts = language.strip().split(";")
1424 if len(parts) > 1 and parts[1].strip().startswith("q="):
1425 try:
1426 score = float(parts[1].strip()[2:])
1427 if score < 0:
1428 raise ValueError()
1429 except (ValueError, TypeError):
1430 score = 0.0
1431 else:
1432 score = 1.0
1433 if score > 0:
1434 locales.append((parts[0], score))
1435 if locales:
1436 locales.sort(key=lambda pair: pair[1], reverse=True)
1437 codes = [loc[0] for loc in locales]
1438 return locale.get(*codes)
1439 return locale.get(default)
1441 @property
1442 def current_user(self) -> Any:
1443 """The authenticated user for this request.
1445 This is set in one of two ways:
1447 * A subclass may override `get_current_user()`, which will be called
1448 automatically the first time ``self.current_user`` is accessed.
1449 `get_current_user()` will only be called once per request,
1450 and is cached for future access::
1452 def get_current_user(self):
1453 user_cookie = self.get_signed_cookie("user")
1454 if user_cookie:
1455 return json.loads(user_cookie)
1456 return None
1458 * It may be set as a normal variable, typically from an overridden
1459 `prepare()`::
1461 @gen.coroutine
1462 def prepare(self):
1463 user_id_cookie = self.get_signed_cookie("user_id")
1464 if user_id_cookie:
1465 self.current_user = yield load_user(user_id_cookie)
1467 Note that `prepare()` may be a coroutine while `get_current_user()`
1468 may not, so the latter form is necessary if loading the user requires
1469 asynchronous operations.
1471 The user object may be any type of the application's choosing.
1472 """
1473 if not hasattr(self, "_current_user"):
1474 self._current_user = self.get_current_user()
1475 return self._current_user
1477 @current_user.setter
1478 def current_user(self, value: Any) -> None:
1479 self._current_user = value
1481 def get_current_user(self) -> Any:
1482 """Override to determine the current user from, e.g., a cookie.
1484 This method may not be a coroutine.
1485 """
1486 return None
1488 def get_login_url(self) -> str:
1489 """Override to customize the login URL based on the request.
1491 By default, we use the ``login_url`` application setting.
1492 """
1493 self.require_setting("login_url", "@tornado.web.authenticated")
1494 return self.application.settings["login_url"]
1496 def get_template_path(self) -> Optional[str]:
1497 """Override to customize template path for each handler.
1499 By default, we use the ``template_path`` application setting.
1500 Return None to load templates relative to the calling file.
1501 """
1502 return self.application.settings.get("template_path")
1504 @property
1505 def xsrf_token(self) -> bytes:
1506 """The XSRF-prevention token for the current user/session.
1508 To prevent cross-site request forgery, we set an '_xsrf' cookie
1509 and include the same '_xsrf' value as an argument with all POST
1510 requests. If the two do not match, we reject the form submission
1511 as a potential forgery.
1513 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1515 This property is of type `bytes`, but it contains only ASCII
1516 characters. If a character string is required, there is no
1517 need to base64-encode it; just decode the byte string as
1518 UTF-8.
1520 .. versionchanged:: 3.2.2
1521 The xsrf token will now be have a random mask applied in every
1522 request, which makes it safe to include the token in pages
1523 that are compressed. See http://breachattack.com for more
1524 information on the issue fixed by this change. Old (version 1)
1525 cookies will be converted to version 2 when this method is called
1526 unless the ``xsrf_cookie_version`` `Application` setting is
1527 set to 1.
1529 .. versionchanged:: 4.3
1530 The ``xsrf_cookie_kwargs`` `Application` setting may be
1531 used to supply additional cookie options (which will be
1532 passed directly to `set_cookie`). For example,
1533 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
1534 will set the ``secure`` and ``httponly`` flags on the
1535 ``_xsrf`` cookie.
1536 """
1537 if not hasattr(self, "_xsrf_token"):
1538 version, token, timestamp = self._get_raw_xsrf_token()
1539 output_version = self.settings.get("xsrf_cookie_version", 2)
1540 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
1541 if output_version == 1:
1542 self._xsrf_token = binascii.b2a_hex(token)
1543 elif output_version == 2:
1544 mask = os.urandom(4)
1545 self._xsrf_token = b"|".join(
1546 [
1547 b"2",
1548 binascii.b2a_hex(mask),
1549 binascii.b2a_hex(_websocket_mask(mask, token)),
1550 utf8(str(int(timestamp))),
1551 ]
1552 )
1553 else:
1554 raise ValueError("unknown xsrf cookie version %d", output_version)
1555 if version is None:
1556 if self.current_user and "expires_days" not in cookie_kwargs:
1557 cookie_kwargs["expires_days"] = 30
1558 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1559 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)
1560 return self._xsrf_token
1562 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
1563 """Read or generate the xsrf token in its raw form.
1565 The raw_xsrf_token is a tuple containing:
1567 * version: the version of the cookie from which this token was read,
1568 or None if we generated a new token in this request.
1569 * token: the raw token data; random (non-ascii) bytes.
1570 * timestamp: the time this token was generated (will not be accurate
1571 for version 1 cookies)
1572 """
1573 if not hasattr(self, "_raw_xsrf_token"):
1574 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1575 cookie = self.get_cookie(cookie_name)
1576 if cookie:
1577 version, token, timestamp = self._decode_xsrf_token(cookie)
1578 else:
1579 version, token, timestamp = None, None, None
1580 if token is None:
1581 version = None
1582 token = os.urandom(16)
1583 timestamp = time.time()
1584 assert token is not None
1585 assert timestamp is not None
1586 self._raw_xsrf_token = (version, token, timestamp)
1587 return self._raw_xsrf_token
1589 def _decode_xsrf_token(
1590 self, cookie: str
1591 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
1592 """Convert a cookie string into a the tuple form returned by
1593 _get_raw_xsrf_token.
1594 """
1596 try:
1597 m = _signed_value_version_re.match(utf8(cookie))
1599 if m:
1600 version = int(m.group(1))
1601 if version == 2:
1602 _, mask_str, masked_token, timestamp_str = cookie.split("|")
1604 mask = binascii.a2b_hex(utf8(mask_str))
1605 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
1606 timestamp = int(timestamp_str)
1607 return version, token, timestamp
1608 else:
1609 # Treat unknown versions as not present instead of failing.
1610 raise Exception("Unknown xsrf cookie version")
1611 else:
1612 version = 1
1613 try:
1614 token = binascii.a2b_hex(utf8(cookie))
1615 except (binascii.Error, TypeError):
1616 token = utf8(cookie)
1617 # We don't have a usable timestamp in older versions.
1618 timestamp = int(time.time())
1619 return (version, token, timestamp)
1620 except Exception:
1621 # Catch exceptions and return nothing instead of failing.
1622 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
1623 return None, None, None
1625 def check_xsrf_cookie(self) -> None:
1626 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
1628 To prevent cross-site request forgery, we set an ``_xsrf``
1629 cookie and include the same value as a non-cookie
1630 field with all ``POST`` requests. If the two do not match, we
1631 reject the form submission as a potential forgery.
1633 The ``_xsrf`` value may be set as either a form field named ``_xsrf``
1634 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
1635 (the latter is accepted for compatibility with Django).
1637 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1639 .. versionchanged:: 3.2.2
1640 Added support for cookie version 2. Both versions 1 and 2 are
1641 supported.
1642 """
1643 # Prior to release 1.1.1, this check was ignored if the HTTP header
1644 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception
1645 # has been shown to be insecure and has been removed. For more
1646 # information please see
1647 # http://www.djangoproject.com/weblog/2011/feb/08/security/
1648 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
1649 input_token = (
1650 self.get_argument("_xsrf", None)
1651 or self.request.headers.get("X-Xsrftoken")
1652 or self.request.headers.get("X-Csrftoken")
1653 )
1654 if not input_token:
1655 raise HTTPError(403, "'_xsrf' argument missing from POST")
1656 _, token, _ = self._decode_xsrf_token(input_token)
1657 _, expected_token, _ = self._get_raw_xsrf_token()
1658 if not token:
1659 raise HTTPError(403, "'_xsrf' argument has invalid format")
1660 if not hmac.compare_digest(utf8(token), utf8(expected_token)):
1661 raise HTTPError(403, "XSRF cookie does not match POST argument")
1663 def xsrf_form_html(self) -> str:
1664 """An HTML ``<input/>`` element to be included with all POST forms.
1666 It defines the ``_xsrf`` input value, which we check on all POST
1667 requests to prevent cross-site request forgery. If you have set
1668 the ``xsrf_cookies`` application setting, you must include this
1669 HTML within all of your HTML forms.
1671 In a template, this method should be called with ``{% module
1672 xsrf_form_html() %}``
1674 See `check_xsrf_cookie()` above for more information.
1675 """
1676 return (
1677 '<input type="hidden" name="_xsrf" value="'
1678 + escape.xhtml_escape(self.xsrf_token)
1679 + '"/>'
1680 )
1682 def static_url(
1683 self, path: str, include_host: Optional[bool] = None, **kwargs: Any
1684 ) -> str:
1685 """Returns a static URL for the given relative static file path.
1687 This method requires you set the ``static_path`` setting in your
1688 application (which specifies the root directory of your static
1689 files).
1691 This method returns a versioned url (by default appending
1692 ``?v=<signature>``), which allows the static files to be
1693 cached indefinitely. This can be disabled by passing
1694 ``include_version=False`` (in the default implementation;
1695 other static file implementations are not required to support
1696 this, but they may support other options).
1698 By default this method returns URLs relative to the current
1699 host, but if ``include_host`` is true the URL returned will be
1700 absolute. If this handler has an ``include_host`` attribute,
1701 that value will be used as the default for all `static_url`
1702 calls that do not pass ``include_host`` as a keyword argument.
1704 """
1705 self.require_setting("static_path", "static_url")
1706 get_url = self.settings.get(
1707 "static_handler_class", StaticFileHandler
1708 ).make_static_url
1710 if include_host is None:
1711 include_host = getattr(self, "include_host", False)
1713 if include_host:
1714 base = self.request.protocol + "://" + self.request.host
1715 else:
1716 base = ""
1718 return base + get_url(self.settings, path, **kwargs)
1720 def require_setting(self, name: str, feature: str = "this feature") -> None:
1721 """Raises an exception if the given app setting is not defined."""
1722 if not self.application.settings.get(name):
1723 raise Exception(
1724 "You must define the '%s' setting in your "
1725 "application to use %s" % (name, feature)
1726 )
1728 def reverse_url(self, name: str, *args: Any) -> str:
1729 """Alias for `Application.reverse_url`."""
1730 return self.application.reverse_url(name, *args)
1732 def compute_etag(self) -> Optional[str]:
1733 """Computes the etag header to be used for this request.
1735 By default uses a hash of the content written so far.
1737 May be overridden to provide custom etag implementations,
1738 or may return None to disable tornado's default etag support.
1739 """
1740 hasher = hashlib.sha1()
1741 for part in self._write_buffer:
1742 hasher.update(part)
1743 return '"%s"' % hasher.hexdigest()
1745 def set_etag_header(self) -> None:
1746 """Sets the response's Etag header using ``self.compute_etag()``.
1748 Note: no header will be set if ``compute_etag()`` returns ``None``.
1750 This method is called automatically when the request is finished.
1751 """
1752 etag = self.compute_etag()
1753 if etag is not None:
1754 self.set_header("Etag", etag)
1756 def check_etag_header(self) -> bool:
1757 """Checks the ``Etag`` header against requests's ``If-None-Match``.
1759 Returns ``True`` if the request's Etag matches and a 304 should be
1760 returned. For example::
1762 self.set_etag_header()
1763 if self.check_etag_header():
1764 self.set_status(304)
1765 return
1767 This method is called automatically when the request is finished,
1768 but may be called earlier for applications that override
1769 `compute_etag` and want to do an early check for ``If-None-Match``
1770 before completing the request. The ``Etag`` header should be set
1771 (perhaps with `set_etag_header`) before calling this method.
1772 """
1773 computed_etag = utf8(self._headers.get("Etag", ""))
1774 # Find all weak and strong etag values from If-None-Match header
1775 # because RFC 7232 allows multiple etag values in a single header.
1776 etags = re.findall(
1777 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
1778 )
1779 if not computed_etag or not etags:
1780 return False
1782 match = False
1783 if etags[0] == b"*":
1784 match = True
1785 else:
1786 # Use a weak comparison when comparing entity-tags.
1787 def val(x: bytes) -> bytes:
1788 return x[2:] if x.startswith(b"W/") else x
1790 for etag in etags:
1791 if val(etag) == val(computed_etag):
1792 match = True
1793 break
1794 return match
1796 async def _execute(
1797 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
1798 ) -> None:
1799 """Executes this request with the given output transforms."""
1800 self._transforms = transforms
1801 try:
1802 if self.request.method not in self.SUPPORTED_METHODS:
1803 raise HTTPError(405)
1805 # If we're not in stream_request_body mode, this is the place where we parse the body.
1806 if not _has_stream_request_body(self.__class__):
1807 try:
1808 self.request._parse_body()
1809 except httputil.HTTPInputError as e:
1810 raise HTTPError(400, "Invalid body: %s" % e) from e
1812 self.path_args = [self.decode_argument(arg) for arg in args]
1813 self.path_kwargs = {
1814 k: self.decode_argument(v, name=k) for (k, v) in kwargs.items()
1815 }
1816 # If XSRF cookies are turned on, reject form submissions without
1817 # the proper cookie
1818 if self.request.method not in (
1819 "GET",
1820 "HEAD",
1821 "OPTIONS",
1822 ) and self.application.settings.get("xsrf_cookies"):
1823 self.check_xsrf_cookie()
1825 result = self.prepare()
1826 if result is not None:
1827 result = await result # type: ignore
1828 if self._prepared_future is not None:
1829 # Tell the Application we've finished with prepare()
1830 # and are ready for the body to arrive.
1831 future_set_result_unless_cancelled(self._prepared_future, None)
1832 if self._finished:
1833 return
1835 if _has_stream_request_body(self.__class__):
1836 # In streaming mode request.body is a Future that signals
1837 # the body has been completely received. The Future has no
1838 # result; the data has been passed to self.data_received
1839 # instead.
1840 try:
1841 await self.request._body_future
1842 except iostream.StreamClosedError:
1843 return
1845 method = getattr(self, self.request.method.lower())
1846 result = method(*self.path_args, **self.path_kwargs)
1847 if result is not None:
1848 result = await result
1849 if self._auto_finish and not self._finished:
1850 self.finish()
1851 except Exception as e:
1852 try:
1853 self._handle_request_exception(e)
1854 except Exception:
1855 app_log.error("Exception in exception handler", exc_info=True)
1856 finally:
1857 # Unset result to avoid circular references
1858 result = None
1859 if self._prepared_future is not None and not self._prepared_future.done():
1860 # In case we failed before setting _prepared_future, do it
1861 # now (to unblock the HTTP server). Note that this is not
1862 # in a finally block to avoid GC issues prior to Python 3.4.
1863 self._prepared_future.set_result(None)
1865 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
1866 """Implement this method to handle streamed request data.
1868 Requires the `.stream_request_body` decorator.
1870 May be a coroutine for flow control.
1871 """
1872 raise NotImplementedError()
1874 def _log(self) -> None:
1875 """Logs the current request.
1877 Sort of deprecated since this functionality was moved to the
1878 Application, but left in place for the benefit of existing apps
1879 that have overridden this method.
1880 """
1881 self.application.log_request(self)
1883 def _request_summary(self) -> str:
1884 return "{} {} ({})".format(
1885 self.request.method,
1886 self.request.uri,
1887 self.request.remote_ip,
1888 )
1890 def _handle_request_exception(self, e: BaseException) -> None:
1891 if isinstance(e, Finish):
1892 # Not an error; just finish the request without logging.
1893 if not self._finished:
1894 self.finish(*e.args)
1895 return
1896 try:
1897 self.log_exception(*sys.exc_info())
1898 except Exception:
1899 # An error here should still get a best-effort send_error()
1900 # to avoid leaking the connection.
1901 app_log.error("Error in exception logger", exc_info=True)
1902 if self._finished:
1903 # Extra errors after the request has been finished should
1904 # be logged, but there is no reason to continue to try and
1905 # send a response.
1906 return
1907 if isinstance(e, HTTPError):
1908 self.send_error(e.status_code, exc_info=sys.exc_info())
1909 else:
1910 self.send_error(500, exc_info=sys.exc_info())
1912 def log_exception(
1913 self,
1914 typ: "Optional[Type[BaseException]]",
1915 value: Optional[BaseException],
1916 tb: Optional[TracebackType],
1917 ) -> None:
1918 """Override to customize logging of uncaught exceptions.
1920 By default logs instances of `HTTPError` as warnings without
1921 stack traces (on the ``tornado.general`` logger), and all
1922 other exceptions as errors with stack traces (on the
1923 ``tornado.application`` logger).
1925 .. versionadded:: 3.1
1926 """
1927 if isinstance(value, HTTPError):
1928 log_message = value.get_message()
1929 if log_message:
1930 format = "%d %s: %s"
1931 args = [value.status_code, self._request_summary(), log_message]
1932 gen_log.warning(format, *args)
1933 else:
1934 app_log.error(
1935 "Uncaught exception %s\n%r",
1936 self._request_summary(),
1937 self.request,
1938 exc_info=(typ, value, tb), # type: ignore
1939 )
1941 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
1942 def render(*args, **kwargs) -> str: # type: ignore
1943 if not hasattr(self, "_active_modules"):
1944 self._active_modules = {} # type: Dict[str, UIModule]
1945 if name not in self._active_modules:
1946 self._active_modules[name] = module(self)
1947 rendered = self._active_modules[name].render(*args, **kwargs)
1948 return _unicode(rendered)
1950 return render
1952 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
1953 return lambda *args, **kwargs: method(self, *args, **kwargs)
1955 def _clear_representation_headers(self) -> None:
1956 # 304 responses should not contain representation metadata
1957 # headers (defined in
1958 # https://tools.ietf.org/html/rfc7231#section-3.1)
1959 # not explicitly allowed by
1960 # https://tools.ietf.org/html/rfc7232#section-4.1
1961 headers = ["Content-Encoding", "Content-Language", "Content-Type"]
1962 for h in headers:
1963 self.clear_header(h)
1966_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler)
1969def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]:
1970 """Apply to `RequestHandler` subclasses to enable streaming body support.
1972 This decorator implies the following changes:
1974 * `.HTTPServerRequest.body` is undefined, and body arguments will not
1975 be included in `RequestHandler.get_argument`.
1976 * `RequestHandler.prepare` is called when the request headers have been
1977 read instead of after the entire body has been read.
1978 * The subclass must define a method ``data_received(self, data):``, which
1979 will be called zero or more times as data is available. Note that
1980 if the request has an empty body, ``data_received`` may not be called.
1981 * ``prepare`` and ``data_received`` may return Futures (such as via
1982 ``@gen.coroutine``, in which case the next method will not be called
1983 until those futures have completed.
1984 * The regular HTTP method (``post``, ``put``, etc) will be called after
1985 the entire body has been read.
1987 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_
1988 for example usage.
1989 """ # noqa: E501
1990 if not issubclass(cls, RequestHandler):
1991 raise TypeError("expected subclass of RequestHandler, got %r", cls)
1992 cls._stream_request_body = True
1993 return cls
1996def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
1997 if not issubclass(cls, RequestHandler):
1998 raise TypeError("expected subclass of RequestHandler, got %r", cls)
1999 return cls._stream_request_body
2002def removeslash(
2003 method: Callable[..., Optional[Awaitable[None]]],
2004) -> Callable[..., Optional[Awaitable[None]]]:
2005 """Use this decorator to remove trailing slashes from the request path.
2007 For example, a request to ``/foo/`` would redirect to ``/foo`` with this
2008 decorator. Your request handler mapping should use a regular expression
2009 like ``r'/foo/*'`` in conjunction with using the decorator.
2010 """
2012 @functools.wraps(method)
2013 def wrapper( # type: ignore
2014 self: RequestHandler, *args, **kwargs
2015 ) -> Optional[Awaitable[None]]:
2016 if self.request.path.endswith("/"):
2017 if self.request.method in ("GET", "HEAD"):
2018 uri = self.request.path.rstrip("/")
2019 if uri: # don't try to redirect '/' to ''
2020 if self.request.query:
2021 uri += "?" + self.request.query
2022 self.redirect(uri, permanent=True)
2023 return None
2024 else:
2025 raise HTTPError(404)
2026 return method(self, *args, **kwargs)
2028 return wrapper
2031def addslash(
2032 method: Callable[..., Optional[Awaitable[None]]],
2033) -> Callable[..., Optional[Awaitable[None]]]:
2034 """Use this decorator to add a missing trailing slash to the request path.
2036 For example, a request to ``/foo`` would redirect to ``/foo/`` with this
2037 decorator. Your request handler mapping should use a regular expression
2038 like ``r'/foo/?'`` in conjunction with using the decorator.
2039 """
2041 @functools.wraps(method)
2042 def wrapper( # type: ignore
2043 self: RequestHandler, *args, **kwargs
2044 ) -> Optional[Awaitable[None]]:
2045 if not self.request.path.endswith("/"):
2046 if self.request.method in ("GET", "HEAD"):
2047 uri = self.request.path + "/"
2048 if self.request.query:
2049 uri += "?" + self.request.query
2050 self.redirect(uri, permanent=True)
2051 return None
2052 raise HTTPError(404)
2053 return method(self, *args, **kwargs)
2055 return wrapper
2058class _ApplicationRouter(ReversibleRuleRouter):
2059 """Routing implementation used internally by `Application`.
2061 Provides a binding between `Application` and `RequestHandler`.
2062 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
2063 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
2064 * it allows to use a list/tuple of rules as `~.routing.Rule` target.
2065 ``process_rule`` implementation will substitute this list with an appropriate
2066 `_ApplicationRouter` instance.
2067 """
2069 def __init__(
2070 self, application: "Application", rules: Optional[_RuleList] = None
2071 ) -> None:
2072 assert isinstance(application, Application)
2073 self.application = application
2074 super().__init__(rules)
2076 def process_rule(self, rule: Rule) -> Rule:
2077 rule = super().process_rule(rule)
2079 if isinstance(rule.target, (list, tuple)):
2080 rule.target = _ApplicationRouter(
2081 self.application, rule.target # type: ignore
2082 )
2084 return rule
2086 def get_target_delegate(
2087 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
2088 ) -> Optional[httputil.HTTPMessageDelegate]:
2089 if isclass(target) and issubclass(target, RequestHandler):
2090 return self.application.get_handler_delegate(
2091 request, target, **target_params
2092 )
2094 return super().get_target_delegate(target, request, **target_params)
2097class Application(ReversibleRouter):
2098 r"""A collection of request handlers that make up a web application.
2100 Instances of this class are callable and can be passed directly to
2101 HTTPServer to serve the application::
2103 application = web.Application([
2104 (r"/", MainPageHandler),
2105 ])
2106 http_server = httpserver.HTTPServer(application)
2107 http_server.listen(8080)
2109 The constructor for this class takes in a list of `~.routing.Rule`
2110 objects or tuples of values corresponding to the arguments of
2111 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
2112 the values in square brackets being optional. The default matcher is
2113 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
2114 instead of ``(PathMatches(regexp), target)``.
2116 A common routing target is a `RequestHandler` subclass, but you can also
2117 use lists of rules as a target, which create a nested routing configuration::
2119 application = web.Application([
2120 (HostMatches("example.com"), [
2121 (r"/", MainPageHandler),
2122 (r"/feed", FeedHandler),
2123 ]),
2124 ])
2126 In addition to this you can use nested `~.routing.Router` instances,
2127 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
2128 (see `~.routing` module docs for more information).
2130 When we receive requests, we iterate over the list in order and
2131 instantiate an instance of the first request class whose regexp
2132 matches the request path. The request class can be specified as
2133 either a class object or a (fully-qualified) name.
2135 A dictionary may be passed as the third element (``target_kwargs``)
2136 of the tuple, which will be used as keyword arguments to the handler's
2137 constructor and `~RequestHandler.initialize` method. This pattern
2138 is used for the `StaticFileHandler` in this example (note that a
2139 `StaticFileHandler` can be installed automatically with the
2140 static_path setting described below)::
2142 application = web.Application([
2143 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2144 ])
2146 We support virtual hosts with the `add_handlers` method, which takes in
2147 a host regular expression as the first argument::
2149 application.add_handlers(r"www\.myhost\.com", [
2150 (r"/article/([0-9]+)", ArticleHandler),
2151 ])
2153 If there's no match for the current request's host, then ``default_host``
2154 parameter value is matched against host regular expressions.
2157 .. warning::
2159 Applications that do not use TLS may be vulnerable to :ref:`DNS
2160 rebinding <dnsrebinding>` attacks. This attack is especially
2161 relevant to applications that only listen on ``127.0.0.1`` or
2162 other private networks. Appropriate host patterns must be used
2163 (instead of the default of ``r'.*'``) to prevent this risk. The
2164 ``default_host`` argument must not be used in applications that
2165 may be vulnerable to DNS rebinding.
2167 You can serve static files by sending the ``static_path`` setting
2168 as a keyword argument. We will serve those files from the
2169 ``/static/`` URI (this is configurable with the
2170 ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
2171 and ``/robots.txt`` from the same directory. A custom subclass of
2172 `StaticFileHandler` can be specified with the
2173 ``static_handler_class`` setting.
2175 .. versionchanged:: 4.5
2176 Integration with the new `tornado.routing` module.
2178 """
2180 def __init__(
2181 self,
2182 handlers: Optional[_RuleList] = None,
2183 default_host: Optional[str] = None,
2184 transforms: Optional[List[Type["OutputTransform"]]] = None,
2185 **settings: Any,
2186 ) -> None:
2187 if transforms is None:
2188 self.transforms = [] # type: List[Type[OutputTransform]]
2189 if settings.get("compress_response") or settings.get("gzip"):
2190 self.transforms.append(GZipContentEncoding)
2191 else:
2192 self.transforms = transforms
2193 self.default_host = default_host
2194 self.settings = settings
2195 self.ui_modules = {
2196 "linkify": _linkify,
2197 "xsrf_form_html": _xsrf_form_html,
2198 "Template": TemplateModule,
2199 }
2200 self.ui_methods = {} # type: Dict[str, Callable[..., str]]
2201 self._load_ui_modules(settings.get("ui_modules", {}))
2202 self._load_ui_methods(settings.get("ui_methods", {}))
2203 if self.settings.get("static_path"):
2204 path = self.settings["static_path"]
2205 handlers = list(handlers or [])
2206 static_url_prefix = settings.get("static_url_prefix", "/static/")
2207 static_handler_class = settings.get(
2208 "static_handler_class", StaticFileHandler
2209 )
2210 static_handler_args = settings.get("static_handler_args", {})
2211 static_handler_args["path"] = path
2212 for pattern in [
2213 re.escape(static_url_prefix) + r"(.*)",
2214 r"/(favicon\.ico)",
2215 r"/(robots\.txt)",
2216 ]:
2217 handlers.insert(0, (pattern, static_handler_class, static_handler_args))
2219 if self.settings.get("debug"):
2220 self.settings.setdefault("autoreload", True)
2221 self.settings.setdefault("compiled_template_cache", False)
2222 self.settings.setdefault("static_hash_cache", False)
2223 self.settings.setdefault("serve_traceback", True)
2225 self.wildcard_router = _ApplicationRouter(self, handlers)
2226 self.default_router = _ApplicationRouter(
2227 self, [Rule(AnyMatches(), self.wildcard_router)]
2228 )
2230 # Automatically reload modified modules
2231 if self.settings.get("autoreload"):
2232 from tornado import autoreload
2234 autoreload.start()
2236 def listen(
2237 self,
2238 port: int,
2239 address: Optional[str] = None,
2240 *,
2241 family: socket.AddressFamily = socket.AF_UNSPEC,
2242 backlog: int = tornado.netutil._DEFAULT_BACKLOG,
2243 flags: Optional[int] = None,
2244 reuse_port: bool = False,
2245 **kwargs: Any,
2246 ) -> HTTPServer:
2247 """Starts an HTTP server for this application on the given port.
2249 This is a convenience alias for creating an `.HTTPServer` object and
2250 calling its listen method. Keyword arguments not supported by
2251 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`
2252 constructor. For advanced uses (e.g. multi-process mode), do not use
2253 this method; create an `.HTTPServer` and call its
2254 `.TCPServer.bind`/`.TCPServer.start` methods directly.
2256 Note that after calling this method you still need to call
2257 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start
2258 the server.
2260 Returns the `.HTTPServer` object.
2262 .. versionchanged:: 4.3
2263 Now returns the `.HTTPServer` object.
2265 .. versionchanged:: 6.2
2266 Added support for new keyword arguments in `.TCPServer.listen`,
2267 including ``reuse_port``.
2268 """
2269 server = HTTPServer(self, **kwargs)
2270 server.listen(
2271 port,
2272 address=address,
2273 family=family,
2274 backlog=backlog,
2275 flags=flags,
2276 reuse_port=reuse_port,
2277 )
2278 return server
2280 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
2281 """Appends the given handlers to our handler list.
2283 Host patterns are processed sequentially in the order they were
2284 added. All matching patterns will be considered.
2285 """
2286 host_matcher = HostMatches(host_pattern)
2287 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
2289 self.default_router.rules.insert(-1, rule)
2291 if self.default_host is not None:
2292 self.wildcard_router.add_rules(
2293 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
2294 )
2296 def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
2297 self.transforms.append(transform_class)
2299 def _load_ui_methods(self, methods: Any) -> None:
2300 if isinstance(methods, types.ModuleType):
2301 self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)})
2302 elif isinstance(methods, list):
2303 for m in methods:
2304 self._load_ui_methods(m)
2305 else:
2306 for name, fn in methods.items():
2307 if (
2308 not name.startswith("_")
2309 and hasattr(fn, "__call__")
2310 and name[0].lower() == name[0]
2311 ):
2312 self.ui_methods[name] = fn
2314 def _load_ui_modules(self, modules: Any) -> None:
2315 if isinstance(modules, types.ModuleType):
2316 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)})
2317 elif isinstance(modules, list):
2318 for m in modules:
2319 self._load_ui_modules(m)
2320 else:
2321 assert isinstance(modules, dict)
2322 for name, cls in modules.items():
2323 try:
2324 if issubclass(cls, UIModule):
2325 self.ui_modules[name] = cls
2326 except TypeError:
2327 pass
2329 def __call__(
2330 self, request: httputil.HTTPServerRequest
2331 ) -> Optional[Awaitable[None]]:
2332 # Legacy HTTPServer interface
2333 dispatcher = self.find_handler(request)
2334 return dispatcher.execute()
2336 def find_handler(
2337 self, request: httputil.HTTPServerRequest, **kwargs: Any
2338 ) -> "_HandlerDelegate":
2339 route = self.default_router.find_handler(request)
2340 if route is not None:
2341 return cast("_HandlerDelegate", route)
2343 if self.settings.get("default_handler_class"):
2344 return self.get_handler_delegate(
2345 request,
2346 self.settings["default_handler_class"],
2347 self.settings.get("default_handler_args", {}),
2348 )
2350 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
2352 def get_handler_delegate(
2353 self,
2354 request: httputil.HTTPServerRequest,
2355 target_class: Type[RequestHandler],
2356 target_kwargs: Optional[Dict[str, Any]] = None,
2357 path_args: Optional[List[bytes]] = None,
2358 path_kwargs: Optional[Dict[str, bytes]] = None,
2359 ) -> "_HandlerDelegate":
2360 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
2361 for application and `RequestHandler` subclass.
2363 :arg httputil.HTTPServerRequest request: current HTTP request.
2364 :arg RequestHandler target_class: a `RequestHandler` class.
2365 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
2366 :arg list path_args: positional arguments for ``target_class`` HTTP method that
2367 will be executed while handling a request (``get``, ``post`` or any other).
2368 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
2369 """
2370 return _HandlerDelegate(
2371 self, request, target_class, target_kwargs, path_args, path_kwargs
2372 )
2374 def reverse_url(self, name: str, *args: Any) -> str:
2375 """Returns a URL path for handler named ``name``
2377 The handler must be added to the application as a named `URLSpec`.
2379 Args will be substituted for capturing groups in the `URLSpec` regex.
2380 They will be converted to strings if necessary, encoded as utf8,
2381 and url-escaped.
2382 """
2383 reversed_url = self.default_router.reverse_url(name, *args)
2384 if reversed_url is not None:
2385 return reversed_url
2387 raise KeyError("%s not found in named urls" % name)
2389 def log_request(self, handler: RequestHandler) -> None:
2390 """Writes a completed HTTP request to the logs.
2392 By default writes to the python root logger. To change
2393 this behavior either subclass Application and override this method,
2394 or pass a function in the application settings dictionary as
2395 ``log_function``.
2396 """
2397 if "log_function" in self.settings:
2398 self.settings["log_function"](handler)
2399 return
2400 if handler.get_status() < 400:
2401 log_method = access_log.info
2402 elif handler.get_status() < 500:
2403 log_method = access_log.warning
2404 else:
2405 log_method = access_log.error
2406 request_time = 1000.0 * handler.request.request_time()
2407 log_method(
2408 "%d %s %.2fms",
2409 handler.get_status(),
2410 handler._request_summary(),
2411 request_time,
2412 )
2415class _HandlerDelegate(httputil.HTTPMessageDelegate):
2416 def __init__(
2417 self,
2418 application: Application,
2419 request: httputil.HTTPServerRequest,
2420 handler_class: Type[RequestHandler],
2421 handler_kwargs: Optional[Dict[str, Any]],
2422 path_args: Optional[List[bytes]],
2423 path_kwargs: Optional[Dict[str, bytes]],
2424 ) -> None:
2425 self.application = application
2426 self.connection = request.connection
2427 self.request = request
2428 self.handler_class = handler_class
2429 self.handler_kwargs = handler_kwargs or {}
2430 self.path_args = path_args or []
2431 self.path_kwargs = path_kwargs or {}
2432 self.chunks = [] # type: List[bytes]
2433 self.stream_request_body = _has_stream_request_body(self.handler_class)
2435 def headers_received(
2436 self,
2437 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
2438 headers: httputil.HTTPHeaders,
2439 ) -> Optional[Awaitable[None]]:
2440 if self.stream_request_body:
2441 self.request._body_future = Future()
2442 return self.execute()
2443 return None
2445 def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
2446 if self.stream_request_body:
2447 return self.handler.data_received(data)
2448 else:
2449 self.chunks.append(data)
2450 return None
2452 def finish(self) -> None:
2453 if self.stream_request_body:
2454 future_set_result_unless_cancelled(self.request._body_future, None)
2455 else:
2456 # Note that the body gets parsed in RequestHandler._execute so it can be in
2457 # the right exception handler scope.
2458 self.request.body = b"".join(self.chunks)
2459 self.execute()
2461 def on_connection_close(self) -> None:
2462 if self.stream_request_body:
2463 self.handler.on_connection_close()
2464 else:
2465 self.chunks = None # type: ignore
2467 def execute(self) -> Optional[Awaitable[None]]:
2468 # If template cache is disabled (usually in the debug mode),
2469 # re-compile templates and reload static files on every
2470 # request so you don't need to restart to see changes
2471 if not self.application.settings.get("compiled_template_cache", True):
2472 with RequestHandler._template_loader_lock:
2473 for loader in RequestHandler._template_loaders.values():
2474 loader.reset()
2475 if not self.application.settings.get("static_hash_cache", True):
2476 static_handler_class = self.application.settings.get(
2477 "static_handler_class", StaticFileHandler
2478 )
2479 static_handler_class.reset()
2481 self.handler = self.handler_class(
2482 self.application, self.request, **self.handler_kwargs
2483 )
2484 transforms = [t(self.request) for t in self.application.transforms]
2486 if self.stream_request_body:
2487 self.handler._prepared_future = Future()
2488 # Note that if an exception escapes handler._execute it will be
2489 # trapped in the Future it returns (which we are ignoring here,
2490 # leaving it to be logged when the Future is GC'd).
2491 # However, that shouldn't happen because _execute has a blanket
2492 # except handler, and we cannot easily access the IOLoop here to
2493 # call add_future (because of the requirement to remain compatible
2494 # with WSGI)
2495 fut = gen.convert_yielded(
2496 self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
2497 )
2498 fut.add_done_callback(lambda f: f.result())
2499 # If we are streaming the request body, then execute() is finished
2500 # when the handler has prepared to receive the body. If not,
2501 # it doesn't matter when execute() finishes (so we return None)
2502 return self.handler._prepared_future
2505class HTTPError(Exception):
2506 """An exception that will turn into an HTTP error response.
2508 Raising an `HTTPError` is a convenient alternative to calling
2509 `RequestHandler.send_error` since it automatically ends the
2510 current function.
2512 To customize the response sent with an `HTTPError`, override
2513 `RequestHandler.write_error`.
2515 :arg int status_code: HTTP status code. Must be listed in
2516 `httplib.responses <http.client.responses>` unless the ``reason``
2517 keyword argument is given.
2518 :arg str log_message: Message to be written to the log for this error
2519 (will not be shown to the user unless the `Application` is in debug
2520 mode). May contain ``%s``-style placeholders, which will be filled
2521 in with remaining positional parameters.
2522 :arg str reason: Keyword-only argument. The HTTP "reason" phrase
2523 to pass in the status line along with ``status_code``. Normally
2524 determined automatically from ``status_code``, but can be used
2525 to use a non-standard numeric code.
2526 """
2528 def __init__(
2529 self,
2530 status_code: int = 500,
2531 log_message: Optional[str] = None,
2532 *args: Any,
2533 **kwargs: Any,
2534 ) -> None:
2535 self.status_code = status_code
2536 self._log_message = log_message
2537 self.args = args
2538 self.reason = kwargs.get("reason", None)
2540 @property
2541 def log_message(self) -> Optional[str]:
2542 """
2543 A backwards compatible way of accessing log_message.
2544 """
2545 if self._log_message and not self.args:
2546 return self._log_message.replace("%", "%%")
2547 return self._log_message
2549 def get_message(self) -> Optional[str]:
2550 if self._log_message and self.args:
2551 return self._log_message % self.args
2552 return self._log_message
2554 def __str__(self) -> str:
2555 message = "HTTP %d: %s" % (
2556 self.status_code,
2557 self.reason or httputil.responses.get(self.status_code, "Unknown"),
2558 )
2559 log_message = self.get_message()
2560 if log_message:
2561 return message + " (" + log_message + ")"
2562 else:
2563 return message
2566class Finish(Exception):
2567 """An exception that ends the request without producing an error response.
2569 When `Finish` is raised in a `RequestHandler`, the request will
2570 end (calling `RequestHandler.finish` if it hasn't already been
2571 called), but the error-handling methods (including
2572 `RequestHandler.write_error`) will not be called.
2574 If `Finish()` was created with no arguments, the pending response
2575 will be sent as-is. If `Finish()` was given an argument, that
2576 argument will be passed to `RequestHandler.finish()`.
2578 This can be a more convenient way to implement custom error pages
2579 than overriding ``write_error`` (especially in library code)::
2581 if self.current_user is None:
2582 self.set_status(401)
2583 self.set_header('WWW-Authenticate', 'Basic realm="something"')
2584 raise Finish()
2586 .. versionchanged:: 4.3
2587 Arguments passed to ``Finish()`` will be passed on to
2588 `RequestHandler.finish`.
2589 """
2591 pass
2594class MissingArgumentError(HTTPError):
2595 """Exception raised by `RequestHandler.get_argument`.
2597 This is a subclass of `HTTPError`, so if it is uncaught a 400 response
2598 code will be used instead of 500 (and a stack trace will not be logged).
2600 .. versionadded:: 3.1
2601 """
2603 def __init__(self, arg_name: str) -> None:
2604 super().__init__(400, "Missing argument %s" % arg_name)
2605 self.arg_name = arg_name
2608class ErrorHandler(RequestHandler):
2609 """Generates an error response with ``status_code`` for all requests."""
2611 def initialize(self, status_code: int) -> None:
2612 self.set_status(status_code)
2614 def prepare(self) -> None:
2615 raise HTTPError(self._status_code)
2617 def check_xsrf_cookie(self) -> None:
2618 # POSTs to an ErrorHandler don't actually have side effects,
2619 # so we don't need to check the xsrf token. This allows POSTs
2620 # to the wrong url to return a 404 instead of 403.
2621 pass
2624class RedirectHandler(RequestHandler):
2625 """Redirects the client to the given URL for all GET requests.
2627 You should provide the keyword argument ``url`` to the handler, e.g.::
2629 application = web.Application([
2630 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
2631 ])
2633 `RedirectHandler` supports regular expression substitutions. E.g., to
2634 swap the first and second parts of a path while preserving the remainder::
2636 application = web.Application([
2637 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
2638 ])
2640 The final URL is formatted with `str.format` and the substrings that match
2641 the capturing groups. In the above example, a request to "/a/b/c" would be
2642 formatted like::
2644 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c"
2646 Use Python's :ref:`format string syntax <formatstrings>` to customize how
2647 values are substituted.
2649 .. versionchanged:: 4.5
2650 Added support for substitutions into the destination URL.
2652 .. versionchanged:: 5.0
2653 If any query arguments are present, they will be copied to the
2654 destination URL.
2655 """
2657 def initialize(self, url: str, permanent: bool = True) -> None:
2658 self._url = url
2659 self._permanent = permanent
2661 def get(self, *args: Any, **kwargs: Any) -> None:
2662 to_url = self._url.format(*args, **kwargs)
2663 if self.request.query_arguments:
2664 # TODO: figure out typing for the next line.
2665 to_url = httputil.url_concat(
2666 to_url,
2667 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore
2668 )
2669 self.redirect(to_url, permanent=self._permanent)
2672class StaticFileHandler(RequestHandler):
2673 """A simple handler that can serve static content from a directory.
2675 A `StaticFileHandler` is configured automatically if you pass the
2676 ``static_path`` keyword argument to `Application`. This handler
2677 can be customized with the ``static_url_prefix``, ``static_handler_class``,
2678 and ``static_handler_args`` settings.
2680 To map an additional path to this handler for a static data directory
2681 you would add a line to your application like::
2683 application = web.Application([
2684 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2685 ])
2687 The handler constructor requires a ``path`` argument, which specifies the
2688 local root directory of the content to be served.
2690 Note that a capture group in the regex is required to parse the value for
2691 the ``path`` argument to the get() method (different than the constructor
2692 argument above); see `URLSpec` for details.
2694 To serve a file like ``index.html`` automatically when a directory is
2695 requested, set ``static_handler_args=dict(default_filename="index.html")``
2696 in your application settings, or add ``default_filename`` as an initializer
2697 argument for your ``StaticFileHandler``.
2699 To maximize the effectiveness of browser caching, this class supports
2700 versioned urls (by default using the argument ``?v=``). If a version
2701 is given, we instruct the browser to cache this file indefinitely.
2702 `make_static_url` (also available as `RequestHandler.static_url`) can
2703 be used to construct a versioned url.
2705 This handler is intended primarily for use in development and light-duty
2706 file serving; for heavy traffic it will be more efficient to use
2707 a dedicated static file server (such as nginx or Apache). We support
2708 the HTTP ``Accept-Ranges`` mechanism to return partial content (because
2709 some browsers require this functionality to be present to seek in
2710 HTML5 audio or video).
2712 **Subclassing notes**
2714 This class is designed to be extensible by subclassing, but because
2715 of the way static urls are generated with class methods rather than
2716 instance methods, the inheritance patterns are somewhat unusual.
2717 Be sure to use the ``@classmethod`` decorator when overriding a
2718 class method. Instance methods may use the attributes ``self.path``
2719 ``self.absolute_path``, and ``self.modified``.
2721 Subclasses should only override methods discussed in this section;
2722 overriding other methods is error-prone. Overriding
2723 ``StaticFileHandler.get`` is particularly problematic due to the
2724 tight coupling with ``compute_etag`` and other methods.
2726 To change the way static urls are generated (e.g. to match the behavior
2727 of another server or CDN), override `make_static_url`, `parse_url_path`,
2728 `get_cache_time`, and/or `get_version`.
2730 To replace all interaction with the filesystem (e.g. to serve
2731 static content from a database), override `get_content`,
2732 `get_content_size`, `get_modified_time`, `get_absolute_path`, and
2733 `validate_absolute_path`.
2735 .. versionchanged:: 3.1
2736 Many of the methods for subclasses were added in Tornado 3.1.
2737 """
2739 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
2741 _static_hashes = {} # type: Dict[str, Optional[str]]
2742 _lock = threading.Lock() # protects _static_hashes
2744 def initialize(self, path: str, default_filename: Optional[str] = None) -> None:
2745 self.root = path
2746 self.default_filename = default_filename
2748 @classmethod
2749 def reset(cls) -> None:
2750 with cls._lock:
2751 cls._static_hashes = {}
2753 def head(self, path: str) -> Awaitable[None]:
2754 return self.get(path, include_body=False)
2756 async def get(self, path: str, include_body: bool = True) -> None:
2757 # Set up our path instance variables.
2758 self.path = self.parse_url_path(path)
2759 del path # make sure we don't refer to path instead of self.path again
2760 absolute_path = self.get_absolute_path(self.root, self.path)
2761 self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
2762 if self.absolute_path is None:
2763 return
2765 self.modified = self.get_modified_time()
2766 self.set_headers()
2768 if self.should_return_304():
2769 self.set_status(304)
2770 return
2772 request_range = None
2773 range_header = self.request.headers.get("Range")
2774 if range_header:
2775 # As per RFC 2616 14.16, if an invalid Range header is specified,
2776 # the request will be treated as if the header didn't exist.
2777 request_range = httputil._parse_request_range(range_header)
2779 size = self.get_content_size()
2780 if request_range:
2781 start, end = request_range
2782 if start is not None and start < 0:
2783 start += size
2784 if start < 0:
2785 start = 0
2786 if (
2787 start is not None
2788 and (start >= size or (end is not None and start >= end))
2789 ) or end == 0:
2790 # As per RFC 2616 14.35.1, a range is not satisfiable only: if
2791 # the first requested byte is equal to or greater than the
2792 # content, or when a suffix with length 0 is specified.
2793 # https://tools.ietf.org/html/rfc7233#section-2.1
2794 # A byte-range-spec is invalid if the last-byte-pos value is present
2795 # and less than the first-byte-pos.
2796 self.set_status(416) # Range Not Satisfiable
2797 self.set_header("Content-Type", "text/plain")
2798 self.set_header("Content-Range", f"bytes */{size}")
2799 return
2800 if end is not None and end > size:
2801 # Clients sometimes blindly use a large range to limit their
2802 # download size; cap the endpoint at the actual file size.
2803 end = size
2804 # Note: only return HTTP 206 if less than the entire range has been
2805 # requested. Not only is this semantically correct, but Chrome
2806 # refuses to play audio if it gets an HTTP 206 in response to
2807 # ``Range: bytes=0-``.
2808 if size != (end or size) - (start or 0):
2809 self.set_status(206) # Partial Content
2810 self.set_header(
2811 "Content-Range", httputil._get_content_range(start, end, size)
2812 )
2813 else:
2814 start = end = None
2816 if start is not None and end is not None:
2817 content_length = end - start
2818 elif end is not None:
2819 content_length = end
2820 elif start is not None:
2821 content_length = size - start
2822 else:
2823 content_length = size
2824 self.set_header("Content-Length", content_length)
2826 if include_body:
2827 content = self.get_content(self.absolute_path, start, end)
2828 if isinstance(content, bytes):
2829 content = [content]
2830 for chunk in content:
2831 try:
2832 self.write(chunk)
2833 await self.flush()
2834 except iostream.StreamClosedError:
2835 return
2836 else:
2837 assert self.request.method == "HEAD"
2839 def compute_etag(self) -> Optional[str]:
2840 """Sets the ``Etag`` header based on static url version.
2842 This allows efficient ``If-None-Match`` checks against cached
2843 versions, and sends the correct ``Etag`` for a partial response
2844 (i.e. the same ``Etag`` as the full file).
2846 .. versionadded:: 3.1
2847 """
2848 assert self.absolute_path is not None
2849 version_hash = self._get_cached_version(self.absolute_path)
2850 if not version_hash:
2851 return None
2852 return f'"{version_hash}"'
2854 def set_headers(self) -> None:
2855 """Sets the content and caching headers on the response.
2857 .. versionadded:: 3.1
2858 """
2859 self.set_header("Accept-Ranges", "bytes")
2860 self.set_etag_header()
2862 if self.modified is not None:
2863 self.set_header("Last-Modified", self.modified)
2865 content_type = self.get_content_type()
2866 if content_type:
2867 self.set_header("Content-Type", content_type)
2869 cache_time = self.get_cache_time(self.path, self.modified, content_type)
2870 if cache_time > 0:
2871 self.set_header(
2872 "Expires",
2873 datetime.datetime.now(datetime.timezone.utc)
2874 + datetime.timedelta(seconds=cache_time),
2875 )
2876 self.set_header("Cache-Control", "max-age=" + str(cache_time))
2878 self.set_extra_headers(self.path)
2880 def should_return_304(self) -> bool:
2881 """Returns True if the headers indicate that we should return 304.
2883 .. versionadded:: 3.1
2884 """
2885 # If client sent If-None-Match, use it, ignore If-Modified-Since
2886 if self.request.headers.get("If-None-Match"):
2887 return self.check_etag_header()
2889 # Check the If-Modified-Since, and don't send the result if the
2890 # content has not been modified
2891 ims_value = self.request.headers.get("If-Modified-Since")
2892 if ims_value is not None:
2893 try:
2894 if_since = email.utils.parsedate_to_datetime(ims_value)
2895 except Exception:
2896 return False
2897 if if_since.tzinfo is None:
2898 if_since = if_since.replace(tzinfo=datetime.timezone.utc)
2899 assert self.modified is not None
2900 if if_since >= self.modified:
2901 return True
2903 return False
2905 @classmethod
2906 def get_absolute_path(cls, root: str, path: str) -> str:
2907 """Returns the absolute location of ``path`` relative to ``root``.
2909 ``root`` is the path configured for this `StaticFileHandler`
2910 (in most cases the ``static_path`` `Application` setting).
2912 This class method may be overridden in subclasses. By default
2913 it returns a filesystem path, but other strings may be used
2914 as long as they are unique and understood by the subclass's
2915 overridden `get_content`.
2917 .. versionadded:: 3.1
2918 """
2919 abspath = os.path.abspath(os.path.join(root, path))
2920 return abspath
2922 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
2923 """Validate and return the absolute path.
2925 ``root`` is the configured path for the `StaticFileHandler`,
2926 and ``path`` is the result of `get_absolute_path`
2928 This is an instance method called during request processing,
2929 so it may raise `HTTPError` or use methods like
2930 `RequestHandler.redirect` (return None after redirecting to
2931 halt further processing). This is where 404 errors for missing files
2932 are generated.
2934 This method may modify the path before returning it, but note that
2935 any such modifications will not be understood by `make_static_url`.
2937 In instance methods, this method's result is available as
2938 ``self.absolute_path``.
2940 .. versionadded:: 3.1
2941 """
2942 # os.path.abspath strips a trailing /.
2943 # We must add it back to `root` so that we only match files
2944 # in a directory named `root` instead of files starting with
2945 # that prefix.
2946 root = os.path.abspath(root)
2947 if not root.endswith(os.path.sep):
2948 # abspath always removes a trailing slash, except when
2949 # root is '/'. This is an unusual case, but several projects
2950 # have independently discovered this technique to disable
2951 # Tornado's path validation and (hopefully) do their own,
2952 # so we need to support it.
2953 root += os.path.sep
2954 # The trailing slash also needs to be temporarily added back
2955 # the requested path so a request to root/ will match.
2956 if not (absolute_path + os.path.sep).startswith(root):
2957 raise HTTPError(403, "%s is not in root static directory", self.path)
2958 if os.path.isdir(absolute_path) and self.default_filename is not None:
2959 # need to look at the request.path here for when path is empty
2960 # but there is some prefix to the path that was already
2961 # trimmed by the routing
2962 if not self.request.path.endswith("/"):
2963 if self.request.path.startswith("//"):
2964 # A redirect with two initial slashes is a "protocol-relative" URL.
2965 # This means the next path segment is treated as a hostname instead
2966 # of a part of the path, making this effectively an open redirect.
2967 # Reject paths starting with two slashes to prevent this.
2968 # This is only reachable under certain configurations.
2969 raise HTTPError(
2970 403, "cannot redirect path with two initial slashes"
2971 )
2972 self.redirect(self.request.path + "/", permanent=True)
2973 return None
2974 absolute_path = os.path.join(absolute_path, self.default_filename)
2975 if not os.path.exists(absolute_path):
2976 raise HTTPError(404)
2977 if not os.path.isfile(absolute_path):
2978 raise HTTPError(403, "%s is not a file", self.path)
2979 return absolute_path
2981 @classmethod
2982 def get_content(
2983 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None
2984 ) -> Generator[bytes, None, None]:
2985 """Retrieve the content of the requested resource which is located
2986 at the given absolute path.
2988 This class method may be overridden by subclasses. Note that its
2989 signature is different from other overridable class methods
2990 (no ``settings`` argument); this is deliberate to ensure that
2991 ``abspath`` is able to stand on its own as a cache key.
2993 This method should either return a byte string or an iterator
2994 of byte strings. The latter is preferred for large files
2995 as it helps reduce memory fragmentation.
2997 .. versionadded:: 3.1
2998 """
2999 with open(abspath, "rb") as file:
3000 if start is not None:
3001 file.seek(start)
3002 if end is not None:
3003 remaining = end - (start or 0) # type: Optional[int]
3004 else:
3005 remaining = None
3006 while True:
3007 chunk_size = 64 * 1024
3008 if remaining is not None and remaining < chunk_size:
3009 chunk_size = remaining
3010 chunk = file.read(chunk_size)
3011 if chunk:
3012 if remaining is not None:
3013 remaining -= len(chunk)
3014 yield chunk
3015 else:
3016 if remaining is not None:
3017 assert remaining == 0
3018 return
3020 @classmethod
3021 def get_content_version(cls, abspath: str) -> str:
3022 """Returns a version string for the resource at the given path.
3024 This class method may be overridden by subclasses. The
3025 default implementation is a SHA-512 hash of the file's contents.
3027 .. versionadded:: 3.1
3028 """
3029 data = cls.get_content(abspath)
3030 hasher = hashlib.sha512()
3031 if isinstance(data, bytes):
3032 hasher.update(data)
3033 else:
3034 for chunk in data:
3035 hasher.update(chunk)
3036 return hasher.hexdigest()
3038 def _stat(self) -> os.stat_result:
3039 assert self.absolute_path is not None
3040 if not hasattr(self, "_stat_result"):
3041 self._stat_result = os.stat(self.absolute_path)
3042 return self._stat_result
3044 def get_content_size(self) -> int:
3045 """Retrieve the total size of the resource at the given path.
3047 This method may be overridden by subclasses.
3049 .. versionadded:: 3.1
3051 .. versionchanged:: 4.0
3052 This method is now always called, instead of only when
3053 partial results are requested.
3054 """
3055 stat_result = self._stat()
3056 return stat_result.st_size
3058 def get_modified_time(self) -> Optional[datetime.datetime]:
3059 """Returns the time that ``self.absolute_path`` was last modified.
3061 May be overridden in subclasses. Should return a `~datetime.datetime`
3062 object or None.
3064 .. versionadded:: 3.1
3066 .. versionchanged:: 6.4
3067 Now returns an aware datetime object instead of a naive one.
3068 Subclasses that override this method may return either kind.
3069 """
3070 stat_result = self._stat()
3071 # NOTE: Historically, this used stat_result[stat.ST_MTIME],
3072 # which truncates the fractional portion of the timestamp. It
3073 # was changed from that form to stat_result.st_mtime to
3074 # satisfy mypy (which disallows the bracket operator), but the
3075 # latter form returns a float instead of an int. For
3076 # consistency with the past (and because we have a unit test
3077 # that relies on this), we truncate the float here, although
3078 # I'm not sure that's the right thing to do.
3079 modified = datetime.datetime.fromtimestamp(
3080 int(stat_result.st_mtime), datetime.timezone.utc
3081 )
3082 return modified
3084 def get_content_type(self) -> str:
3085 """Returns the ``Content-Type`` header to be used for this request.
3087 .. versionadded:: 3.1
3088 """
3089 assert self.absolute_path is not None
3090 mime_type, encoding = mimetypes.guess_type(self.absolute_path)
3091 # per RFC 6713, use the appropriate type for a gzip compressed file
3092 if encoding == "gzip":
3093 return "application/gzip"
3094 # As of 2015-07-21 there is no bzip2 encoding defined at
3095 # http://www.iana.org/assignments/media-types/media-types.xhtml
3096 # So for that (and any other encoding), use octet-stream.
3097 elif encoding is not None:
3098 return "application/octet-stream"
3099 elif mime_type is not None:
3100 return mime_type
3101 # if mime_type not detected, use application/octet-stream
3102 else:
3103 return "application/octet-stream"
3105 def set_extra_headers(self, path: str) -> None:
3106 """For subclass to add extra headers to the response"""
3107 pass
3109 def get_cache_time(
3110 self, path: str, modified: Optional[datetime.datetime], mime_type: str
3111 ) -> int:
3112 """Override to customize cache control behavior.
3114 Return a positive number of seconds to make the result
3115 cacheable for that amount of time or 0 to mark resource as
3116 cacheable for an unspecified amount of time (subject to
3117 browser heuristics).
3119 By default returns cache expiry of 10 years for resources requested
3120 with ``v`` argument.
3121 """
3122 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
3124 @classmethod
3125 def make_static_url(
3126 cls, settings: Dict[str, Any], path: str, include_version: bool = True
3127 ) -> str:
3128 """Constructs a versioned url for the given path.
3130 This method may be overridden in subclasses (but note that it
3131 is a class method rather than an instance method). Subclasses
3132 are only required to implement the signature
3133 ``make_static_url(cls, settings, path)``; other keyword
3134 arguments may be passed through `~RequestHandler.static_url`
3135 but are not standard.
3137 ``settings`` is the `Application.settings` dictionary. ``path``
3138 is the static path being requested. The url returned should be
3139 relative to the current host.
3141 ``include_version`` determines whether the generated URL should
3142 include the query string containing the version hash of the
3143 file corresponding to the given ``path``.
3145 """
3146 url = settings.get("static_url_prefix", "/static/") + path
3147 if not include_version:
3148 return url
3150 version_hash = cls.get_version(settings, path)
3151 if not version_hash:
3152 return url
3154 return f"{url}?v={version_hash}"
3156 def parse_url_path(self, url_path: str) -> str:
3157 """Converts a static URL path into a filesystem path.
3159 ``url_path`` is the path component of the URL with
3160 ``static_url_prefix`` removed. The return value should be
3161 filesystem path relative to ``static_path``.
3163 This is the inverse of `make_static_url`.
3164 """
3165 if os.path.sep != "/":
3166 url_path = url_path.replace("/", os.path.sep)
3167 return url_path
3169 @classmethod
3170 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
3171 """Generate the version string to be used in static URLs.
3173 ``settings`` is the `Application.settings` dictionary and ``path``
3174 is the relative location of the requested asset on the filesystem.
3175 The returned value should be a string, or ``None`` if no version
3176 could be determined.
3178 .. versionchanged:: 3.1
3179 This method was previously recommended for subclasses to override;
3180 `get_content_version` is now preferred as it allows the base
3181 class to handle caching of the result.
3182 """
3183 abs_path = cls.get_absolute_path(settings["static_path"], path)
3184 return cls._get_cached_version(abs_path)
3186 @classmethod
3187 def _get_cached_version(cls, abs_path: str) -> Optional[str]:
3188 with cls._lock:
3189 hashes = cls._static_hashes
3190 if abs_path not in hashes:
3191 try:
3192 hashes[abs_path] = cls.get_content_version(abs_path)
3193 except Exception:
3194 gen_log.error("Could not open static file %r", abs_path)
3195 hashes[abs_path] = None
3196 hsh = hashes.get(abs_path)
3197 if hsh:
3198 return hsh
3199 return None
3202class FallbackHandler(RequestHandler):
3203 """A `RequestHandler` that wraps another HTTP server callback.
3205 The fallback is a callable object that accepts an
3206 `~.httputil.HTTPServerRequest`, such as an `Application` or
3207 `tornado.wsgi.WSGIContainer`. This is most useful to use both
3208 Tornado ``RequestHandlers`` and WSGI in the same server. Typical
3209 usage::
3211 wsgi_app = tornado.wsgi.WSGIContainer(
3212 django.core.handlers.wsgi.WSGIHandler())
3213 application = tornado.web.Application([
3214 (r"/foo", FooHandler),
3215 (r".*", FallbackHandler, dict(fallback=wsgi_app)),
3216 ])
3217 """
3219 def initialize(
3220 self, fallback: Callable[[httputil.HTTPServerRequest], None]
3221 ) -> None:
3222 self.fallback = fallback
3224 def prepare(self) -> None:
3225 self.fallback(self.request)
3226 self._finished = True
3227 self.on_finish()
3230class OutputTransform:
3231 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
3233 Applications are not expected to create their own OutputTransforms
3234 or interact with them directly; the framework chooses which transforms
3235 (if any) to apply.
3236 """
3238 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3239 pass
3241 def transform_first_chunk(
3242 self,
3243 status_code: int,
3244 headers: httputil.HTTPHeaders,
3245 chunk: bytes,
3246 finishing: bool,
3247 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3248 return status_code, headers, chunk
3250 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3251 return chunk
3254class GZipContentEncoding(OutputTransform):
3255 """Applies the gzip content encoding to the response.
3257 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
3259 .. versionchanged:: 4.0
3260 Now compresses all mime types beginning with ``text/``, instead
3261 of just a whitelist. (the whitelist is still used for certain
3262 non-text mime types).
3263 """
3265 # Whitelist of compressible mime types (in addition to any types
3266 # beginning with "text/").
3267 CONTENT_TYPES = {
3268 "application/javascript",
3269 "application/x-javascript",
3270 "application/xml",
3271 "application/atom+xml",
3272 "application/json",
3273 "application/xhtml+xml",
3274 "image/svg+xml",
3275 }
3276 # Python's GzipFile defaults to level 9, while most other gzip
3277 # tools (including gzip itself) default to 6, which is probably a
3278 # better CPU/size tradeoff.
3279 GZIP_LEVEL = 6
3280 # Responses that are too short are unlikely to benefit from gzipping
3281 # after considering the "Content-Encoding: gzip" header and the header
3282 # inside the gzip encoding.
3283 # Note that responses written in multiple chunks will be compressed
3284 # regardless of size.
3285 MIN_LENGTH = 1024
3287 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3288 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
3290 def _compressible_type(self, ctype: str) -> bool:
3291 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
3293 def transform_first_chunk(
3294 self,
3295 status_code: int,
3296 headers: httputil.HTTPHeaders,
3297 chunk: bytes,
3298 finishing: bool,
3299 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3300 # TODO: can/should this type be inherited from the superclass?
3301 if "Vary" in headers:
3302 headers["Vary"] += ", Accept-Encoding"
3303 else:
3304 headers["Vary"] = "Accept-Encoding"
3305 if self._gzipping:
3306 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
3307 self._gzipping = (
3308 self._compressible_type(ctype)
3309 and (not finishing or len(chunk) >= self.MIN_LENGTH)
3310 and ("Content-Encoding" not in headers)
3311 )
3312 if self._gzipping:
3313 headers["Content-Encoding"] = "gzip"
3314 self._gzip_value = BytesIO()
3315 self._gzip_file = gzip.GzipFile(
3316 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
3317 )
3318 chunk = self.transform_chunk(chunk, finishing)
3319 if "Content-Length" in headers:
3320 # The original content length is no longer correct.
3321 # If this is the last (and only) chunk, we can set the new
3322 # content-length; otherwise we remove it and fall back to
3323 # chunked encoding.
3324 if finishing:
3325 headers["Content-Length"] = str(len(chunk))
3326 else:
3327 del headers["Content-Length"]
3328 return status_code, headers, chunk
3330 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3331 if self._gzipping:
3332 self._gzip_file.write(chunk)
3333 if finishing:
3334 self._gzip_file.close()
3335 else:
3336 self._gzip_file.flush()
3337 chunk = self._gzip_value.getvalue()
3338 self._gzip_value.truncate(0)
3339 self._gzip_value.seek(0)
3340 return chunk
3343def authenticated(
3344 method: Callable[..., Optional[Awaitable[None]]],
3345) -> Callable[..., Optional[Awaitable[None]]]:
3346 """Decorate methods with this to require that the user be logged in.
3348 If the user is not logged in, they will be redirected to the configured
3349 `login url <RequestHandler.get_login_url>`.
3351 If you configure a login url with a query parameter, Tornado will
3352 assume you know what you're doing and use it as-is. If not, it
3353 will add a `next` parameter so the login page knows where to send
3354 you once you're logged in.
3355 """
3357 @functools.wraps(method)
3358 def wrapper( # type: ignore
3359 self: RequestHandler, *args, **kwargs
3360 ) -> Optional[Awaitable[None]]:
3361 if not self.current_user:
3362 if self.request.method in ("GET", "HEAD"):
3363 url = self.get_login_url()
3364 if "?" not in url:
3365 if urllib.parse.urlsplit(url).scheme:
3366 # if login url is absolute, make next absolute too
3367 next_url = self.request.full_url()
3368 else:
3369 assert self.request.uri is not None
3370 next_url = self.request.uri
3371 url += "?" + urlencode(dict(next=next_url))
3372 self.redirect(url)
3373 return None
3374 raise HTTPError(403)
3375 return method(self, *args, **kwargs)
3377 return wrapper
3380class UIModule:
3381 """A re-usable, modular UI unit on a page.
3383 UI modules often execute additional queries, and they can include
3384 additional CSS and JavaScript that will be included in the output
3385 page, which is automatically inserted on page render.
3387 Subclasses of UIModule must override the `render` method.
3388 """
3390 def __init__(self, handler: RequestHandler) -> None:
3391 self.handler = handler
3392 self.request = handler.request
3393 self.ui = handler.ui
3394 self.locale = handler.locale
3396 @property
3397 def current_user(self) -> Any:
3398 return self.handler.current_user
3400 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]:
3401 """Override in subclasses to return this module's output."""
3402 raise NotImplementedError()
3404 def embedded_javascript(self) -> Optional[str]:
3405 """Override to return a JavaScript string
3406 to be embedded in the page."""
3407 return None
3409 def javascript_files(self) -> Optional[Iterable[str]]:
3410 """Override to return a list of JavaScript files needed by this module.
3412 If the return values are relative paths, they will be passed to
3413 `RequestHandler.static_url`; otherwise they will be used as-is.
3414 """
3415 return None
3417 def embedded_css(self) -> Optional[str]:
3418 """Override to return a CSS string
3419 that will be embedded in the page."""
3420 return None
3422 def css_files(self) -> Optional[Iterable[str]]:
3423 """Override to returns a list of CSS files required by this module.
3425 If the return values are relative paths, they will be passed to
3426 `RequestHandler.static_url`; otherwise they will be used as-is.
3427 """
3428 return None
3430 def html_head(self) -> Optional[str]:
3431 """Override to return an HTML string that will be put in the <head/>
3432 element.
3433 """
3434 return None
3436 def html_body(self) -> Optional[str]:
3437 """Override to return an HTML string that will be put at the end of
3438 the <body/> element.
3439 """
3440 return None
3442 def render_string(self, path: str, **kwargs: Any) -> bytes:
3443 """Renders a template and returns it as a string."""
3444 return self.handler.render_string(path, **kwargs)
3447class _linkify(UIModule):
3448 def render(self, text: str, **kwargs: Any) -> str:
3449 return escape.linkify(text, **kwargs)
3452class _xsrf_form_html(UIModule):
3453 def render(self) -> str:
3454 return self.handler.xsrf_form_html()
3457class TemplateModule(UIModule):
3458 """UIModule that simply renders the given template.
3460 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
3461 but the module version gets its own namespace (with kwargs passed to
3462 Template()) instead of inheriting the outer template's namespace.
3464 Templates rendered through this module also get access to UIModule's
3465 automatic JavaScript/CSS features. Simply call set_resources
3466 inside the template and give it keyword arguments corresponding to
3467 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
3468 Note that these resources are output once per template file, not once
3469 per instantiation of the template, so they must not depend on
3470 any arguments to the template.
3471 """
3473 def __init__(self, handler: RequestHandler) -> None:
3474 super().__init__(handler)
3475 # keep resources in both a list and a dict to preserve order
3476 self._resource_list = [] # type: List[Dict[str, Any]]
3477 self._resource_dict = {} # type: Dict[str, Dict[str, Any]]
3479 def render(self, path: str, **kwargs: Any) -> bytes:
3480 def set_resources(**kwargs) -> str: # type: ignore
3481 if path not in self._resource_dict:
3482 self._resource_list.append(kwargs)
3483 self._resource_dict[path] = kwargs
3484 else:
3485 if self._resource_dict[path] != kwargs:
3486 raise ValueError(
3487 "set_resources called with different "
3488 "resources for the same template"
3489 )
3490 return ""
3492 return self.render_string(path, set_resources=set_resources, **kwargs)
3494 def _get_resources(self, key: str) -> Iterable[str]:
3495 return (r[key] for r in self._resource_list if key in r)
3497 def embedded_javascript(self) -> str:
3498 return "\n".join(self._get_resources("embedded_javascript"))
3500 def javascript_files(self) -> Iterable[str]:
3501 result = []
3502 for f in self._get_resources("javascript_files"):
3503 if isinstance(f, (unicode_type, bytes)):
3504 result.append(f)
3505 else:
3506 result.extend(f)
3507 return result
3509 def embedded_css(self) -> str:
3510 return "\n".join(self._get_resources("embedded_css"))
3512 def css_files(self) -> Iterable[str]:
3513 result = []
3514 for f in self._get_resources("css_files"):
3515 if isinstance(f, (unicode_type, bytes)):
3516 result.append(f)
3517 else:
3518 result.extend(f)
3519 return result
3521 def html_head(self) -> str:
3522 return "".join(self._get_resources("html_head"))
3524 def html_body(self) -> str:
3525 return "".join(self._get_resources("html_body"))
3528class _UIModuleNamespace:
3529 """Lazy namespace which creates UIModule proxies bound to a handler."""
3531 def __init__(
3532 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
3533 ) -> None:
3534 self.handler = handler
3535 self.ui_modules = ui_modules
3537 def __getitem__(self, key: str) -> Callable[..., str]:
3538 return self.handler._ui_module(key, self.ui_modules[key])
3540 def __getattr__(self, key: str) -> Callable[..., str]:
3541 try:
3542 return self[key]
3543 except KeyError as e:
3544 raise AttributeError(str(e))
3547def create_signed_value(
3548 secret: _CookieSecretTypes,
3549 name: str,
3550 value: Union[str, bytes],
3551 version: Optional[int] = None,
3552 clock: Optional[Callable[[], float]] = None,
3553 key_version: Optional[int] = None,
3554) -> bytes:
3555 if version is None:
3556 version = DEFAULT_SIGNED_VALUE_VERSION
3557 if clock is None:
3558 clock = time.time
3560 timestamp = utf8(str(int(clock())))
3561 value = base64.b64encode(utf8(value))
3562 if version == 1:
3563 assert not isinstance(secret, dict)
3564 signature = _create_signature_v1(secret, name, value, timestamp)
3565 value = b"|".join([value, timestamp, signature])
3566 return value
3567 elif version == 2:
3568 # The v2 format consists of a version number and a series of
3569 # length-prefixed fields "%d:%s", the last of which is a
3570 # signature, all separated by pipes. All numbers are in
3571 # decimal format with no leading zeros. The signature is an
3572 # HMAC-SHA256 of the whole string up to that point, including
3573 # the final pipe.
3574 #
3575 # The fields are:
3576 # - format version (i.e. 2; no length prefix)
3577 # - key version (integer, default is 0)
3578 # - timestamp (integer seconds since epoch)
3579 # - name (not encoded; assumed to be ~alphanumeric)
3580 # - value (base64-encoded)
3581 # - signature (hex-encoded; no length prefix)
3582 def format_field(s: Union[str, bytes]) -> bytes:
3583 return utf8("%d:" % len(s)) + utf8(s)
3585 to_sign = b"|".join(
3586 [
3587 b"2",
3588 format_field(str(key_version or 0)),
3589 format_field(timestamp),
3590 format_field(name),
3591 format_field(value),
3592 b"",
3593 ]
3594 )
3596 if isinstance(secret, dict):
3597 assert (
3598 key_version is not None
3599 ), "Key version must be set when sign key dict is used"
3600 assert version >= 2, "Version must be at least 2 for key version support"
3601 secret = secret[key_version]
3603 signature = _create_signature_v2(secret, to_sign)
3604 return to_sign + signature
3605 else:
3606 raise ValueError("Unsupported version %d" % version)
3609# A leading version number in decimal
3610# with no leading zeros, followed by a pipe.
3611_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$")
3614def _get_version(value: bytes) -> int:
3615 # Figures out what version value is. Version 1 did not include an
3616 # explicit version field and started with arbitrary base64 data,
3617 # which makes this tricky.
3618 m = _signed_value_version_re.match(value)
3619 if m is None:
3620 version = 1
3621 else:
3622 try:
3623 version = int(m.group(1))
3624 if version > 999:
3625 # Certain payloads from the version-less v1 format may
3626 # be parsed as valid integers. Due to base64 padding
3627 # restrictions, this can only happen for numbers whose
3628 # length is a multiple of 4, so we can treat all
3629 # numbers up to 999 as versions, and for the rest we
3630 # fall back to v1 format.
3631 version = 1
3632 except ValueError:
3633 version = 1
3634 return version
3637def decode_signed_value(
3638 secret: _CookieSecretTypes,
3639 name: str,
3640 value: Union[None, str, bytes],
3641 max_age_days: float = 31,
3642 clock: Optional[Callable[[], float]] = None,
3643 min_version: Optional[int] = None,
3644) -> Optional[bytes]:
3645 if clock is None:
3646 clock = time.time
3647 if min_version is None:
3648 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
3649 if min_version > 2:
3650 raise ValueError("Unsupported min_version %d" % min_version)
3651 if not value:
3652 return None
3654 value = utf8(value)
3655 version = _get_version(value)
3657 if version < min_version:
3658 return None
3659 if version == 1:
3660 assert not isinstance(secret, dict)
3661 return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
3662 elif version == 2:
3663 return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
3664 else:
3665 return None
3668def _decode_signed_value_v1(
3669 secret: Union[str, bytes],
3670 name: str,
3671 value: bytes,
3672 max_age_days: float,
3673 clock: Callable[[], float],
3674) -> Optional[bytes]:
3675 parts = utf8(value).split(b"|")
3676 if len(parts) != 3:
3677 return None
3678 signature = _create_signature_v1(secret, name, parts[0], parts[1])
3679 if not hmac.compare_digest(parts[2], signature):
3680 gen_log.warning("Invalid cookie signature %r", value)
3681 return None
3682 timestamp = int(parts[1])
3683 if timestamp < clock() - max_age_days * 86400:
3684 gen_log.warning("Expired cookie %r", value)
3685 return None
3686 if timestamp > clock() + 31 * 86400:
3687 # _cookie_signature does not hash a delimiter between the
3688 # parts of the cookie, so an attacker could transfer trailing
3689 # digits from the payload to the timestamp without altering the
3690 # signature. For backwards compatibility, sanity-check timestamp
3691 # here instead of modifying _cookie_signature.
3692 gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
3693 return None
3694 if parts[1].startswith(b"0"):
3695 gen_log.warning("Tampered cookie %r", value)
3696 return None
3697 try:
3698 return base64.b64decode(parts[0])
3699 except Exception:
3700 return None
3703def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
3704 def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
3705 length, _, rest = s.partition(b":")
3706 n = int(length)
3707 field_value = rest[:n]
3708 # In python 3, indexing bytes returns small integers; we must
3709 # use a slice to get a byte string as in python 2.
3710 if rest[n : n + 1] != b"|":
3711 raise ValueError("malformed v2 signed value field")
3712 rest = rest[n + 1 :]
3713 return field_value, rest
3715 rest = value[2:] # remove version number
3716 key_version, rest = _consume_field(rest)
3717 timestamp, rest = _consume_field(rest)
3718 name_field, rest = _consume_field(rest)
3719 value_field, passed_sig = _consume_field(rest)
3720 return int(key_version), timestamp, name_field, value_field, passed_sig
3723def _decode_signed_value_v2(
3724 secret: _CookieSecretTypes,
3725 name: str,
3726 value: bytes,
3727 max_age_days: float,
3728 clock: Callable[[], float],
3729) -> Optional[bytes]:
3730 try:
3731 (
3732 key_version,
3733 timestamp_bytes,
3734 name_field,
3735 value_field,
3736 passed_sig,
3737 ) = _decode_fields_v2(value)
3738 except ValueError:
3739 return None
3740 signed_string = value[: -len(passed_sig)]
3742 if isinstance(secret, dict):
3743 try:
3744 secret = secret[key_version]
3745 except KeyError:
3746 return None
3748 expected_sig = _create_signature_v2(secret, signed_string)
3749 if not hmac.compare_digest(passed_sig, expected_sig):
3750 return None
3751 if name_field != utf8(name):
3752 return None
3753 timestamp = int(timestamp_bytes)
3754 if timestamp < clock() - max_age_days * 86400:
3755 # The signature has expired.
3756 return None
3757 try:
3758 return base64.b64decode(value_field)
3759 except Exception:
3760 return None
3763def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
3764 value = utf8(value)
3765 version = _get_version(value)
3766 if version < 2:
3767 return None
3768 try:
3769 key_version, _, _, _, _ = _decode_fields_v2(value)
3770 except ValueError:
3771 return None
3773 return key_version
3776def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
3777 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
3778 for part in parts:
3779 hash.update(utf8(part))
3780 return utf8(hash.hexdigest())
3783def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
3784 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
3785 hash.update(utf8(s))
3786 return utf8(hash.hexdigest())
3789def is_absolute(path: str) -> bool:
3790 return any(path.startswith(x) for x in ["/", "http:", "https:"])