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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1461 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 

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

43 

44Thread-safety notes 

45------------------- 

46 

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

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

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

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

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

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

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

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

55the executor do not refer to Tornado objects. 

56 

57""" 

58 

59import base64 

60import binascii 

61import datetime 

62import email.utils 

63import functools 

64import gzip 

65import hashlib 

66import hmac 

67import http.cookies 

68from inspect import isclass 

69from io import BytesIO 

70import mimetypes 

71import numbers 

72import os.path 

73import re 

74import socket 

75import sys 

76import threading 

77import time 

78import warnings 

79import tornado 

80import traceback 

81import types 

82import urllib.parse 

83from urllib.parse import urlencode 

84 

85from tornado.concurrent import Future, future_set_result_unless_cancelled 

86from tornado import escape 

87from tornado import gen 

88from tornado.httpserver import HTTPServer 

89from tornado import httputil 

90from tornado import iostream 

91from tornado import locale 

92from tornado.log import access_log, app_log, gen_log 

93from tornado import template 

94from tornado.escape import utf8, _unicode 

95from tornado.routing import ( 

96 AnyMatches, 

97 DefaultHostMatches, 

98 HostMatches, 

99 ReversibleRouter, 

100 Rule, 

101 ReversibleRuleRouter, 

102 URLSpec, 

103 _RuleList, 

104) 

105from tornado.util import ObjectDict, unicode_type, _websocket_mask 

106 

107url = URLSpec 

108 

109from typing import ( 

110 Dict, 

111 Any, 

112 Union, 

113 Optional, 

114 Awaitable, 

115 Tuple, 

116 List, 

117 Callable, 

118 Iterable, 

119 Generator, 

120 Type, 

121 TypeVar, 

122 cast, 

123 overload, 

124) 

125from types import TracebackType 

126import typing 

127 

128if typing.TYPE_CHECKING: 

129 from typing import Set # noqa: F401 

130 

131 

132# The following types are accepted by RequestHandler.set_header 

133# and related methods. 

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

135 

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

137 

138 

139MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

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

141 

142Signed values older than this version cannot be decoded. 

143 

144.. versionadded:: 3.2.1 

145""" 

146 

147MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

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

149 

150Signed values newer than this version cannot be decoded. 

151 

152.. versionadded:: 3.2.1 

153""" 

154 

155DEFAULT_SIGNED_VALUE_VERSION = 2 

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

157 

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

159 

160.. versionadded:: 3.2.1 

161""" 

162 

163DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

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

165 

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

167 

168.. versionadded:: 3.2.1 

169""" 

170 

171 

172class _ArgDefaultMarker: 

173 pass 

174 

175 

176_ARG_DEFAULT = _ArgDefaultMarker() 

177 

178 

179class RequestHandler: 

180 """Base class for HTTP request handlers. 

181 

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

183 "Entry points" section below. 

184 

185 Applications should not construct `RequestHandler` objects 

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

187 `~RequestHandler.initialize` instead). 

188 

189 """ 

190 

191 SUPPORTED_METHODS: Tuple[str, ...] = ( 

192 "GET", 

193 "HEAD", 

194 "POST", 

195 "DELETE", 

196 "PATCH", 

197 "PUT", 

198 "OPTIONS", 

199 ) 

200 

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

202 _template_loader_lock = threading.Lock() 

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

204 

205 _stream_request_body = False 

206 

207 # Will be set in _execute. 

208 _transforms = None # type: List[OutputTransform] 

209 path_args = None # type: List[str] 

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

211 

212 def __init__( 

213 self, 

214 application: "Application", 

215 request: httputil.HTTPServerRequest, 

216 **kwargs: Any, 

217 ) -> None: 

218 super().__init__() 

219 

220 self.application = application 

221 self.request = request 

222 self._headers_written = False 

223 self._finished = False 

224 self._auto_finish = True 

225 self._prepared_future = None 

226 self.ui = ObjectDict( 

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

228 ) 

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

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

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

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

233 # possible conflicts. 

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

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

236 self.clear() 

237 assert self.request.connection is not None 

238 # TODO: need to add set_close_callback to HTTPConnection interface 

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

240 self.on_connection_close 

241 ) 

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

243 

244 def _initialize(self) -> None: 

245 pass 

246 

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

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

249 

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

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

252 

253 Example:: 

254 

255 class ProfileHandler(RequestHandler): 

256 def initialize(self, database): 

257 self.database = database 

258 

259 def get(self, username): 

260 ... 

261 

262 app = Application([ 

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

264 ]) 

265 """ 

266 

267 @property 

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

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

270 return self.application.settings 

271 

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

273 raise HTTPError(405) 

274 

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

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

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

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

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

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

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

282 

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

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

285 

286 Override this method to perform common initialization regardless 

287 of the request method. There is no guarantee that ``prepare`` will 

288 be called if an error occurs that is handled by the framework. 

289 

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

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

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

293 until the ``Awaitable`` is done. 

294 

295 .. versionadded:: 3.1 

296 Asynchronous support. 

297 """ 

298 pass 

299 

300 def on_finish(self) -> None: 

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

302 

303 Override this method to perform cleanup, logging, etc. This method is primarily intended as 

304 a counterpart to `prepare`. However, there are a few error cases where ``on_finish`` may be 

305 called when ``prepare`` has not. (These are considered bugs and may be fixed in the future, 

306 but for now you may need to check to see if the initialization work done in ``prepare`` has 

307 occurred) 

308 

309 ``on_finish`` may not produce any output, as it is called after the response has been sent 

310 to the client. 

311 """ 

312 pass 

313 

314 def on_connection_close(self) -> None: 

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

316 

317 Override this to clean up resources associated with 

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

319 the connection was closed during asynchronous processing; if you 

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

321 instead. 

322 

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

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

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

326 connection. 

327 """ 

328 if _has_stream_request_body(self.__class__): 

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

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

331 self.request._body_future.exception() 

332 

333 def clear(self) -> None: 

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

335 self._headers = httputil.HTTPHeaders( 

336 { 

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

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

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

340 } 

341 ) 

342 self.set_default_headers() 

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

344 self._status_code = 200 

345 self._reason = httputil.responses[200] 

346 

347 def set_default_headers(self) -> None: 

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

349 

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

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

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

353 during error handling. 

354 """ 

355 pass 

356 

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

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

359 

360 :arg int status_code: Response status code. 

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

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

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

364 

365 .. versionchanged:: 5.0 

366 

367 No longer validates that the response code is in 

368 `http.client.responses`. 

369 """ 

370 self._status_code = status_code 

371 if reason is not None: 

372 self._reason = escape.native_str(reason) 

373 else: 

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

375 

376 def get_status(self) -> int: 

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

378 return self._status_code 

379 

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

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

382 

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

384 are formatted according to the HTTP specification for the 

385 ``Date`` header). 

386 

387 """ 

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

389 

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

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

392 

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

394 to return multiple values for the same header. 

395 """ 

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

397 

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

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

400 

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

402 set by `add_header`. 

403 """ 

404 if name in self._headers: 

405 del self._headers[name] 

406 

407 # https://www.rfc-editor.org/rfc/rfc9110#name-field-values 

408 _VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*") 

409 

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

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

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

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

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

415 if isinstance(value, str): 

416 retval = value 

417 elif isinstance(value, bytes): 

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

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

420 retval = value.decode("latin1") 

421 elif isinstance(value, numbers.Integral): 

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

423 return str(value) 

424 elif isinstance(value, datetime.datetime): 

425 return httputil.format_timestamp(value) 

426 else: 

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

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

429 # additional headers or split the request. 

430 if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None: 

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

432 return retval 

433 

434 @overload 

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

436 pass 

437 

438 @overload 

439 def get_argument( # noqa: F811 

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

441 ) -> str: 

442 pass 

443 

444 @overload 

445 def get_argument( # noqa: F811 

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

447 ) -> Optional[str]: 

448 pass 

449 

450 def get_argument( # noqa: F811 

451 self, 

452 name: str, 

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

454 strip: bool = True, 

455 ) -> Optional[str]: 

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

457 

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

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

460 

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

462 last value. 

463 

464 This method searches both the query and body arguments. 

465 """ 

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

467 

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

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

470 

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

472 

473 This method searches both the query and body arguments. 

474 """ 

475 

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

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

478 # `get_argument`.) 

479 assert isinstance(strip, bool) 

480 

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

482 

483 @overload 

484 def get_body_argument(self, name: str, default: str, strip: bool = True) -> str: 

485 pass 

486 

487 @overload 

488 def get_body_argument( # noqa: F811 

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

490 ) -> str: 

491 pass 

492 

493 @overload 

494 def get_body_argument( # noqa: F811 

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

496 ) -> Optional[str]: 

497 pass 

498 

499 def get_body_argument( # noqa: F811 

500 self, 

501 name: str, 

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

503 strip: bool = True, 

504 ) -> Optional[str]: 

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

506 from the request body. 

507 

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

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

510 

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

512 last value. 

513 

514 .. versionadded:: 3.2 

515 """ 

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

517 

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

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

520 

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

522 

523 .. versionadded:: 3.2 

524 """ 

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

526 

527 @overload 

528 def get_query_argument(self, name: str, default: str, strip: bool = True) -> str: 

529 pass 

530 

531 @overload 

532 def get_query_argument( # noqa: F811 

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

534 ) -> str: 

535 pass 

536 

537 @overload 

538 def get_query_argument( # noqa: F811 

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

540 ) -> Optional[str]: 

541 pass 

542 

543 def get_query_argument( # noqa: F811 

544 self, 

545 name: str, 

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

547 strip: bool = True, 

548 ) -> Optional[str]: 

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

550 from the request query string. 

551 

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

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

554 

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

556 last value. 

557 

558 .. versionadded:: 3.2 

559 """ 

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

561 

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

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

564 

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

566 

567 .. versionadded:: 3.2 

568 """ 

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

570 

571 def _get_argument( 

572 self, 

573 name: str, 

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

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

576 strip: bool = True, 

577 ) -> Optional[str]: 

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

579 if not args: 

580 if isinstance(default, _ArgDefaultMarker): 

581 raise MissingArgumentError(name) 

582 return default 

583 return args[-1] 

584 

585 def _get_arguments( 

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

587 ) -> List[str]: 

588 values = [] 

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

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

591 if isinstance(s, unicode_type): 

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

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

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

595 if strip: 

596 s = s.strip() 

597 values.append(s) 

598 return values 

599 

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

601 """Decodes an argument from the request. 

602 

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

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

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

606 

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

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

609 

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

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

612 """ 

613 try: 

614 return _unicode(value) 

615 except UnicodeDecodeError: 

616 raise HTTPError( 

617 400, "Invalid unicode in {}: {!r}".format(name or "url", value[:40]) 

618 ) 

619 

620 @property 

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

622 """An alias for 

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

624 return self.request.cookies 

625 

626 @overload 

627 def get_cookie(self, name: str, default: str) -> str: 

628 pass 

629 

630 @overload 

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

632 pass 

633 

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

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

636 

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

638 

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

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

641 handler. 

642 """ 

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

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

645 return default 

646 

647 def set_cookie( 

648 self, 

649 name: str, 

650 value: Union[str, bytes], 

651 domain: Optional[str] = None, 

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

653 path: str = "/", 

654 expires_days: Optional[float] = None, 

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

656 *, 

657 max_age: Optional[int] = None, 

658 httponly: bool = False, 

659 secure: bool = False, 

660 samesite: Optional[str] = None, 

661 **kwargs: Any, 

662 ) -> None: 

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

664 

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

666 they are not present until the next request. 

667 

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

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

670 for more information. 

671 

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

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

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

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

676 is used). 

677 

678 .. deprecated:: 6.3 

679 Keyword arguments are currently accepted case-insensitively. 

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

681 arguments. 

682 """ 

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

684 name = escape.native_str(name) 

685 value = escape.native_str(value) 

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

687 # Don't let us accidentally inject bad stuff 

688 raise ValueError(f"Invalid cookie {name!r}: {value!r}") 

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

690 self._new_cookie = ( 

691 http.cookies.SimpleCookie() 

692 ) # type: http.cookies.SimpleCookie 

693 if name in self._new_cookie: 

694 del self._new_cookie[name] 

695 self._new_cookie[name] = value 

696 morsel = self._new_cookie[name] 

697 if domain: 

698 morsel["domain"] = domain 

699 if expires_days is not None and not expires: 

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

701 days=expires_days 

702 ) 

703 if expires: 

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

705 if path: 

706 morsel["path"] = path 

707 if max_age: 

708 # Note change from _ to -. 

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

710 if httponly: 

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

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

713 morsel["httponly"] = True 

714 if secure: 

715 morsel["secure"] = True 

716 if samesite: 

717 morsel["samesite"] = samesite 

718 if kwargs: 

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

720 # kwargs for backwards compatibility until we can remove deprecated 

721 # features. 

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

723 morsel[k] = v 

724 warnings.warn( 

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

726 "(should be lowercase)", 

727 DeprecationWarning, 

728 ) 

729 

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

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

732 

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

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

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

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

737 arguments are ignored. 

738 

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

740 seen until the following request. 

741 

742 .. versionchanged:: 6.3 

743 

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

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

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

747 """ 

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

749 if excluded_arg in kwargs: 

750 raise TypeError( 

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

752 ) 

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

754 days=365 

755 ) 

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

757 

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

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

760 

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

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

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

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

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

766 when setting cookies. 

767 

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

769 until the following request. 

770 

771 .. versionchanged:: 3.2 

772 

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

774 

775 .. versionchanged:: 6.3 

776 

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

778 

779 .. deprecated:: 6.3 

780 

781 The increasingly complex rules governing cookies have made it 

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

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

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

785 """ 

786 for name in self.request.cookies: 

787 self.clear_cookie(name, **kwargs) 

788 

789 def set_signed_cookie( 

790 self, 

791 name: str, 

792 value: Union[str, bytes], 

793 expires_days: Optional[float] = 30, 

794 version: Optional[int] = None, 

795 **kwargs: Any, 

796 ) -> None: 

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

798 

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

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

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

802 

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

804 

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

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

807 parameter to `get_signed_cookie`. 

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

809 

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

811 strings (unlike regular cookies) 

812 

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

814 seen until the following request. 

815 

816 .. versionchanged:: 3.2.1 

817 

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

819 and made it the default. 

820 

821 .. versionchanged:: 6.3 

822 

823 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

826 """ 

827 self.set_cookie( 

828 name, 

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

830 expires_days=expires_days, 

831 **kwargs, 

832 ) 

833 

834 set_secure_cookie = set_signed_cookie 

835 

836 def create_signed_value( 

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

838 ) -> bytes: 

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

840 

841 Normally used via set_signed_cookie, but provided as a separate 

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

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

844 

845 .. versionchanged:: 3.2.1 

846 

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

848 and made it the default. 

849 """ 

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

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

852 key_version = None 

853 if isinstance(secret, dict): 

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

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

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

857 

858 return create_signed_value( 

859 secret, name, value, version=version, key_version=key_version 

860 ) 

861 

862 def get_signed_cookie( 

863 self, 

864 name: str, 

865 value: Optional[str] = None, 

866 max_age_days: float = 31, 

867 min_version: Optional[int] = None, 

868 ) -> Optional[bytes]: 

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

870 

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

872 `get_cookie`). 

873 

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

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

876 `set_signed_cookie` in this handler. 

877 

878 .. versionchanged:: 3.2.1 

879 

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

881 both versions 1 and 2 are accepted by default. 

882 

883 .. versionchanged:: 6.3 

884 

885 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

888 

889 """ 

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

891 if value is None: 

892 value = self.get_cookie(name) 

893 return decode_signed_value( 

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

895 name, 

896 value, 

897 max_age_days=max_age_days, 

898 min_version=min_version, 

899 ) 

900 

901 get_secure_cookie = get_signed_cookie 

902 

903 def get_signed_cookie_key_version( 

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

905 ) -> Optional[int]: 

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

907 

908 The version is returned as int. 

909 

910 .. versionchanged:: 6.3 

911 

912 Renamed from ``get_secure_cookie_key_version`` to 

913 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

915 remains as an alias. 

916 

917 """ 

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

919 if value is None: 

920 value = self.get_cookie(name) 

921 if value is None: 

922 return None 

923 return get_signature_key_version(value) 

924 

925 get_secure_cookie_key_version = get_signed_cookie_key_version 

926 

927 def redirect( 

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

929 ) -> None: 

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

931 

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

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

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

935 The default is 302 (temporary). 

936 """ 

937 if self._headers_written: 

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

939 if status is None: 

940 status = 301 if permanent else 302 

941 else: 

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

943 self.set_status(status) 

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

945 self.finish() 

946 

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

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

949 

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

951 

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

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

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

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

956 

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

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

959 wrapped in a dictionary. More details at 

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

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

962 """ 

963 if self._finished: 

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

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

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

967 if isinstance(chunk, list): 

968 message += ( 

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

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

971 ) 

972 raise TypeError(message) 

973 if isinstance(chunk, dict): 

974 chunk = escape.json_encode(chunk) 

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

976 chunk = utf8(chunk) 

977 self._write_buffer.append(chunk) 

978 

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

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

981 

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

983 after it. 

984 

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

986 Awaiting this `.Future` is optional. 

987 

988 .. versionchanged:: 5.1 

989 

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

991 """ 

992 if self._finished: 

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

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

995 

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

997 js_embed = [] 

998 js_files = [] 

999 css_embed = [] 

1000 css_files = [] 

1001 html_heads = [] 

1002 html_bodies = [] 

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

1004 embed_part = module.embedded_javascript() 

1005 if embed_part: 

1006 js_embed.append(utf8(embed_part)) 

1007 file_part = module.javascript_files() 

1008 if file_part: 

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

1010 js_files.append(_unicode(file_part)) 

1011 else: 

1012 js_files.extend(file_part) 

1013 embed_part = module.embedded_css() 

1014 if embed_part: 

1015 css_embed.append(utf8(embed_part)) 

1016 file_part = module.css_files() 

1017 if file_part: 

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

1019 css_files.append(_unicode(file_part)) 

1020 else: 

1021 css_files.extend(file_part) 

1022 head_part = module.html_head() 

1023 if head_part: 

1024 html_heads.append(utf8(head_part)) 

1025 body_part = module.html_body() 

1026 if body_part: 

1027 html_bodies.append(utf8(body_part)) 

1028 

1029 if js_files: 

1030 # Maintain order of JavaScript files given by modules 

1031 js = self.render_linked_js(js_files) 

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

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

1034 if js_embed: 

1035 js_bytes = self.render_embed_js(js_embed) 

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

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

1038 if css_files: 

1039 css = self.render_linked_css(css_files) 

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

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

1042 if css_embed: 

1043 css_bytes = self.render_embed_css(css_embed) 

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

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

1046 if html_heads: 

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

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

1049 if html_bodies: 

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

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

1052 return self.finish(html) 

1053 

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

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

1056 rendered webpage. 

1057 

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

1059 """ 

1060 paths = [] 

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

1062 

1063 for path in js_files: 

1064 if not is_absolute(path): 

1065 path = self.static_url(path) 

1066 if path not in unique_paths: 

1067 paths.append(path) 

1068 unique_paths.add(path) 

1069 

1070 return "".join( 

1071 '<script src="' 

1072 + escape.xhtml_escape(p) 

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

1074 for p in paths 

1075 ) 

1076 

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

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

1079 rendered webpage. 

1080 

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

1082 """ 

1083 return ( 

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

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

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

1087 ) 

1088 

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

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

1091 rendered webpage. 

1092 

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

1094 """ 

1095 paths = [] 

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

1097 

1098 for path in css_files: 

1099 if not is_absolute(path): 

1100 path = self.static_url(path) 

1101 if path not in unique_paths: 

1102 paths.append(path) 

1103 unique_paths.add(path) 

1104 

1105 return "".join( 

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

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

1108 for p in paths 

1109 ) 

1110 

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

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

1113 rendered webpage. 

1114 

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

1116 """ 

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

1118 

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

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

1121 

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

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

1124 """ 

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

1126 template_path = self.get_template_path() 

1127 if not template_path: 

1128 frame = sys._getframe(0) 

1129 web_file = frame.f_code.co_filename 

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

1131 frame = frame.f_back 

1132 assert frame.f_code.co_filename is not None 

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

1134 with RequestHandler._template_loader_lock: 

1135 if template_path not in RequestHandler._template_loaders: 

1136 loader = self.create_template_loader(template_path) 

1137 RequestHandler._template_loaders[template_path] = loader 

1138 else: 

1139 loader = RequestHandler._template_loaders[template_path] 

1140 t = loader.load(template_name) 

1141 namespace = self.get_template_namespace() 

1142 namespace.update(kwargs) 

1143 return t.generate(**namespace) 

1144 

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

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

1147 

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

1149 

1150 The results of this method will be combined with additional 

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

1152 to `render` or `render_string`. 

1153 """ 

1154 namespace = dict( 

1155 handler=self, 

1156 request=self.request, 

1157 current_user=self.current_user, 

1158 locale=self.locale, 

1159 _=self.locale.translate, 

1160 pgettext=self.locale.pgettext, 

1161 static_url=self.static_url, 

1162 xsrf_form_html=self.xsrf_form_html, 

1163 reverse_url=self.reverse_url, 

1164 ) 

1165 namespace.update(self.ui) 

1166 return namespace 

1167 

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

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

1170 

1171 May be overridden by subclasses. By default returns a 

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

1173 ``autoescape`` and ``template_whitespace`` application 

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

1175 supplied, uses that instead. 

1176 """ 

1177 settings = self.application.settings 

1178 if "template_loader" in settings: 

1179 return settings["template_loader"] 

1180 kwargs = {} 

1181 if "autoescape" in settings: 

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

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

1184 kwargs["autoescape"] = settings["autoescape"] 

1185 if "template_whitespace" in settings: 

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

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

1188 

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

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

1191 

1192 .. versionchanged:: 4.0 

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

1194 

1195 .. versionchanged:: 6.0 

1196 

1197 The ``callback`` argument was removed. 

1198 """ 

1199 assert self.request.connection is not None 

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

1201 self._write_buffer = [] 

1202 if not self._headers_written: 

1203 self._headers_written = True 

1204 for transform in self._transforms: 

1205 assert chunk is not None 

1206 ( 

1207 self._status_code, 

1208 self._headers, 

1209 chunk, 

1210 ) = transform.transform_first_chunk( 

1211 self._status_code, self._headers, chunk, include_footers 

1212 ) 

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

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

1215 chunk = b"" 

1216 

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

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

1219 # is sent). 

1220 if hasattr(self, "_new_cookie"): 

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

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

1223 

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

1225 return self.request.connection.write_headers( 

1226 start_line, self._headers, chunk 

1227 ) 

1228 else: 

1229 for transform in self._transforms: 

1230 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1234 else: 

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

1236 future.set_result(None) 

1237 return future 

1238 

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

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

1241 

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

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

1244 

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

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

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

1248 data can be sent. 

1249 

1250 .. versionchanged:: 5.1 

1251 

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

1253 """ 

1254 if self._finished: 

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

1256 

1257 if chunk is not None: 

1258 self.write(chunk) 

1259 

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

1261 # we have not flushed any content yet. 

1262 if not self._headers_written: 

1263 if ( 

1264 self._status_code == 200 

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

1266 and "Etag" not in self._headers 

1267 ): 

1268 self.set_etag_header() 

1269 if self.check_etag_header(): 

1270 self._write_buffer = [] 

1271 self.set_status(304) 

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

1273 assert not self._write_buffer, ( 

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

1275 ) 

1276 self._clear_representation_headers() 

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

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

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

1280 

1281 assert self.request.connection is not None 

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

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

1284 # garbage collection of the RequestHandler when there 

1285 # are keepalive connections) 

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

1287 

1288 future = self.flush(include_footers=True) 

1289 self.request.connection.finish() 

1290 self._log() 

1291 self._finished = True 

1292 self.on_finish() 

1293 self._break_cycles() 

1294 return future 

1295 

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

1297 """Take control of the underlying stream. 

1298 

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

1300 further HTTP processing. Intended for implementing protocols 

1301 like websockets that tunnel over an HTTP handshake. 

1302 

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

1304 

1305 .. versionadded:: 5.1 

1306 """ 

1307 self._finished = True 

1308 # TODO: add detach to HTTPConnection? 

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

1310 

1311 def _break_cycles(self) -> None: 

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

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

1314 self.ui = None # type: ignore 

1315 

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

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

1318 

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

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

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

1322 and replaced with the error page. 

1323 

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

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

1326 """ 

1327 if self._headers_written: 

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

1329 if not self._finished: 

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

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

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

1333 # socket. 

1334 try: 

1335 self.finish() 

1336 except Exception: 

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

1338 return 

1339 self.clear() 

1340 

1341 reason = kwargs.get("reason") 

1342 if "exc_info" in kwargs: 

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

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

1345 reason = exception.reason 

1346 self.set_status(status_code, reason=reason) 

1347 try: 

1348 self.write_error(status_code, **kwargs) 

1349 except Exception: 

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

1351 if not self._finished: 

1352 self.finish() 

1353 

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

1355 """Override to implement custom error pages. 

1356 

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

1358 to produce output as usual. 

1359 

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

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

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

1363 the "current" exception for purposes of methods like 

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

1365 """ 

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

1367 # in debug mode, try to send a traceback 

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

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

1370 self.write(line) 

1371 self.finish() 

1372 else: 

1373 self.finish( 

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

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

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

1377 ) 

1378 

1379 @property 

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

1381 """The locale for the current session. 

1382 

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

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

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

1386 header. 

1387 

1388 .. versionchanged: 4.1 

1389 Added a property setter. 

1390 """ 

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

1392 loc = self.get_user_locale() 

1393 if loc is not None: 

1394 self._locale = loc 

1395 else: 

1396 self._locale = self.get_browser_locale() 

1397 assert self._locale 

1398 return self._locale 

1399 

1400 @locale.setter 

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

1402 self._locale = value 

1403 

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

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

1406 

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

1408 

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

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

1411 """ 

1412 return None 

1413 

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

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

1416 

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

1418 """ 

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

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

1421 locales = [] 

1422 for language in languages: 

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

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

1425 try: 

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

1427 if score < 0: 

1428 raise ValueError() 

1429 except (ValueError, TypeError): 

1430 score = 0.0 

1431 else: 

1432 score = 1.0 

1433 if score > 0: 

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

1435 if locales: 

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

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

1438 return locale.get(*codes) 

1439 return locale.get(default) 

1440 

1441 @property 

1442 def current_user(self) -> Any: 

1443 """The authenticated user for this request. 

1444 

1445 This is set in one of two ways: 

1446 

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

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

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

1450 and is cached for future access:: 

1451 

1452 def get_current_user(self): 

1453 user_cookie = self.get_signed_cookie("user") 

1454 if user_cookie: 

1455 return json.loads(user_cookie) 

1456 return None 

1457 

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

1459 `prepare()`:: 

1460 

1461 @gen.coroutine 

1462 def prepare(self): 

1463 user_id_cookie = self.get_signed_cookie("user_id") 

1464 if user_id_cookie: 

1465 self.current_user = yield load_user(user_id_cookie) 

1466 

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

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

1469 asynchronous operations. 

1470 

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

1472 """ 

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

1474 self._current_user = self.get_current_user() 

1475 return self._current_user 

1476 

1477 @current_user.setter 

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

1479 self._current_user = value 

1480 

1481 def get_current_user(self) -> Any: 

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

1483 

1484 This method may not be a coroutine. 

1485 """ 

1486 return None 

1487 

1488 def get_login_url(self) -> str: 

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

1490 

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

1492 """ 

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

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

1495 

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

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

1498 

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

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

1501 """ 

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

1503 

1504 @property 

1505 def xsrf_token(self) -> bytes: 

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

1507 

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

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

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

1511 as a potential forgery. 

1512 

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

1514 

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

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

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

1518 UTF-8. 

1519 

1520 .. versionchanged:: 3.2.2 

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

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

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

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

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

1526 unless the ``xsrf_cookie_version`` `Application` setting is 

1527 set to 1. 

1528 

1529 .. versionchanged:: 4.3 

1530 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1531 used to supply additional cookie options (which will be 

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

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

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

1535 ``_xsrf`` cookie. 

1536 """ 

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

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

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

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

1541 if output_version == 1: 

1542 self._xsrf_token = binascii.b2a_hex(token) 

1543 elif output_version == 2: 

1544 mask = os.urandom(4) 

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

1546 [ 

1547 b"2", 

1548 binascii.b2a_hex(mask), 

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

1550 utf8(str(int(timestamp))), 

1551 ] 

1552 ) 

1553 else: 

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

1555 if version is None: 

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

1557 cookie_kwargs["expires_days"] = 30 

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

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

1560 return self._xsrf_token 

1561 

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

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

1564 

1565 The raw_xsrf_token is a tuple containing: 

1566 

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

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

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

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

1571 for version 1 cookies) 

1572 """ 

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

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

1575 cookie = self.get_cookie(cookie_name) 

1576 if cookie: 

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

1578 else: 

1579 version, token, timestamp = None, None, None 

1580 if token is None: 

1581 version = None 

1582 token = os.urandom(16) 

1583 timestamp = time.time() 

1584 assert token is not None 

1585 assert timestamp is not None 

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

1587 return self._raw_xsrf_token 

1588 

1589 def _decode_xsrf_token( 

1590 self, cookie: str 

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

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

1593 _get_raw_xsrf_token. 

1594 """ 

1595 

1596 try: 

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

1598 

1599 if m: 

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

1601 if version == 2: 

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

1603 

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

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

1606 timestamp = int(timestamp_str) 

1607 return version, token, timestamp 

1608 else: 

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

1610 raise Exception("Unknown xsrf cookie version") 

1611 else: 

1612 version = 1 

1613 try: 

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

1615 except (binascii.Error, TypeError): 

1616 token = utf8(cookie) 

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

1618 timestamp = int(time.time()) 

1619 return (version, token, timestamp) 

1620 except Exception: 

1621 # Catch exceptions and return nothing instead of failing. 

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

1623 return None, None, None 

1624 

1625 def check_xsrf_cookie(self) -> None: 

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

1627 

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

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

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

1631 reject the form submission as a potential forgery. 

1632 

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

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

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

1636 

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

1638 

1639 .. versionchanged:: 3.2.2 

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

1641 supported. 

1642 """ 

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

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

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

1646 # information please see 

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

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

1649 input_token = ( 

1650 self.get_argument("_xsrf", None) 

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

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

1653 ) 

1654 if not input_token: 

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

1656 _, token, _ = self._decode_xsrf_token(input_token) 

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

1658 if not token: 

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

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

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

1662 

1663 def xsrf_form_html(self) -> str: 

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

1665 

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

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

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

1669 HTML within all of your HTML forms. 

1670 

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

1672 xsrf_form_html() %}`` 

1673 

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

1675 """ 

1676 return ( 

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

1678 + escape.xhtml_escape(self.xsrf_token) 

1679 + '"/>' 

1680 ) 

1681 

1682 def static_url( 

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

1684 ) -> str: 

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

1686 

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

1688 application (which specifies the root directory of your static 

1689 files). 

1690 

1691 This method returns a versioned url (by default appending 

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

1693 cached indefinitely. This can be disabled by passing 

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

1695 other static file implementations are not required to support 

1696 this, but they may support other options). 

1697 

1698 By default this method returns URLs relative to the current 

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

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

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

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

1703 

1704 """ 

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

1706 get_url = self.settings.get( 

1707 "static_handler_class", StaticFileHandler 

1708 ).make_static_url 

1709 

1710 if include_host is None: 

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

1712 

1713 if include_host: 

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

1715 else: 

1716 base = "" 

1717 

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

1719 

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

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

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

1723 raise Exception( 

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

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

1726 ) 

1727 

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

1729 """Alias for `Application.reverse_url`.""" 

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

1731 

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

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

1734 

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

1736 

1737 May be overridden to provide custom etag implementations, 

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

1739 """ 

1740 hasher = hashlib.sha1() 

1741 for part in self._write_buffer: 

1742 hasher.update(part) 

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

1744 

1745 def set_etag_header(self) -> None: 

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

1747 

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

1749 

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

1751 """ 

1752 etag = self.compute_etag() 

1753 if etag is not None: 

1754 self.set_header("Etag", etag) 

1755 

1756 def check_etag_header(self) -> bool: 

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

1758 

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

1760 returned. For example:: 

1761 

1762 self.set_etag_header() 

1763 if self.check_etag_header(): 

1764 self.set_status(304) 

1765 return 

1766 

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

1768 but may be called earlier for applications that override 

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

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

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

1772 """ 

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

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

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

1776 etags = re.findall( 

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

1778 ) 

1779 if not computed_etag or not etags: 

1780 return False 

1781 

1782 match = False 

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

1784 match = True 

1785 else: 

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

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

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

1789 

1790 for etag in etags: 

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

1792 match = True 

1793 break 

1794 return match 

1795 

1796 async def _execute( 

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

1798 ) -> None: 

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

1800 self._transforms = transforms 

1801 try: 

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

1803 raise HTTPError(405) 

1804 

1805 # If we're not in stream_request_body mode, this is the place where we parse the body. 

1806 if not _has_stream_request_body(self.__class__): 

1807 try: 

1808 self.request._parse_body() 

1809 except httputil.HTTPInputError as e: 

1810 raise HTTPError(400, "Invalid body: %s" % e) from e 

1811 

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

1813 self.path_kwargs = { 

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

1815 } 

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

1817 # the proper cookie 

1818 if self.request.method not in ( 

1819 "GET", 

1820 "HEAD", 

1821 "OPTIONS", 

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

1823 self.check_xsrf_cookie() 

1824 

1825 result = self.prepare() 

1826 if result is not None: 

1827 result = await result # type: ignore 

1828 if self._prepared_future is not None: 

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

1830 # and are ready for the body to arrive. 

1831 future_set_result_unless_cancelled(self._prepared_future, None) 

1832 if self._finished: 

1833 return 

1834 

1835 if _has_stream_request_body(self.__class__): 

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

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

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

1839 # instead. 

1840 try: 

1841 await self.request._body_future 

1842 except iostream.StreamClosedError: 

1843 return 

1844 

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

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

1847 if result is not None: 

1848 result = await result 

1849 if self._auto_finish and not self._finished: 

1850 self.finish() 

1851 except Exception as e: 

1852 try: 

1853 self._handle_request_exception(e) 

1854 except Exception: 

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

1856 finally: 

1857 # Unset result to avoid circular references 

1858 result = None 

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

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

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

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

1863 self._prepared_future.set_result(None) 

1864 

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

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

1867 

1868 Requires the `.stream_request_body` decorator. 

1869 

1870 May be a coroutine for flow control. 

1871 """ 

1872 raise NotImplementedError() 

1873 

1874 def _log(self) -> None: 

1875 """Logs the current request. 

1876 

1877 Sort of deprecated since this functionality was moved to the 

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

1879 that have overridden this method. 

1880 """ 

1881 self.application.log_request(self) 

1882 

1883 def _request_summary(self) -> str: 

1884 return "{} {} ({})".format( 

1885 self.request.method, 

1886 self.request.uri, 

1887 self.request.remote_ip, 

1888 ) 

1889 

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

1891 if isinstance(e, Finish): 

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

1893 if not self._finished: 

1894 self.finish(*e.args) 

1895 return 

1896 try: 

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

1898 except Exception: 

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

1900 # to avoid leaking the connection. 

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

1902 if self._finished: 

1903 # Extra errors after the request has been finished should 

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

1905 # send a response. 

1906 return 

1907 if isinstance(e, HTTPError): 

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

1909 else: 

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

1911 

1912 def log_exception( 

1913 self, 

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

1915 value: Optional[BaseException], 

1916 tb: Optional[TracebackType], 

1917 ) -> None: 

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

1919 

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

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

1922 other exceptions as errors with stack traces (on the 

1923 ``tornado.application`` logger). 

1924 

1925 .. versionadded:: 3.1 

1926 """ 

1927 if isinstance(value, HTTPError): 

1928 log_message = value.get_message() 

1929 if log_message: 

1930 format = "%d %s: %s" 

1931 args = [value.status_code, self._request_summary(), log_message] 

1932 gen_log.warning(format, *args) 

1933 else: 

1934 app_log.error( 

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

1936 self._request_summary(), 

1937 self.request, 

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

1939 ) 

1940 

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

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

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

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

1945 if name not in self._active_modules: 

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

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

1948 return _unicode(rendered) 

1949 

1950 return render 

1951 

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

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

1954 

1955 def _clear_representation_headers(self) -> None: 

1956 # 304 responses should not contain representation metadata 

1957 # headers (defined in 

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

1959 # not explicitly allowed by 

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

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

1962 for h in headers: 

1963 self.clear_header(h) 

1964 

1965 

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

1967 

1968 

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

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

1971 

1972 This decorator implies the following changes: 

1973 

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

1975 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

1983 until those futures have completed. 

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

1985 the entire body has been read. 

1986 

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

1988 for example usage. 

1989 """ # noqa: E501 

1990 if not issubclass(cls, RequestHandler): 

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

1992 cls._stream_request_body = True 

1993 return cls 

1994 

1995 

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

1997 if not issubclass(cls, RequestHandler): 

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

1999 return cls._stream_request_body 

2000 

2001 

2002def removeslash( 

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

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

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

2006 

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

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

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

2010 """ 

2011 

2012 @functools.wraps(method) 

2013 def wrapper( # type: ignore 

2014 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

2020 if self.request.query: 

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

2022 self.redirect(uri, permanent=True) 

2023 return None 

2024 else: 

2025 raise HTTPError(404) 

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

2027 

2028 return wrapper 

2029 

2030 

2031def addslash( 

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

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

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

2035 

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

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

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

2039 """ 

2040 

2041 @functools.wraps(method) 

2042 def wrapper( # type: ignore 

2043 self: RequestHandler, *args, **kwargs 

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

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

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

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

2048 if self.request.query: 

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

2050 self.redirect(uri, permanent=True) 

2051 return None 

2052 raise HTTPError(404) 

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

2054 

2055 return wrapper 

2056 

2057 

2058class _ApplicationRouter(ReversibleRuleRouter): 

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

2060 

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

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

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

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

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

2066 `_ApplicationRouter` instance. 

2067 """ 

2068 

2069 def __init__( 

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

2071 ) -> None: 

2072 assert isinstance(application, Application) 

2073 self.application = application 

2074 super().__init__(rules) 

2075 

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

2077 rule = super().process_rule(rule) 

2078 

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

2080 rule.target = _ApplicationRouter( 

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

2082 ) 

2083 

2084 return rule 

2085 

2086 def get_target_delegate( 

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

2088 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

2090 return self.application.get_handler_delegate( 

2091 request, target, **target_params 

2092 ) 

2093 

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

2095 

2096 

2097class Application(ReversibleRouter): 

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

2099 

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

2101 HTTPServer to serve the application:: 

2102 

2103 application = web.Application([ 

2104 (r"/", MainPageHandler), 

2105 ]) 

2106 http_server = httpserver.HTTPServer(application) 

2107 http_server.listen(8080) 

2108 

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

2110 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2115 

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

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

2118 

2119 application = web.Application([ 

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

2121 (r"/", MainPageHandler), 

2122 (r"/feed", FeedHandler), 

2123 ]), 

2124 ]) 

2125 

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

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

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

2129 

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

2131 instantiate an instance of the first request class whose regexp 

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

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

2134 

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

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

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

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

2139 `StaticFileHandler` can be installed automatically with the 

2140 static_path setting described below):: 

2141 

2142 application = web.Application([ 

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

2144 ]) 

2145 

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

2147 a host regular expression as the first argument:: 

2148 

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

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

2151 ]) 

2152 

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

2154 parameter value is matched against host regular expressions. 

2155 

2156 

2157 .. warning:: 

2158 

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

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

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

2162 other private networks. Appropriate host patterns must be used 

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

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

2165 may be vulnerable to DNS rebinding. 

2166 

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

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

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

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

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

2172 `StaticFileHandler` can be specified with the 

2173 ``static_handler_class`` setting. 

2174 

2175 .. versionchanged:: 4.5 

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

2177 

2178 """ 

2179 

2180 def __init__( 

2181 self, 

2182 handlers: Optional[_RuleList] = None, 

2183 default_host: Optional[str] = None, 

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

2185 **settings: Any, 

2186 ) -> None: 

2187 if transforms is None: 

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

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

2190 self.transforms.append(GZipContentEncoding) 

2191 else: 

2192 self.transforms = transforms 

2193 self.default_host = default_host 

2194 self.settings = settings 

2195 self.ui_modules = { 

2196 "linkify": _linkify, 

2197 "xsrf_form_html": _xsrf_form_html, 

2198 "Template": TemplateModule, 

2199 } 

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

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

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

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

2204 path = self.settings["static_path"] 

2205 handlers = list(handlers or []) 

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

2207 static_handler_class = settings.get( 

2208 "static_handler_class", StaticFileHandler 

2209 ) 

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

2211 static_handler_args["path"] = path 

2212 for pattern in [ 

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

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

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

2216 ]: 

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

2218 

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

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

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

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

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

2224 

2225 self.wildcard_router = _ApplicationRouter(self, handlers) 

2226 self.default_router = _ApplicationRouter( 

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

2228 ) 

2229 

2230 # Automatically reload modified modules 

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

2232 from tornado import autoreload 

2233 

2234 autoreload.start() 

2235 

2236 def listen( 

2237 self, 

2238 port: int, 

2239 address: Optional[str] = None, 

2240 *, 

2241 family: socket.AddressFamily = socket.AF_UNSPEC, 

2242 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2243 flags: Optional[int] = None, 

2244 reuse_port: bool = False, 

2245 **kwargs: Any, 

2246 ) -> HTTPServer: 

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

2248 

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

2250 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2255 

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

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

2258 the server. 

2259 

2260 Returns the `.HTTPServer` object. 

2261 

2262 .. versionchanged:: 4.3 

2263 Now returns the `.HTTPServer` object. 

2264 

2265 .. versionchanged:: 6.2 

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

2267 including ``reuse_port``. 

2268 """ 

2269 server = HTTPServer(self, **kwargs) 

2270 server.listen( 

2271 port, 

2272 address=address, 

2273 family=family, 

2274 backlog=backlog, 

2275 flags=flags, 

2276 reuse_port=reuse_port, 

2277 ) 

2278 return server 

2279 

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

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

2282 

2283 Host patterns are processed sequentially in the order they were 

2284 added. All matching patterns will be considered. 

2285 """ 

2286 host_matcher = HostMatches(host_pattern) 

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

2288 

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

2290 

2291 if self.default_host is not None: 

2292 self.wildcard_router.add_rules( 

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

2294 ) 

2295 

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

2297 self.transforms.append(transform_class) 

2298 

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

2300 if isinstance(methods, types.ModuleType): 

2301 self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)}) 

2302 elif isinstance(methods, list): 

2303 for m in methods: 

2304 self._load_ui_methods(m) 

2305 else: 

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

2307 if ( 

2308 not name.startswith("_") 

2309 and hasattr(fn, "__call__") 

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

2311 ): 

2312 self.ui_methods[name] = fn 

2313 

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

2315 if isinstance(modules, types.ModuleType): 

2316 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)}) 

2317 elif isinstance(modules, list): 

2318 for m in modules: 

2319 self._load_ui_modules(m) 

2320 else: 

2321 assert isinstance(modules, dict) 

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

2323 try: 

2324 if issubclass(cls, UIModule): 

2325 self.ui_modules[name] = cls 

2326 except TypeError: 

2327 pass 

2328 

2329 def __call__( 

2330 self, request: httputil.HTTPServerRequest 

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

2332 # Legacy HTTPServer interface 

2333 dispatcher = self.find_handler(request) 

2334 return dispatcher.execute() 

2335 

2336 def find_handler( 

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

2338 ) -> "_HandlerDelegate": 

2339 route = self.default_router.find_handler(request) 

2340 if route is not None: 

2341 return cast("_HandlerDelegate", route) 

2342 

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

2344 return self.get_handler_delegate( 

2345 request, 

2346 self.settings["default_handler_class"], 

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

2348 ) 

2349 

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

2351 

2352 def get_handler_delegate( 

2353 self, 

2354 request: httputil.HTTPServerRequest, 

2355 target_class: Type[RequestHandler], 

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

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

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

2359 ) -> "_HandlerDelegate": 

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

2361 for application and `RequestHandler` subclass. 

2362 

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

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

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

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

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

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

2369 """ 

2370 return _HandlerDelegate( 

2371 self, request, target_class, target_kwargs, path_args, path_kwargs 

2372 ) 

2373 

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

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

2376 

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

2378 

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

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

2381 and url-escaped. 

2382 """ 

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

2384 if reversed_url is not None: 

2385 return reversed_url 

2386 

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

2388 

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

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

2391 

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

2393 this behavior either subclass Application and override this method, 

2394 or pass a function in the application settings dictionary as 

2395 ``log_function``. 

2396 """ 

2397 if "log_function" in self.settings: 

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

2399 return 

2400 if handler.get_status() < 400: 

2401 log_method = access_log.info 

2402 elif handler.get_status() < 500: 

2403 log_method = access_log.warning 

2404 else: 

2405 log_method = access_log.error 

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

2407 log_method( 

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

2409 handler.get_status(), 

2410 handler._request_summary(), 

2411 request_time, 

2412 ) 

2413 

2414 

2415class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2416 def __init__( 

2417 self, 

2418 application: Application, 

2419 request: httputil.HTTPServerRequest, 

2420 handler_class: Type[RequestHandler], 

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

2422 path_args: Optional[List[bytes]], 

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

2424 ) -> None: 

2425 self.application = application 

2426 self.connection = request.connection 

2427 self.request = request 

2428 self.handler_class = handler_class 

2429 self.handler_kwargs = handler_kwargs or {} 

2430 self.path_args = path_args or [] 

2431 self.path_kwargs = path_kwargs or {} 

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

2433 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2434 

2435 def headers_received( 

2436 self, 

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

2438 headers: httputil.HTTPHeaders, 

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

2440 if self.stream_request_body: 

2441 self.request._body_future = Future() 

2442 return self.execute() 

2443 return None 

2444 

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

2446 if self.stream_request_body: 

2447 return self.handler.data_received(data) 

2448 else: 

2449 self.chunks.append(data) 

2450 return None 

2451 

2452 def finish(self) -> None: 

2453 if self.stream_request_body: 

2454 future_set_result_unless_cancelled(self.request._body_future, None) 

2455 else: 

2456 # Note that the body gets parsed in RequestHandler._execute so it can be in 

2457 # the right exception handler scope. 

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

2459 self.execute() 

2460 

2461 def on_connection_close(self) -> None: 

2462 if self.stream_request_body: 

2463 self.handler.on_connection_close() 

2464 else: 

2465 self.chunks = None # type: ignore 

2466 

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

2468 # If template cache is disabled (usually in the debug mode), 

2469 # re-compile templates and reload static files on every 

2470 # request so you don't need to restart to see changes 

2471 if not self.application.settings.get("compiled_template_cache", True): 

2472 with RequestHandler._template_loader_lock: 

2473 for loader in RequestHandler._template_loaders.values(): 

2474 loader.reset() 

2475 if not self.application.settings.get("static_hash_cache", True): 

2476 static_handler_class = self.application.settings.get( 

2477 "static_handler_class", StaticFileHandler 

2478 ) 

2479 static_handler_class.reset() 

2480 

2481 self.handler = self.handler_class( 

2482 self.application, self.request, **self.handler_kwargs 

2483 ) 

2484 transforms = [t(self.request) for t in self.application.transforms] 

2485 

2486 if self.stream_request_body: 

2487 self.handler._prepared_future = Future() 

2488 # Note that if an exception escapes handler._execute it will be 

2489 # trapped in the Future it returns (which we are ignoring here, 

2490 # leaving it to be logged when the Future is GC'd). 

2491 # However, that shouldn't happen because _execute has a blanket 

2492 # except handler, and we cannot easily access the IOLoop here to 

2493 # call add_future (because of the requirement to remain compatible 

2494 # with WSGI) 

2495 fut = gen.convert_yielded( 

2496 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 

2497 ) 

2498 fut.add_done_callback(lambda f: f.result()) 

2499 # If we are streaming the request body, then execute() is finished 

2500 # when the handler has prepared to receive the body. If not, 

2501 # it doesn't matter when execute() finishes (so we return None) 

2502 return self.handler._prepared_future 

2503 

2504 

2505class HTTPError(Exception): 

2506 """An exception that will turn into an HTTP error response. 

2507 

2508 Raising an `HTTPError` is a convenient alternative to calling 

2509 `RequestHandler.send_error` since it automatically ends the 

2510 current function. 

2511 

2512 To customize the response sent with an `HTTPError`, override 

2513 `RequestHandler.write_error`. 

2514 

2515 :arg int status_code: HTTP status code. Must be listed in 

2516 `httplib.responses <http.client.responses>` unless the ``reason`` 

2517 keyword argument is given. 

2518 :arg str log_message: Message to be written to the log for this error 

2519 (will not be shown to the user unless the `Application` is in debug 

2520 mode). May contain ``%s``-style placeholders, which will be filled 

2521 in with remaining positional parameters. 

2522 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2523 to pass in the status line along with ``status_code``. Normally 

2524 determined automatically from ``status_code``, but can be used 

2525 to use a non-standard numeric code. 

2526 """ 

2527 

2528 def __init__( 

2529 self, 

2530 status_code: int = 500, 

2531 log_message: Optional[str] = None, 

2532 *args: Any, 

2533 **kwargs: Any, 

2534 ) -> None: 

2535 self.status_code = status_code 

2536 self._log_message = log_message 

2537 self.args = args 

2538 self.reason = kwargs.get("reason", None) 

2539 

2540 @property 

2541 def log_message(self) -> Optional[str]: 

2542 """ 

2543 A backwards compatible way of accessing log_message. 

2544 """ 

2545 if self._log_message and not self.args: 

2546 return self._log_message.replace("%", "%%") 

2547 return self._log_message 

2548 

2549 def get_message(self) -> Optional[str]: 

2550 if self._log_message and self.args: 

2551 return self._log_message % self.args 

2552 return self._log_message 

2553 

2554 def __str__(self) -> str: 

2555 message = "HTTP %d: %s" % ( 

2556 self.status_code, 

2557 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2558 ) 

2559 log_message = self.get_message() 

2560 if log_message: 

2561 return message + " (" + log_message + ")" 

2562 else: 

2563 return message 

2564 

2565 

2566class Finish(Exception): 

2567 """An exception that ends the request without producing an error response. 

2568 

2569 When `Finish` is raised in a `RequestHandler`, the request will 

2570 end (calling `RequestHandler.finish` if it hasn't already been 

2571 called), but the error-handling methods (including 

2572 `RequestHandler.write_error`) will not be called. 

2573 

2574 If `Finish()` was created with no arguments, the pending response 

2575 will be sent as-is. If `Finish()` was given an argument, that 

2576 argument will be passed to `RequestHandler.finish()`. 

2577 

2578 This can be a more convenient way to implement custom error pages 

2579 than overriding ``write_error`` (especially in library code):: 

2580 

2581 if self.current_user is None: 

2582 self.set_status(401) 

2583 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2584 raise Finish() 

2585 

2586 .. versionchanged:: 4.3 

2587 Arguments passed to ``Finish()`` will be passed on to 

2588 `RequestHandler.finish`. 

2589 """ 

2590 

2591 pass 

2592 

2593 

2594class MissingArgumentError(HTTPError): 

2595 """Exception raised by `RequestHandler.get_argument`. 

2596 

2597 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2598 code will be used instead of 500 (and a stack trace will not be logged). 

2599 

2600 .. versionadded:: 3.1 

2601 """ 

2602 

2603 def __init__(self, arg_name: str) -> None: 

2604 super().__init__(400, "Missing argument %s" % arg_name) 

2605 self.arg_name = arg_name 

2606 

2607 

2608class ErrorHandler(RequestHandler): 

2609 """Generates an error response with ``status_code`` for all requests.""" 

2610 

2611 def initialize(self, status_code: int) -> None: 

2612 self.set_status(status_code) 

2613 

2614 def prepare(self) -> None: 

2615 raise HTTPError(self._status_code) 

2616 

2617 def check_xsrf_cookie(self) -> None: 

2618 # POSTs to an ErrorHandler don't actually have side effects, 

2619 # so we don't need to check the xsrf token. This allows POSTs 

2620 # to the wrong url to return a 404 instead of 403. 

2621 pass 

2622 

2623 

2624class RedirectHandler(RequestHandler): 

2625 """Redirects the client to the given URL for all GET requests. 

2626 

2627 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2628 

2629 application = web.Application([ 

2630 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2631 ]) 

2632 

2633 `RedirectHandler` supports regular expression substitutions. E.g., to 

2634 swap the first and second parts of a path while preserving the remainder:: 

2635 

2636 application = web.Application([ 

2637 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2638 ]) 

2639 

2640 The final URL is formatted with `str.format` and the substrings that match 

2641 the capturing groups. In the above example, a request to "/a/b/c" would be 

2642 formatted like:: 

2643 

2644 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2645 

2646 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2647 values are substituted. 

2648 

2649 .. versionchanged:: 4.5 

2650 Added support for substitutions into the destination URL. 

2651 

2652 .. versionchanged:: 5.0 

2653 If any query arguments are present, they will be copied to the 

2654 destination URL. 

2655 """ 

2656 

2657 def initialize(self, url: str, permanent: bool = True) -> None: 

2658 self._url = url 

2659 self._permanent = permanent 

2660 

2661 def get(self, *args: Any, **kwargs: Any) -> None: 

2662 to_url = self._url.format(*args, **kwargs) 

2663 if self.request.query_arguments: 

2664 # TODO: figure out typing for the next line. 

2665 to_url = httputil.url_concat( 

2666 to_url, 

2667 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2668 ) 

2669 self.redirect(to_url, permanent=self._permanent) 

2670 

2671 

2672class StaticFileHandler(RequestHandler): 

2673 """A simple handler that can serve static content from a directory. 

2674 

2675 A `StaticFileHandler` is configured automatically if you pass the 

2676 ``static_path`` keyword argument to `Application`. This handler 

2677 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2678 and ``static_handler_args`` settings. 

2679 

2680 To map an additional path to this handler for a static data directory 

2681 you would add a line to your application like:: 

2682 

2683 application = web.Application([ 

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

2685 ]) 

2686 

2687 The handler constructor requires a ``path`` argument, which specifies the 

2688 local root directory of the content to be served. 

2689 

2690 Note that a capture group in the regex is required to parse the value for 

2691 the ``path`` argument to the get() method (different than the constructor 

2692 argument above); see `URLSpec` for details. 

2693 

2694 To serve a file like ``index.html`` automatically when a directory is 

2695 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2696 in your application settings, or add ``default_filename`` as an initializer 

2697 argument for your ``StaticFileHandler``. 

2698 

2699 To maximize the effectiveness of browser caching, this class supports 

2700 versioned urls (by default using the argument ``?v=``). If a version 

2701 is given, we instruct the browser to cache this file indefinitely. 

2702 `make_static_url` (also available as `RequestHandler.static_url`) can 

2703 be used to construct a versioned url. 

2704 

2705 This handler is intended primarily for use in development and light-duty 

2706 file serving; for heavy traffic it will be more efficient to use 

2707 a dedicated static file server (such as nginx or Apache). We support 

2708 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2709 some browsers require this functionality to be present to seek in 

2710 HTML5 audio or video). 

2711 

2712 **Subclassing notes** 

2713 

2714 This class is designed to be extensible by subclassing, but because 

2715 of the way static urls are generated with class methods rather than 

2716 instance methods, the inheritance patterns are somewhat unusual. 

2717 Be sure to use the ``@classmethod`` decorator when overriding a 

2718 class method. Instance methods may use the attributes ``self.path`` 

2719 ``self.absolute_path``, and ``self.modified``. 

2720 

2721 Subclasses should only override methods discussed in this section; 

2722 overriding other methods is error-prone. Overriding 

2723 ``StaticFileHandler.get`` is particularly problematic due to the 

2724 tight coupling with ``compute_etag`` and other methods. 

2725 

2726 To change the way static urls are generated (e.g. to match the behavior 

2727 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2728 `get_cache_time`, and/or `get_version`. 

2729 

2730 To replace all interaction with the filesystem (e.g. to serve 

2731 static content from a database), override `get_content`, 

2732 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2733 `validate_absolute_path`. 

2734 

2735 .. versionchanged:: 3.1 

2736 Many of the methods for subclasses were added in Tornado 3.1. 

2737 """ 

2738 

2739 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2740 

2741 _static_hashes = {} # type: Dict[str, Optional[str]] 

2742 _lock = threading.Lock() # protects _static_hashes 

2743 

2744 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2745 self.root = path 

2746 self.default_filename = default_filename 

2747 

2748 @classmethod 

2749 def reset(cls) -> None: 

2750 with cls._lock: 

2751 cls._static_hashes = {} 

2752 

2753 def head(self, path: str) -> Awaitable[None]: 

2754 return self.get(path, include_body=False) 

2755 

2756 async def get(self, path: str, include_body: bool = True) -> None: 

2757 # Set up our path instance variables. 

2758 self.path = self.parse_url_path(path) 

2759 del path # make sure we don't refer to path instead of self.path again 

2760 absolute_path = self.get_absolute_path(self.root, self.path) 

2761 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2762 if self.absolute_path is None: 

2763 return 

2764 

2765 self.modified = self.get_modified_time() 

2766 self.set_headers() 

2767 

2768 if self.should_return_304(): 

2769 self.set_status(304) 

2770 return 

2771 

2772 request_range = None 

2773 range_header = self.request.headers.get("Range") 

2774 if range_header: 

2775 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2776 # the request will be treated as if the header didn't exist. 

2777 request_range = httputil._parse_request_range(range_header) 

2778 

2779 size = self.get_content_size() 

2780 if request_range: 

2781 start, end = request_range 

2782 if start is not None and start < 0: 

2783 start += size 

2784 if start < 0: 

2785 start = 0 

2786 if ( 

2787 start is not None 

2788 and (start >= size or (end is not None and start >= end)) 

2789 ) or end == 0: 

2790 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2791 # the first requested byte is equal to or greater than the 

2792 # content, or when a suffix with length 0 is specified. 

2793 # https://tools.ietf.org/html/rfc7233#section-2.1 

2794 # A byte-range-spec is invalid if the last-byte-pos value is present 

2795 # and less than the first-byte-pos. 

2796 self.set_status(416) # Range Not Satisfiable 

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

2798 self.set_header("Content-Range", f"bytes */{size}") 

2799 return 

2800 if end is not None and end > size: 

2801 # Clients sometimes blindly use a large range to limit their 

2802 # download size; cap the endpoint at the actual file size. 

2803 end = size 

2804 # Note: only return HTTP 206 if less than the entire range has been 

2805 # requested. Not only is this semantically correct, but Chrome 

2806 # refuses to play audio if it gets an HTTP 206 in response to 

2807 # ``Range: bytes=0-``. 

2808 if size != (end or size) - (start or 0): 

2809 self.set_status(206) # Partial Content 

2810 self.set_header( 

2811 "Content-Range", httputil._get_content_range(start, end, size) 

2812 ) 

2813 else: 

2814 start = end = None 

2815 

2816 if start is not None and end is not None: 

2817 content_length = end - start 

2818 elif end is not None: 

2819 content_length = end 

2820 elif start is not None: 

2821 content_length = size - start 

2822 else: 

2823 content_length = size 

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

2825 

2826 if include_body: 

2827 content = self.get_content(self.absolute_path, start, end) 

2828 if isinstance(content, bytes): 

2829 content = [content] 

2830 for chunk in content: 

2831 try: 

2832 self.write(chunk) 

2833 await self.flush() 

2834 except iostream.StreamClosedError: 

2835 return 

2836 else: 

2837 assert self.request.method == "HEAD" 

2838 

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

2840 """Sets the ``Etag`` header based on static url version. 

2841 

2842 This allows efficient ``If-None-Match`` checks against cached 

2843 versions, and sends the correct ``Etag`` for a partial response 

2844 (i.e. the same ``Etag`` as the full file). 

2845 

2846 .. versionadded:: 3.1 

2847 """ 

2848 assert self.absolute_path is not None 

2849 version_hash = self._get_cached_version(self.absolute_path) 

2850 if not version_hash: 

2851 return None 

2852 return f'"{version_hash}"' 

2853 

2854 def set_headers(self) -> None: 

2855 """Sets the content and caching headers on the response. 

2856 

2857 .. versionadded:: 3.1 

2858 """ 

2859 self.set_header("Accept-Ranges", "bytes") 

2860 self.set_etag_header() 

2861 

2862 if self.modified is not None: 

2863 self.set_header("Last-Modified", self.modified) 

2864 

2865 content_type = self.get_content_type() 

2866 if content_type: 

2867 self.set_header("Content-Type", content_type) 

2868 

2869 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2870 if cache_time > 0: 

2871 self.set_header( 

2872 "Expires", 

2873 datetime.datetime.now(datetime.timezone.utc) 

2874 + datetime.timedelta(seconds=cache_time), 

2875 ) 

2876 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2877 

2878 self.set_extra_headers(self.path) 

2879 

2880 def should_return_304(self) -> bool: 

2881 """Returns True if the headers indicate that we should return 304. 

2882 

2883 .. versionadded:: 3.1 

2884 """ 

2885 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2886 if self.request.headers.get("If-None-Match"): 

2887 return self.check_etag_header() 

2888 

2889 # Check the If-Modified-Since, and don't send the result if the 

2890 # content has not been modified 

2891 ims_value = self.request.headers.get("If-Modified-Since") 

2892 if ims_value is not None: 

2893 try: 

2894 if_since = email.utils.parsedate_to_datetime(ims_value) 

2895 except Exception: 

2896 return False 

2897 if if_since.tzinfo is None: 

2898 if_since = if_since.replace(tzinfo=datetime.timezone.utc) 

2899 assert self.modified is not None 

2900 if if_since >= self.modified: 

2901 return True 

2902 

2903 return False 

2904 

2905 @classmethod 

2906 def get_absolute_path(cls, root: str, path: str) -> str: 

2907 """Returns the absolute location of ``path`` relative to ``root``. 

2908 

2909 ``root`` is the path configured for this `StaticFileHandler` 

2910 (in most cases the ``static_path`` `Application` setting). 

2911 

2912 This class method may be overridden in subclasses. By default 

2913 it returns a filesystem path, but other strings may be used 

2914 as long as they are unique and understood by the subclass's 

2915 overridden `get_content`. 

2916 

2917 .. versionadded:: 3.1 

2918 """ 

2919 abspath = os.path.abspath(os.path.join(root, path)) 

2920 return abspath 

2921 

2922 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2923 """Validate and return the absolute path. 

2924 

2925 ``root`` is the configured path for the `StaticFileHandler`, 

2926 and ``path`` is the result of `get_absolute_path` 

2927 

2928 This is an instance method called during request processing, 

2929 so it may raise `HTTPError` or use methods like 

2930 `RequestHandler.redirect` (return None after redirecting to 

2931 halt further processing). This is where 404 errors for missing files 

2932 are generated. 

2933 

2934 This method may modify the path before returning it, but note that 

2935 any such modifications will not be understood by `make_static_url`. 

2936 

2937 In instance methods, this method's result is available as 

2938 ``self.absolute_path``. 

2939 

2940 .. versionadded:: 3.1 

2941 """ 

2942 # os.path.abspath strips a trailing /. 

2943 # We must add it back to `root` so that we only match files 

2944 # in a directory named `root` instead of files starting with 

2945 # that prefix. 

2946 root = os.path.abspath(root) 

2947 if not root.endswith(os.path.sep): 

2948 # abspath always removes a trailing slash, except when 

2949 # root is '/'. This is an unusual case, but several projects 

2950 # have independently discovered this technique to disable 

2951 # Tornado's path validation and (hopefully) do their own, 

2952 # so we need to support it. 

2953 root += os.path.sep 

2954 # The trailing slash also needs to be temporarily added back 

2955 # the requested path so a request to root/ will match. 

2956 if not (absolute_path + os.path.sep).startswith(root): 

2957 raise HTTPError(403, "%s is not in root static directory", self.path) 

2958 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2959 # need to look at the request.path here for when path is empty 

2960 # but there is some prefix to the path that was already 

2961 # trimmed by the routing 

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

2963 if self.request.path.startswith("//"): 

2964 # A redirect with two initial slashes is a "protocol-relative" URL. 

2965 # This means the next path segment is treated as a hostname instead 

2966 # of a part of the path, making this effectively an open redirect. 

2967 # Reject paths starting with two slashes to prevent this. 

2968 # This is only reachable under certain configurations. 

2969 raise HTTPError( 

2970 403, "cannot redirect path with two initial slashes" 

2971 ) 

2972 self.redirect(self.request.path + "/", permanent=True) 

2973 return None 

2974 absolute_path = os.path.join(absolute_path, self.default_filename) 

2975 if not os.path.exists(absolute_path): 

2976 raise HTTPError(404) 

2977 if not os.path.isfile(absolute_path): 

2978 raise HTTPError(403, "%s is not a file", self.path) 

2979 return absolute_path 

2980 

2981 @classmethod 

2982 def get_content( 

2983 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

2984 ) -> Generator[bytes, None, None]: 

2985 """Retrieve the content of the requested resource which is located 

2986 at the given absolute path. 

2987 

2988 This class method may be overridden by subclasses. Note that its 

2989 signature is different from other overridable class methods 

2990 (no ``settings`` argument); this is deliberate to ensure that 

2991 ``abspath`` is able to stand on its own as a cache key. 

2992 

2993 This method should either return a byte string or an iterator 

2994 of byte strings. The latter is preferred for large files 

2995 as it helps reduce memory fragmentation. 

2996 

2997 .. versionadded:: 3.1 

2998 """ 

2999 with open(abspath, "rb") as file: 

3000 if start is not None: 

3001 file.seek(start) 

3002 if end is not None: 

3003 remaining = end - (start or 0) # type: Optional[int] 

3004 else: 

3005 remaining = None 

3006 while True: 

3007 chunk_size = 64 * 1024 

3008 if remaining is not None and remaining < chunk_size: 

3009 chunk_size = remaining 

3010 chunk = file.read(chunk_size) 

3011 if chunk: 

3012 if remaining is not None: 

3013 remaining -= len(chunk) 

3014 yield chunk 

3015 else: 

3016 if remaining is not None: 

3017 assert remaining == 0 

3018 return 

3019 

3020 @classmethod 

3021 def get_content_version(cls, abspath: str) -> str: 

3022 """Returns a version string for the resource at the given path. 

3023 

3024 This class method may be overridden by subclasses. The 

3025 default implementation is a SHA-512 hash of the file's contents. 

3026 

3027 .. versionadded:: 3.1 

3028 """ 

3029 data = cls.get_content(abspath) 

3030 hasher = hashlib.sha512() 

3031 if isinstance(data, bytes): 

3032 hasher.update(data) 

3033 else: 

3034 for chunk in data: 

3035 hasher.update(chunk) 

3036 return hasher.hexdigest() 

3037 

3038 def _stat(self) -> os.stat_result: 

3039 assert self.absolute_path is not None 

3040 if not hasattr(self, "_stat_result"): 

3041 self._stat_result = os.stat(self.absolute_path) 

3042 return self._stat_result 

3043 

3044 def get_content_size(self) -> int: 

3045 """Retrieve the total size of the resource at the given path. 

3046 

3047 This method may be overridden by subclasses. 

3048 

3049 .. versionadded:: 3.1 

3050 

3051 .. versionchanged:: 4.0 

3052 This method is now always called, instead of only when 

3053 partial results are requested. 

3054 """ 

3055 stat_result = self._stat() 

3056 return stat_result.st_size 

3057 

3058 def get_modified_time(self) -> Optional[datetime.datetime]: 

3059 """Returns the time that ``self.absolute_path`` was last modified. 

3060 

3061 May be overridden in subclasses. Should return a `~datetime.datetime` 

3062 object or None. 

3063 

3064 .. versionadded:: 3.1 

3065 

3066 .. versionchanged:: 6.4 

3067 Now returns an aware datetime object instead of a naive one. 

3068 Subclasses that override this method may return either kind. 

3069 """ 

3070 stat_result = self._stat() 

3071 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3072 # which truncates the fractional portion of the timestamp. It 

3073 # was changed from that form to stat_result.st_mtime to 

3074 # satisfy mypy (which disallows the bracket operator), but the 

3075 # latter form returns a float instead of an int. For 

3076 # consistency with the past (and because we have a unit test 

3077 # that relies on this), we truncate the float here, although 

3078 # I'm not sure that's the right thing to do. 

3079 modified = datetime.datetime.fromtimestamp( 

3080 int(stat_result.st_mtime), datetime.timezone.utc 

3081 ) 

3082 return modified 

3083 

3084 def get_content_type(self) -> str: 

3085 """Returns the ``Content-Type`` header to be used for this request. 

3086 

3087 .. versionadded:: 3.1 

3088 """ 

3089 assert self.absolute_path is not None 

3090 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3091 # per RFC 6713, use the appropriate type for a gzip compressed file 

3092 if encoding == "gzip": 

3093 return "application/gzip" 

3094 # As of 2015-07-21 there is no bzip2 encoding defined at 

3095 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3096 # So for that (and any other encoding), use octet-stream. 

3097 elif encoding is not None: 

3098 return "application/octet-stream" 

3099 elif mime_type is not None: 

3100 return mime_type 

3101 # if mime_type not detected, use application/octet-stream 

3102 else: 

3103 return "application/octet-stream" 

3104 

3105 def set_extra_headers(self, path: str) -> None: 

3106 """For subclass to add extra headers to the response""" 

3107 pass 

3108 

3109 def get_cache_time( 

3110 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3111 ) -> int: 

3112 """Override to customize cache control behavior. 

3113 

3114 Return a positive number of seconds to make the result 

3115 cacheable for that amount of time or 0 to mark resource as 

3116 cacheable for an unspecified amount of time (subject to 

3117 browser heuristics). 

3118 

3119 By default returns cache expiry of 10 years for resources requested 

3120 with ``v`` argument. 

3121 """ 

3122 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3123 

3124 @classmethod 

3125 def make_static_url( 

3126 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3127 ) -> str: 

3128 """Constructs a versioned url for the given path. 

3129 

3130 This method may be overridden in subclasses (but note that it 

3131 is a class method rather than an instance method). Subclasses 

3132 are only required to implement the signature 

3133 ``make_static_url(cls, settings, path)``; other keyword 

3134 arguments may be passed through `~RequestHandler.static_url` 

3135 but are not standard. 

3136 

3137 ``settings`` is the `Application.settings` dictionary. ``path`` 

3138 is the static path being requested. The url returned should be 

3139 relative to the current host. 

3140 

3141 ``include_version`` determines whether the generated URL should 

3142 include the query string containing the version hash of the 

3143 file corresponding to the given ``path``. 

3144 

3145 """ 

3146 url = settings.get("static_url_prefix", "/static/") + path 

3147 if not include_version: 

3148 return url 

3149 

3150 version_hash = cls.get_version(settings, path) 

3151 if not version_hash: 

3152 return url 

3153 

3154 return f"{url}?v={version_hash}" 

3155 

3156 def parse_url_path(self, url_path: str) -> str: 

3157 """Converts a static URL path into a filesystem path. 

3158 

3159 ``url_path`` is the path component of the URL with 

3160 ``static_url_prefix`` removed. The return value should be 

3161 filesystem path relative to ``static_path``. 

3162 

3163 This is the inverse of `make_static_url`. 

3164 """ 

3165 if os.path.sep != "/": 

3166 url_path = url_path.replace("/", os.path.sep) 

3167 return url_path 

3168 

3169 @classmethod 

3170 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3171 """Generate the version string to be used in static URLs. 

3172 

3173 ``settings`` is the `Application.settings` dictionary and ``path`` 

3174 is the relative location of the requested asset on the filesystem. 

3175 The returned value should be a string, or ``None`` if no version 

3176 could be determined. 

3177 

3178 .. versionchanged:: 3.1 

3179 This method was previously recommended for subclasses to override; 

3180 `get_content_version` is now preferred as it allows the base 

3181 class to handle caching of the result. 

3182 """ 

3183 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3184 return cls._get_cached_version(abs_path) 

3185 

3186 @classmethod 

3187 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3188 with cls._lock: 

3189 hashes = cls._static_hashes 

3190 if abs_path not in hashes: 

3191 try: 

3192 hashes[abs_path] = cls.get_content_version(abs_path) 

3193 except Exception: 

3194 gen_log.error("Could not open static file %r", abs_path) 

3195 hashes[abs_path] = None 

3196 hsh = hashes.get(abs_path) 

3197 if hsh: 

3198 return hsh 

3199 return None 

3200 

3201 

3202class FallbackHandler(RequestHandler): 

3203 """A `RequestHandler` that wraps another HTTP server callback. 

3204 

3205 The fallback is a callable object that accepts an 

3206 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3207 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3208 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3209 usage:: 

3210 

3211 wsgi_app = tornado.wsgi.WSGIContainer( 

3212 django.core.handlers.wsgi.WSGIHandler()) 

3213 application = tornado.web.Application([ 

3214 (r"/foo", FooHandler), 

3215 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3216 ]) 

3217 """ 

3218 

3219 def initialize( 

3220 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3221 ) -> None: 

3222 self.fallback = fallback 

3223 

3224 def prepare(self) -> None: 

3225 self.fallback(self.request) 

3226 self._finished = True 

3227 self.on_finish() 

3228 

3229 

3230class OutputTransform: 

3231 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3232 

3233 Applications are not expected to create their own OutputTransforms 

3234 or interact with them directly; the framework chooses which transforms 

3235 (if any) to apply. 

3236 """ 

3237 

3238 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3239 pass 

3240 

3241 def transform_first_chunk( 

3242 self, 

3243 status_code: int, 

3244 headers: httputil.HTTPHeaders, 

3245 chunk: bytes, 

3246 finishing: bool, 

3247 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3248 return status_code, headers, chunk 

3249 

3250 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3251 return chunk 

3252 

3253 

3254class GZipContentEncoding(OutputTransform): 

3255 """Applies the gzip content encoding to the response. 

3256 

3257 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3258 

3259 .. versionchanged:: 4.0 

3260 Now compresses all mime types beginning with ``text/``, instead 

3261 of just a whitelist. (the whitelist is still used for certain 

3262 non-text mime types). 

3263 """ 

3264 

3265 # Whitelist of compressible mime types (in addition to any types 

3266 # beginning with "text/"). 

3267 CONTENT_TYPES = { 

3268 "application/javascript", 

3269 "application/x-javascript", 

3270 "application/xml", 

3271 "application/atom+xml", 

3272 "application/json", 

3273 "application/xhtml+xml", 

3274 "image/svg+xml", 

3275 } 

3276 # Python's GzipFile defaults to level 9, while most other gzip 

3277 # tools (including gzip itself) default to 6, which is probably a 

3278 # better CPU/size tradeoff. 

3279 GZIP_LEVEL = 6 

3280 # Responses that are too short are unlikely to benefit from gzipping 

3281 # after considering the "Content-Encoding: gzip" header and the header 

3282 # inside the gzip encoding. 

3283 # Note that responses written in multiple chunks will be compressed 

3284 # regardless of size. 

3285 MIN_LENGTH = 1024 

3286 

3287 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3288 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3289 

3290 def _compressible_type(self, ctype: str) -> bool: 

3291 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3292 

3293 def transform_first_chunk( 

3294 self, 

3295 status_code: int, 

3296 headers: httputil.HTTPHeaders, 

3297 chunk: bytes, 

3298 finishing: bool, 

3299 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3300 # TODO: can/should this type be inherited from the superclass? 

3301 if "Vary" in headers: 

3302 headers["Vary"] += ", Accept-Encoding" 

3303 else: 

3304 headers["Vary"] = "Accept-Encoding" 

3305 if self._gzipping: 

3306 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3307 self._gzipping = ( 

3308 self._compressible_type(ctype) 

3309 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3310 and ("Content-Encoding" not in headers) 

3311 ) 

3312 if self._gzipping: 

3313 headers["Content-Encoding"] = "gzip" 

3314 self._gzip_value = BytesIO() 

3315 self._gzip_file = gzip.GzipFile( 

3316 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3317 ) 

3318 chunk = self.transform_chunk(chunk, finishing) 

3319 if "Content-Length" in headers: 

3320 # The original content length is no longer correct. 

3321 # If this is the last (and only) chunk, we can set the new 

3322 # content-length; otherwise we remove it and fall back to 

3323 # chunked encoding. 

3324 if finishing: 

3325 headers["Content-Length"] = str(len(chunk)) 

3326 else: 

3327 del headers["Content-Length"] 

3328 return status_code, headers, chunk 

3329 

3330 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3331 if self._gzipping: 

3332 self._gzip_file.write(chunk) 

3333 if finishing: 

3334 self._gzip_file.close() 

3335 else: 

3336 self._gzip_file.flush() 

3337 chunk = self._gzip_value.getvalue() 

3338 self._gzip_value.truncate(0) 

3339 self._gzip_value.seek(0) 

3340 return chunk 

3341 

3342 

3343def authenticated( 

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

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

3346 """Decorate methods with this to require that the user be logged in. 

3347 

3348 If the user is not logged in, they will be redirected to the configured 

3349 `login url <RequestHandler.get_login_url>`. 

3350 

3351 If you configure a login url with a query parameter, Tornado will 

3352 assume you know what you're doing and use it as-is. If not, it 

3353 will add a `next` parameter so the login page knows where to send 

3354 you once you're logged in. 

3355 """ 

3356 

3357 @functools.wraps(method) 

3358 def wrapper( # type: ignore 

3359 self: RequestHandler, *args, **kwargs 

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

3361 if not self.current_user: 

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

3363 url = self.get_login_url() 

3364 if "?" not in url: 

3365 if urllib.parse.urlsplit(url).scheme: 

3366 # if login url is absolute, make next absolute too 

3367 next_url = self.request.full_url() 

3368 else: 

3369 assert self.request.uri is not None 

3370 next_url = self.request.uri 

3371 url += "?" + urlencode(dict(next=next_url)) 

3372 self.redirect(url) 

3373 return None 

3374 raise HTTPError(403) 

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

3376 

3377 return wrapper 

3378 

3379 

3380class UIModule: 

3381 """A re-usable, modular UI unit on a page. 

3382 

3383 UI modules often execute additional queries, and they can include 

3384 additional CSS and JavaScript that will be included in the output 

3385 page, which is automatically inserted on page render. 

3386 

3387 Subclasses of UIModule must override the `render` method. 

3388 """ 

3389 

3390 def __init__(self, handler: RequestHandler) -> None: 

3391 self.handler = handler 

3392 self.request = handler.request 

3393 self.ui = handler.ui 

3394 self.locale = handler.locale 

3395 

3396 @property 

3397 def current_user(self) -> Any: 

3398 return self.handler.current_user 

3399 

3400 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]: 

3401 """Override in subclasses to return this module's output.""" 

3402 raise NotImplementedError() 

3403 

3404 def embedded_javascript(self) -> Optional[str]: 

3405 """Override to return a JavaScript string 

3406 to be embedded in the page.""" 

3407 return None 

3408 

3409 def javascript_files(self) -> Optional[Iterable[str]]: 

3410 """Override to return a list of JavaScript files needed by this module. 

3411 

3412 If the return values are relative paths, they will be passed to 

3413 `RequestHandler.static_url`; otherwise they will be used as-is. 

3414 """ 

3415 return None 

3416 

3417 def embedded_css(self) -> Optional[str]: 

3418 """Override to return a CSS string 

3419 that will be embedded in the page.""" 

3420 return None 

3421 

3422 def css_files(self) -> Optional[Iterable[str]]: 

3423 """Override to returns a list of CSS files required by this module. 

3424 

3425 If the return values are relative paths, they will be passed to 

3426 `RequestHandler.static_url`; otherwise they will be used as-is. 

3427 """ 

3428 return None 

3429 

3430 def html_head(self) -> Optional[str]: 

3431 """Override to return an HTML string that will be put in the <head/> 

3432 element. 

3433 """ 

3434 return None 

3435 

3436 def html_body(self) -> Optional[str]: 

3437 """Override to return an HTML string that will be put at the end of 

3438 the <body/> element. 

3439 """ 

3440 return None 

3441 

3442 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3443 """Renders a template and returns it as a string.""" 

3444 return self.handler.render_string(path, **kwargs) 

3445 

3446 

3447class _linkify(UIModule): 

3448 def render(self, text: str, **kwargs: Any) -> str: 

3449 return escape.linkify(text, **kwargs) 

3450 

3451 

3452class _xsrf_form_html(UIModule): 

3453 def render(self) -> str: 

3454 return self.handler.xsrf_form_html() 

3455 

3456 

3457class TemplateModule(UIModule): 

3458 """UIModule that simply renders the given template. 

3459 

3460 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3461 but the module version gets its own namespace (with kwargs passed to 

3462 Template()) instead of inheriting the outer template's namespace. 

3463 

3464 Templates rendered through this module also get access to UIModule's 

3465 automatic JavaScript/CSS features. Simply call set_resources 

3466 inside the template and give it keyword arguments corresponding to 

3467 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3468 Note that these resources are output once per template file, not once 

3469 per instantiation of the template, so they must not depend on 

3470 any arguments to the template. 

3471 """ 

3472 

3473 def __init__(self, handler: RequestHandler) -> None: 

3474 super().__init__(handler) 

3475 # keep resources in both a list and a dict to preserve order 

3476 self._resource_list = [] # type: List[Dict[str, Any]] 

3477 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3478 

3479 def render(self, path: str, **kwargs: Any) -> bytes: 

3480 def set_resources(**kwargs) -> str: # type: ignore 

3481 if path not in self._resource_dict: 

3482 self._resource_list.append(kwargs) 

3483 self._resource_dict[path] = kwargs 

3484 else: 

3485 if self._resource_dict[path] != kwargs: 

3486 raise ValueError( 

3487 "set_resources called with different " 

3488 "resources for the same template" 

3489 ) 

3490 return "" 

3491 

3492 return self.render_string(path, set_resources=set_resources, **kwargs) 

3493 

3494 def _get_resources(self, key: str) -> Iterable[str]: 

3495 return (r[key] for r in self._resource_list if key in r) 

3496 

3497 def embedded_javascript(self) -> str: 

3498 return "\n".join(self._get_resources("embedded_javascript")) 

3499 

3500 def javascript_files(self) -> Iterable[str]: 

3501 result = [] 

3502 for f in self._get_resources("javascript_files"): 

3503 if isinstance(f, (unicode_type, bytes)): 

3504 result.append(f) 

3505 else: 

3506 result.extend(f) 

3507 return result 

3508 

3509 def embedded_css(self) -> str: 

3510 return "\n".join(self._get_resources("embedded_css")) 

3511 

3512 def css_files(self) -> Iterable[str]: 

3513 result = [] 

3514 for f in self._get_resources("css_files"): 

3515 if isinstance(f, (unicode_type, bytes)): 

3516 result.append(f) 

3517 else: 

3518 result.extend(f) 

3519 return result 

3520 

3521 def html_head(self) -> str: 

3522 return "".join(self._get_resources("html_head")) 

3523 

3524 def html_body(self) -> str: 

3525 return "".join(self._get_resources("html_body")) 

3526 

3527 

3528class _UIModuleNamespace: 

3529 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3530 

3531 def __init__( 

3532 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3533 ) -> None: 

3534 self.handler = handler 

3535 self.ui_modules = ui_modules 

3536 

3537 def __getitem__(self, key: str) -> Callable[..., str]: 

3538 return self.handler._ui_module(key, self.ui_modules[key]) 

3539 

3540 def __getattr__(self, key: str) -> Callable[..., str]: 

3541 try: 

3542 return self[key] 

3543 except KeyError as e: 

3544 raise AttributeError(str(e)) 

3545 

3546 

3547def create_signed_value( 

3548 secret: _CookieSecretTypes, 

3549 name: str, 

3550 value: Union[str, bytes], 

3551 version: Optional[int] = None, 

3552 clock: Optional[Callable[[], float]] = None, 

3553 key_version: Optional[int] = None, 

3554) -> bytes: 

3555 if version is None: 

3556 version = DEFAULT_SIGNED_VALUE_VERSION 

3557 if clock is None: 

3558 clock = time.time 

3559 

3560 timestamp = utf8(str(int(clock()))) 

3561 value = base64.b64encode(utf8(value)) 

3562 if version == 1: 

3563 assert not isinstance(secret, dict) 

3564 signature = _create_signature_v1(secret, name, value, timestamp) 

3565 value = b"|".join([value, timestamp, signature]) 

3566 return value 

3567 elif version == 2: 

3568 # The v2 format consists of a version number and a series of 

3569 # length-prefixed fields "%d:%s", the last of which is a 

3570 # signature, all separated by pipes. All numbers are in 

3571 # decimal format with no leading zeros. The signature is an 

3572 # HMAC-SHA256 of the whole string up to that point, including 

3573 # the final pipe. 

3574 # 

3575 # The fields are: 

3576 # - format version (i.e. 2; no length prefix) 

3577 # - key version (integer, default is 0) 

3578 # - timestamp (integer seconds since epoch) 

3579 # - name (not encoded; assumed to be ~alphanumeric) 

3580 # - value (base64-encoded) 

3581 # - signature (hex-encoded; no length prefix) 

3582 def format_field(s: Union[str, bytes]) -> bytes: 

3583 return utf8("%d:" % len(s)) + utf8(s) 

3584 

3585 to_sign = b"|".join( 

3586 [ 

3587 b"2", 

3588 format_field(str(key_version or 0)), 

3589 format_field(timestamp), 

3590 format_field(name), 

3591 format_field(value), 

3592 b"", 

3593 ] 

3594 ) 

3595 

3596 if isinstance(secret, dict): 

3597 assert ( 

3598 key_version is not None 

3599 ), "Key version must be set when sign key dict is used" 

3600 assert version >= 2, "Version must be at least 2 for key version support" 

3601 secret = secret[key_version] 

3602 

3603 signature = _create_signature_v2(secret, to_sign) 

3604 return to_sign + signature 

3605 else: 

3606 raise ValueError("Unsupported version %d" % version) 

3607 

3608 

3609# A leading version number in decimal 

3610# with no leading zeros, followed by a pipe. 

3611_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3612 

3613 

3614def _get_version(value: bytes) -> int: 

3615 # Figures out what version value is. Version 1 did not include an 

3616 # explicit version field and started with arbitrary base64 data, 

3617 # which makes this tricky. 

3618 m = _signed_value_version_re.match(value) 

3619 if m is None: 

3620 version = 1 

3621 else: 

3622 try: 

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

3624 if version > 999: 

3625 # Certain payloads from the version-less v1 format may 

3626 # be parsed as valid integers. Due to base64 padding 

3627 # restrictions, this can only happen for numbers whose 

3628 # length is a multiple of 4, so we can treat all 

3629 # numbers up to 999 as versions, and for the rest we 

3630 # fall back to v1 format. 

3631 version = 1 

3632 except ValueError: 

3633 version = 1 

3634 return version 

3635 

3636 

3637def decode_signed_value( 

3638 secret: _CookieSecretTypes, 

3639 name: str, 

3640 value: Union[None, str, bytes], 

3641 max_age_days: float = 31, 

3642 clock: Optional[Callable[[], float]] = None, 

3643 min_version: Optional[int] = None, 

3644) -> Optional[bytes]: 

3645 if clock is None: 

3646 clock = time.time 

3647 if min_version is None: 

3648 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3649 if min_version > 2: 

3650 raise ValueError("Unsupported min_version %d" % min_version) 

3651 if not value: 

3652 return None 

3653 

3654 value = utf8(value) 

3655 version = _get_version(value) 

3656 

3657 if version < min_version: 

3658 return None 

3659 if version == 1: 

3660 assert not isinstance(secret, dict) 

3661 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3662 elif version == 2: 

3663 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3664 else: 

3665 return None 

3666 

3667 

3668def _decode_signed_value_v1( 

3669 secret: Union[str, bytes], 

3670 name: str, 

3671 value: bytes, 

3672 max_age_days: float, 

3673 clock: Callable[[], float], 

3674) -> Optional[bytes]: 

3675 parts = utf8(value).split(b"|") 

3676 if len(parts) != 3: 

3677 return None 

3678 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3679 if not hmac.compare_digest(parts[2], signature): 

3680 gen_log.warning("Invalid cookie signature %r", value) 

3681 return None 

3682 timestamp = int(parts[1]) 

3683 if timestamp < clock() - max_age_days * 86400: 

3684 gen_log.warning("Expired cookie %r", value) 

3685 return None 

3686 if timestamp > clock() + 31 * 86400: 

3687 # _cookie_signature does not hash a delimiter between the 

3688 # parts of the cookie, so an attacker could transfer trailing 

3689 # digits from the payload to the timestamp without altering the 

3690 # signature. For backwards compatibility, sanity-check timestamp 

3691 # here instead of modifying _cookie_signature. 

3692 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3693 return None 

3694 if parts[1].startswith(b"0"): 

3695 gen_log.warning("Tampered cookie %r", value) 

3696 return None 

3697 try: 

3698 return base64.b64decode(parts[0]) 

3699 except Exception: 

3700 return None 

3701 

3702 

3703def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3704 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3705 length, _, rest = s.partition(b":") 

3706 n = int(length) 

3707 field_value = rest[:n] 

3708 # In python 3, indexing bytes returns small integers; we must 

3709 # use a slice to get a byte string as in python 2. 

3710 if rest[n : n + 1] != b"|": 

3711 raise ValueError("malformed v2 signed value field") 

3712 rest = rest[n + 1 :] 

3713 return field_value, rest 

3714 

3715 rest = value[2:] # remove version number 

3716 key_version, rest = _consume_field(rest) 

3717 timestamp, rest = _consume_field(rest) 

3718 name_field, rest = _consume_field(rest) 

3719 value_field, passed_sig = _consume_field(rest) 

3720 return int(key_version), timestamp, name_field, value_field, passed_sig 

3721 

3722 

3723def _decode_signed_value_v2( 

3724 secret: _CookieSecretTypes, 

3725 name: str, 

3726 value: bytes, 

3727 max_age_days: float, 

3728 clock: Callable[[], float], 

3729) -> Optional[bytes]: 

3730 try: 

3731 ( 

3732 key_version, 

3733 timestamp_bytes, 

3734 name_field, 

3735 value_field, 

3736 passed_sig, 

3737 ) = _decode_fields_v2(value) 

3738 except ValueError: 

3739 return None 

3740 signed_string = value[: -len(passed_sig)] 

3741 

3742 if isinstance(secret, dict): 

3743 try: 

3744 secret = secret[key_version] 

3745 except KeyError: 

3746 return None 

3747 

3748 expected_sig = _create_signature_v2(secret, signed_string) 

3749 if not hmac.compare_digest(passed_sig, expected_sig): 

3750 return None 

3751 if name_field != utf8(name): 

3752 return None 

3753 timestamp = int(timestamp_bytes) 

3754 if timestamp < clock() - max_age_days * 86400: 

3755 # The signature has expired. 

3756 return None 

3757 try: 

3758 return base64.b64decode(value_field) 

3759 except Exception: 

3760 return None 

3761 

3762 

3763def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3764 value = utf8(value) 

3765 version = _get_version(value) 

3766 if version < 2: 

3767 return None 

3768 try: 

3769 key_version, _, _, _, _ = _decode_fields_v2(value) 

3770 except ValueError: 

3771 return None 

3772 

3773 return key_version 

3774 

3775 

3776def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3777 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3778 for part in parts: 

3779 hash.update(utf8(part)) 

3780 return utf8(hash.hexdigest()) 

3781 

3782 

3783def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3784 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3785 hash.update(utf8(s)) 

3786 return utf8(hash.hexdigest()) 

3787 

3788 

3789def is_absolute(path: str) -> bool: 

3790 return any(path.startswith(x) for x in ["/", "http:", "https:"])