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