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