Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tornado/web.py: 21%

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

1414 statements  

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. 

15 

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>`_. 

20 

21Here is a simple "Hello, world" example app: 

22 

23.. testcode:: 

24 

25 import asyncio 

26 import tornado 

27 

28 class MainHandler(tornado.web.RequestHandler): 

29 def get(self): 

30 self.write("Hello, world") 

31 

32 async def main(): 

33 application = tornado.web.Application([ 

34 (r"/", MainHandler), 

35 ]) 

36 application.listen(8888) 

37 await asyncio.Event().wait() 

38 

39 if __name__ == "__main__": 

40 asyncio.run(main()) 

41 

42.. testoutput:: 

43 :hide: 

44 

45 

46See the :doc:`guide` for additional information. 

47 

48Thread-safety notes 

49------------------- 

50 

51In general, methods on `RequestHandler` and elsewhere in Tornado are 

52not thread-safe. In particular, methods such as 

53`~RequestHandler.write()`, `~RequestHandler.finish()`, and 

54`~RequestHandler.flush()` must only be called from the main thread. If 

55you use multiple threads it is important to use `.IOLoop.add_callback` 

56to transfer control back to the main thread before finishing the 

57request, or to limit your use of other threads to 

58`.IOLoop.run_in_executor` and ensure that your callbacks running in 

59the executor do not refer to Tornado objects. 

60 

61""" 

62 

63import base64 

64import binascii 

65import datetime 

66import email.utils 

67import functools 

68import gzip 

69import hashlib 

70import hmac 

71import http.cookies 

72from inspect import isclass 

73from io import BytesIO 

74import mimetypes 

75import numbers 

76import os.path 

77import re 

78import socket 

79import sys 

80import threading 

81import time 

82import warnings 

83import tornado 

84import traceback 

85import types 

86import urllib.parse 

87from urllib.parse import urlencode 

88 

89from tornado.concurrent import Future, future_set_result_unless_cancelled 

90from tornado import escape 

91from tornado import gen 

92from tornado.httpserver import HTTPServer 

93from tornado import httputil 

94from tornado import iostream 

95from tornado import locale 

96from tornado.log import access_log, app_log, gen_log 

97from tornado import template 

98from tornado.escape import utf8, _unicode 

99from tornado.routing import ( 

100 AnyMatches, 

101 DefaultHostMatches, 

102 HostMatches, 

103 ReversibleRouter, 

104 Rule, 

105 ReversibleRuleRouter, 

106 URLSpec, 

107 _RuleList, 

108) 

109from tornado.util import ObjectDict, unicode_type, _websocket_mask 

110 

111url = URLSpec 

112 

113from typing import ( 

114 Dict, 

115 Any, 

116 Union, 

117 Optional, 

118 Awaitable, 

119 Tuple, 

120 List, 

121 Callable, 

122 Iterable, 

123 Generator, 

124 Type, 

125 TypeVar, 

126 cast, 

127 overload, 

128) 

129from types import TracebackType 

130import typing 

131 

132if typing.TYPE_CHECKING: 

133 from typing import Set # noqa: F401 

134 

135 

136# The following types are accepted by RequestHandler.set_header 

137# and related methods. 

138_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime] 

139 

140_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]] 

141 

142 

143MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

144"""The oldest signed value version supported by this version of Tornado. 

145 

146Signed values older than this version cannot be decoded. 

147 

148.. versionadded:: 3.2.1 

149""" 

150 

151MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

152"""The newest signed value version supported by this version of Tornado. 

153 

154Signed values newer than this version cannot be decoded. 

155 

156.. versionadded:: 3.2.1 

157""" 

158 

159DEFAULT_SIGNED_VALUE_VERSION = 2 

160"""The signed value version produced by `.RequestHandler.create_signed_value`. 

161 

162May be overridden by passing a ``version`` keyword argument. 

163 

164.. versionadded:: 3.2.1 

165""" 

166 

167DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

168"""The oldest signed value accepted by `.RequestHandler.get_signed_cookie`. 

169 

170May be overridden by passing a ``min_version`` keyword argument. 

171 

172.. versionadded:: 3.2.1 

173""" 

174 

175 

176class _ArgDefaultMarker: 

177 pass 

178 

179 

180_ARG_DEFAULT = _ArgDefaultMarker() 

181 

182 

183class RequestHandler(object): 

184 """Base class for HTTP request handlers. 

185 

186 Subclasses must define at least one of the methods defined in the 

187 "Entry points" section below. 

188 

189 Applications should not construct `RequestHandler` objects 

190 directly and subclasses should not override ``__init__`` (override 

191 `~RequestHandler.initialize` instead). 

192 

193 """ 

194 

195 SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS") 

196 

197 _template_loaders = {} # type: Dict[str, template.BaseLoader] 

198 _template_loader_lock = threading.Lock() 

199 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]") 

200 

201 _stream_request_body = False 

202 

203 # Will be set in _execute. 

204 _transforms = None # type: List[OutputTransform] 

205 path_args = None # type: List[str] 

206 path_kwargs = None # type: Dict[str, str] 

207 

208 def __init__( 

209 self, 

210 application: "Application", 

211 request: httputil.HTTPServerRequest, 

212 **kwargs: Any, 

213 ) -> None: 

214 super().__init__() 

215 

216 self.application = application 

217 self.request = request 

218 self._headers_written = False 

219 self._finished = False 

220 self._auto_finish = True 

221 self._prepared_future = None 

222 self.ui = ObjectDict( 

223 (n, self._ui_method(m)) for n, m in application.ui_methods.items() 

224 ) 

225 # UIModules are available as both `modules` and `_tt_modules` in the 

226 # template namespace. Historically only `modules` was available 

227 # but could be clobbered by user additions to the namespace. 

228 # The template {% module %} directive looks in `_tt_modules` to avoid 

229 # possible conflicts. 

230 self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules) 

231 self.ui["modules"] = self.ui["_tt_modules"] 

232 self.clear() 

233 assert self.request.connection is not None 

234 # TODO: need to add set_close_callback to HTTPConnection interface 

235 self.request.connection.set_close_callback( # type: ignore 

236 self.on_connection_close 

237 ) 

238 self.initialize(**kwargs) # type: ignore 

239 

240 def _initialize(self) -> None: 

241 pass 

242 

243 initialize = _initialize # type: Callable[..., None] 

244 """Hook for subclass initialization. Called for each request. 

245 

246 A dictionary passed as the third argument of a ``URLSpec`` will be 

247 supplied as keyword arguments to ``initialize()``. 

248 

249 Example:: 

250 

251 class ProfileHandler(RequestHandler): 

252 def initialize(self, database): 

253 self.database = database 

254 

255 def get(self, username): 

256 ... 

257 

258 app = Application([ 

259 (r'/user/(.*)', ProfileHandler, dict(database=database)), 

260 ]) 

261 """ 

262 

263 @property 

264 def settings(self) -> Dict[str, Any]: 

265 """An alias for `self.application.settings <Application.settings>`.""" 

266 return self.application.settings 

267 

268 def _unimplemented_method(self, *args: str, **kwargs: str) -> None: 

269 raise HTTPError(405) 

270 

271 head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

272 get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

273 post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

274 delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

275 patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

276 put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

277 options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

278 

279 def prepare(self) -> Optional[Awaitable[None]]: 

280 """Called at the beginning of a request before `get`/`post`/etc. 

281 

282 Override this method to perform common initialization regardless 

283 of the request method. 

284 

285 Asynchronous support: Use ``async def`` or decorate this method with 

286 `.gen.coroutine` to make it asynchronous. 

287 If this method returns an ``Awaitable`` execution will not proceed 

288 until the ``Awaitable`` is done. 

289 

290 .. versionadded:: 3.1 

291 Asynchronous support. 

292 """ 

293 pass 

294 

295 def on_finish(self) -> None: 

296 """Called after the end of a request. 

297 

298 Override this method to perform cleanup, logging, etc. 

299 This method is a counterpart to `prepare`. ``on_finish`` may 

300 not produce any output, as it is called after the response 

301 has been sent to the client. 

302 """ 

303 pass 

304 

305 def on_connection_close(self) -> None: 

306 """Called in async handlers if the client closed the connection. 

307 

308 Override this to clean up resources associated with 

309 long-lived connections. Note that this method is called only if 

310 the connection was closed during asynchronous processing; if you 

311 need to do cleanup after every request override `on_finish` 

312 instead. 

313 

314 Proxies may keep a connection open for a time (perhaps 

315 indefinitely) after the client has gone away, so this method 

316 may not be called promptly after the end user closes their 

317 connection. 

318 """ 

319 if _has_stream_request_body(self.__class__): 

320 if not self.request._body_future.done(): 

321 self.request._body_future.set_exception(iostream.StreamClosedError()) 

322 self.request._body_future.exception() 

323 

324 def clear(self) -> None: 

325 """Resets all headers and content for this response.""" 

326 self._headers = httputil.HTTPHeaders( 

327 { 

328 "Server": "TornadoServer/%s" % tornado.version, 

329 "Content-Type": "text/html; charset=UTF-8", 

330 "Date": httputil.format_timestamp(time.time()), 

331 } 

332 ) 

333 self.set_default_headers() 

334 self._write_buffer = [] # type: List[bytes] 

335 self._status_code = 200 

336 self._reason = httputil.responses[200] 

337 

338 def set_default_headers(self) -> None: 

339 """Override this to set HTTP headers at the beginning of the request. 

340 

341 For example, this is the place to set a custom ``Server`` header. 

342 Note that setting such headers in the normal flow of request 

343 processing may not do what you want, since headers may be reset 

344 during error handling. 

345 """ 

346 pass 

347 

348 def set_status(self, status_code: int, reason: Optional[str] = None) -> None: 

349 """Sets the status code for our response. 

350 

351 :arg int status_code: Response status code. 

352 :arg str reason: Human-readable reason phrase describing the status 

353 code. If ``None``, it will be filled in from 

354 `http.client.responses` or "Unknown". 

355 

356 .. versionchanged:: 5.0 

357 

358 No longer validates that the response code is in 

359 `http.client.responses`. 

360 """ 

361 self._status_code = status_code 

362 if reason is not None: 

363 self._reason = escape.native_str(reason) 

364 else: 

365 self._reason = httputil.responses.get(status_code, "Unknown") 

366 

367 def get_status(self) -> int: 

368 """Returns the status code for our response.""" 

369 return self._status_code 

370 

371 def set_header(self, name: str, value: _HeaderTypes) -> None: 

372 """Sets the given response header name and value. 

373 

374 All header values are converted to strings (`datetime` objects 

375 are formatted according to the HTTP specification for the 

376 ``Date`` header). 

377 

378 """ 

379 self._headers[name] = self._convert_header_value(value) 

380 

381 def add_header(self, name: str, value: _HeaderTypes) -> None: 

382 """Adds the given response header and value. 

383 

384 Unlike `set_header`, `add_header` may be called multiple times 

385 to return multiple values for the same header. 

386 """ 

387 self._headers.add(name, self._convert_header_value(value)) 

388 

389 def clear_header(self, name: str) -> None: 

390 """Clears an outgoing header, undoing a previous `set_header` call. 

391 

392 Note that this method does not apply to multi-valued headers 

393 set by `add_header`. 

394 """ 

395 if name in self._headers: 

396 del self._headers[name] 

397 

398 _INVALID_HEADER_CHAR_RE = re.compile(r"[\x00-\x1f]") 

399 

400 def _convert_header_value(self, value: _HeaderTypes) -> str: 

401 # Convert the input value to a str. This type check is a bit 

402 # subtle: The bytes case only executes on python 3, and the 

403 # unicode case only executes on python 2, because the other 

404 # cases are covered by the first match for str. 

405 if isinstance(value, str): 

406 retval = value 

407 elif isinstance(value, bytes): 

408 # Non-ascii characters in headers are not well supported, 

409 # but if you pass bytes, use latin1 so they pass through as-is. 

410 retval = value.decode("latin1") 

411 elif isinstance(value, numbers.Integral): 

412 # return immediately since we know the converted value will be safe 

413 return str(value) 

414 elif isinstance(value, datetime.datetime): 

415 return httputil.format_timestamp(value) 

416 else: 

417 raise TypeError("Unsupported header value %r" % value) 

418 # If \n is allowed into the header, it is possible to inject 

419 # additional headers or split the request. 

420 if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval): 

421 raise ValueError("Unsafe header value %r", retval) 

422 return retval 

423 

424 @overload 

425 def get_argument(self, name: str, default: str, strip: bool = True) -> str: 

426 pass 

427 

428 @overload 

429 def get_argument( # noqa: F811 

430 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True 

431 ) -> str: 

432 pass 

433 

434 @overload 

435 def get_argument( # noqa: F811 

436 self, name: str, default: None, strip: bool = True 

437 ) -> Optional[str]: 

438 pass 

439 

440 def get_argument( # noqa: F811 

441 self, 

442 name: str, 

443 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

444 strip: bool = True, 

445 ) -> Optional[str]: 

446 """Returns the value of the argument with the given name. 

447 

448 If default is not provided, the argument is considered to be 

449 required, and we raise a `MissingArgumentError` if it is missing. 

450 

451 If the argument appears in the request more than once, we return the 

452 last value. 

453 

454 This method searches both the query and body arguments. 

455 """ 

456 return self._get_argument(name, default, self.request.arguments, strip) 

457 

458 def get_arguments(self, name: str, strip: bool = True) -> List[str]: 

459 """Returns a list of the arguments with the given name. 

460 

461 If the argument is not present, returns an empty list. 

462 

463 This method searches both the query and body arguments. 

464 """ 

465 

466 # Make sure `get_arguments` isn't accidentally being called with a 

467 # positional argument that's assumed to be a default (like in 

468 # `get_argument`.) 

469 assert isinstance(strip, bool) 

470 

471 return self._get_arguments(name, self.request.arguments, strip) 

472 

473 def get_body_argument( 

474 self, 

475 name: str, 

476 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

477 strip: bool = True, 

478 ) -> Optional[str]: 

479 """Returns the value of the argument with the given name 

480 from the request body. 

481 

482 If default is not provided, the argument is considered to be 

483 required, and we raise a `MissingArgumentError` if it is missing. 

484 

485 If the argument appears in the url more than once, we return the 

486 last value. 

487 

488 .. versionadded:: 3.2 

489 """ 

490 return self._get_argument(name, default, self.request.body_arguments, strip) 

491 

492 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]: 

493 """Returns a list of the body arguments with the given name. 

494 

495 If the argument is not present, returns an empty list. 

496 

497 .. versionadded:: 3.2 

498 """ 

499 return self._get_arguments(name, self.request.body_arguments, strip) 

500 

501 def get_query_argument( 

502 self, 

503 name: str, 

504 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

505 strip: bool = True, 

506 ) -> Optional[str]: 

507 """Returns the value of the argument with the given name 

508 from the request query string. 

509 

510 If default is not provided, the argument is considered to be 

511 required, and we raise a `MissingArgumentError` if it is missing. 

512 

513 If the argument appears in the url more than once, we return the 

514 last value. 

515 

516 .. versionadded:: 3.2 

517 """ 

518 return self._get_argument(name, default, self.request.query_arguments, strip) 

519 

520 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]: 

521 """Returns a list of the query arguments with the given name. 

522 

523 If the argument is not present, returns an empty list. 

524 

525 .. versionadded:: 3.2 

526 """ 

527 return self._get_arguments(name, self.request.query_arguments, strip) 

528 

529 def _get_argument( 

530 self, 

531 name: str, 

532 default: Union[None, str, _ArgDefaultMarker], 

533 source: Dict[str, List[bytes]], 

534 strip: bool = True, 

535 ) -> Optional[str]: 

536 args = self._get_arguments(name, source, strip=strip) 

537 if not args: 

538 if isinstance(default, _ArgDefaultMarker): 

539 raise MissingArgumentError(name) 

540 return default 

541 return args[-1] 

542 

543 def _get_arguments( 

544 self, name: str, source: Dict[str, List[bytes]], strip: bool = True 

545 ) -> List[str]: 

546 values = [] 

547 for v in source.get(name, []): 

548 s = self.decode_argument(v, name=name) 

549 if isinstance(s, unicode_type): 

550 # Get rid of any weird control chars (unless decoding gave 

551 # us bytes, in which case leave it alone) 

552 s = RequestHandler._remove_control_chars_regex.sub(" ", s) 

553 if strip: 

554 s = s.strip() 

555 values.append(s) 

556 return values 

557 

558 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str: 

559 """Decodes an argument from the request. 

560 

561 The argument has been percent-decoded and is now a byte string. 

562 By default, this method decodes the argument as utf-8 and returns 

563 a unicode string, but this may be overridden in subclasses. 

564 

565 This method is used as a filter for both `get_argument()` and for 

566 values extracted from the url and passed to `get()`/`post()`/etc. 

567 

568 The name of the argument is provided if known, but may be None 

569 (e.g. for unnamed groups in the url regex). 

570 """ 

571 try: 

572 return _unicode(value) 

573 except UnicodeDecodeError: 

574 raise HTTPError( 

575 400, "Invalid unicode in %s: %r" % (name or "url", value[:40]) 

576 ) 

577 

578 @property 

579 def cookies(self) -> Dict[str, http.cookies.Morsel]: 

580 """An alias for 

581 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`.""" 

582 return self.request.cookies 

583 

584 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]: 

585 """Returns the value of the request cookie with the given name. 

586 

587 If the named cookie is not present, returns ``default``. 

588 

589 This method only returns cookies that were present in the request. 

590 It does not see the outgoing cookies set by `set_cookie` in this 

591 handler. 

592 """ 

593 if self.request.cookies is not None and name in self.request.cookies: 

594 return self.request.cookies[name].value 

595 return default 

596 

597 def set_cookie( 

598 self, 

599 name: str, 

600 value: Union[str, bytes], 

601 domain: Optional[str] = None, 

602 expires: Optional[Union[float, Tuple, datetime.datetime]] = None, 

603 path: str = "/", 

604 expires_days: Optional[float] = None, 

605 # Keyword-only args start here for historical reasons. 

606 *, 

607 max_age: Optional[int] = None, 

608 httponly: bool = False, 

609 secure: bool = False, 

610 samesite: Optional[str] = None, 

611 **kwargs: Any, 

612 ) -> None: 

613 """Sets an outgoing cookie name/value with the given options. 

614 

615 Newly-set cookies are not immediately visible via `get_cookie`; 

616 they are not present until the next request. 

617 

618 Most arguments are passed directly to `http.cookies.Morsel` directly. 

619 See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie 

620 for more information. 

621 

622 ``expires`` may be a numeric timestamp as returned by `time.time`, 

623 a time tuple as returned by `time.gmtime`, or a 

624 `datetime.datetime` object. ``expires_days`` is provided as a convenience 

625 to set an expiration time in days from today (if both are set, ``expires`` 

626 is used). 

627 

628 .. deprecated:: 6.3 

629 Keyword arguments are currently accepted case-insensitively. 

630 In Tornado 7.0 this will be changed to only accept lowercase 

631 arguments. 

632 """ 

633 # The cookie library only accepts type str, in both python 2 and 3 

634 name = escape.native_str(name) 

635 value = escape.native_str(value) 

636 if re.search(r"[\x00-\x20]", name + value): 

637 # Don't let us accidentally inject bad stuff 

638 raise ValueError("Invalid cookie %r: %r" % (name, value)) 

639 if not hasattr(self, "_new_cookie"): 

640 self._new_cookie = ( 

641 http.cookies.SimpleCookie() 

642 ) # type: http.cookies.SimpleCookie 

643 if name in self._new_cookie: 

644 del self._new_cookie[name] 

645 self._new_cookie[name] = value 

646 morsel = self._new_cookie[name] 

647 if domain: 

648 morsel["domain"] = domain 

649 if expires_days is not None and not expires: 

650 expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( 

651 days=expires_days 

652 ) 

653 if expires: 

654 morsel["expires"] = httputil.format_timestamp(expires) 

655 if path: 

656 morsel["path"] = path 

657 if max_age: 

658 # Note change from _ to -. 

659 morsel["max-age"] = str(max_age) 

660 if httponly: 

661 # Note that SimpleCookie ignores the value here. The presense of an 

662 # httponly (or secure) key is treated as true. 

663 morsel["httponly"] = True 

664 if secure: 

665 morsel["secure"] = True 

666 if samesite: 

667 morsel["samesite"] = samesite 

668 if kwargs: 

669 # The setitem interface is case-insensitive, so continue to support 

670 # kwargs for backwards compatibility until we can remove deprecated 

671 # features. 

672 for k, v in kwargs.items(): 

673 morsel[k] = v 

674 warnings.warn( 

675 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} " 

676 "(should be lowercase)", 

677 DeprecationWarning, 

678 ) 

679 

680 def clear_cookie(self, name: str, **kwargs: Any) -> None: 

681 """Deletes the cookie with the given name. 

682 

683 This method accepts the same arguments as `set_cookie`, except for 

684 ``expires`` and ``max_age``. Clearing a cookie requires the same 

685 ``domain`` and ``path`` arguments as when it was set. In some cases the 

686 ``samesite`` and ``secure`` arguments are also required to match. Other 

687 arguments are ignored. 

688 

689 Similar to `set_cookie`, the effect of this method will not be 

690 seen until the following request. 

691 

692 .. versionchanged:: 6.3 

693 

694 Now accepts all keyword arguments that ``set_cookie`` does. 

695 The ``samesite`` and ``secure`` flags have recently become 

696 required for clearing ``samesite="none"`` cookies. 

697 """ 

698 for excluded_arg in ["expires", "max_age"]: 

699 if excluded_arg in kwargs: 

700 raise TypeError( 

701 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'" 

702 ) 

703 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( 

704 days=365 

705 ) 

706 self.set_cookie(name, value="", expires=expires, **kwargs) 

707 

708 def clear_all_cookies(self, **kwargs: Any) -> None: 

709 """Attempt to delete all the cookies the user sent with this request. 

710 

711 See `clear_cookie` for more information on keyword arguments. Due to 

712 limitations of the cookie protocol, it is impossible to determine on the 

713 server side which values are necessary for the ``domain``, ``path``, 

714 ``samesite``, or ``secure`` arguments, this method can only be 

715 successful if you consistently use the same values for these arguments 

716 when setting cookies. 

717 

718 Similar to `set_cookie`, the effect of this method will not be seen 

719 until the following request. 

720 

721 .. versionchanged:: 3.2 

722 

723 Added the ``path`` and ``domain`` parameters. 

724 

725 .. versionchanged:: 6.3 

726 

727 Now accepts all keyword arguments that ``set_cookie`` does. 

728 

729 .. deprecated:: 6.3 

730 

731 The increasingly complex rules governing cookies have made it 

732 impossible for a ``clear_all_cookies`` method to work reliably 

733 since all we know about cookies are their names. Applications 

734 should generally use ``clear_cookie`` one at a time instead. 

735 """ 

736 for name in self.request.cookies: 

737 self.clear_cookie(name, **kwargs) 

738 

739 def set_signed_cookie( 

740 self, 

741 name: str, 

742 value: Union[str, bytes], 

743 expires_days: Optional[float] = 30, 

744 version: Optional[int] = None, 

745 **kwargs: Any, 

746 ) -> None: 

747 """Signs and timestamps a cookie so it cannot be forged. 

748 

749 You must specify the ``cookie_secret`` setting in your Application 

750 to use this method. It should be a long, random sequence of bytes 

751 to be used as the HMAC secret for the signature. 

752 

753 To read a cookie set with this method, use `get_signed_cookie()`. 

754 

755 Note that the ``expires_days`` parameter sets the lifetime of the 

756 cookie in the browser, but is independent of the ``max_age_days`` 

757 parameter to `get_signed_cookie`. 

758 A value of None limits the lifetime to the current browser session. 

759 

760 Secure cookies may contain arbitrary byte values, not just unicode 

761 strings (unlike regular cookies) 

762 

763 Similar to `set_cookie`, the effect of this method will not be 

764 seen until the following request. 

765 

766 .. versionchanged:: 3.2.1 

767 

768 Added the ``version`` argument. Introduced cookie version 2 

769 and made it the default. 

770 

771 .. versionchanged:: 6.3 

772 

773 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

774 avoid confusion with other uses of "secure" in cookie attributes 

775 and prefixes. The old name remains as an alias. 

776 """ 

777 self.set_cookie( 

778 name, 

779 self.create_signed_value(name, value, version=version), 

780 expires_days=expires_days, 

781 **kwargs, 

782 ) 

783 

784 set_secure_cookie = set_signed_cookie 

785 

786 def create_signed_value( 

787 self, name: str, value: Union[str, bytes], version: Optional[int] = None 

788 ) -> bytes: 

789 """Signs and timestamps a string so it cannot be forged. 

790 

791 Normally used via set_signed_cookie, but provided as a separate 

792 method for non-cookie uses. To decode a value not stored 

793 as a cookie use the optional value argument to get_signed_cookie. 

794 

795 .. versionchanged:: 3.2.1 

796 

797 Added the ``version`` argument. Introduced cookie version 2 

798 and made it the default. 

799 """ 

800 self.require_setting("cookie_secret", "secure cookies") 

801 secret = self.application.settings["cookie_secret"] 

802 key_version = None 

803 if isinstance(secret, dict): 

804 if self.application.settings.get("key_version") is None: 

805 raise Exception("key_version setting must be used for secret_key dicts") 

806 key_version = self.application.settings["key_version"] 

807 

808 return create_signed_value( 

809 secret, name, value, version=version, key_version=key_version 

810 ) 

811 

812 def get_signed_cookie( 

813 self, 

814 name: str, 

815 value: Optional[str] = None, 

816 max_age_days: float = 31, 

817 min_version: Optional[int] = None, 

818 ) -> Optional[bytes]: 

819 """Returns the given signed cookie if it validates, or None. 

820 

821 The decoded cookie value is returned as a byte string (unlike 

822 `get_cookie`). 

823 

824 Similar to `get_cookie`, this method only returns cookies that 

825 were present in the request. It does not see outgoing cookies set by 

826 `set_signed_cookie` in this handler. 

827 

828 .. versionchanged:: 3.2.1 

829 

830 Added the ``min_version`` argument. Introduced cookie version 2; 

831 both versions 1 and 2 are accepted by default. 

832 

833 .. versionchanged:: 6.3 

834 

835 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

836 avoid confusion with other uses of "secure" in cookie attributes 

837 and prefixes. The old name remains as an alias. 

838 

839 """ 

840 self.require_setting("cookie_secret", "secure cookies") 

841 if value is None: 

842 value = self.get_cookie(name) 

843 return decode_signed_value( 

844 self.application.settings["cookie_secret"], 

845 name, 

846 value, 

847 max_age_days=max_age_days, 

848 min_version=min_version, 

849 ) 

850 

851 get_secure_cookie = get_signed_cookie 

852 

853 def get_signed_cookie_key_version( 

854 self, name: str, value: Optional[str] = None 

855 ) -> Optional[int]: 

856 """Returns the signing key version of the secure cookie. 

857 

858 The version is returned as int. 

859 

860 .. versionchanged:: 6.3 

861 

862 Renamed from ``get_secure_cookie_key_version`` to 

863 ``set_signed_cookie_key_version`` to avoid confusion with other 

864 uses of "secure" in cookie attributes and prefixes. The old name 

865 remains as an alias. 

866 

867 """ 

868 self.require_setting("cookie_secret", "secure cookies") 

869 if value is None: 

870 value = self.get_cookie(name) 

871 if value is None: 

872 return None 

873 return get_signature_key_version(value) 

874 

875 get_secure_cookie_key_version = get_signed_cookie_key_version 

876 

877 def redirect( 

878 self, url: str, permanent: bool = False, status: Optional[int] = None 

879 ) -> None: 

880 """Sends a redirect to the given (optionally relative) URL. 

881 

882 If the ``status`` argument is specified, that value is used as the 

883 HTTP status code; otherwise either 301 (permanent) or 302 

884 (temporary) is chosen based on the ``permanent`` argument. 

885 The default is 302 (temporary). 

886 """ 

887 if self._headers_written: 

888 raise Exception("Cannot redirect after headers have been written") 

889 if status is None: 

890 status = 301 if permanent else 302 

891 else: 

892 assert isinstance(status, int) and 300 <= status <= 399 

893 self.set_status(status) 

894 self.set_header("Location", utf8(url)) 

895 self.finish() 

896 

897 def write(self, chunk: Union[str, bytes, dict]) -> None: 

898 """Writes the given chunk to the output buffer. 

899 

900 To write the output to the network, use the `flush()` method below. 

901 

902 If the given chunk is a dictionary, we write it as JSON and set 

903 the Content-Type of the response to be ``application/json``. 

904 (if you want to send JSON as a different ``Content-Type``, call 

905 ``set_header`` *after* calling ``write()``). 

906 

907 Note that lists are not converted to JSON because of a potential 

908 cross-site security vulnerability. All JSON output should be 

909 wrapped in a dictionary. More details at 

910 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and 

911 https://github.com/facebook/tornado/issues/1009 

912 """ 

913 if self._finished: 

914 raise RuntimeError("Cannot write() after finish()") 

915 if not isinstance(chunk, (bytes, unicode_type, dict)): 

916 message = "write() only accepts bytes, unicode, and dict objects" 

917 if isinstance(chunk, list): 

918 message += ( 

919 ". Lists not accepted for security reasons; see " 

920 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501 

921 ) 

922 raise TypeError(message) 

923 if isinstance(chunk, dict): 

924 chunk = escape.json_encode(chunk) 

925 self.set_header("Content-Type", "application/json; charset=UTF-8") 

926 chunk = utf8(chunk) 

927 self._write_buffer.append(chunk) 

928 

929 def render(self, template_name: str, **kwargs: Any) -> "Future[None]": 

930 """Renders the template with the given arguments as the response. 

931 

932 ``render()`` calls ``finish()``, so no other output methods can be called 

933 after it. 

934 

935 Returns a `.Future` with the same semantics as the one returned by `finish`. 

936 Awaiting this `.Future` is optional. 

937 

938 .. versionchanged:: 5.1 

939 

940 Now returns a `.Future` instead of ``None``. 

941 """ 

942 if self._finished: 

943 raise RuntimeError("Cannot render() after finish()") 

944 html = self.render_string(template_name, **kwargs) 

945 

946 # Insert the additional JS and CSS added by the modules on the page 

947 js_embed = [] 

948 js_files = [] 

949 css_embed = [] 

950 css_files = [] 

951 html_heads = [] 

952 html_bodies = [] 

953 for module in getattr(self, "_active_modules", {}).values(): 

954 embed_part = module.embedded_javascript() 

955 if embed_part: 

956 js_embed.append(utf8(embed_part)) 

957 file_part = module.javascript_files() 

958 if file_part: 

959 if isinstance(file_part, (unicode_type, bytes)): 

960 js_files.append(_unicode(file_part)) 

961 else: 

962 js_files.extend(file_part) 

963 embed_part = module.embedded_css() 

964 if embed_part: 

965 css_embed.append(utf8(embed_part)) 

966 file_part = module.css_files() 

967 if file_part: 

968 if isinstance(file_part, (unicode_type, bytes)): 

969 css_files.append(_unicode(file_part)) 

970 else: 

971 css_files.extend(file_part) 

972 head_part = module.html_head() 

973 if head_part: 

974 html_heads.append(utf8(head_part)) 

975 body_part = module.html_body() 

976 if body_part: 

977 html_bodies.append(utf8(body_part)) 

978 

979 if js_files: 

980 # Maintain order of JavaScript files given by modules 

981 js = self.render_linked_js(js_files) 

982 sloc = html.rindex(b"</body>") 

983 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:] 

984 if js_embed: 

985 js_bytes = self.render_embed_js(js_embed) 

986 sloc = html.rindex(b"</body>") 

987 html = html[:sloc] + js_bytes + b"\n" + html[sloc:] 

988 if css_files: 

989 css = self.render_linked_css(css_files) 

990 hloc = html.index(b"</head>") 

991 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:] 

992 if css_embed: 

993 css_bytes = self.render_embed_css(css_embed) 

994 hloc = html.index(b"</head>") 

995 html = html[:hloc] + css_bytes + b"\n" + html[hloc:] 

996 if html_heads: 

997 hloc = html.index(b"</head>") 

998 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:] 

999 if html_bodies: 

1000 hloc = html.index(b"</body>") 

1001 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:] 

1002 return self.finish(html) 

1003 

1004 def render_linked_js(self, js_files: Iterable[str]) -> str: 

1005 """Default method used to render the final js links for the 

1006 rendered webpage. 

1007 

1008 Override this method in a sub-classed controller to change the output. 

1009 """ 

1010 paths = [] 

1011 unique_paths = set() # type: Set[str] 

1012 

1013 for path in js_files: 

1014 if not is_absolute(path): 

1015 path = self.static_url(path) 

1016 if path not in unique_paths: 

1017 paths.append(path) 

1018 unique_paths.add(path) 

1019 

1020 return "".join( 

1021 '<script src="' 

1022 + escape.xhtml_escape(p) 

1023 + '" type="text/javascript"></script>' 

1024 for p in paths 

1025 ) 

1026 

1027 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes: 

1028 """Default method used to render the final embedded js for the 

1029 rendered webpage. 

1030 

1031 Override this method in a sub-classed controller to change the output. 

1032 """ 

1033 return ( 

1034 b'<script type="text/javascript">\n//<![CDATA[\n' 

1035 + b"\n".join(js_embed) 

1036 + b"\n//]]>\n</script>" 

1037 ) 

1038 

1039 def render_linked_css(self, css_files: Iterable[str]) -> str: 

1040 """Default method used to render the final css links for the 

1041 rendered webpage. 

1042 

1043 Override this method in a sub-classed controller to change the output. 

1044 """ 

1045 paths = [] 

1046 unique_paths = set() # type: Set[str] 

1047 

1048 for path in css_files: 

1049 if not is_absolute(path): 

1050 path = self.static_url(path) 

1051 if path not in unique_paths: 

1052 paths.append(path) 

1053 unique_paths.add(path) 

1054 

1055 return "".join( 

1056 '<link href="' + escape.xhtml_escape(p) + '" ' 

1057 'type="text/css" rel="stylesheet"/>' 

1058 for p in paths 

1059 ) 

1060 

1061 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes: 

1062 """Default method used to render the final embedded css for the 

1063 rendered webpage. 

1064 

1065 Override this method in a sub-classed controller to change the output. 

1066 """ 

1067 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>" 

1068 

1069 def render_string(self, template_name: str, **kwargs: Any) -> bytes: 

1070 """Generate the given template with the given arguments. 

1071 

1072 We return the generated byte string (in utf8). To generate and 

1073 write a template as a response, use render() above. 

1074 """ 

1075 # If no template_path is specified, use the path of the calling file 

1076 template_path = self.get_template_path() 

1077 if not template_path: 

1078 frame = sys._getframe(0) 

1079 web_file = frame.f_code.co_filename 

1080 while frame.f_code.co_filename == web_file and frame.f_back is not None: 

1081 frame = frame.f_back 

1082 assert frame.f_code.co_filename is not None 

1083 template_path = os.path.dirname(frame.f_code.co_filename) 

1084 with RequestHandler._template_loader_lock: 

1085 if template_path not in RequestHandler._template_loaders: 

1086 loader = self.create_template_loader(template_path) 

1087 RequestHandler._template_loaders[template_path] = loader 

1088 else: 

1089 loader = RequestHandler._template_loaders[template_path] 

1090 t = loader.load(template_name) 

1091 namespace = self.get_template_namespace() 

1092 namespace.update(kwargs) 

1093 return t.generate(**namespace) 

1094 

1095 def get_template_namespace(self) -> Dict[str, Any]: 

1096 """Returns a dictionary to be used as the default template namespace. 

1097 

1098 May be overridden by subclasses to add or modify values. 

1099 

1100 The results of this method will be combined with additional 

1101 defaults in the `tornado.template` module and keyword arguments 

1102 to `render` or `render_string`. 

1103 """ 

1104 namespace = dict( 

1105 handler=self, 

1106 request=self.request, 

1107 current_user=self.current_user, 

1108 locale=self.locale, 

1109 _=self.locale.translate, 

1110 pgettext=self.locale.pgettext, 

1111 static_url=self.static_url, 

1112 xsrf_form_html=self.xsrf_form_html, 

1113 reverse_url=self.reverse_url, 

1114 ) 

1115 namespace.update(self.ui) 

1116 return namespace 

1117 

1118 def create_template_loader(self, template_path: str) -> template.BaseLoader: 

1119 """Returns a new template loader for the given path. 

1120 

1121 May be overridden by subclasses. By default returns a 

1122 directory-based loader on the given path, using the 

1123 ``autoescape`` and ``template_whitespace`` application 

1124 settings. If a ``template_loader`` application setting is 

1125 supplied, uses that instead. 

1126 """ 

1127 settings = self.application.settings 

1128 if "template_loader" in settings: 

1129 return settings["template_loader"] 

1130 kwargs = {} 

1131 if "autoescape" in settings: 

1132 # autoescape=None means "no escaping", so we have to be sure 

1133 # to only pass this kwarg if the user asked for it. 

1134 kwargs["autoescape"] = settings["autoescape"] 

1135 if "template_whitespace" in settings: 

1136 kwargs["whitespace"] = settings["template_whitespace"] 

1137 return template.Loader(template_path, **kwargs) 

1138 

1139 def flush(self, include_footers: bool = False) -> "Future[None]": 

1140 """Flushes the current output buffer to the network. 

1141 

1142 .. versionchanged:: 4.0 

1143 Now returns a `.Future` if no callback is given. 

1144 

1145 .. versionchanged:: 6.0 

1146 

1147 The ``callback`` argument was removed. 

1148 """ 

1149 assert self.request.connection is not None 

1150 chunk = b"".join(self._write_buffer) 

1151 self._write_buffer = [] 

1152 if not self._headers_written: 

1153 self._headers_written = True 

1154 for transform in self._transforms: 

1155 assert chunk is not None 

1156 ( 

1157 self._status_code, 

1158 self._headers, 

1159 chunk, 

1160 ) = transform.transform_first_chunk( 

1161 self._status_code, self._headers, chunk, include_footers 

1162 ) 

1163 # Ignore the chunk and only write the headers for HEAD requests 

1164 if self.request.method == "HEAD": 

1165 chunk = b"" 

1166 

1167 # Finalize the cookie headers (which have been stored in a side 

1168 # object so an outgoing cookie could be overwritten before it 

1169 # is sent). 

1170 if hasattr(self, "_new_cookie"): 

1171 for cookie in self._new_cookie.values(): 

1172 self.add_header("Set-Cookie", cookie.OutputString(None)) 

1173 

1174 start_line = httputil.ResponseStartLine("", self._status_code, self._reason) 

1175 return self.request.connection.write_headers( 

1176 start_line, self._headers, chunk 

1177 ) 

1178 else: 

1179 for transform in self._transforms: 

1180 chunk = transform.transform_chunk(chunk, include_footers) 

1181 # Ignore the chunk and only write the headers for HEAD requests 

1182 if self.request.method != "HEAD": 

1183 return self.request.connection.write(chunk) 

1184 else: 

1185 future = Future() # type: Future[None] 

1186 future.set_result(None) 

1187 return future 

1188 

1189 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]": 

1190 """Finishes this response, ending the HTTP request. 

1191 

1192 Passing a ``chunk`` to ``finish()`` is equivalent to passing that 

1193 chunk to ``write()`` and then calling ``finish()`` with no arguments. 

1194 

1195 Returns a `.Future` which may optionally be awaited to track the sending 

1196 of the response to the client. This `.Future` resolves when all the response 

1197 data has been sent, and raises an error if the connection is closed before all 

1198 data can be sent. 

1199 

1200 .. versionchanged:: 5.1 

1201 

1202 Now returns a `.Future` instead of ``None``. 

1203 """ 

1204 if self._finished: 

1205 raise RuntimeError("finish() called twice") 

1206 

1207 if chunk is not None: 

1208 self.write(chunk) 

1209 

1210 # Automatically support ETags and add the Content-Length header if 

1211 # we have not flushed any content yet. 

1212 if not self._headers_written: 

1213 if ( 

1214 self._status_code == 200 

1215 and self.request.method in ("GET", "HEAD") 

1216 and "Etag" not in self._headers 

1217 ): 

1218 self.set_etag_header() 

1219 if self.check_etag_header(): 

1220 self._write_buffer = [] 

1221 self.set_status(304) 

1222 if self._status_code in (204, 304) or (100 <= self._status_code < 200): 

1223 assert not self._write_buffer, ( 

1224 "Cannot send body with %s" % self._status_code 

1225 ) 

1226 self._clear_representation_headers() 

1227 elif "Content-Length" not in self._headers: 

1228 content_length = sum(len(part) for part in self._write_buffer) 

1229 self.set_header("Content-Length", content_length) 

1230 

1231 assert self.request.connection is not None 

1232 # Now that the request is finished, clear the callback we 

1233 # set on the HTTPConnection (which would otherwise prevent the 

1234 # garbage collection of the RequestHandler when there 

1235 # are keepalive connections) 

1236 self.request.connection.set_close_callback(None) # type: ignore 

1237 

1238 future = self.flush(include_footers=True) 

1239 self.request.connection.finish() 

1240 self._log() 

1241 self._finished = True 

1242 self.on_finish() 

1243 self._break_cycles() 

1244 return future 

1245 

1246 def detach(self) -> iostream.IOStream: 

1247 """Take control of the underlying stream. 

1248 

1249 Returns the underlying `.IOStream` object and stops all 

1250 further HTTP processing. Intended for implementing protocols 

1251 like websockets that tunnel over an HTTP handshake. 

1252 

1253 This method is only supported when HTTP/1.1 is used. 

1254 

1255 .. versionadded:: 5.1 

1256 """ 

1257 self._finished = True 

1258 # TODO: add detach to HTTPConnection? 

1259 return self.request.connection.detach() # type: ignore 

1260 

1261 def _break_cycles(self) -> None: 

1262 # Break up a reference cycle between this handler and the 

1263 # _ui_module closures to allow for faster GC on CPython. 

1264 self.ui = None # type: ignore 

1265 

1266 def send_error(self, status_code: int = 500, **kwargs: Any) -> None: 

1267 """Sends the given HTTP error code to the browser. 

1268 

1269 If `flush()` has already been called, it is not possible to send 

1270 an error, so this method will simply terminate the response. 

1271 If output has been written but not yet flushed, it will be discarded 

1272 and replaced with the error page. 

1273 

1274 Override `write_error()` to customize the error page that is returned. 

1275 Additional keyword arguments are passed through to `write_error`. 

1276 """ 

1277 if self._headers_written: 

1278 gen_log.error("Cannot send error response after headers written") 

1279 if not self._finished: 

1280 # If we get an error between writing headers and finishing, 

1281 # we are unlikely to be able to finish due to a 

1282 # Content-Length mismatch. Try anyway to release the 

1283 # socket. 

1284 try: 

1285 self.finish() 

1286 except Exception: 

1287 gen_log.error("Failed to flush partial response", exc_info=True) 

1288 return 

1289 self.clear() 

1290 

1291 reason = kwargs.get("reason") 

1292 if "exc_info" in kwargs: 

1293 exception = kwargs["exc_info"][1] 

1294 if isinstance(exception, HTTPError) and exception.reason: 

1295 reason = exception.reason 

1296 self.set_status(status_code, reason=reason) 

1297 try: 

1298 self.write_error(status_code, **kwargs) 

1299 except Exception: 

1300 app_log.error("Uncaught exception in write_error", exc_info=True) 

1301 if not self._finished: 

1302 self.finish() 

1303 

1304 def write_error(self, status_code: int, **kwargs: Any) -> None: 

1305 """Override to implement custom error pages. 

1306 

1307 ``write_error`` may call `write`, `render`, `set_header`, etc 

1308 to produce output as usual. 

1309 

1310 If this error was caused by an uncaught exception (including 

1311 HTTPError), an ``exc_info`` triple will be available as 

1312 ``kwargs["exc_info"]``. Note that this exception may not be 

1313 the "current" exception for purposes of methods like 

1314 ``sys.exc_info()`` or ``traceback.format_exc``. 

1315 """ 

1316 if self.settings.get("serve_traceback") and "exc_info" in kwargs: 

1317 # in debug mode, try to send a traceback 

1318 self.set_header("Content-Type", "text/plain") 

1319 for line in traceback.format_exception(*kwargs["exc_info"]): 

1320 self.write(line) 

1321 self.finish() 

1322 else: 

1323 self.finish( 

1324 "<html><title>%(code)d: %(message)s</title>" 

1325 "<body>%(code)d: %(message)s</body></html>" 

1326 % {"code": status_code, "message": self._reason} 

1327 ) 

1328 

1329 @property 

1330 def locale(self) -> tornado.locale.Locale: 

1331 """The locale for the current session. 

1332 

1333 Determined by either `get_user_locale`, which you can override to 

1334 set the locale based on, e.g., a user preference stored in a 

1335 database, or `get_browser_locale`, which uses the ``Accept-Language`` 

1336 header. 

1337 

1338 .. versionchanged: 4.1 

1339 Added a property setter. 

1340 """ 

1341 if not hasattr(self, "_locale"): 

1342 loc = self.get_user_locale() 

1343 if loc is not None: 

1344 self._locale = loc 

1345 else: 

1346 self._locale = self.get_browser_locale() 

1347 assert self._locale 

1348 return self._locale 

1349 

1350 @locale.setter 

1351 def locale(self, value: tornado.locale.Locale) -> None: 

1352 self._locale = value 

1353 

1354 def get_user_locale(self) -> Optional[tornado.locale.Locale]: 

1355 """Override to determine the locale from the authenticated user. 

1356 

1357 If None is returned, we fall back to `get_browser_locale()`. 

1358 

1359 This method should return a `tornado.locale.Locale` object, 

1360 most likely obtained via a call like ``tornado.locale.get("en")`` 

1361 """ 

1362 return None 

1363 

1364 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale: 

1365 """Determines the user's locale from ``Accept-Language`` header. 

1366 

1367 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 

1368 """ 

1369 if "Accept-Language" in self.request.headers: 

1370 languages = self.request.headers["Accept-Language"].split(",") 

1371 locales = [] 

1372 for language in languages: 

1373 parts = language.strip().split(";") 

1374 if len(parts) > 1 and parts[1].strip().startswith("q="): 

1375 try: 

1376 score = float(parts[1].strip()[2:]) 

1377 if score < 0: 

1378 raise ValueError() 

1379 except (ValueError, TypeError): 

1380 score = 0.0 

1381 else: 

1382 score = 1.0 

1383 if score > 0: 

1384 locales.append((parts[0], score)) 

1385 if locales: 

1386 locales.sort(key=lambda pair: pair[1], reverse=True) 

1387 codes = [loc[0] for loc in locales] 

1388 return locale.get(*codes) 

1389 return locale.get(default) 

1390 

1391 @property 

1392 def current_user(self) -> Any: 

1393 """The authenticated user for this request. 

1394 

1395 This is set in one of two ways: 

1396 

1397 * A subclass may override `get_current_user()`, which will be called 

1398 automatically the first time ``self.current_user`` is accessed. 

1399 `get_current_user()` will only be called once per request, 

1400 and is cached for future access:: 

1401 

1402 def get_current_user(self): 

1403 user_cookie = self.get_signed_cookie("user") 

1404 if user_cookie: 

1405 return json.loads(user_cookie) 

1406 return None 

1407 

1408 * It may be set as a normal variable, typically from an overridden 

1409 `prepare()`:: 

1410 

1411 @gen.coroutine 

1412 def prepare(self): 

1413 user_id_cookie = self.get_signed_cookie("user_id") 

1414 if user_id_cookie: 

1415 self.current_user = yield load_user(user_id_cookie) 

1416 

1417 Note that `prepare()` may be a coroutine while `get_current_user()` 

1418 may not, so the latter form is necessary if loading the user requires 

1419 asynchronous operations. 

1420 

1421 The user object may be any type of the application's choosing. 

1422 """ 

1423 if not hasattr(self, "_current_user"): 

1424 self._current_user = self.get_current_user() 

1425 return self._current_user 

1426 

1427 @current_user.setter 

1428 def current_user(self, value: Any) -> None: 

1429 self._current_user = value 

1430 

1431 def get_current_user(self) -> Any: 

1432 """Override to determine the current user from, e.g., a cookie. 

1433 

1434 This method may not be a coroutine. 

1435 """ 

1436 return None 

1437 

1438 def get_login_url(self) -> str: 

1439 """Override to customize the login URL based on the request. 

1440 

1441 By default, we use the ``login_url`` application setting. 

1442 """ 

1443 self.require_setting("login_url", "@tornado.web.authenticated") 

1444 return self.application.settings["login_url"] 

1445 

1446 def get_template_path(self) -> Optional[str]: 

1447 """Override to customize template path for each handler. 

1448 

1449 By default, we use the ``template_path`` application setting. 

1450 Return None to load templates relative to the calling file. 

1451 """ 

1452 return self.application.settings.get("template_path") 

1453 

1454 @property 

1455 def xsrf_token(self) -> bytes: 

1456 """The XSRF-prevention token for the current user/session. 

1457 

1458 To prevent cross-site request forgery, we set an '_xsrf' cookie 

1459 and include the same '_xsrf' value as an argument with all POST 

1460 requests. If the two do not match, we reject the form submission 

1461 as a potential forgery. 

1462 

1463 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 

1464 

1465 This property is of type `bytes`, but it contains only ASCII 

1466 characters. If a character string is required, there is no 

1467 need to base64-encode it; just decode the byte string as 

1468 UTF-8. 

1469 

1470 .. versionchanged:: 3.2.2 

1471 The xsrf token will now be have a random mask applied in every 

1472 request, which makes it safe to include the token in pages 

1473 that are compressed. See http://breachattack.com for more 

1474 information on the issue fixed by this change. Old (version 1) 

1475 cookies will be converted to version 2 when this method is called 

1476 unless the ``xsrf_cookie_version`` `Application` setting is 

1477 set to 1. 

1478 

1479 .. versionchanged:: 4.3 

1480 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1481 used to supply additional cookie options (which will be 

1482 passed directly to `set_cookie`). For example, 

1483 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)`` 

1484 will set the ``secure`` and ``httponly`` flags on the 

1485 ``_xsrf`` cookie. 

1486 """ 

1487 if not hasattr(self, "_xsrf_token"): 

1488 version, token, timestamp = self._get_raw_xsrf_token() 

1489 output_version = self.settings.get("xsrf_cookie_version", 2) 

1490 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {}) 

1491 if output_version == 1: 

1492 self._xsrf_token = binascii.b2a_hex(token) 

1493 elif output_version == 2: 

1494 mask = os.urandom(4) 

1495 self._xsrf_token = b"|".join( 

1496 [ 

1497 b"2", 

1498 binascii.b2a_hex(mask), 

1499 binascii.b2a_hex(_websocket_mask(mask, token)), 

1500 utf8(str(int(timestamp))), 

1501 ] 

1502 ) 

1503 else: 

1504 raise ValueError("unknown xsrf cookie version %d", output_version) 

1505 if version is None: 

1506 if self.current_user and "expires_days" not in cookie_kwargs: 

1507 cookie_kwargs["expires_days"] = 30 

1508 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf") 

1509 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs) 

1510 return self._xsrf_token 

1511 

1512 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]: 

1513 """Read or generate the xsrf token in its raw form. 

1514 

1515 The raw_xsrf_token is a tuple containing: 

1516 

1517 * version: the version of the cookie from which this token was read, 

1518 or None if we generated a new token in this request. 

1519 * token: the raw token data; random (non-ascii) bytes. 

1520 * timestamp: the time this token was generated (will not be accurate 

1521 for version 1 cookies) 

1522 """ 

1523 if not hasattr(self, "_raw_xsrf_token"): 

1524 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf") 

1525 cookie = self.get_cookie(cookie_name) 

1526 if cookie: 

1527 version, token, timestamp = self._decode_xsrf_token(cookie) 

1528 else: 

1529 version, token, timestamp = None, None, None 

1530 if token is None: 

1531 version = None 

1532 token = os.urandom(16) 

1533 timestamp = time.time() 

1534 assert token is not None 

1535 assert timestamp is not None 

1536 self._raw_xsrf_token = (version, token, timestamp) 

1537 return self._raw_xsrf_token 

1538 

1539 def _decode_xsrf_token( 

1540 self, cookie: str 

1541 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]: 

1542 """Convert a cookie string into a the tuple form returned by 

1543 _get_raw_xsrf_token. 

1544 """ 

1545 

1546 try: 

1547 m = _signed_value_version_re.match(utf8(cookie)) 

1548 

1549 if m: 

1550 version = int(m.group(1)) 

1551 if version == 2: 

1552 _, mask_str, masked_token, timestamp_str = cookie.split("|") 

1553 

1554 mask = binascii.a2b_hex(utf8(mask_str)) 

1555 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token))) 

1556 timestamp = int(timestamp_str) 

1557 return version, token, timestamp 

1558 else: 

1559 # Treat unknown versions as not present instead of failing. 

1560 raise Exception("Unknown xsrf cookie version") 

1561 else: 

1562 version = 1 

1563 try: 

1564 token = binascii.a2b_hex(utf8(cookie)) 

1565 except (binascii.Error, TypeError): 

1566 token = utf8(cookie) 

1567 # We don't have a usable timestamp in older versions. 

1568 timestamp = int(time.time()) 

1569 return (version, token, timestamp) 

1570 except Exception: 

1571 # Catch exceptions and return nothing instead of failing. 

1572 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True) 

1573 return None, None, None 

1574 

1575 def check_xsrf_cookie(self) -> None: 

1576 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument. 

1577 

1578 To prevent cross-site request forgery, we set an ``_xsrf`` 

1579 cookie and include the same value as a non-cookie 

1580 field with all ``POST`` requests. If the two do not match, we 

1581 reject the form submission as a potential forgery. 

1582 

1583 The ``_xsrf`` value may be set as either a form field named ``_xsrf`` 

1584 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken`` 

1585 (the latter is accepted for compatibility with Django). 

1586 

1587 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 

1588 

1589 .. versionchanged:: 3.2.2 

1590 Added support for cookie version 2. Both versions 1 and 2 are 

1591 supported. 

1592 """ 

1593 # Prior to release 1.1.1, this check was ignored if the HTTP header 

1594 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception 

1595 # has been shown to be insecure and has been removed. For more 

1596 # information please see 

1597 # http://www.djangoproject.com/weblog/2011/feb/08/security/ 

1598 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails 

1599 token = ( 

1600 self.get_argument("_xsrf", None) 

1601 or self.request.headers.get("X-Xsrftoken") 

1602 or self.request.headers.get("X-Csrftoken") 

1603 ) 

1604 if not token: 

1605 raise HTTPError(403, "'_xsrf' argument missing from POST") 

1606 _, token, _ = self._decode_xsrf_token(token) 

1607 _, expected_token, _ = self._get_raw_xsrf_token() 

1608 if not token: 

1609 raise HTTPError(403, "'_xsrf' argument has invalid format") 

1610 if not hmac.compare_digest(utf8(token), utf8(expected_token)): 

1611 raise HTTPError(403, "XSRF cookie does not match POST argument") 

1612 

1613 def xsrf_form_html(self) -> str: 

1614 """An HTML ``<input/>`` element to be included with all POST forms. 

1615 

1616 It defines the ``_xsrf`` input value, which we check on all POST 

1617 requests to prevent cross-site request forgery. If you have set 

1618 the ``xsrf_cookies`` application setting, you must include this 

1619 HTML within all of your HTML forms. 

1620 

1621 In a template, this method should be called with ``{% module 

1622 xsrf_form_html() %}`` 

1623 

1624 See `check_xsrf_cookie()` above for more information. 

1625 """ 

1626 return ( 

1627 '<input type="hidden" name="_xsrf" value="' 

1628 + escape.xhtml_escape(self.xsrf_token) 

1629 + '"/>' 

1630 ) 

1631 

1632 def static_url( 

1633 self, path: str, include_host: Optional[bool] = None, **kwargs: Any 

1634 ) -> str: 

1635 """Returns a static URL for the given relative static file path. 

1636 

1637 This method requires you set the ``static_path`` setting in your 

1638 application (which specifies the root directory of your static 

1639 files). 

1640 

1641 This method returns a versioned url (by default appending 

1642 ``?v=<signature>``), which allows the static files to be 

1643 cached indefinitely. This can be disabled by passing 

1644 ``include_version=False`` (in the default implementation; 

1645 other static file implementations are not required to support 

1646 this, but they may support other options). 

1647 

1648 By default this method returns URLs relative to the current 

1649 host, but if ``include_host`` is true the URL returned will be 

1650 absolute. If this handler has an ``include_host`` attribute, 

1651 that value will be used as the default for all `static_url` 

1652 calls that do not pass ``include_host`` as a keyword argument. 

1653 

1654 """ 

1655 self.require_setting("static_path", "static_url") 

1656 get_url = self.settings.get( 

1657 "static_handler_class", StaticFileHandler 

1658 ).make_static_url 

1659 

1660 if include_host is None: 

1661 include_host = getattr(self, "include_host", False) 

1662 

1663 if include_host: 

1664 base = self.request.protocol + "://" + self.request.host 

1665 else: 

1666 base = "" 

1667 

1668 return base + get_url(self.settings, path, **kwargs) 

1669 

1670 def require_setting(self, name: str, feature: str = "this feature") -> None: 

1671 """Raises an exception if the given app setting is not defined.""" 

1672 if not self.application.settings.get(name): 

1673 raise Exception( 

1674 "You must define the '%s' setting in your " 

1675 "application to use %s" % (name, feature) 

1676 ) 

1677 

1678 def reverse_url(self, name: str, *args: Any) -> str: 

1679 """Alias for `Application.reverse_url`.""" 

1680 return self.application.reverse_url(name, *args) 

1681 

1682 def compute_etag(self) -> Optional[str]: 

1683 """Computes the etag header to be used for this request. 

1684 

1685 By default uses a hash of the content written so far. 

1686 

1687 May be overridden to provide custom etag implementations, 

1688 or may return None to disable tornado's default etag support. 

1689 """ 

1690 hasher = hashlib.sha1() 

1691 for part in self._write_buffer: 

1692 hasher.update(part) 

1693 return '"%s"' % hasher.hexdigest() 

1694 

1695 def set_etag_header(self) -> None: 

1696 """Sets the response's Etag header using ``self.compute_etag()``. 

1697 

1698 Note: no header will be set if ``compute_etag()`` returns ``None``. 

1699 

1700 This method is called automatically when the request is finished. 

1701 """ 

1702 etag = self.compute_etag() 

1703 if etag is not None: 

1704 self.set_header("Etag", etag) 

1705 

1706 def check_etag_header(self) -> bool: 

1707 """Checks the ``Etag`` header against requests's ``If-None-Match``. 

1708 

1709 Returns ``True`` if the request's Etag matches and a 304 should be 

1710 returned. For example:: 

1711 

1712 self.set_etag_header() 

1713 if self.check_etag_header(): 

1714 self.set_status(304) 

1715 return 

1716 

1717 This method is called automatically when the request is finished, 

1718 but may be called earlier for applications that override 

1719 `compute_etag` and want to do an early check for ``If-None-Match`` 

1720 before completing the request. The ``Etag`` header should be set 

1721 (perhaps with `set_etag_header`) before calling this method. 

1722 """ 

1723 computed_etag = utf8(self._headers.get("Etag", "")) 

1724 # Find all weak and strong etag values from If-None-Match header 

1725 # because RFC 7232 allows multiple etag values in a single header. 

1726 etags = re.findall( 

1727 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", "")) 

1728 ) 

1729 if not computed_etag or not etags: 

1730 return False 

1731 

1732 match = False 

1733 if etags[0] == b"*": 

1734 match = True 

1735 else: 

1736 # Use a weak comparison when comparing entity-tags. 

1737 def val(x: bytes) -> bytes: 

1738 return x[2:] if x.startswith(b"W/") else x 

1739 

1740 for etag in etags: 

1741 if val(etag) == val(computed_etag): 

1742 match = True 

1743 break 

1744 return match 

1745 

1746 async def _execute( 

1747 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes 

1748 ) -> None: 

1749 """Executes this request with the given output transforms.""" 

1750 self._transforms = transforms 

1751 try: 

1752 if self.request.method not in self.SUPPORTED_METHODS: 

1753 raise HTTPError(405) 

1754 self.path_args = [self.decode_argument(arg) for arg in args] 

1755 self.path_kwargs = dict( 

1756 (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items() 

1757 ) 

1758 # If XSRF cookies are turned on, reject form submissions without 

1759 # the proper cookie 

1760 if self.request.method not in ( 

1761 "GET", 

1762 "HEAD", 

1763 "OPTIONS", 

1764 ) and self.application.settings.get("xsrf_cookies"): 

1765 self.check_xsrf_cookie() 

1766 

1767 result = self.prepare() 

1768 if result is not None: 

1769 result = await result # type: ignore 

1770 if self._prepared_future is not None: 

1771 # Tell the Application we've finished with prepare() 

1772 # and are ready for the body to arrive. 

1773 future_set_result_unless_cancelled(self._prepared_future, None) 

1774 if self._finished: 

1775 return 

1776 

1777 if _has_stream_request_body(self.__class__): 

1778 # In streaming mode request.body is a Future that signals 

1779 # the body has been completely received. The Future has no 

1780 # result; the data has been passed to self.data_received 

1781 # instead. 

1782 try: 

1783 await self.request._body_future 

1784 except iostream.StreamClosedError: 

1785 return 

1786 

1787 method = getattr(self, self.request.method.lower()) 

1788 result = method(*self.path_args, **self.path_kwargs) 

1789 if result is not None: 

1790 result = await result 

1791 if self._auto_finish and not self._finished: 

1792 self.finish() 

1793 except Exception as e: 

1794 try: 

1795 self._handle_request_exception(e) 

1796 except Exception: 

1797 app_log.error("Exception in exception handler", exc_info=True) 

1798 finally: 

1799 # Unset result to avoid circular references 

1800 result = None 

1801 if self._prepared_future is not None and not self._prepared_future.done(): 

1802 # In case we failed before setting _prepared_future, do it 

1803 # now (to unblock the HTTP server). Note that this is not 

1804 # in a finally block to avoid GC issues prior to Python 3.4. 

1805 self._prepared_future.set_result(None) 

1806 

1807 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: 

1808 """Implement this method to handle streamed request data. 

1809 

1810 Requires the `.stream_request_body` decorator. 

1811 

1812 May be a coroutine for flow control. 

1813 """ 

1814 raise NotImplementedError() 

1815 

1816 def _log(self) -> None: 

1817 """Logs the current request. 

1818 

1819 Sort of deprecated since this functionality was moved to the 

1820 Application, but left in place for the benefit of existing apps 

1821 that have overridden this method. 

1822 """ 

1823 self.application.log_request(self) 

1824 

1825 def _request_summary(self) -> str: 

1826 return "%s %s (%s)" % ( 

1827 self.request.method, 

1828 self.request.uri, 

1829 self.request.remote_ip, 

1830 ) 

1831 

1832 def _handle_request_exception(self, e: BaseException) -> None: 

1833 if isinstance(e, Finish): 

1834 # Not an error; just finish the request without logging. 

1835 if not self._finished: 

1836 self.finish(*e.args) 

1837 return 

1838 try: 

1839 self.log_exception(*sys.exc_info()) 

1840 except Exception: 

1841 # An error here should still get a best-effort send_error() 

1842 # to avoid leaking the connection. 

1843 app_log.error("Error in exception logger", exc_info=True) 

1844 if self._finished: 

1845 # Extra errors after the request has been finished should 

1846 # be logged, but there is no reason to continue to try and 

1847 # send a response. 

1848 return 

1849 if isinstance(e, HTTPError): 

1850 self.send_error(e.status_code, exc_info=sys.exc_info()) 

1851 else: 

1852 self.send_error(500, exc_info=sys.exc_info()) 

1853 

1854 def log_exception( 

1855 self, 

1856 typ: "Optional[Type[BaseException]]", 

1857 value: Optional[BaseException], 

1858 tb: Optional[TracebackType], 

1859 ) -> None: 

1860 """Override to customize logging of uncaught exceptions. 

1861 

1862 By default logs instances of `HTTPError` as warnings without 

1863 stack traces (on the ``tornado.general`` logger), and all 

1864 other exceptions as errors with stack traces (on the 

1865 ``tornado.application`` logger). 

1866 

1867 .. versionadded:: 3.1 

1868 """ 

1869 if isinstance(value, HTTPError): 

1870 if value.log_message: 

1871 format = "%d %s: " + value.log_message 

1872 args = [value.status_code, self._request_summary()] + list(value.args) 

1873 gen_log.warning(format, *args) 

1874 else: 

1875 app_log.error( 

1876 "Uncaught exception %s\n%r", 

1877 self._request_summary(), 

1878 self.request, 

1879 exc_info=(typ, value, tb), # type: ignore 

1880 ) 

1881 

1882 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]: 

1883 def render(*args, **kwargs) -> str: # type: ignore 

1884 if not hasattr(self, "_active_modules"): 

1885 self._active_modules = {} # type: Dict[str, UIModule] 

1886 if name not in self._active_modules: 

1887 self._active_modules[name] = module(self) 

1888 rendered = self._active_modules[name].render(*args, **kwargs) 

1889 return rendered 

1890 

1891 return render 

1892 

1893 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]: 

1894 return lambda *args, **kwargs: method(self, *args, **kwargs) 

1895 

1896 def _clear_representation_headers(self) -> None: 

1897 # 304 responses should not contain representation metadata 

1898 # headers (defined in 

1899 # https://tools.ietf.org/html/rfc7231#section-3.1) 

1900 # not explicitly allowed by 

1901 # https://tools.ietf.org/html/rfc7232#section-4.1 

1902 headers = ["Content-Encoding", "Content-Language", "Content-Type"] 

1903 for h in headers: 

1904 self.clear_header(h) 

1905 

1906 

1907_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler) 

1908 

1909 

1910def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]: 

1911 """Apply to `RequestHandler` subclasses to enable streaming body support. 

1912 

1913 This decorator implies the following changes: 

1914 

1915 * `.HTTPServerRequest.body` is undefined, and body arguments will not 

1916 be included in `RequestHandler.get_argument`. 

1917 * `RequestHandler.prepare` is called when the request headers have been 

1918 read instead of after the entire body has been read. 

1919 * The subclass must define a method ``data_received(self, data):``, which 

1920 will be called zero or more times as data is available. Note that 

1921 if the request has an empty body, ``data_received`` may not be called. 

1922 * ``prepare`` and ``data_received`` may return Futures (such as via 

1923 ``@gen.coroutine``, in which case the next method will not be called 

1924 until those futures have completed. 

1925 * The regular HTTP method (``post``, ``put``, etc) will be called after 

1926 the entire body has been read. 

1927 

1928 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_ 

1929 for example usage. 

1930 """ # noqa: E501 

1931 if not issubclass(cls, RequestHandler): 

1932 raise TypeError("expected subclass of RequestHandler, got %r", cls) 

1933 cls._stream_request_body = True 

1934 return cls 

1935 

1936 

1937def _has_stream_request_body(cls: Type[RequestHandler]) -> bool: 

1938 if not issubclass(cls, RequestHandler): 

1939 raise TypeError("expected subclass of RequestHandler, got %r", cls) 

1940 return cls._stream_request_body 

1941 

1942 

1943def removeslash( 

1944 method: Callable[..., Optional[Awaitable[None]]] 

1945) -> Callable[..., Optional[Awaitable[None]]]: 

1946 """Use this decorator to remove trailing slashes from the request path. 

1947 

1948 For example, a request to ``/foo/`` would redirect to ``/foo`` with this 

1949 decorator. Your request handler mapping should use a regular expression 

1950 like ``r'/foo/*'`` in conjunction with using the decorator. 

1951 """ 

1952 

1953 @functools.wraps(method) 

1954 def wrapper( # type: ignore 

1955 self: RequestHandler, *args, **kwargs 

1956 ) -> Optional[Awaitable[None]]: 

1957 if self.request.path.endswith("/"): 

1958 if self.request.method in ("GET", "HEAD"): 

1959 uri = self.request.path.rstrip("/") 

1960 if uri: # don't try to redirect '/' to '' 

1961 if self.request.query: 

1962 uri += "?" + self.request.query 

1963 self.redirect(uri, permanent=True) 

1964 return None 

1965 else: 

1966 raise HTTPError(404) 

1967 return method(self, *args, **kwargs) 

1968 

1969 return wrapper 

1970 

1971 

1972def addslash( 

1973 method: Callable[..., Optional[Awaitable[None]]] 

1974) -> Callable[..., Optional[Awaitable[None]]]: 

1975 """Use this decorator to add a missing trailing slash to the request path. 

1976 

1977 For example, a request to ``/foo`` would redirect to ``/foo/`` with this 

1978 decorator. Your request handler mapping should use a regular expression 

1979 like ``r'/foo/?'`` in conjunction with using the decorator. 

1980 """ 

1981 

1982 @functools.wraps(method) 

1983 def wrapper( # type: ignore 

1984 self: RequestHandler, *args, **kwargs 

1985 ) -> Optional[Awaitable[None]]: 

1986 if not self.request.path.endswith("/"): 

1987 if self.request.method in ("GET", "HEAD"): 

1988 uri = self.request.path + "/" 

1989 if self.request.query: 

1990 uri += "?" + self.request.query 

1991 self.redirect(uri, permanent=True) 

1992 return None 

1993 raise HTTPError(404) 

1994 return method(self, *args, **kwargs) 

1995 

1996 return wrapper 

1997 

1998 

1999class _ApplicationRouter(ReversibleRuleRouter): 

2000 """Routing implementation used internally by `Application`. 

2001 

2002 Provides a binding between `Application` and `RequestHandler`. 

2003 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways: 

2004 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and 

2005 * it allows to use a list/tuple of rules as `~.routing.Rule` target. 

2006 ``process_rule`` implementation will substitute this list with an appropriate 

2007 `_ApplicationRouter` instance. 

2008 """ 

2009 

2010 def __init__( 

2011 self, application: "Application", rules: Optional[_RuleList] = None 

2012 ) -> None: 

2013 assert isinstance(application, Application) 

2014 self.application = application 

2015 super().__init__(rules) 

2016 

2017 def process_rule(self, rule: Rule) -> Rule: 

2018 rule = super().process_rule(rule) 

2019 

2020 if isinstance(rule.target, (list, tuple)): 

2021 rule.target = _ApplicationRouter( 

2022 self.application, rule.target # type: ignore 

2023 ) 

2024 

2025 return rule 

2026 

2027 def get_target_delegate( 

2028 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any 

2029 ) -> Optional[httputil.HTTPMessageDelegate]: 

2030 if isclass(target) and issubclass(target, RequestHandler): 

2031 return self.application.get_handler_delegate( 

2032 request, target, **target_params 

2033 ) 

2034 

2035 return super().get_target_delegate(target, request, **target_params) 

2036 

2037 

2038class Application(ReversibleRouter): 

2039 r"""A collection of request handlers that make up a web application. 

2040 

2041 Instances of this class are callable and can be passed directly to 

2042 HTTPServer to serve the application:: 

2043 

2044 application = web.Application([ 

2045 (r"/", MainPageHandler), 

2046 ]) 

2047 http_server = httpserver.HTTPServer(application) 

2048 http_server.listen(8080) 

2049 

2050 The constructor for this class takes in a list of `~.routing.Rule` 

2051 objects or tuples of values corresponding to the arguments of 

2052 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``, 

2053 the values in square brackets being optional. The default matcher is 

2054 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used 

2055 instead of ``(PathMatches(regexp), target)``. 

2056 

2057 A common routing target is a `RequestHandler` subclass, but you can also 

2058 use lists of rules as a target, which create a nested routing configuration:: 

2059 

2060 application = web.Application([ 

2061 (HostMatches("example.com"), [ 

2062 (r"/", MainPageHandler), 

2063 (r"/feed", FeedHandler), 

2064 ]), 

2065 ]) 

2066 

2067 In addition to this you can use nested `~.routing.Router` instances, 

2068 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets 

2069 (see `~.routing` module docs for more information). 

2070 

2071 When we receive requests, we iterate over the list in order and 

2072 instantiate an instance of the first request class whose regexp 

2073 matches the request path. The request class can be specified as 

2074 either a class object or a (fully-qualified) name. 

2075 

2076 A dictionary may be passed as the third element (``target_kwargs``) 

2077 of the tuple, which will be used as keyword arguments to the handler's 

2078 constructor and `~RequestHandler.initialize` method. This pattern 

2079 is used for the `StaticFileHandler` in this example (note that a 

2080 `StaticFileHandler` can be installed automatically with the 

2081 static_path setting described below):: 

2082 

2083 application = web.Application([ 

2084 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2085 ]) 

2086 

2087 We support virtual hosts with the `add_handlers` method, which takes in 

2088 a host regular expression as the first argument:: 

2089 

2090 application.add_handlers(r"www\.myhost\.com", [ 

2091 (r"/article/([0-9]+)", ArticleHandler), 

2092 ]) 

2093 

2094 If there's no match for the current request's host, then ``default_host`` 

2095 parameter value is matched against host regular expressions. 

2096 

2097 

2098 .. warning:: 

2099 

2100 Applications that do not use TLS may be vulnerable to :ref:`DNS 

2101 rebinding <dnsrebinding>` attacks. This attack is especially 

2102 relevant to applications that only listen on ``127.0.0.1`` or 

2103 other private networks. Appropriate host patterns must be used 

2104 (instead of the default of ``r'.*'``) to prevent this risk. The 

2105 ``default_host`` argument must not be used in applications that 

2106 may be vulnerable to DNS rebinding. 

2107 

2108 You can serve static files by sending the ``static_path`` setting 

2109 as a keyword argument. We will serve those files from the 

2110 ``/static/`` URI (this is configurable with the 

2111 ``static_url_prefix`` setting), and we will serve ``/favicon.ico`` 

2112 and ``/robots.txt`` from the same directory. A custom subclass of 

2113 `StaticFileHandler` can be specified with the 

2114 ``static_handler_class`` setting. 

2115 

2116 .. versionchanged:: 4.5 

2117 Integration with the new `tornado.routing` module. 

2118 

2119 """ 

2120 

2121 def __init__( 

2122 self, 

2123 handlers: Optional[_RuleList] = None, 

2124 default_host: Optional[str] = None, 

2125 transforms: Optional[List[Type["OutputTransform"]]] = None, 

2126 **settings: Any, 

2127 ) -> None: 

2128 if transforms is None: 

2129 self.transforms = [] # type: List[Type[OutputTransform]] 

2130 if settings.get("compress_response") or settings.get("gzip"): 

2131 self.transforms.append(GZipContentEncoding) 

2132 else: 

2133 self.transforms = transforms 

2134 self.default_host = default_host 

2135 self.settings = settings 

2136 self.ui_modules = { 

2137 "linkify": _linkify, 

2138 "xsrf_form_html": _xsrf_form_html, 

2139 "Template": TemplateModule, 

2140 } 

2141 self.ui_methods = {} # type: Dict[str, Callable[..., str]] 

2142 self._load_ui_modules(settings.get("ui_modules", {})) 

2143 self._load_ui_methods(settings.get("ui_methods", {})) 

2144 if self.settings.get("static_path"): 

2145 path = self.settings["static_path"] 

2146 handlers = list(handlers or []) 

2147 static_url_prefix = settings.get("static_url_prefix", "/static/") 

2148 static_handler_class = settings.get( 

2149 "static_handler_class", StaticFileHandler 

2150 ) 

2151 static_handler_args = settings.get("static_handler_args", {}) 

2152 static_handler_args["path"] = path 

2153 for pattern in [ 

2154 re.escape(static_url_prefix) + r"(.*)", 

2155 r"/(favicon\.ico)", 

2156 r"/(robots\.txt)", 

2157 ]: 

2158 handlers.insert(0, (pattern, static_handler_class, static_handler_args)) 

2159 

2160 if self.settings.get("debug"): 

2161 self.settings.setdefault("autoreload", True) 

2162 self.settings.setdefault("compiled_template_cache", False) 

2163 self.settings.setdefault("static_hash_cache", False) 

2164 self.settings.setdefault("serve_traceback", True) 

2165 

2166 self.wildcard_router = _ApplicationRouter(self, handlers) 

2167 self.default_router = _ApplicationRouter( 

2168 self, [Rule(AnyMatches(), self.wildcard_router)] 

2169 ) 

2170 

2171 # Automatically reload modified modules 

2172 if self.settings.get("autoreload"): 

2173 from tornado import autoreload 

2174 

2175 autoreload.start() 

2176 

2177 def listen( 

2178 self, 

2179 port: int, 

2180 address: Optional[str] = None, 

2181 *, 

2182 family: socket.AddressFamily = socket.AF_UNSPEC, 

2183 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2184 flags: Optional[int] = None, 

2185 reuse_port: bool = False, 

2186 **kwargs: Any, 

2187 ) -> HTTPServer: 

2188 """Starts an HTTP server for this application on the given port. 

2189 

2190 This is a convenience alias for creating an `.HTTPServer` object and 

2191 calling its listen method. Keyword arguments not supported by 

2192 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer` 

2193 constructor. For advanced uses (e.g. multi-process mode), do not use 

2194 this method; create an `.HTTPServer` and call its 

2195 `.TCPServer.bind`/`.TCPServer.start` methods directly. 

2196 

2197 Note that after calling this method you still need to call 

2198 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start 

2199 the server. 

2200 

2201 Returns the `.HTTPServer` object. 

2202 

2203 .. versionchanged:: 4.3 

2204 Now returns the `.HTTPServer` object. 

2205 

2206 .. versionchanged:: 6.2 

2207 Added support for new keyword arguments in `.TCPServer.listen`, 

2208 including ``reuse_port``. 

2209 """ 

2210 server = HTTPServer(self, **kwargs) 

2211 server.listen( 

2212 port, 

2213 address=address, 

2214 family=family, 

2215 backlog=backlog, 

2216 flags=flags, 

2217 reuse_port=reuse_port, 

2218 ) 

2219 return server 

2220 

2221 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None: 

2222 """Appends the given handlers to our handler list. 

2223 

2224 Host patterns are processed sequentially in the order they were 

2225 added. All matching patterns will be considered. 

2226 """ 

2227 host_matcher = HostMatches(host_pattern) 

2228 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers)) 

2229 

2230 self.default_router.rules.insert(-1, rule) 

2231 

2232 if self.default_host is not None: 

2233 self.wildcard_router.add_rules( 

2234 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)] 

2235 ) 

2236 

2237 def add_transform(self, transform_class: Type["OutputTransform"]) -> None: 

2238 self.transforms.append(transform_class) 

2239 

2240 def _load_ui_methods(self, methods: Any) -> None: 

2241 if isinstance(methods, types.ModuleType): 

2242 self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods))) 

2243 elif isinstance(methods, list): 

2244 for m in methods: 

2245 self._load_ui_methods(m) 

2246 else: 

2247 for name, fn in methods.items(): 

2248 if ( 

2249 not name.startswith("_") 

2250 and hasattr(fn, "__call__") 

2251 and name[0].lower() == name[0] 

2252 ): 

2253 self.ui_methods[name] = fn 

2254 

2255 def _load_ui_modules(self, modules: Any) -> None: 

2256 if isinstance(modules, types.ModuleType): 

2257 self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules))) 

2258 elif isinstance(modules, list): 

2259 for m in modules: 

2260 self._load_ui_modules(m) 

2261 else: 

2262 assert isinstance(modules, dict) 

2263 for name, cls in modules.items(): 

2264 try: 

2265 if issubclass(cls, UIModule): 

2266 self.ui_modules[name] = cls 

2267 except TypeError: 

2268 pass 

2269 

2270 def __call__( 

2271 self, request: httputil.HTTPServerRequest 

2272 ) -> Optional[Awaitable[None]]: 

2273 # Legacy HTTPServer interface 

2274 dispatcher = self.find_handler(request) 

2275 return dispatcher.execute() 

2276 

2277 def find_handler( 

2278 self, request: httputil.HTTPServerRequest, **kwargs: Any 

2279 ) -> "_HandlerDelegate": 

2280 route = self.default_router.find_handler(request) 

2281 if route is not None: 

2282 return cast("_HandlerDelegate", route) 

2283 

2284 if self.settings.get("default_handler_class"): 

2285 return self.get_handler_delegate( 

2286 request, 

2287 self.settings["default_handler_class"], 

2288 self.settings.get("default_handler_args", {}), 

2289 ) 

2290 

2291 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404}) 

2292 

2293 def get_handler_delegate( 

2294 self, 

2295 request: httputil.HTTPServerRequest, 

2296 target_class: Type[RequestHandler], 

2297 target_kwargs: Optional[Dict[str, Any]] = None, 

2298 path_args: Optional[List[bytes]] = None, 

2299 path_kwargs: Optional[Dict[str, bytes]] = None, 

2300 ) -> "_HandlerDelegate": 

2301 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request 

2302 for application and `RequestHandler` subclass. 

2303 

2304 :arg httputil.HTTPServerRequest request: current HTTP request. 

2305 :arg RequestHandler target_class: a `RequestHandler` class. 

2306 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor. 

2307 :arg list path_args: positional arguments for ``target_class`` HTTP method that 

2308 will be executed while handling a request (``get``, ``post`` or any other). 

2309 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method. 

2310 """ 

2311 return _HandlerDelegate( 

2312 self, request, target_class, target_kwargs, path_args, path_kwargs 

2313 ) 

2314 

2315 def reverse_url(self, name: str, *args: Any) -> str: 

2316 """Returns a URL path for handler named ``name`` 

2317 

2318 The handler must be added to the application as a named `URLSpec`. 

2319 

2320 Args will be substituted for capturing groups in the `URLSpec` regex. 

2321 They will be converted to strings if necessary, encoded as utf8, 

2322 and url-escaped. 

2323 """ 

2324 reversed_url = self.default_router.reverse_url(name, *args) 

2325 if reversed_url is not None: 

2326 return reversed_url 

2327 

2328 raise KeyError("%s not found in named urls" % name) 

2329 

2330 def log_request(self, handler: RequestHandler) -> None: 

2331 """Writes a completed HTTP request to the logs. 

2332 

2333 By default writes to the python root logger. To change 

2334 this behavior either subclass Application and override this method, 

2335 or pass a function in the application settings dictionary as 

2336 ``log_function``. 

2337 """ 

2338 if "log_function" in self.settings: 

2339 self.settings["log_function"](handler) 

2340 return 

2341 if handler.get_status() < 400: 

2342 log_method = access_log.info 

2343 elif handler.get_status() < 500: 

2344 log_method = access_log.warning 

2345 else: 

2346 log_method = access_log.error 

2347 request_time = 1000.0 * handler.request.request_time() 

2348 log_method( 

2349 "%d %s %.2fms", 

2350 handler.get_status(), 

2351 handler._request_summary(), 

2352 request_time, 

2353 ) 

2354 

2355 

2356class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2357 def __init__( 

2358 self, 

2359 application: Application, 

2360 request: httputil.HTTPServerRequest, 

2361 handler_class: Type[RequestHandler], 

2362 handler_kwargs: Optional[Dict[str, Any]], 

2363 path_args: Optional[List[bytes]], 

2364 path_kwargs: Optional[Dict[str, bytes]], 

2365 ) -> None: 

2366 self.application = application 

2367 self.connection = request.connection 

2368 self.request = request 

2369 self.handler_class = handler_class 

2370 self.handler_kwargs = handler_kwargs or {} 

2371 self.path_args = path_args or [] 

2372 self.path_kwargs = path_kwargs or {} 

2373 self.chunks = [] # type: List[bytes] 

2374 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2375 

2376 def headers_received( 

2377 self, 

2378 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], 

2379 headers: httputil.HTTPHeaders, 

2380 ) -> Optional[Awaitable[None]]: 

2381 if self.stream_request_body: 

2382 self.request._body_future = Future() 

2383 return self.execute() 

2384 return None 

2385 

2386 def data_received(self, data: bytes) -> Optional[Awaitable[None]]: 

2387 if self.stream_request_body: 

2388 return self.handler.data_received(data) 

2389 else: 

2390 self.chunks.append(data) 

2391 return None 

2392 

2393 def finish(self) -> None: 

2394 if self.stream_request_body: 

2395 future_set_result_unless_cancelled(self.request._body_future, None) 

2396 else: 

2397 self.request.body = b"".join(self.chunks) 

2398 self.request._parse_body() 

2399 self.execute() 

2400 

2401 def on_connection_close(self) -> None: 

2402 if self.stream_request_body: 

2403 self.handler.on_connection_close() 

2404 else: 

2405 self.chunks = None # type: ignore 

2406 

2407 def execute(self) -> Optional[Awaitable[None]]: 

2408 # If template cache is disabled (usually in the debug mode), 

2409 # re-compile templates and reload static files on every 

2410 # request so you don't need to restart to see changes 

2411 if not self.application.settings.get("compiled_template_cache", True): 

2412 with RequestHandler._template_loader_lock: 

2413 for loader in RequestHandler._template_loaders.values(): 

2414 loader.reset() 

2415 if not self.application.settings.get("static_hash_cache", True): 

2416 static_handler_class = self.application.settings.get( 

2417 "static_handler_class", StaticFileHandler 

2418 ) 

2419 static_handler_class.reset() 

2420 

2421 self.handler = self.handler_class( 

2422 self.application, self.request, **self.handler_kwargs 

2423 ) 

2424 transforms = [t(self.request) for t in self.application.transforms] 

2425 

2426 if self.stream_request_body: 

2427 self.handler._prepared_future = Future() 

2428 # Note that if an exception escapes handler._execute it will be 

2429 # trapped in the Future it returns (which we are ignoring here, 

2430 # leaving it to be logged when the Future is GC'd). 

2431 # However, that shouldn't happen because _execute has a blanket 

2432 # except handler, and we cannot easily access the IOLoop here to 

2433 # call add_future (because of the requirement to remain compatible 

2434 # with WSGI) 

2435 fut = gen.convert_yielded( 

2436 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 

2437 ) 

2438 fut.add_done_callback(lambda f: f.result()) 

2439 # If we are streaming the request body, then execute() is finished 

2440 # when the handler has prepared to receive the body. If not, 

2441 # it doesn't matter when execute() finishes (so we return None) 

2442 return self.handler._prepared_future 

2443 

2444 

2445class HTTPError(Exception): 

2446 """An exception that will turn into an HTTP error response. 

2447 

2448 Raising an `HTTPError` is a convenient alternative to calling 

2449 `RequestHandler.send_error` since it automatically ends the 

2450 current function. 

2451 

2452 To customize the response sent with an `HTTPError`, override 

2453 `RequestHandler.write_error`. 

2454 

2455 :arg int status_code: HTTP status code. Must be listed in 

2456 `httplib.responses <http.client.responses>` unless the ``reason`` 

2457 keyword argument is given. 

2458 :arg str log_message: Message to be written to the log for this error 

2459 (will not be shown to the user unless the `Application` is in debug 

2460 mode). May contain ``%s``-style placeholders, which will be filled 

2461 in with remaining positional parameters. 

2462 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2463 to pass in the status line along with ``status_code``. Normally 

2464 determined automatically from ``status_code``, but can be used 

2465 to use a non-standard numeric code. 

2466 """ 

2467 

2468 def __init__( 

2469 self, 

2470 status_code: int = 500, 

2471 log_message: Optional[str] = None, 

2472 *args: Any, 

2473 **kwargs: Any, 

2474 ) -> None: 

2475 self.status_code = status_code 

2476 self.log_message = log_message 

2477 self.args = args 

2478 self.reason = kwargs.get("reason", None) 

2479 if log_message and not args: 

2480 self.log_message = log_message.replace("%", "%%") 

2481 

2482 def __str__(self) -> str: 

2483 message = "HTTP %d: %s" % ( 

2484 self.status_code, 

2485 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2486 ) 

2487 if self.log_message: 

2488 return message + " (" + (self.log_message % self.args) + ")" 

2489 else: 

2490 return message 

2491 

2492 

2493class Finish(Exception): 

2494 """An exception that ends the request without producing an error response. 

2495 

2496 When `Finish` is raised in a `RequestHandler`, the request will 

2497 end (calling `RequestHandler.finish` if it hasn't already been 

2498 called), but the error-handling methods (including 

2499 `RequestHandler.write_error`) will not be called. 

2500 

2501 If `Finish()` was created with no arguments, the pending response 

2502 will be sent as-is. If `Finish()` was given an argument, that 

2503 argument will be passed to `RequestHandler.finish()`. 

2504 

2505 This can be a more convenient way to implement custom error pages 

2506 than overriding ``write_error`` (especially in library code):: 

2507 

2508 if self.current_user is None: 

2509 self.set_status(401) 

2510 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2511 raise Finish() 

2512 

2513 .. versionchanged:: 4.3 

2514 Arguments passed to ``Finish()`` will be passed on to 

2515 `RequestHandler.finish`. 

2516 """ 

2517 

2518 pass 

2519 

2520 

2521class MissingArgumentError(HTTPError): 

2522 """Exception raised by `RequestHandler.get_argument`. 

2523 

2524 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2525 code will be used instead of 500 (and a stack trace will not be logged). 

2526 

2527 .. versionadded:: 3.1 

2528 """ 

2529 

2530 def __init__(self, arg_name: str) -> None: 

2531 super().__init__(400, "Missing argument %s" % arg_name) 

2532 self.arg_name = arg_name 

2533 

2534 

2535class ErrorHandler(RequestHandler): 

2536 """Generates an error response with ``status_code`` for all requests.""" 

2537 

2538 def initialize(self, status_code: int) -> None: 

2539 self.set_status(status_code) 

2540 

2541 def prepare(self) -> None: 

2542 raise HTTPError(self._status_code) 

2543 

2544 def check_xsrf_cookie(self) -> None: 

2545 # POSTs to an ErrorHandler don't actually have side effects, 

2546 # so we don't need to check the xsrf token. This allows POSTs 

2547 # to the wrong url to return a 404 instead of 403. 

2548 pass 

2549 

2550 

2551class RedirectHandler(RequestHandler): 

2552 """Redirects the client to the given URL for all GET requests. 

2553 

2554 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2555 

2556 application = web.Application([ 

2557 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2558 ]) 

2559 

2560 `RedirectHandler` supports regular expression substitutions. E.g., to 

2561 swap the first and second parts of a path while preserving the remainder:: 

2562 

2563 application = web.Application([ 

2564 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2565 ]) 

2566 

2567 The final URL is formatted with `str.format` and the substrings that match 

2568 the capturing groups. In the above example, a request to "/a/b/c" would be 

2569 formatted like:: 

2570 

2571 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2572 

2573 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2574 values are substituted. 

2575 

2576 .. versionchanged:: 4.5 

2577 Added support for substitutions into the destination URL. 

2578 

2579 .. versionchanged:: 5.0 

2580 If any query arguments are present, they will be copied to the 

2581 destination URL. 

2582 """ 

2583 

2584 def initialize(self, url: str, permanent: bool = True) -> None: 

2585 self._url = url 

2586 self._permanent = permanent 

2587 

2588 def get(self, *args: Any, **kwargs: Any) -> None: 

2589 to_url = self._url.format(*args, **kwargs) 

2590 if self.request.query_arguments: 

2591 # TODO: figure out typing for the next line. 

2592 to_url = httputil.url_concat( 

2593 to_url, 

2594 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2595 ) 

2596 self.redirect(to_url, permanent=self._permanent) 

2597 

2598 

2599class StaticFileHandler(RequestHandler): 

2600 """A simple handler that can serve static content from a directory. 

2601 

2602 A `StaticFileHandler` is configured automatically if you pass the 

2603 ``static_path`` keyword argument to `Application`. This handler 

2604 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2605 and ``static_handler_args`` settings. 

2606 

2607 To map an additional path to this handler for a static data directory 

2608 you would add a line to your application like:: 

2609 

2610 application = web.Application([ 

2611 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2612 ]) 

2613 

2614 The handler constructor requires a ``path`` argument, which specifies the 

2615 local root directory of the content to be served. 

2616 

2617 Note that a capture group in the regex is required to parse the value for 

2618 the ``path`` argument to the get() method (different than the constructor 

2619 argument above); see `URLSpec` for details. 

2620 

2621 To serve a file like ``index.html`` automatically when a directory is 

2622 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2623 in your application settings, or add ``default_filename`` as an initializer 

2624 argument for your ``StaticFileHandler``. 

2625 

2626 To maximize the effectiveness of browser caching, this class supports 

2627 versioned urls (by default using the argument ``?v=``). If a version 

2628 is given, we instruct the browser to cache this file indefinitely. 

2629 `make_static_url` (also available as `RequestHandler.static_url`) can 

2630 be used to construct a versioned url. 

2631 

2632 This handler is intended primarily for use in development and light-duty 

2633 file serving; for heavy traffic it will be more efficient to use 

2634 a dedicated static file server (such as nginx or Apache). We support 

2635 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2636 some browsers require this functionality to be present to seek in 

2637 HTML5 audio or video). 

2638 

2639 **Subclassing notes** 

2640 

2641 This class is designed to be extensible by subclassing, but because 

2642 of the way static urls are generated with class methods rather than 

2643 instance methods, the inheritance patterns are somewhat unusual. 

2644 Be sure to use the ``@classmethod`` decorator when overriding a 

2645 class method. Instance methods may use the attributes ``self.path`` 

2646 ``self.absolute_path``, and ``self.modified``. 

2647 

2648 Subclasses should only override methods discussed in this section; 

2649 overriding other methods is error-prone. Overriding 

2650 ``StaticFileHandler.get`` is particularly problematic due to the 

2651 tight coupling with ``compute_etag`` and other methods. 

2652 

2653 To change the way static urls are generated (e.g. to match the behavior 

2654 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2655 `get_cache_time`, and/or `get_version`. 

2656 

2657 To replace all interaction with the filesystem (e.g. to serve 

2658 static content from a database), override `get_content`, 

2659 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2660 `validate_absolute_path`. 

2661 

2662 .. versionchanged:: 3.1 

2663 Many of the methods for subclasses were added in Tornado 3.1. 

2664 """ 

2665 

2666 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2667 

2668 _static_hashes = {} # type: Dict[str, Optional[str]] 

2669 _lock = threading.Lock() # protects _static_hashes 

2670 

2671 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2672 self.root = path 

2673 self.default_filename = default_filename 

2674 

2675 @classmethod 

2676 def reset(cls) -> None: 

2677 with cls._lock: 

2678 cls._static_hashes = {} 

2679 

2680 def head(self, path: str) -> Awaitable[None]: 

2681 return self.get(path, include_body=False) 

2682 

2683 async def get(self, path: str, include_body: bool = True) -> None: 

2684 # Set up our path instance variables. 

2685 self.path = self.parse_url_path(path) 

2686 del path # make sure we don't refer to path instead of self.path again 

2687 absolute_path = self.get_absolute_path(self.root, self.path) 

2688 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2689 if self.absolute_path is None: 

2690 return 

2691 

2692 self.modified = self.get_modified_time() 

2693 self.set_headers() 

2694 

2695 if self.should_return_304(): 

2696 self.set_status(304) 

2697 return 

2698 

2699 request_range = None 

2700 range_header = self.request.headers.get("Range") 

2701 if range_header: 

2702 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2703 # the request will be treated as if the header didn't exist. 

2704 request_range = httputil._parse_request_range(range_header) 

2705 

2706 size = self.get_content_size() 

2707 if request_range: 

2708 start, end = request_range 

2709 if start is not None and start < 0: 

2710 start += size 

2711 if start < 0: 

2712 start = 0 

2713 if ( 

2714 start is not None 

2715 and (start >= size or (end is not None and start >= end)) 

2716 ) or end == 0: 

2717 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2718 # the first requested byte is equal to or greater than the 

2719 # content, or when a suffix with length 0 is specified. 

2720 # https://tools.ietf.org/html/rfc7233#section-2.1 

2721 # A byte-range-spec is invalid if the last-byte-pos value is present 

2722 # and less than the first-byte-pos. 

2723 self.set_status(416) # Range Not Satisfiable 

2724 self.set_header("Content-Type", "text/plain") 

2725 self.set_header("Content-Range", "bytes */%s" % (size,)) 

2726 return 

2727 if end is not None and end > size: 

2728 # Clients sometimes blindly use a large range to limit their 

2729 # download size; cap the endpoint at the actual file size. 

2730 end = size 

2731 # Note: only return HTTP 206 if less than the entire range has been 

2732 # requested. Not only is this semantically correct, but Chrome 

2733 # refuses to play audio if it gets an HTTP 206 in response to 

2734 # ``Range: bytes=0-``. 

2735 if size != (end or size) - (start or 0): 

2736 self.set_status(206) # Partial Content 

2737 self.set_header( 

2738 "Content-Range", httputil._get_content_range(start, end, size) 

2739 ) 

2740 else: 

2741 start = end = None 

2742 

2743 if start is not None and end is not None: 

2744 content_length = end - start 

2745 elif end is not None: 

2746 content_length = end 

2747 elif start is not None: 

2748 content_length = size - start 

2749 else: 

2750 content_length = size 

2751 self.set_header("Content-Length", content_length) 

2752 

2753 if include_body: 

2754 content = self.get_content(self.absolute_path, start, end) 

2755 if isinstance(content, bytes): 

2756 content = [content] 

2757 for chunk in content: 

2758 try: 

2759 self.write(chunk) 

2760 await self.flush() 

2761 except iostream.StreamClosedError: 

2762 return 

2763 else: 

2764 assert self.request.method == "HEAD" 

2765 

2766 def compute_etag(self) -> Optional[str]: 

2767 """Sets the ``Etag`` header based on static url version. 

2768 

2769 This allows efficient ``If-None-Match`` checks against cached 

2770 versions, and sends the correct ``Etag`` for a partial response 

2771 (i.e. the same ``Etag`` as the full file). 

2772 

2773 .. versionadded:: 3.1 

2774 """ 

2775 assert self.absolute_path is not None 

2776 version_hash = self._get_cached_version(self.absolute_path) 

2777 if not version_hash: 

2778 return None 

2779 return '"%s"' % (version_hash,) 

2780 

2781 def set_headers(self) -> None: 

2782 """Sets the content and caching headers on the response. 

2783 

2784 .. versionadded:: 3.1 

2785 """ 

2786 self.set_header("Accept-Ranges", "bytes") 

2787 self.set_etag_header() 

2788 

2789 if self.modified is not None: 

2790 self.set_header("Last-Modified", self.modified) 

2791 

2792 content_type = self.get_content_type() 

2793 if content_type: 

2794 self.set_header("Content-Type", content_type) 

2795 

2796 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2797 if cache_time > 0: 

2798 self.set_header( 

2799 "Expires", 

2800 datetime.datetime.now(datetime.timezone.utc) 

2801 + datetime.timedelta(seconds=cache_time), 

2802 ) 

2803 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2804 

2805 self.set_extra_headers(self.path) 

2806 

2807 def should_return_304(self) -> bool: 

2808 """Returns True if the headers indicate that we should return 304. 

2809 

2810 .. versionadded:: 3.1 

2811 """ 

2812 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2813 if self.request.headers.get("If-None-Match"): 

2814 return self.check_etag_header() 

2815 

2816 # Check the If-Modified-Since, and don't send the result if the 

2817 # content has not been modified 

2818 ims_value = self.request.headers.get("If-Modified-Since") 

2819 if ims_value is not None: 

2820 if_since = email.utils.parsedate_to_datetime(ims_value) 

2821 if if_since.tzinfo is None: 

2822 if_since = if_since.replace(tzinfo=datetime.timezone.utc) 

2823 assert self.modified is not None 

2824 if if_since >= self.modified: 

2825 return True 

2826 

2827 return False 

2828 

2829 @classmethod 

2830 def get_absolute_path(cls, root: str, path: str) -> str: 

2831 """Returns the absolute location of ``path`` relative to ``root``. 

2832 

2833 ``root`` is the path configured for this `StaticFileHandler` 

2834 (in most cases the ``static_path`` `Application` setting). 

2835 

2836 This class method may be overridden in subclasses. By default 

2837 it returns a filesystem path, but other strings may be used 

2838 as long as they are unique and understood by the subclass's 

2839 overridden `get_content`. 

2840 

2841 .. versionadded:: 3.1 

2842 """ 

2843 abspath = os.path.abspath(os.path.join(root, path)) 

2844 return abspath 

2845 

2846 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2847 """Validate and return the absolute path. 

2848 

2849 ``root`` is the configured path for the `StaticFileHandler`, 

2850 and ``path`` is the result of `get_absolute_path` 

2851 

2852 This is an instance method called during request processing, 

2853 so it may raise `HTTPError` or use methods like 

2854 `RequestHandler.redirect` (return None after redirecting to 

2855 halt further processing). This is where 404 errors for missing files 

2856 are generated. 

2857 

2858 This method may modify the path before returning it, but note that 

2859 any such modifications will not be understood by `make_static_url`. 

2860 

2861 In instance methods, this method's result is available as 

2862 ``self.absolute_path``. 

2863 

2864 .. versionadded:: 3.1 

2865 """ 

2866 # os.path.abspath strips a trailing /. 

2867 # We must add it back to `root` so that we only match files 

2868 # in a directory named `root` instead of files starting with 

2869 # that prefix. 

2870 root = os.path.abspath(root) 

2871 if not root.endswith(os.path.sep): 

2872 # abspath always removes a trailing slash, except when 

2873 # root is '/'. This is an unusual case, but several projects 

2874 # have independently discovered this technique to disable 

2875 # Tornado's path validation and (hopefully) do their own, 

2876 # so we need to support it. 

2877 root += os.path.sep 

2878 # The trailing slash also needs to be temporarily added back 

2879 # the requested path so a request to root/ will match. 

2880 if not (absolute_path + os.path.sep).startswith(root): 

2881 raise HTTPError(403, "%s is not in root static directory", self.path) 

2882 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2883 # need to look at the request.path here for when path is empty 

2884 # but there is some prefix to the path that was already 

2885 # trimmed by the routing 

2886 if not self.request.path.endswith("/"): 

2887 if self.request.path.startswith("//"): 

2888 # A redirect with two initial slashes is a "protocol-relative" URL. 

2889 # This means the next path segment is treated as a hostname instead 

2890 # of a part of the path, making this effectively an open redirect. 

2891 # Reject paths starting with two slashes to prevent this. 

2892 # This is only reachable under certain configurations. 

2893 raise HTTPError( 

2894 403, "cannot redirect path with two initial slashes" 

2895 ) 

2896 self.redirect(self.request.path + "/", permanent=True) 

2897 return None 

2898 absolute_path = os.path.join(absolute_path, self.default_filename) 

2899 if not os.path.exists(absolute_path): 

2900 raise HTTPError(404) 

2901 if not os.path.isfile(absolute_path): 

2902 raise HTTPError(403, "%s is not a file", self.path) 

2903 return absolute_path 

2904 

2905 @classmethod 

2906 def get_content( 

2907 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

2908 ) -> Generator[bytes, None, None]: 

2909 """Retrieve the content of the requested resource which is located 

2910 at the given absolute path. 

2911 

2912 This class method may be overridden by subclasses. Note that its 

2913 signature is different from other overridable class methods 

2914 (no ``settings`` argument); this is deliberate to ensure that 

2915 ``abspath`` is able to stand on its own as a cache key. 

2916 

2917 This method should either return a byte string or an iterator 

2918 of byte strings. The latter is preferred for large files 

2919 as it helps reduce memory fragmentation. 

2920 

2921 .. versionadded:: 3.1 

2922 """ 

2923 with open(abspath, "rb") as file: 

2924 if start is not None: 

2925 file.seek(start) 

2926 if end is not None: 

2927 remaining = end - (start or 0) # type: Optional[int] 

2928 else: 

2929 remaining = None 

2930 while True: 

2931 chunk_size = 64 * 1024 

2932 if remaining is not None and remaining < chunk_size: 

2933 chunk_size = remaining 

2934 chunk = file.read(chunk_size) 

2935 if chunk: 

2936 if remaining is not None: 

2937 remaining -= len(chunk) 

2938 yield chunk 

2939 else: 

2940 if remaining is not None: 

2941 assert remaining == 0 

2942 return 

2943 

2944 @classmethod 

2945 def get_content_version(cls, abspath: str) -> str: 

2946 """Returns a version string for the resource at the given path. 

2947 

2948 This class method may be overridden by subclasses. The 

2949 default implementation is a SHA-512 hash of the file's contents. 

2950 

2951 .. versionadded:: 3.1 

2952 """ 

2953 data = cls.get_content(abspath) 

2954 hasher = hashlib.sha512() 

2955 if isinstance(data, bytes): 

2956 hasher.update(data) 

2957 else: 

2958 for chunk in data: 

2959 hasher.update(chunk) 

2960 return hasher.hexdigest() 

2961 

2962 def _stat(self) -> os.stat_result: 

2963 assert self.absolute_path is not None 

2964 if not hasattr(self, "_stat_result"): 

2965 self._stat_result = os.stat(self.absolute_path) 

2966 return self._stat_result 

2967 

2968 def get_content_size(self) -> int: 

2969 """Retrieve the total size of the resource at the given path. 

2970 

2971 This method may be overridden by subclasses. 

2972 

2973 .. versionadded:: 3.1 

2974 

2975 .. versionchanged:: 4.0 

2976 This method is now always called, instead of only when 

2977 partial results are requested. 

2978 """ 

2979 stat_result = self._stat() 

2980 return stat_result.st_size 

2981 

2982 def get_modified_time(self) -> Optional[datetime.datetime]: 

2983 """Returns the time that ``self.absolute_path`` was last modified. 

2984 

2985 May be overridden in subclasses. Should return a `~datetime.datetime` 

2986 object or None. 

2987 

2988 .. versionadded:: 3.1 

2989 

2990 .. versionchanged:: 6.4 

2991 Now returns an aware datetime object instead of a naive one. 

2992 Subclasses that override this method may return either kind. 

2993 """ 

2994 stat_result = self._stat() 

2995 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

2996 # which truncates the fractional portion of the timestamp. It 

2997 # was changed from that form to stat_result.st_mtime to 

2998 # satisfy mypy (which disallows the bracket operator), but the 

2999 # latter form returns a float instead of an int. For 

3000 # consistency with the past (and because we have a unit test 

3001 # that relies on this), we truncate the float here, although 

3002 # I'm not sure that's the right thing to do. 

3003 modified = datetime.datetime.fromtimestamp( 

3004 int(stat_result.st_mtime), datetime.timezone.utc 

3005 ) 

3006 return modified 

3007 

3008 def get_content_type(self) -> str: 

3009 """Returns the ``Content-Type`` header to be used for this request. 

3010 

3011 .. versionadded:: 3.1 

3012 """ 

3013 assert self.absolute_path is not None 

3014 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3015 # per RFC 6713, use the appropriate type for a gzip compressed file 

3016 if encoding == "gzip": 

3017 return "application/gzip" 

3018 # As of 2015-07-21 there is no bzip2 encoding defined at 

3019 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3020 # So for that (and any other encoding), use octet-stream. 

3021 elif encoding is not None: 

3022 return "application/octet-stream" 

3023 elif mime_type is not None: 

3024 return mime_type 

3025 # if mime_type not detected, use application/octet-stream 

3026 else: 

3027 return "application/octet-stream" 

3028 

3029 def set_extra_headers(self, path: str) -> None: 

3030 """For subclass to add extra headers to the response""" 

3031 pass 

3032 

3033 def get_cache_time( 

3034 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3035 ) -> int: 

3036 """Override to customize cache control behavior. 

3037 

3038 Return a positive number of seconds to make the result 

3039 cacheable for that amount of time or 0 to mark resource as 

3040 cacheable for an unspecified amount of time (subject to 

3041 browser heuristics). 

3042 

3043 By default returns cache expiry of 10 years for resources requested 

3044 with ``v`` argument. 

3045 """ 

3046 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3047 

3048 @classmethod 

3049 def make_static_url( 

3050 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3051 ) -> str: 

3052 """Constructs a versioned url for the given path. 

3053 

3054 This method may be overridden in subclasses (but note that it 

3055 is a class method rather than an instance method). Subclasses 

3056 are only required to implement the signature 

3057 ``make_static_url(cls, settings, path)``; other keyword 

3058 arguments may be passed through `~RequestHandler.static_url` 

3059 but are not standard. 

3060 

3061 ``settings`` is the `Application.settings` dictionary. ``path`` 

3062 is the static path being requested. The url returned should be 

3063 relative to the current host. 

3064 

3065 ``include_version`` determines whether the generated URL should 

3066 include the query string containing the version hash of the 

3067 file corresponding to the given ``path``. 

3068 

3069 """ 

3070 url = settings.get("static_url_prefix", "/static/") + path 

3071 if not include_version: 

3072 return url 

3073 

3074 version_hash = cls.get_version(settings, path) 

3075 if not version_hash: 

3076 return url 

3077 

3078 return "%s?v=%s" % (url, version_hash) 

3079 

3080 def parse_url_path(self, url_path: str) -> str: 

3081 """Converts a static URL path into a filesystem path. 

3082 

3083 ``url_path`` is the path component of the URL with 

3084 ``static_url_prefix`` removed. The return value should be 

3085 filesystem path relative to ``static_path``. 

3086 

3087 This is the inverse of `make_static_url`. 

3088 """ 

3089 if os.path.sep != "/": 

3090 url_path = url_path.replace("/", os.path.sep) 

3091 return url_path 

3092 

3093 @classmethod 

3094 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3095 """Generate the version string to be used in static URLs. 

3096 

3097 ``settings`` is the `Application.settings` dictionary and ``path`` 

3098 is the relative location of the requested asset on the filesystem. 

3099 The returned value should be a string, or ``None`` if no version 

3100 could be determined. 

3101 

3102 .. versionchanged:: 3.1 

3103 This method was previously recommended for subclasses to override; 

3104 `get_content_version` is now preferred as it allows the base 

3105 class to handle caching of the result. 

3106 """ 

3107 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3108 return cls._get_cached_version(abs_path) 

3109 

3110 @classmethod 

3111 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3112 with cls._lock: 

3113 hashes = cls._static_hashes 

3114 if abs_path not in hashes: 

3115 try: 

3116 hashes[abs_path] = cls.get_content_version(abs_path) 

3117 except Exception: 

3118 gen_log.error("Could not open static file %r", abs_path) 

3119 hashes[abs_path] = None 

3120 hsh = hashes.get(abs_path) 

3121 if hsh: 

3122 return hsh 

3123 return None 

3124 

3125 

3126class FallbackHandler(RequestHandler): 

3127 """A `RequestHandler` that wraps another HTTP server callback. 

3128 

3129 The fallback is a callable object that accepts an 

3130 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3131 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3132 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3133 usage:: 

3134 

3135 wsgi_app = tornado.wsgi.WSGIContainer( 

3136 django.core.handlers.wsgi.WSGIHandler()) 

3137 application = tornado.web.Application([ 

3138 (r"/foo", FooHandler), 

3139 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3140 ]) 

3141 """ 

3142 

3143 def initialize( 

3144 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3145 ) -> None: 

3146 self.fallback = fallback 

3147 

3148 def prepare(self) -> None: 

3149 self.fallback(self.request) 

3150 self._finished = True 

3151 self.on_finish() 

3152 

3153 

3154class OutputTransform(object): 

3155 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3156 

3157 Applications are not expected to create their own OutputTransforms 

3158 or interact with them directly; the framework chooses which transforms 

3159 (if any) to apply. 

3160 """ 

3161 

3162 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3163 pass 

3164 

3165 def transform_first_chunk( 

3166 self, 

3167 status_code: int, 

3168 headers: httputil.HTTPHeaders, 

3169 chunk: bytes, 

3170 finishing: bool, 

3171 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3172 return status_code, headers, chunk 

3173 

3174 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3175 return chunk 

3176 

3177 

3178class GZipContentEncoding(OutputTransform): 

3179 """Applies the gzip content encoding to the response. 

3180 

3181 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3182 

3183 .. versionchanged:: 4.0 

3184 Now compresses all mime types beginning with ``text/``, instead 

3185 of just a whitelist. (the whitelist is still used for certain 

3186 non-text mime types). 

3187 """ 

3188 

3189 # Whitelist of compressible mime types (in addition to any types 

3190 # beginning with "text/"). 

3191 CONTENT_TYPES = set( 

3192 [ 

3193 "application/javascript", 

3194 "application/x-javascript", 

3195 "application/xml", 

3196 "application/atom+xml", 

3197 "application/json", 

3198 "application/xhtml+xml", 

3199 "image/svg+xml", 

3200 ] 

3201 ) 

3202 # Python's GzipFile defaults to level 9, while most other gzip 

3203 # tools (including gzip itself) default to 6, which is probably a 

3204 # better CPU/size tradeoff. 

3205 GZIP_LEVEL = 6 

3206 # Responses that are too short are unlikely to benefit from gzipping 

3207 # after considering the "Content-Encoding: gzip" header and the header 

3208 # inside the gzip encoding. 

3209 # Note that responses written in multiple chunks will be compressed 

3210 # regardless of size. 

3211 MIN_LENGTH = 1024 

3212 

3213 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3214 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3215 

3216 def _compressible_type(self, ctype: str) -> bool: 

3217 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3218 

3219 def transform_first_chunk( 

3220 self, 

3221 status_code: int, 

3222 headers: httputil.HTTPHeaders, 

3223 chunk: bytes, 

3224 finishing: bool, 

3225 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3226 # TODO: can/should this type be inherited from the superclass? 

3227 if "Vary" in headers: 

3228 headers["Vary"] += ", Accept-Encoding" 

3229 else: 

3230 headers["Vary"] = "Accept-Encoding" 

3231 if self._gzipping: 

3232 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3233 self._gzipping = ( 

3234 self._compressible_type(ctype) 

3235 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3236 and ("Content-Encoding" not in headers) 

3237 ) 

3238 if self._gzipping: 

3239 headers["Content-Encoding"] = "gzip" 

3240 self._gzip_value = BytesIO() 

3241 self._gzip_file = gzip.GzipFile( 

3242 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3243 ) 

3244 chunk = self.transform_chunk(chunk, finishing) 

3245 if "Content-Length" in headers: 

3246 # The original content length is no longer correct. 

3247 # If this is the last (and only) chunk, we can set the new 

3248 # content-length; otherwise we remove it and fall back to 

3249 # chunked encoding. 

3250 if finishing: 

3251 headers["Content-Length"] = str(len(chunk)) 

3252 else: 

3253 del headers["Content-Length"] 

3254 return status_code, headers, chunk 

3255 

3256 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3257 if self._gzipping: 

3258 self._gzip_file.write(chunk) 

3259 if finishing: 

3260 self._gzip_file.close() 

3261 else: 

3262 self._gzip_file.flush() 

3263 chunk = self._gzip_value.getvalue() 

3264 self._gzip_value.truncate(0) 

3265 self._gzip_value.seek(0) 

3266 return chunk 

3267 

3268 

3269def authenticated( 

3270 method: Callable[..., Optional[Awaitable[None]]] 

3271) -> Callable[..., Optional[Awaitable[None]]]: 

3272 """Decorate methods with this to require that the user be logged in. 

3273 

3274 If the user is not logged in, they will be redirected to the configured 

3275 `login url <RequestHandler.get_login_url>`. 

3276 

3277 If you configure a login url with a query parameter, Tornado will 

3278 assume you know what you're doing and use it as-is. If not, it 

3279 will add a `next` parameter so the login page knows where to send 

3280 you once you're logged in. 

3281 """ 

3282 

3283 @functools.wraps(method) 

3284 def wrapper( # type: ignore 

3285 self: RequestHandler, *args, **kwargs 

3286 ) -> Optional[Awaitable[None]]: 

3287 if not self.current_user: 

3288 if self.request.method in ("GET", "HEAD"): 

3289 url = self.get_login_url() 

3290 if "?" not in url: 

3291 if urllib.parse.urlsplit(url).scheme: 

3292 # if login url is absolute, make next absolute too 

3293 next_url = self.request.full_url() 

3294 else: 

3295 assert self.request.uri is not None 

3296 next_url = self.request.uri 

3297 url += "?" + urlencode(dict(next=next_url)) 

3298 self.redirect(url) 

3299 return None 

3300 raise HTTPError(403) 

3301 return method(self, *args, **kwargs) 

3302 

3303 return wrapper 

3304 

3305 

3306class UIModule(object): 

3307 """A re-usable, modular UI unit on a page. 

3308 

3309 UI modules often execute additional queries, and they can include 

3310 additional CSS and JavaScript that will be included in the output 

3311 page, which is automatically inserted on page render. 

3312 

3313 Subclasses of UIModule must override the `render` method. 

3314 """ 

3315 

3316 def __init__(self, handler: RequestHandler) -> None: 

3317 self.handler = handler 

3318 self.request = handler.request 

3319 self.ui = handler.ui 

3320 self.locale = handler.locale 

3321 

3322 @property 

3323 def current_user(self) -> Any: 

3324 return self.handler.current_user 

3325 

3326 def render(self, *args: Any, **kwargs: Any) -> str: 

3327 """Override in subclasses to return this module's output.""" 

3328 raise NotImplementedError() 

3329 

3330 def embedded_javascript(self) -> Optional[str]: 

3331 """Override to return a JavaScript string 

3332 to be embedded in the page.""" 

3333 return None 

3334 

3335 def javascript_files(self) -> Optional[Iterable[str]]: 

3336 """Override to return a list of JavaScript files needed by this module. 

3337 

3338 If the return values are relative paths, they will be passed to 

3339 `RequestHandler.static_url`; otherwise they will be used as-is. 

3340 """ 

3341 return None 

3342 

3343 def embedded_css(self) -> Optional[str]: 

3344 """Override to return a CSS string 

3345 that will be embedded in the page.""" 

3346 return None 

3347 

3348 def css_files(self) -> Optional[Iterable[str]]: 

3349 """Override to returns a list of CSS files required by this module. 

3350 

3351 If the return values are relative paths, they will be passed to 

3352 `RequestHandler.static_url`; otherwise they will be used as-is. 

3353 """ 

3354 return None 

3355 

3356 def html_head(self) -> Optional[str]: 

3357 """Override to return an HTML string that will be put in the <head/> 

3358 element. 

3359 """ 

3360 return None 

3361 

3362 def html_body(self) -> Optional[str]: 

3363 """Override to return an HTML string that will be put at the end of 

3364 the <body/> element. 

3365 """ 

3366 return None 

3367 

3368 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3369 """Renders a template and returns it as a string.""" 

3370 return self.handler.render_string(path, **kwargs) 

3371 

3372 

3373class _linkify(UIModule): 

3374 def render(self, text: str, **kwargs: Any) -> str: # type: ignore 

3375 return escape.linkify(text, **kwargs) 

3376 

3377 

3378class _xsrf_form_html(UIModule): 

3379 def render(self) -> str: # type: ignore 

3380 return self.handler.xsrf_form_html() 

3381 

3382 

3383class TemplateModule(UIModule): 

3384 """UIModule that simply renders the given template. 

3385 

3386 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3387 but the module version gets its own namespace (with kwargs passed to 

3388 Template()) instead of inheriting the outer template's namespace. 

3389 

3390 Templates rendered through this module also get access to UIModule's 

3391 automatic JavaScript/CSS features. Simply call set_resources 

3392 inside the template and give it keyword arguments corresponding to 

3393 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3394 Note that these resources are output once per template file, not once 

3395 per instantiation of the template, so they must not depend on 

3396 any arguments to the template. 

3397 """ 

3398 

3399 def __init__(self, handler: RequestHandler) -> None: 

3400 super().__init__(handler) 

3401 # keep resources in both a list and a dict to preserve order 

3402 self._resource_list = [] # type: List[Dict[str, Any]] 

3403 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3404 

3405 def render(self, path: str, **kwargs: Any) -> bytes: # type: ignore 

3406 def set_resources(**kwargs) -> str: # type: ignore 

3407 if path not in self._resource_dict: 

3408 self._resource_list.append(kwargs) 

3409 self._resource_dict[path] = kwargs 

3410 else: 

3411 if self._resource_dict[path] != kwargs: 

3412 raise ValueError( 

3413 "set_resources called with different " 

3414 "resources for the same template" 

3415 ) 

3416 return "" 

3417 

3418 return self.render_string(path, set_resources=set_resources, **kwargs) 

3419 

3420 def _get_resources(self, key: str) -> Iterable[str]: 

3421 return (r[key] for r in self._resource_list if key in r) 

3422 

3423 def embedded_javascript(self) -> str: 

3424 return "\n".join(self._get_resources("embedded_javascript")) 

3425 

3426 def javascript_files(self) -> Iterable[str]: 

3427 result = [] 

3428 for f in self._get_resources("javascript_files"): 

3429 if isinstance(f, (unicode_type, bytes)): 

3430 result.append(f) 

3431 else: 

3432 result.extend(f) 

3433 return result 

3434 

3435 def embedded_css(self) -> str: 

3436 return "\n".join(self._get_resources("embedded_css")) 

3437 

3438 def css_files(self) -> Iterable[str]: 

3439 result = [] 

3440 for f in self._get_resources("css_files"): 

3441 if isinstance(f, (unicode_type, bytes)): 

3442 result.append(f) 

3443 else: 

3444 result.extend(f) 

3445 return result 

3446 

3447 def html_head(self) -> str: 

3448 return "".join(self._get_resources("html_head")) 

3449 

3450 def html_body(self) -> str: 

3451 return "".join(self._get_resources("html_body")) 

3452 

3453 

3454class _UIModuleNamespace(object): 

3455 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3456 

3457 def __init__( 

3458 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3459 ) -> None: 

3460 self.handler = handler 

3461 self.ui_modules = ui_modules 

3462 

3463 def __getitem__(self, key: str) -> Callable[..., str]: 

3464 return self.handler._ui_module(key, self.ui_modules[key]) 

3465 

3466 def __getattr__(self, key: str) -> Callable[..., str]: 

3467 try: 

3468 return self[key] 

3469 except KeyError as e: 

3470 raise AttributeError(str(e)) 

3471 

3472 

3473def create_signed_value( 

3474 secret: _CookieSecretTypes, 

3475 name: str, 

3476 value: Union[str, bytes], 

3477 version: Optional[int] = None, 

3478 clock: Optional[Callable[[], float]] = None, 

3479 key_version: Optional[int] = None, 

3480) -> bytes: 

3481 if version is None: 

3482 version = DEFAULT_SIGNED_VALUE_VERSION 

3483 if clock is None: 

3484 clock = time.time 

3485 

3486 timestamp = utf8(str(int(clock()))) 

3487 value = base64.b64encode(utf8(value)) 

3488 if version == 1: 

3489 assert not isinstance(secret, dict) 

3490 signature = _create_signature_v1(secret, name, value, timestamp) 

3491 value = b"|".join([value, timestamp, signature]) 

3492 return value 

3493 elif version == 2: 

3494 # The v2 format consists of a version number and a series of 

3495 # length-prefixed fields "%d:%s", the last of which is a 

3496 # signature, all separated by pipes. All numbers are in 

3497 # decimal format with no leading zeros. The signature is an 

3498 # HMAC-SHA256 of the whole string up to that point, including 

3499 # the final pipe. 

3500 # 

3501 # The fields are: 

3502 # - format version (i.e. 2; no length prefix) 

3503 # - key version (integer, default is 0) 

3504 # - timestamp (integer seconds since epoch) 

3505 # - name (not encoded; assumed to be ~alphanumeric) 

3506 # - value (base64-encoded) 

3507 # - signature (hex-encoded; no length prefix) 

3508 def format_field(s: Union[str, bytes]) -> bytes: 

3509 return utf8("%d:" % len(s)) + utf8(s) 

3510 

3511 to_sign = b"|".join( 

3512 [ 

3513 b"2", 

3514 format_field(str(key_version or 0)), 

3515 format_field(timestamp), 

3516 format_field(name), 

3517 format_field(value), 

3518 b"", 

3519 ] 

3520 ) 

3521 

3522 if isinstance(secret, dict): 

3523 assert ( 

3524 key_version is not None 

3525 ), "Key version must be set when sign key dict is used" 

3526 assert version >= 2, "Version must be at least 2 for key version support" 

3527 secret = secret[key_version] 

3528 

3529 signature = _create_signature_v2(secret, to_sign) 

3530 return to_sign + signature 

3531 else: 

3532 raise ValueError("Unsupported version %d" % version) 

3533 

3534 

3535# A leading version number in decimal 

3536# with no leading zeros, followed by a pipe. 

3537_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3538 

3539 

3540def _get_version(value: bytes) -> int: 

3541 # Figures out what version value is. Version 1 did not include an 

3542 # explicit version field and started with arbitrary base64 data, 

3543 # which makes this tricky. 

3544 m = _signed_value_version_re.match(value) 

3545 if m is None: 

3546 version = 1 

3547 else: 

3548 try: 

3549 version = int(m.group(1)) 

3550 if version > 999: 

3551 # Certain payloads from the version-less v1 format may 

3552 # be parsed as valid integers. Due to base64 padding 

3553 # restrictions, this can only happen for numbers whose 

3554 # length is a multiple of 4, so we can treat all 

3555 # numbers up to 999 as versions, and for the rest we 

3556 # fall back to v1 format. 

3557 version = 1 

3558 except ValueError: 

3559 version = 1 

3560 return version 

3561 

3562 

3563def decode_signed_value( 

3564 secret: _CookieSecretTypes, 

3565 name: str, 

3566 value: Union[None, str, bytes], 

3567 max_age_days: float = 31, 

3568 clock: Optional[Callable[[], float]] = None, 

3569 min_version: Optional[int] = None, 

3570) -> Optional[bytes]: 

3571 if clock is None: 

3572 clock = time.time 

3573 if min_version is None: 

3574 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3575 if min_version > 2: 

3576 raise ValueError("Unsupported min_version %d" % min_version) 

3577 if not value: 

3578 return None 

3579 

3580 value = utf8(value) 

3581 version = _get_version(value) 

3582 

3583 if version < min_version: 

3584 return None 

3585 if version == 1: 

3586 assert not isinstance(secret, dict) 

3587 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3588 elif version == 2: 

3589 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3590 else: 

3591 return None 

3592 

3593 

3594def _decode_signed_value_v1( 

3595 secret: Union[str, bytes], 

3596 name: str, 

3597 value: bytes, 

3598 max_age_days: float, 

3599 clock: Callable[[], float], 

3600) -> Optional[bytes]: 

3601 parts = utf8(value).split(b"|") 

3602 if len(parts) != 3: 

3603 return None 

3604 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3605 if not hmac.compare_digest(parts[2], signature): 

3606 gen_log.warning("Invalid cookie signature %r", value) 

3607 return None 

3608 timestamp = int(parts[1]) 

3609 if timestamp < clock() - max_age_days * 86400: 

3610 gen_log.warning("Expired cookie %r", value) 

3611 return None 

3612 if timestamp > clock() + 31 * 86400: 

3613 # _cookie_signature does not hash a delimiter between the 

3614 # parts of the cookie, so an attacker could transfer trailing 

3615 # digits from the payload to the timestamp without altering the 

3616 # signature. For backwards compatibility, sanity-check timestamp 

3617 # here instead of modifying _cookie_signature. 

3618 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3619 return None 

3620 if parts[1].startswith(b"0"): 

3621 gen_log.warning("Tampered cookie %r", value) 

3622 return None 

3623 try: 

3624 return base64.b64decode(parts[0]) 

3625 except Exception: 

3626 return None 

3627 

3628 

3629def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3630 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3631 length, _, rest = s.partition(b":") 

3632 n = int(length) 

3633 field_value = rest[:n] 

3634 # In python 3, indexing bytes returns small integers; we must 

3635 # use a slice to get a byte string as in python 2. 

3636 if rest[n : n + 1] != b"|": 

3637 raise ValueError("malformed v2 signed value field") 

3638 rest = rest[n + 1 :] 

3639 return field_value, rest 

3640 

3641 rest = value[2:] # remove version number 

3642 key_version, rest = _consume_field(rest) 

3643 timestamp, rest = _consume_field(rest) 

3644 name_field, rest = _consume_field(rest) 

3645 value_field, passed_sig = _consume_field(rest) 

3646 return int(key_version), timestamp, name_field, value_field, passed_sig 

3647 

3648 

3649def _decode_signed_value_v2( 

3650 secret: _CookieSecretTypes, 

3651 name: str, 

3652 value: bytes, 

3653 max_age_days: float, 

3654 clock: Callable[[], float], 

3655) -> Optional[bytes]: 

3656 try: 

3657 ( 

3658 key_version, 

3659 timestamp_bytes, 

3660 name_field, 

3661 value_field, 

3662 passed_sig, 

3663 ) = _decode_fields_v2(value) 

3664 except ValueError: 

3665 return None 

3666 signed_string = value[: -len(passed_sig)] 

3667 

3668 if isinstance(secret, dict): 

3669 try: 

3670 secret = secret[key_version] 

3671 except KeyError: 

3672 return None 

3673 

3674 expected_sig = _create_signature_v2(secret, signed_string) 

3675 if not hmac.compare_digest(passed_sig, expected_sig): 

3676 return None 

3677 if name_field != utf8(name): 

3678 return None 

3679 timestamp = int(timestamp_bytes) 

3680 if timestamp < clock() - max_age_days * 86400: 

3681 # The signature has expired. 

3682 return None 

3683 try: 

3684 return base64.b64decode(value_field) 

3685 except Exception: 

3686 return None 

3687 

3688 

3689def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3690 value = utf8(value) 

3691 version = _get_version(value) 

3692 if version < 2: 

3693 return None 

3694 try: 

3695 key_version, _, _, _, _ = _decode_fields_v2(value) 

3696 except ValueError: 

3697 return None 

3698 

3699 return key_version 

3700 

3701 

3702def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3703 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3704 for part in parts: 

3705 hash.update(utf8(part)) 

3706 return utf8(hash.hexdigest()) 

3707 

3708 

3709def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3710 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3711 hash.update(utf8(s)) 

3712 return utf8(hash.hexdigest()) 

3713 

3714 

3715def is_absolute(path: str) -> bool: 

3716 return any(path.startswith(x) for x in ["/", "http:", "https:"])