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

1464 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 (for example, the "Not Found" in ``HTTP/1.1 404 Not Found``). 

363 Normally determined automatically from `http.client.responses`; this 

364 argument should only be used if you need to use a non-standard 

365 status code. 

366 

367 .. versionchanged:: 5.0 

368 

369 No longer validates that the response code is in 

370 `http.client.responses`. 

371 """ 

372 self._status_code = status_code 

373 if reason is not None: 

374 if "<" in reason or not httputil._ABNF.reason_phrase.fullmatch(reason): 

375 # Logically this would be better as an exception, but this method 

376 # is called on error-handling paths that would need some refactoring 

377 # to tolerate internal errors cleanly. 

378 # 

379 # The check for "<" is a defense-in-depth against XSS attacks (we also 

380 # escape the reason when rendering error pages). 

381 reason = "Unknown" 

382 self._reason = escape.native_str(reason) 

383 else: 

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

385 

386 def get_status(self) -> int: 

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

388 return self._status_code 

389 

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

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

392 

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

394 are formatted according to the HTTP specification for the 

395 ``Date`` header). 

396 

397 """ 

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

399 

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

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

402 

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

404 to return multiple values for the same header. 

405 """ 

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

407 

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

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

410 

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

412 set by `add_header`. 

413 """ 

414 if name in self._headers: 

415 del self._headers[name] 

416 

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

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

419 

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

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

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

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

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

425 if isinstance(value, str): 

426 retval = value 

427 elif isinstance(value, bytes): 

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

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

430 retval = value.decode("latin1") 

431 elif isinstance(value, numbers.Integral): 

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

433 return str(value) 

434 elif isinstance(value, datetime.datetime): 

435 return httputil.format_timestamp(value) 

436 else: 

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

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

439 # additional headers or split the request. 

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

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

442 return retval 

443 

444 @overload 

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

446 pass 

447 

448 @overload 

449 def get_argument( # noqa: F811 

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

451 ) -> str: 

452 pass 

453 

454 @overload 

455 def get_argument( # noqa: F811 

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

457 ) -> Optional[str]: 

458 pass 

459 

460 def get_argument( # noqa: F811 

461 self, 

462 name: str, 

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

464 strip: bool = True, 

465 ) -> Optional[str]: 

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

467 

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

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

470 

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

472 last value. 

473 

474 This method searches both the query and body arguments. 

475 """ 

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

477 

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

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

480 

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

482 

483 This method searches both the query and body arguments. 

484 """ 

485 

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

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

488 # `get_argument`.) 

489 assert isinstance(strip, bool) 

490 

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

492 

493 @overload 

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

495 pass 

496 

497 @overload 

498 def get_body_argument( # noqa: F811 

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

500 ) -> str: 

501 pass 

502 

503 @overload 

504 def get_body_argument( # noqa: F811 

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

506 ) -> Optional[str]: 

507 pass 

508 

509 def get_body_argument( # noqa: F811 

510 self, 

511 name: str, 

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

513 strip: bool = True, 

514 ) -> Optional[str]: 

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

516 from the request body. 

517 

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

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

520 

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

522 last value. 

523 

524 .. versionadded:: 3.2 

525 """ 

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

527 

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

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

530 

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

532 

533 .. versionadded:: 3.2 

534 """ 

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

536 

537 @overload 

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

539 pass 

540 

541 @overload 

542 def get_query_argument( # noqa: F811 

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

544 ) -> str: 

545 pass 

546 

547 @overload 

548 def get_query_argument( # noqa: F811 

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

550 ) -> Optional[str]: 

551 pass 

552 

553 def get_query_argument( # noqa: F811 

554 self, 

555 name: str, 

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

557 strip: bool = True, 

558 ) -> Optional[str]: 

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

560 from the request query string. 

561 

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

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

564 

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

566 last value. 

567 

568 .. versionadded:: 3.2 

569 """ 

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

571 

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

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

574 

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

576 

577 .. versionadded:: 3.2 

578 """ 

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

580 

581 def _get_argument( 

582 self, 

583 name: str, 

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

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

586 strip: bool = True, 

587 ) -> Optional[str]: 

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

589 if not args: 

590 if isinstance(default, _ArgDefaultMarker): 

591 raise MissingArgumentError(name) 

592 return default 

593 return args[-1] 

594 

595 def _get_arguments( 

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

597 ) -> List[str]: 

598 values = [] 

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

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

601 if isinstance(s, unicode_type): 

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

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

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

605 if strip: 

606 s = s.strip() 

607 values.append(s) 

608 return values 

609 

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

611 """Decodes an argument from the request. 

612 

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

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

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

616 

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

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

619 

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

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

622 """ 

623 try: 

624 return _unicode(value) 

625 except UnicodeDecodeError: 

626 raise HTTPError( 

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

628 ) 

629 

630 @property 

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

632 """An alias for 

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

634 return self.request.cookies 

635 

636 @overload 

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

638 pass 

639 

640 @overload 

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

642 pass 

643 

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

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

646 

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

648 

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

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

651 handler. 

652 """ 

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

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

655 return default 

656 

657 def set_cookie( 

658 self, 

659 name: str, 

660 value: Union[str, bytes], 

661 domain: Optional[str] = None, 

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

663 path: str = "/", 

664 expires_days: Optional[float] = None, 

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

666 *, 

667 max_age: Optional[int] = None, 

668 httponly: bool = False, 

669 secure: bool = False, 

670 samesite: Optional[str] = None, 

671 **kwargs: Any, 

672 ) -> None: 

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

674 

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

676 they are not present until the next request. 

677 

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

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

680 for more information. 

681 

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

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

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

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

686 is used). 

687 

688 .. deprecated:: 6.3 

689 Keyword arguments are currently accepted case-insensitively. 

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

691 arguments. 

692 """ 

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

694 name = escape.native_str(name) 

695 value = escape.native_str(value) 

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

697 # Don't let us accidentally inject bad stuff 

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

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

700 self._new_cookie = ( 

701 http.cookies.SimpleCookie() 

702 ) # type: http.cookies.SimpleCookie 

703 if name in self._new_cookie: 

704 del self._new_cookie[name] 

705 self._new_cookie[name] = value 

706 morsel = self._new_cookie[name] 

707 if domain: 

708 morsel["domain"] = domain 

709 if expires_days is not None and not expires: 

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

711 days=expires_days 

712 ) 

713 if expires: 

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

715 if path: 

716 morsel["path"] = path 

717 if max_age: 

718 # Note change from _ to -. 

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

720 if httponly: 

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

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

723 morsel["httponly"] = True 

724 if secure: 

725 morsel["secure"] = True 

726 if samesite: 

727 morsel["samesite"] = samesite 

728 if kwargs: 

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

730 # kwargs for backwards compatibility until we can remove deprecated 

731 # features. 

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

733 morsel[k] = v 

734 warnings.warn( 

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

736 "(should be lowercase)", 

737 DeprecationWarning, 

738 ) 

739 

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

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

742 

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

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

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

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

747 arguments are ignored. 

748 

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

750 seen until the following request. 

751 

752 .. versionchanged:: 6.3 

753 

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

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

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

757 """ 

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

759 if excluded_arg in kwargs: 

760 raise TypeError( 

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

762 ) 

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

764 days=365 

765 ) 

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

767 

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

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

770 

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

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

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

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

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

776 when setting cookies. 

777 

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

779 until the following request. 

780 

781 .. versionchanged:: 3.2 

782 

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

784 

785 .. versionchanged:: 6.3 

786 

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

788 

789 .. deprecated:: 6.3 

790 

791 The increasingly complex rules governing cookies have made it 

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

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

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

795 """ 

796 for name in self.request.cookies: 

797 self.clear_cookie(name, **kwargs) 

798 

799 def set_signed_cookie( 

800 self, 

801 name: str, 

802 value: Union[str, bytes], 

803 expires_days: Optional[float] = 30, 

804 version: Optional[int] = None, 

805 **kwargs: Any, 

806 ) -> None: 

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

808 

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

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

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

812 

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

814 

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

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

817 parameter to `get_signed_cookie`. 

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

819 

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

821 strings (unlike regular cookies) 

822 

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

824 seen until the following request. 

825 

826 .. versionchanged:: 3.2.1 

827 

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

829 and made it the default. 

830 

831 .. versionchanged:: 6.3 

832 

833 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

836 """ 

837 self.set_cookie( 

838 name, 

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

840 expires_days=expires_days, 

841 **kwargs, 

842 ) 

843 

844 set_secure_cookie = set_signed_cookie 

845 

846 def create_signed_value( 

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

848 ) -> bytes: 

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

850 

851 Normally used via set_signed_cookie, but provided as a separate 

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

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

854 

855 .. versionchanged:: 3.2.1 

856 

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

858 and made it the default. 

859 """ 

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

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

862 key_version = None 

863 if isinstance(secret, dict): 

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

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

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

867 

868 return create_signed_value( 

869 secret, name, value, version=version, key_version=key_version 

870 ) 

871 

872 def get_signed_cookie( 

873 self, 

874 name: str, 

875 value: Optional[str] = None, 

876 max_age_days: float = 31, 

877 min_version: Optional[int] = None, 

878 ) -> Optional[bytes]: 

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

880 

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

882 `get_cookie`). 

883 

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

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

886 `set_signed_cookie` in this handler. 

887 

888 .. versionchanged:: 3.2.1 

889 

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

891 both versions 1 and 2 are accepted by default. 

892 

893 .. versionchanged:: 6.3 

894 

895 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

898 

899 """ 

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

901 if value is None: 

902 value = self.get_cookie(name) 

903 return decode_signed_value( 

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

905 name, 

906 value, 

907 max_age_days=max_age_days, 

908 min_version=min_version, 

909 ) 

910 

911 get_secure_cookie = get_signed_cookie 

912 

913 def get_signed_cookie_key_version( 

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

915 ) -> Optional[int]: 

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

917 

918 The version is returned as int. 

919 

920 .. versionchanged:: 6.3 

921 

922 Renamed from ``get_secure_cookie_key_version`` to 

923 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

925 remains as an alias. 

926 

927 """ 

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

929 if value is None: 

930 value = self.get_cookie(name) 

931 if value is None: 

932 return None 

933 return get_signature_key_version(value) 

934 

935 get_secure_cookie_key_version = get_signed_cookie_key_version 

936 

937 def redirect( 

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

939 ) -> None: 

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

941 

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

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

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

945 The default is 302 (temporary). 

946 """ 

947 if self._headers_written: 

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

949 if status is None: 

950 status = 301 if permanent else 302 

951 else: 

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

953 self.set_status(status) 

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

955 self.finish() 

956 

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

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

959 

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

961 

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

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

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

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

966 

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

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

969 wrapped in a dictionary. More details at 

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

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

972 """ 

973 if self._finished: 

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

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

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

977 if isinstance(chunk, list): 

978 message += ( 

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

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

981 ) 

982 raise TypeError(message) 

983 if isinstance(chunk, dict): 

984 chunk = escape.json_encode(chunk) 

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

986 chunk = utf8(chunk) 

987 self._write_buffer.append(chunk) 

988 

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

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

991 

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

993 after it. 

994 

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

996 Awaiting this `.Future` is optional. 

997 

998 .. versionchanged:: 5.1 

999 

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

1001 """ 

1002 if self._finished: 

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

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

1005 

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

1007 js_embed = [] 

1008 js_files = [] 

1009 css_embed = [] 

1010 css_files = [] 

1011 html_heads = [] 

1012 html_bodies = [] 

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

1014 embed_part = module.embedded_javascript() 

1015 if embed_part: 

1016 js_embed.append(utf8(embed_part)) 

1017 file_part = module.javascript_files() 

1018 if file_part: 

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

1020 js_files.append(_unicode(file_part)) 

1021 else: 

1022 js_files.extend(file_part) 

1023 embed_part = module.embedded_css() 

1024 if embed_part: 

1025 css_embed.append(utf8(embed_part)) 

1026 file_part = module.css_files() 

1027 if file_part: 

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

1029 css_files.append(_unicode(file_part)) 

1030 else: 

1031 css_files.extend(file_part) 

1032 head_part = module.html_head() 

1033 if head_part: 

1034 html_heads.append(utf8(head_part)) 

1035 body_part = module.html_body() 

1036 if body_part: 

1037 html_bodies.append(utf8(body_part)) 

1038 

1039 if js_files: 

1040 # Maintain order of JavaScript files given by modules 

1041 js = self.render_linked_js(js_files) 

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

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

1044 if js_embed: 

1045 js_bytes = self.render_embed_js(js_embed) 

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

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

1048 if css_files: 

1049 css = self.render_linked_css(css_files) 

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

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

1052 if css_embed: 

1053 css_bytes = self.render_embed_css(css_embed) 

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

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

1056 if html_heads: 

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

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

1059 if html_bodies: 

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

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

1062 return self.finish(html) 

1063 

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

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

1066 rendered webpage. 

1067 

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

1069 """ 

1070 paths = [] 

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

1072 

1073 for path in js_files: 

1074 if not is_absolute(path): 

1075 path = self.static_url(path) 

1076 if path not in unique_paths: 

1077 paths.append(path) 

1078 unique_paths.add(path) 

1079 

1080 return "".join( 

1081 '<script src="' 

1082 + escape.xhtml_escape(p) 

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

1084 for p in paths 

1085 ) 

1086 

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

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

1089 rendered webpage. 

1090 

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

1092 """ 

1093 return ( 

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

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

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

1097 ) 

1098 

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

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

1101 rendered webpage. 

1102 

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

1104 """ 

1105 paths = [] 

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

1107 

1108 for path in css_files: 

1109 if not is_absolute(path): 

1110 path = self.static_url(path) 

1111 if path not in unique_paths: 

1112 paths.append(path) 

1113 unique_paths.add(path) 

1114 

1115 return "".join( 

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

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

1118 for p in paths 

1119 ) 

1120 

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

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

1123 rendered webpage. 

1124 

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

1126 """ 

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

1128 

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

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

1131 

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

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

1134 """ 

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

1136 template_path = self.get_template_path() 

1137 if not template_path: 

1138 frame = sys._getframe(0) 

1139 web_file = frame.f_code.co_filename 

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

1141 frame = frame.f_back 

1142 assert frame.f_code.co_filename is not None 

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

1144 with RequestHandler._template_loader_lock: 

1145 if template_path not in RequestHandler._template_loaders: 

1146 loader = self.create_template_loader(template_path) 

1147 RequestHandler._template_loaders[template_path] = loader 

1148 else: 

1149 loader = RequestHandler._template_loaders[template_path] 

1150 t = loader.load(template_name) 

1151 namespace = self.get_template_namespace() 

1152 namespace.update(kwargs) 

1153 return t.generate(**namespace) 

1154 

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

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

1157 

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

1159 

1160 The results of this method will be combined with additional 

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

1162 to `render` or `render_string`. 

1163 """ 

1164 namespace = dict( 

1165 handler=self, 

1166 request=self.request, 

1167 current_user=self.current_user, 

1168 locale=self.locale, 

1169 _=self.locale.translate, 

1170 pgettext=self.locale.pgettext, 

1171 static_url=self.static_url, 

1172 xsrf_form_html=self.xsrf_form_html, 

1173 reverse_url=self.reverse_url, 

1174 ) 

1175 namespace.update(self.ui) 

1176 return namespace 

1177 

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

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

1180 

1181 May be overridden by subclasses. By default returns a 

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

1183 ``autoescape`` and ``template_whitespace`` application 

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

1185 supplied, uses that instead. 

1186 """ 

1187 settings = self.application.settings 

1188 if "template_loader" in settings: 

1189 return settings["template_loader"] 

1190 kwargs = {} 

1191 if "autoescape" in settings: 

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

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

1194 kwargs["autoescape"] = settings["autoescape"] 

1195 if "template_whitespace" in settings: 

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

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

1198 

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

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

1201 

1202 .. versionchanged:: 4.0 

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

1204 

1205 .. versionchanged:: 6.0 

1206 

1207 The ``callback`` argument was removed. 

1208 """ 

1209 assert self.request.connection is not None 

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

1211 self._write_buffer = [] 

1212 if not self._headers_written: 

1213 self._headers_written = True 

1214 for transform in self._transforms: 

1215 assert chunk is not None 

1216 ( 

1217 self._status_code, 

1218 self._headers, 

1219 chunk, 

1220 ) = transform.transform_first_chunk( 

1221 self._status_code, self._headers, chunk, include_footers 

1222 ) 

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

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

1225 chunk = b"" 

1226 

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

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

1229 # is sent). 

1230 if hasattr(self, "_new_cookie"): 

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

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

1233 

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

1235 return self.request.connection.write_headers( 

1236 start_line, self._headers, chunk 

1237 ) 

1238 else: 

1239 for transform in self._transforms: 

1240 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1244 else: 

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

1246 future.set_result(None) 

1247 return future 

1248 

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

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

1251 

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

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

1254 

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

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

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

1258 data can be sent. 

1259 

1260 .. versionchanged:: 5.1 

1261 

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

1263 """ 

1264 if self._finished: 

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

1266 

1267 if chunk is not None: 

1268 self.write(chunk) 

1269 

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

1271 # we have not flushed any content yet. 

1272 if not self._headers_written: 

1273 if ( 

1274 self._status_code == 200 

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

1276 and "Etag" not in self._headers 

1277 ): 

1278 self.set_etag_header() 

1279 if self.check_etag_header(): 

1280 self._write_buffer = [] 

1281 self.set_status(304) 

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

1283 assert not self._write_buffer, ( 

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

1285 ) 

1286 self._clear_representation_headers() 

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

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

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

1290 

1291 assert self.request.connection is not None 

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

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

1294 # garbage collection of the RequestHandler when there 

1295 # are keepalive connections) 

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

1297 

1298 future = self.flush(include_footers=True) 

1299 self.request.connection.finish() 

1300 self._log() 

1301 self._finished = True 

1302 self.on_finish() 

1303 self._break_cycles() 

1304 return future 

1305 

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

1307 """Take control of the underlying stream. 

1308 

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

1310 further HTTP processing. Intended for implementing protocols 

1311 like websockets that tunnel over an HTTP handshake. 

1312 

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

1314 

1315 .. versionadded:: 5.1 

1316 """ 

1317 self._finished = True 

1318 # TODO: add detach to HTTPConnection? 

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

1320 

1321 def _break_cycles(self) -> None: 

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

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

1324 self.ui = None # type: ignore 

1325 

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

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

1328 

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

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

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

1332 and replaced with the error page. 

1333 

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

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

1336 """ 

1337 if self._headers_written: 

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

1339 if not self._finished: 

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

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

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

1343 # socket. 

1344 try: 

1345 self.finish() 

1346 except Exception: 

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

1348 return 

1349 self.clear() 

1350 

1351 reason = kwargs.get("reason") 

1352 if "exc_info" in kwargs: 

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

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

1355 reason = exception.reason 

1356 self.set_status(status_code, reason=reason) 

1357 try: 

1358 if status_code != 304: 

1359 self.write_error(status_code, **kwargs) 

1360 except Exception: 

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

1362 if not self._finished: 

1363 self.finish() 

1364 

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

1366 """Override to implement custom error pages. 

1367 

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

1369 to produce output as usual. 

1370 

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

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

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

1374 the "current" exception for purposes of methods like 

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

1376 """ 

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

1378 # in debug mode, try to send a traceback 

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

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

1381 self.write(line) 

1382 self.finish() 

1383 else: 

1384 self.finish( 

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

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

1387 % {"code": status_code, "message": escape.xhtml_escape(self._reason)} 

1388 ) 

1389 

1390 @property 

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

1392 """The locale for the current session. 

1393 

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

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

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

1397 header. 

1398 

1399 .. versionchanged: 4.1 

1400 Added a property setter. 

1401 """ 

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

1403 loc = self.get_user_locale() 

1404 if loc is not None: 

1405 self._locale = loc 

1406 else: 

1407 self._locale = self.get_browser_locale() 

1408 assert self._locale 

1409 return self._locale 

1410 

1411 @locale.setter 

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

1413 self._locale = value 

1414 

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

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

1417 

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

1419 

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

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

1422 """ 

1423 return None 

1424 

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

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

1427 

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

1429 """ 

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

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

1432 locales = [] 

1433 for language in languages: 

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

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

1436 try: 

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

1438 if score < 0: 

1439 raise ValueError() 

1440 except (ValueError, TypeError): 

1441 score = 0.0 

1442 else: 

1443 score = 1.0 

1444 if score > 0: 

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

1446 if locales: 

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

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

1449 return locale.get(*codes) 

1450 return locale.get(default) 

1451 

1452 @property 

1453 def current_user(self) -> Any: 

1454 """The authenticated user for this request. 

1455 

1456 This is set in one of two ways: 

1457 

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

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

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

1461 and is cached for future access:: 

1462 

1463 def get_current_user(self): 

1464 user_cookie = self.get_signed_cookie("user") 

1465 if user_cookie: 

1466 return json.loads(user_cookie) 

1467 return None 

1468 

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

1470 `prepare()`:: 

1471 

1472 @gen.coroutine 

1473 def prepare(self): 

1474 user_id_cookie = self.get_signed_cookie("user_id") 

1475 if user_id_cookie: 

1476 self.current_user = yield load_user(user_id_cookie) 

1477 

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

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

1480 asynchronous operations. 

1481 

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

1483 """ 

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

1485 self._current_user = self.get_current_user() 

1486 return self._current_user 

1487 

1488 @current_user.setter 

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

1490 self._current_user = value 

1491 

1492 def get_current_user(self) -> Any: 

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

1494 

1495 This method may not be a coroutine. 

1496 """ 

1497 return None 

1498 

1499 def get_login_url(self) -> str: 

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

1501 

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

1503 """ 

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

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

1506 

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

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

1509 

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

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

1512 """ 

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

1514 

1515 @property 

1516 def xsrf_token(self) -> bytes: 

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

1518 

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

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

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

1522 as a potential forgery. 

1523 

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

1525 

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

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

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

1529 UTF-8. 

1530 

1531 .. versionchanged:: 3.2.2 

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

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

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

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

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

1537 unless the ``xsrf_cookie_version`` `Application` setting is 

1538 set to 1. 

1539 

1540 .. versionchanged:: 4.3 

1541 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1542 used to supply additional cookie options (which will be 

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

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

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

1546 ``_xsrf`` cookie. 

1547 """ 

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

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

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

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

1552 if output_version == 1: 

1553 self._xsrf_token = binascii.b2a_hex(token) 

1554 elif output_version == 2: 

1555 mask = os.urandom(4) 

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

1557 [ 

1558 b"2", 

1559 binascii.b2a_hex(mask), 

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

1561 utf8(str(int(timestamp))), 

1562 ] 

1563 ) 

1564 else: 

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

1566 if version is None: 

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

1568 cookie_kwargs["expires_days"] = 30 

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

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

1571 return self._xsrf_token 

1572 

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

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

1575 

1576 The raw_xsrf_token is a tuple containing: 

1577 

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

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

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

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

1582 for version 1 cookies) 

1583 """ 

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

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

1586 cookie = self.get_cookie(cookie_name) 

1587 if cookie: 

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

1589 else: 

1590 version, token, timestamp = None, None, None 

1591 if token is None: 

1592 version = None 

1593 token = os.urandom(16) 

1594 timestamp = time.time() 

1595 assert token is not None 

1596 assert timestamp is not None 

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

1598 return self._raw_xsrf_token 

1599 

1600 def _decode_xsrf_token( 

1601 self, cookie: str 

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

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

1604 _get_raw_xsrf_token. 

1605 """ 

1606 

1607 try: 

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

1609 

1610 if m: 

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

1612 if version == 2: 

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

1614 

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

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

1617 timestamp = int(timestamp_str) 

1618 return version, token, timestamp 

1619 else: 

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

1621 raise Exception("Unknown xsrf cookie version") 

1622 else: 

1623 version = 1 

1624 try: 

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

1626 except (binascii.Error, TypeError): 

1627 token = utf8(cookie) 

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

1629 timestamp = int(time.time()) 

1630 return (version, token, timestamp) 

1631 except Exception: 

1632 # Catch exceptions and return nothing instead of failing. 

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

1634 return None, None, None 

1635 

1636 def check_xsrf_cookie(self) -> None: 

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

1638 

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

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

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

1642 reject the form submission as a potential forgery. 

1643 

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

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

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

1647 

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

1649 

1650 .. versionchanged:: 3.2.2 

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

1652 supported. 

1653 """ 

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

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

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

1657 # information please see 

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

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

1660 input_token = ( 

1661 self.get_argument("_xsrf", None) 

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

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

1664 ) 

1665 if not input_token: 

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

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

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

1669 if not token: 

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

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

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

1673 

1674 def xsrf_form_html(self) -> str: 

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

1676 

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

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

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

1680 HTML within all of your HTML forms. 

1681 

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

1683 xsrf_form_html() %}`` 

1684 

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

1686 """ 

1687 return ( 

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

1689 + escape.xhtml_escape(self.xsrf_token) 

1690 + '"/>' 

1691 ) 

1692 

1693 def static_url( 

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

1695 ) -> str: 

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

1697 

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

1699 application (which specifies the root directory of your static 

1700 files). 

1701 

1702 This method returns a versioned url (by default appending 

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

1704 cached indefinitely. This can be disabled by passing 

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

1706 other static file implementations are not required to support 

1707 this, but they may support other options). 

1708 

1709 By default this method returns URLs relative to the current 

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

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

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

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

1714 

1715 """ 

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

1717 get_url = self.settings.get( 

1718 "static_handler_class", StaticFileHandler 

1719 ).make_static_url 

1720 

1721 if include_host is None: 

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

1723 

1724 if include_host: 

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

1726 else: 

1727 base = "" 

1728 

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

1730 

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

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

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

1734 raise Exception( 

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

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

1737 ) 

1738 

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

1740 """Alias for `Application.reverse_url`.""" 

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

1742 

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

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

1745 

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

1747 

1748 May be overridden to provide custom etag implementations, 

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

1750 """ 

1751 hasher = hashlib.sha1() 

1752 for part in self._write_buffer: 

1753 hasher.update(part) 

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

1755 

1756 def set_etag_header(self) -> None: 

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

1758 

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

1760 

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

1762 """ 

1763 etag = self.compute_etag() 

1764 if etag is not None: 

1765 self.set_header("Etag", etag) 

1766 

1767 def check_etag_header(self) -> bool: 

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

1769 

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

1771 returned. For example:: 

1772 

1773 self.set_etag_header() 

1774 if self.check_etag_header(): 

1775 self.set_status(304) 

1776 return 

1777 

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

1779 but may be called earlier for applications that override 

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

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

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

1783 """ 

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

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

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

1787 etags = re.findall( 

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

1789 ) 

1790 if not computed_etag or not etags: 

1791 return False 

1792 

1793 match = False 

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

1795 match = True 

1796 else: 

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

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

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

1800 

1801 for etag in etags: 

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

1803 match = True 

1804 break 

1805 return match 

1806 

1807 async def _execute( 

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

1809 ) -> None: 

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

1811 self._transforms = transforms 

1812 try: 

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

1814 raise HTTPError(405) 

1815 

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

1817 if not _has_stream_request_body(self.__class__): 

1818 try: 

1819 self.request._parse_body() 

1820 except httputil.HTTPInputError as e: 

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

1822 

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

1824 self.path_kwargs = { 

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

1826 } 

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

1828 # the proper cookie 

1829 if self.request.method not in ( 

1830 "GET", 

1831 "HEAD", 

1832 "OPTIONS", 

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

1834 self.check_xsrf_cookie() 

1835 

1836 result = self.prepare() 

1837 if result is not None: 

1838 result = await result # type: ignore 

1839 if self._prepared_future is not None: 

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

1841 # and are ready for the body to arrive. 

1842 future_set_result_unless_cancelled(self._prepared_future, None) 

1843 if self._finished: 

1844 return 

1845 

1846 if _has_stream_request_body(self.__class__): 

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

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

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

1850 # instead. 

1851 try: 

1852 await self.request._body_future 

1853 except iostream.StreamClosedError: 

1854 return 

1855 

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

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

1858 if result is not None: 

1859 result = await result 

1860 if self._auto_finish and not self._finished: 

1861 self.finish() 

1862 except Exception as e: 

1863 try: 

1864 self._handle_request_exception(e) 

1865 except Exception: 

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

1867 finally: 

1868 # Unset result to avoid circular references 

1869 result = None 

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

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

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

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

1874 self._prepared_future.set_result(None) 

1875 

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

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

1878 

1879 Requires the `.stream_request_body` decorator. 

1880 

1881 May be a coroutine for flow control. 

1882 """ 

1883 raise NotImplementedError() 

1884 

1885 def _log(self) -> None: 

1886 """Logs the current request. 

1887 

1888 Sort of deprecated since this functionality was moved to the 

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

1890 that have overridden this method. 

1891 """ 

1892 self.application.log_request(self) 

1893 

1894 def _request_summary(self) -> str: 

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

1896 self.request.method, 

1897 self.request.uri, 

1898 self.request.remote_ip, 

1899 ) 

1900 

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

1902 if isinstance(e, Finish): 

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

1904 if not self._finished: 

1905 self.finish(*e.args) 

1906 return 

1907 try: 

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

1909 except Exception: 

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

1911 # to avoid leaking the connection. 

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

1913 if self._finished: 

1914 # Extra errors after the request has been finished should 

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

1916 # send a response. 

1917 return 

1918 if isinstance(e, HTTPError): 

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

1920 else: 

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

1922 

1923 def log_exception( 

1924 self, 

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

1926 value: Optional[BaseException], 

1927 tb: Optional[TracebackType], 

1928 ) -> None: 

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

1930 

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

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

1933 other exceptions as errors with stack traces (on the 

1934 ``tornado.application`` logger). 

1935 

1936 .. versionadded:: 3.1 

1937 """ 

1938 if isinstance(value, HTTPError): 

1939 log_message = value.get_message() 

1940 if log_message: 

1941 format = "%d %s: %s" 

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

1943 gen_log.warning(format, *args) 

1944 else: 

1945 app_log.error( 

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

1947 self._request_summary(), 

1948 self.request, 

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

1950 ) 

1951 

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

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

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

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

1956 if name not in self._active_modules: 

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

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

1959 return _unicode(rendered) 

1960 

1961 return render 

1962 

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

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

1965 

1966 def _clear_representation_headers(self) -> None: 

1967 # 304 responses should not contain representation metadata 

1968 # headers (defined in 

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

1970 # not explicitly allowed by 

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

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

1973 for h in headers: 

1974 self.clear_header(h) 

1975 

1976 

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

1978 

1979 

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

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

1982 

1983 This decorator implies the following changes: 

1984 

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

1986 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

1994 until those futures have completed. 

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

1996 the entire body has been read. 

1997 

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

1999 for example usage. 

2000 """ # noqa: E501 

2001 if not issubclass(cls, RequestHandler): 

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

2003 cls._stream_request_body = True 

2004 return cls 

2005 

2006 

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

2008 if not issubclass(cls, RequestHandler): 

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

2010 return cls._stream_request_body 

2011 

2012 

2013def removeslash( 

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

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

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

2017 

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

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

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

2021 """ 

2022 

2023 @functools.wraps(method) 

2024 def wrapper( # type: ignore 

2025 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

2031 if self.request.query: 

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

2033 self.redirect(uri, permanent=True) 

2034 return None 

2035 else: 

2036 raise HTTPError(404) 

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

2038 

2039 return wrapper 

2040 

2041 

2042def addslash( 

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

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

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

2046 

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

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

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

2050 """ 

2051 

2052 @functools.wraps(method) 

2053 def wrapper( # type: ignore 

2054 self: RequestHandler, *args, **kwargs 

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

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

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

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

2059 if self.request.query: 

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

2061 self.redirect(uri, permanent=True) 

2062 return None 

2063 raise HTTPError(404) 

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

2065 

2066 return wrapper 

2067 

2068 

2069class _ApplicationRouter(ReversibleRuleRouter): 

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

2071 

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

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

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

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

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

2077 `_ApplicationRouter` instance. 

2078 """ 

2079 

2080 def __init__( 

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

2082 ) -> None: 

2083 assert isinstance(application, Application) 

2084 self.application = application 

2085 super().__init__(rules) 

2086 

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

2088 rule = super().process_rule(rule) 

2089 

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

2091 rule.target = _ApplicationRouter( 

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

2093 ) 

2094 

2095 return rule 

2096 

2097 def get_target_delegate( 

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

2099 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

2101 return self.application.get_handler_delegate( 

2102 request, target, **target_params 

2103 ) 

2104 

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

2106 

2107 

2108class Application(ReversibleRouter): 

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

2110 

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

2112 HTTPServer to serve the application:: 

2113 

2114 application = web.Application([ 

2115 (r"/", MainPageHandler), 

2116 ]) 

2117 http_server = httpserver.HTTPServer(application) 

2118 http_server.listen(8080) 

2119 

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

2121 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2126 

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

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

2129 

2130 application = web.Application([ 

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

2132 (r"/", MainPageHandler), 

2133 (r"/feed", FeedHandler), 

2134 ]), 

2135 ]) 

2136 

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

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

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

2140 

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

2142 instantiate an instance of the first request class whose regexp 

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

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

2145 

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

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

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

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

2150 `StaticFileHandler` can be installed automatically with the 

2151 static_path setting described below):: 

2152 

2153 application = web.Application([ 

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

2155 ]) 

2156 

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

2158 a host regular expression as the first argument:: 

2159 

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

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

2162 ]) 

2163 

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

2165 parameter value is matched against host regular expressions. 

2166 

2167 

2168 .. warning:: 

2169 

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

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

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

2173 other private networks. Appropriate host patterns must be used 

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

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

2176 may be vulnerable to DNS rebinding. 

2177 

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

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

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

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

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

2183 `StaticFileHandler` can be specified with the 

2184 ``static_handler_class`` setting. 

2185 

2186 .. versionchanged:: 4.5 

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

2188 

2189 """ 

2190 

2191 def __init__( 

2192 self, 

2193 handlers: Optional[_RuleList] = None, 

2194 default_host: Optional[str] = None, 

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

2196 **settings: Any, 

2197 ) -> None: 

2198 if transforms is None: 

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

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

2201 self.transforms.append(GZipContentEncoding) 

2202 else: 

2203 self.transforms = transforms 

2204 self.default_host = default_host 

2205 self.settings = settings 

2206 self.ui_modules = { 

2207 "linkify": _linkify, 

2208 "xsrf_form_html": _xsrf_form_html, 

2209 "Template": TemplateModule, 

2210 } 

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

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

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

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

2215 path = self.settings["static_path"] 

2216 handlers = list(handlers or []) 

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

2218 static_handler_class = settings.get( 

2219 "static_handler_class", StaticFileHandler 

2220 ) 

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

2222 static_handler_args["path"] = path 

2223 for pattern in [ 

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

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

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

2227 ]: 

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

2229 

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

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

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

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

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

2235 

2236 self.wildcard_router = _ApplicationRouter(self, handlers) 

2237 self.default_router = _ApplicationRouter( 

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

2239 ) 

2240 

2241 # Automatically reload modified modules 

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

2243 from tornado import autoreload 

2244 

2245 autoreload.start() 

2246 

2247 def listen( 

2248 self, 

2249 port: int, 

2250 address: Optional[str] = None, 

2251 *, 

2252 family: socket.AddressFamily = socket.AF_UNSPEC, 

2253 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2254 flags: Optional[int] = None, 

2255 reuse_port: bool = False, 

2256 **kwargs: Any, 

2257 ) -> HTTPServer: 

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

2259 

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

2261 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2266 

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

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

2269 the server. 

2270 

2271 Returns the `.HTTPServer` object. 

2272 

2273 .. versionchanged:: 4.3 

2274 Now returns the `.HTTPServer` object. 

2275 

2276 .. versionchanged:: 6.2 

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

2278 including ``reuse_port``. 

2279 """ 

2280 server = HTTPServer(self, **kwargs) 

2281 server.listen( 

2282 port, 

2283 address=address, 

2284 family=family, 

2285 backlog=backlog, 

2286 flags=flags, 

2287 reuse_port=reuse_port, 

2288 ) 

2289 return server 

2290 

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

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

2293 

2294 Host patterns are processed sequentially in the order they were 

2295 added. All matching patterns will be considered. 

2296 """ 

2297 host_matcher = HostMatches(host_pattern) 

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

2299 

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

2301 

2302 if self.default_host is not None: 

2303 self.wildcard_router.add_rules( 

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

2305 ) 

2306 

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

2308 self.transforms.append(transform_class) 

2309 

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

2311 if isinstance(methods, types.ModuleType): 

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

2313 elif isinstance(methods, list): 

2314 for m in methods: 

2315 self._load_ui_methods(m) 

2316 else: 

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

2318 if ( 

2319 not name.startswith("_") 

2320 and hasattr(fn, "__call__") 

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

2322 ): 

2323 self.ui_methods[name] = fn 

2324 

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

2326 if isinstance(modules, types.ModuleType): 

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

2328 elif isinstance(modules, list): 

2329 for m in modules: 

2330 self._load_ui_modules(m) 

2331 else: 

2332 assert isinstance(modules, dict) 

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

2334 try: 

2335 if issubclass(cls, UIModule): 

2336 self.ui_modules[name] = cls 

2337 except TypeError: 

2338 pass 

2339 

2340 def __call__( 

2341 self, request: httputil.HTTPServerRequest 

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

2343 # Legacy HTTPServer interface 

2344 dispatcher = self.find_handler(request) 

2345 return dispatcher.execute() 

2346 

2347 def find_handler( 

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

2349 ) -> "_HandlerDelegate": 

2350 route = self.default_router.find_handler(request) 

2351 if route is not None: 

2352 return cast("_HandlerDelegate", route) 

2353 

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

2355 return self.get_handler_delegate( 

2356 request, 

2357 self.settings["default_handler_class"], 

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

2359 ) 

2360 

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

2362 

2363 def get_handler_delegate( 

2364 self, 

2365 request: httputil.HTTPServerRequest, 

2366 target_class: Type[RequestHandler], 

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

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

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

2370 ) -> "_HandlerDelegate": 

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

2372 for application and `RequestHandler` subclass. 

2373 

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

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

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

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

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

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

2380 """ 

2381 return _HandlerDelegate( 

2382 self, request, target_class, target_kwargs, path_args, path_kwargs 

2383 ) 

2384 

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

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

2387 

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

2389 

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

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

2392 and url-escaped. 

2393 """ 

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

2395 if reversed_url is not None: 

2396 return reversed_url 

2397 

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

2399 

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

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

2402 

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

2404 this behavior either subclass Application and override this method, 

2405 or pass a function in the application settings dictionary as 

2406 ``log_function``. 

2407 """ 

2408 if "log_function" in self.settings: 

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

2410 return 

2411 if handler.get_status() < 400: 

2412 log_method = access_log.info 

2413 elif handler.get_status() < 500: 

2414 log_method = access_log.warning 

2415 else: 

2416 log_method = access_log.error 

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

2418 log_method( 

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

2420 handler.get_status(), 

2421 handler._request_summary(), 

2422 request_time, 

2423 ) 

2424 

2425 

2426class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2427 def __init__( 

2428 self, 

2429 application: Application, 

2430 request: httputil.HTTPServerRequest, 

2431 handler_class: Type[RequestHandler], 

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

2433 path_args: Optional[List[bytes]], 

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

2435 ) -> None: 

2436 self.application = application 

2437 self.connection = request.connection 

2438 self.request = request 

2439 self.handler_class = handler_class 

2440 self.handler_kwargs = handler_kwargs or {} 

2441 self.path_args = path_args or [] 

2442 self.path_kwargs = path_kwargs or {} 

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

2444 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2445 

2446 def headers_received( 

2447 self, 

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

2449 headers: httputil.HTTPHeaders, 

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

2451 if self.stream_request_body: 

2452 self.request._body_future = Future() 

2453 return self.execute() 

2454 return None 

2455 

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

2457 if self.stream_request_body: 

2458 return self.handler.data_received(data) 

2459 else: 

2460 self.chunks.append(data) 

2461 return None 

2462 

2463 def finish(self) -> None: 

2464 if self.stream_request_body: 

2465 future_set_result_unless_cancelled(self.request._body_future, None) 

2466 else: 

2467 # Note that the body gets parsed in RequestHandler._execute so it can be in 

2468 # the right exception handler scope. 

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

2470 self.execute() 

2471 

2472 def on_connection_close(self) -> None: 

2473 if self.stream_request_body: 

2474 self.handler.on_connection_close() 

2475 else: 

2476 self.chunks = None # type: ignore 

2477 

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

2479 # If template cache is disabled (usually in the debug mode), 

2480 # re-compile templates and reload static files on every 

2481 # request so you don't need to restart to see changes 

2482 if not self.application.settings.get("compiled_template_cache", True): 

2483 with RequestHandler._template_loader_lock: 

2484 for loader in RequestHandler._template_loaders.values(): 

2485 loader.reset() 

2486 if not self.application.settings.get("static_hash_cache", True): 

2487 static_handler_class = self.application.settings.get( 

2488 "static_handler_class", StaticFileHandler 

2489 ) 

2490 static_handler_class.reset() 

2491 

2492 self.handler = self.handler_class( 

2493 self.application, self.request, **self.handler_kwargs 

2494 ) 

2495 transforms = [t(self.request) for t in self.application.transforms] 

2496 

2497 if self.stream_request_body: 

2498 self.handler._prepared_future = Future() 

2499 # Note that if an exception escapes handler._execute it will be 

2500 # trapped in the Future it returns (which we are ignoring here, 

2501 # leaving it to be logged when the Future is GC'd). 

2502 # However, that shouldn't happen because _execute has a blanket 

2503 # except handler, and we cannot easily access the IOLoop here to 

2504 # call add_future (because of the requirement to remain compatible 

2505 # with WSGI) 

2506 fut = gen.convert_yielded( 

2507 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 

2508 ) 

2509 fut.add_done_callback(lambda f: f.result()) 

2510 # If we are streaming the request body, then execute() is finished 

2511 # when the handler has prepared to receive the body. If not, 

2512 # it doesn't matter when execute() finishes (so we return None) 

2513 return self.handler._prepared_future 

2514 

2515 

2516class HTTPError(Exception): 

2517 """An exception that will turn into an HTTP error response. 

2518 

2519 Raising an `HTTPError` is a convenient alternative to calling 

2520 `RequestHandler.send_error` since it automatically ends the 

2521 current function. 

2522 

2523 To customize the response sent with an `HTTPError`, override 

2524 `RequestHandler.write_error`. 

2525 

2526 :arg int status_code: HTTP status code. Must be listed in 

2527 `httplib.responses <http.client.responses>` unless the ``reason`` 

2528 keyword argument is given. 

2529 :arg str log_message: Message to be written to the log for this error 

2530 (will not be shown to the user unless the `Application` is in debug 

2531 mode). May contain ``%s``-style placeholders, which will be filled 

2532 in with remaining positional parameters. 

2533 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2534 to pass in the status line along with ``status_code`` (for example, 

2535 the "Not Found" in ``HTTP/1.1 404 Not Found``). Normally 

2536 determined automatically from ``status_code``, but can be used 

2537 to use a non-standard numeric code. This is not a general-purpose 

2538 error message. 

2539 """ 

2540 

2541 def __init__( 

2542 self, 

2543 status_code: int = 500, 

2544 log_message: Optional[str] = None, 

2545 *args: Any, 

2546 **kwargs: Any, 

2547 ) -> None: 

2548 self.status_code = status_code 

2549 self._log_message = log_message 

2550 self.args = args 

2551 self.reason = kwargs.get("reason", None) 

2552 

2553 @property 

2554 def log_message(self) -> Optional[str]: 

2555 """ 

2556 A backwards compatible way of accessing log_message. 

2557 """ 

2558 if self._log_message and not self.args: 

2559 return self._log_message.replace("%", "%%") 

2560 return self._log_message 

2561 

2562 def get_message(self) -> Optional[str]: 

2563 if self._log_message and self.args: 

2564 return self._log_message % self.args 

2565 return self._log_message 

2566 

2567 def __str__(self) -> str: 

2568 message = "HTTP %d: %s" % ( 

2569 self.status_code, 

2570 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2571 ) 

2572 log_message = self.get_message() 

2573 if log_message: 

2574 return message + " (" + log_message + ")" 

2575 else: 

2576 return message 

2577 

2578 

2579class Finish(Exception): 

2580 """An exception that ends the request without producing an error response. 

2581 

2582 When `Finish` is raised in a `RequestHandler`, the request will 

2583 end (calling `RequestHandler.finish` if it hasn't already been 

2584 called), but the error-handling methods (including 

2585 `RequestHandler.write_error`) will not be called. 

2586 

2587 If `Finish()` was created with no arguments, the pending response 

2588 will be sent as-is. If `Finish()` was given an argument, that 

2589 argument will be passed to `RequestHandler.finish()`. 

2590 

2591 This can be a more convenient way to implement custom error pages 

2592 than overriding ``write_error`` (especially in library code):: 

2593 

2594 if self.current_user is None: 

2595 self.set_status(401) 

2596 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2597 raise Finish() 

2598 

2599 .. versionchanged:: 4.3 

2600 Arguments passed to ``Finish()`` will be passed on to 

2601 `RequestHandler.finish`. 

2602 """ 

2603 

2604 pass 

2605 

2606 

2607class MissingArgumentError(HTTPError): 

2608 """Exception raised by `RequestHandler.get_argument`. 

2609 

2610 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2611 code will be used instead of 500 (and a stack trace will not be logged). 

2612 

2613 .. versionadded:: 3.1 

2614 """ 

2615 

2616 def __init__(self, arg_name: str) -> None: 

2617 super().__init__(400, "Missing argument %s" % arg_name) 

2618 self.arg_name = arg_name 

2619 

2620 

2621class ErrorHandler(RequestHandler): 

2622 """Generates an error response with ``status_code`` for all requests.""" 

2623 

2624 def initialize(self, status_code: int) -> None: 

2625 self.set_status(status_code) 

2626 

2627 def prepare(self) -> None: 

2628 raise HTTPError(self._status_code) 

2629 

2630 def check_xsrf_cookie(self) -> None: 

2631 # POSTs to an ErrorHandler don't actually have side effects, 

2632 # so we don't need to check the xsrf token. This allows POSTs 

2633 # to the wrong url to return a 404 instead of 403. 

2634 pass 

2635 

2636 

2637class RedirectHandler(RequestHandler): 

2638 """Redirects the client to the given URL for all GET requests. 

2639 

2640 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2641 

2642 application = web.Application([ 

2643 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2644 ]) 

2645 

2646 `RedirectHandler` supports regular expression substitutions. E.g., to 

2647 swap the first and second parts of a path while preserving the remainder:: 

2648 

2649 application = web.Application([ 

2650 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2651 ]) 

2652 

2653 The final URL is formatted with `str.format` and the substrings that match 

2654 the capturing groups. In the above example, a request to "/a/b/c" would be 

2655 formatted like:: 

2656 

2657 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2658 

2659 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2660 values are substituted. 

2661 

2662 .. versionchanged:: 4.5 

2663 Added support for substitutions into the destination URL. 

2664 

2665 .. versionchanged:: 5.0 

2666 If any query arguments are present, they will be copied to the 

2667 destination URL. 

2668 """ 

2669 

2670 def initialize(self, url: str, permanent: bool = True) -> None: 

2671 self._url = url 

2672 self._permanent = permanent 

2673 

2674 def get(self, *args: Any, **kwargs: Any) -> None: 

2675 to_url = self._url.format(*args, **kwargs) 

2676 if self.request.query_arguments: 

2677 # TODO: figure out typing for the next line. 

2678 to_url = httputil.url_concat( 

2679 to_url, 

2680 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2681 ) 

2682 self.redirect(to_url, permanent=self._permanent) 

2683 

2684 

2685class StaticFileHandler(RequestHandler): 

2686 """A simple handler that can serve static content from a directory. 

2687 

2688 A `StaticFileHandler` is configured automatically if you pass the 

2689 ``static_path`` keyword argument to `Application`. This handler 

2690 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2691 and ``static_handler_args`` settings. 

2692 

2693 To map an additional path to this handler for a static data directory 

2694 you would add a line to your application like:: 

2695 

2696 application = web.Application([ 

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

2698 ]) 

2699 

2700 The handler constructor requires a ``path`` argument, which specifies the 

2701 local root directory of the content to be served. 

2702 

2703 Note that a capture group in the regex is required to parse the value for 

2704 the ``path`` argument to the get() method (different than the constructor 

2705 argument above); see `URLSpec` for details. 

2706 

2707 To serve a file like ``index.html`` automatically when a directory is 

2708 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2709 in your application settings, or add ``default_filename`` as an initializer 

2710 argument for your ``StaticFileHandler``. 

2711 

2712 To maximize the effectiveness of browser caching, this class supports 

2713 versioned urls (by default using the argument ``?v=``). If a version 

2714 is given, we instruct the browser to cache this file indefinitely. 

2715 `make_static_url` (also available as `RequestHandler.static_url`) can 

2716 be used to construct a versioned url. 

2717 

2718 This handler is intended primarily for use in development and light-duty 

2719 file serving; for heavy traffic it will be more efficient to use 

2720 a dedicated static file server (such as nginx or Apache). We support 

2721 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2722 some browsers require this functionality to be present to seek in 

2723 HTML5 audio or video). 

2724 

2725 **Subclassing notes** 

2726 

2727 This class is designed to be extensible by subclassing, but because 

2728 of the way static urls are generated with class methods rather than 

2729 instance methods, the inheritance patterns are somewhat unusual. 

2730 Be sure to use the ``@classmethod`` decorator when overriding a 

2731 class method. Instance methods may use the attributes ``self.path`` 

2732 ``self.absolute_path``, and ``self.modified``. 

2733 

2734 Subclasses should only override methods discussed in this section; 

2735 overriding other methods is error-prone. Overriding 

2736 ``StaticFileHandler.get`` is particularly problematic due to the 

2737 tight coupling with ``compute_etag`` and other methods. 

2738 

2739 To change the way static urls are generated (e.g. to match the behavior 

2740 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2741 `get_cache_time`, and/or `get_version`. 

2742 

2743 To replace all interaction with the filesystem (e.g. to serve 

2744 static content from a database), override `get_content`, 

2745 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2746 `validate_absolute_path`. 

2747 

2748 .. versionchanged:: 3.1 

2749 Many of the methods for subclasses were added in Tornado 3.1. 

2750 """ 

2751 

2752 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2753 

2754 _static_hashes = {} # type: Dict[str, Optional[str]] 

2755 _lock = threading.Lock() # protects _static_hashes 

2756 

2757 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2758 self.root = path 

2759 self.default_filename = default_filename 

2760 

2761 @classmethod 

2762 def reset(cls) -> None: 

2763 with cls._lock: 

2764 cls._static_hashes = {} 

2765 

2766 def head(self, path: str) -> Awaitable[None]: 

2767 return self.get(path, include_body=False) 

2768 

2769 async def get(self, path: str, include_body: bool = True) -> None: 

2770 # Set up our path instance variables. 

2771 self.path = self.parse_url_path(path) 

2772 del path # make sure we don't refer to path instead of self.path again 

2773 absolute_path = self.get_absolute_path(self.root, self.path) 

2774 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2775 if self.absolute_path is None: 

2776 return 

2777 

2778 self.modified = self.get_modified_time() 

2779 self.set_headers() 

2780 

2781 if self.should_return_304(): 

2782 self.set_status(304) 

2783 return 

2784 

2785 request_range = None 

2786 range_header = self.request.headers.get("Range") 

2787 if range_header: 

2788 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2789 # the request will be treated as if the header didn't exist. 

2790 request_range = httputil._parse_request_range(range_header) 

2791 

2792 size = self.get_content_size() 

2793 if request_range: 

2794 start, end = request_range 

2795 if start is not None and start < 0: 

2796 start += size 

2797 if start < 0: 

2798 start = 0 

2799 if ( 

2800 start is not None 

2801 and (start >= size or (end is not None and start >= end)) 

2802 ) or end == 0: 

2803 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2804 # the first requested byte is equal to or greater than the 

2805 # content, or when a suffix with length 0 is specified. 

2806 # https://tools.ietf.org/html/rfc7233#section-2.1 

2807 # A byte-range-spec is invalid if the last-byte-pos value is present 

2808 # and less than the first-byte-pos. 

2809 self.set_status(416) # Range Not Satisfiable 

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

2811 self.set_header("Content-Range", f"bytes */{size}") 

2812 return 

2813 if end is not None and end > size: 

2814 # Clients sometimes blindly use a large range to limit their 

2815 # download size; cap the endpoint at the actual file size. 

2816 end = size 

2817 # Note: only return HTTP 206 if less than the entire range has been 

2818 # requested. Not only is this semantically correct, but Chrome 

2819 # refuses to play audio if it gets an HTTP 206 in response to 

2820 # ``Range: bytes=0-``. 

2821 if size != (end or size) - (start or 0): 

2822 self.set_status(206) # Partial Content 

2823 self.set_header( 

2824 "Content-Range", httputil._get_content_range(start, end, size) 

2825 ) 

2826 else: 

2827 start = end = None 

2828 

2829 if start is not None and end is not None: 

2830 content_length = end - start 

2831 elif end is not None: 

2832 content_length = end 

2833 elif start is not None: 

2834 content_length = size - start 

2835 else: 

2836 content_length = size 

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

2838 

2839 if include_body: 

2840 content = self.get_content(self.absolute_path, start, end) 

2841 if isinstance(content, bytes): 

2842 content = [content] 

2843 for chunk in content: 

2844 try: 

2845 self.write(chunk) 

2846 await self.flush() 

2847 except iostream.StreamClosedError: 

2848 return 

2849 else: 

2850 assert self.request.method == "HEAD" 

2851 

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

2853 """Sets the ``Etag`` header based on static url version. 

2854 

2855 This allows efficient ``If-None-Match`` checks against cached 

2856 versions, and sends the correct ``Etag`` for a partial response 

2857 (i.e. the same ``Etag`` as the full file). 

2858 

2859 .. versionadded:: 3.1 

2860 """ 

2861 assert self.absolute_path is not None 

2862 version_hash = self._get_cached_version(self.absolute_path) 

2863 if not version_hash: 

2864 return None 

2865 return f'"{version_hash}"' 

2866 

2867 def set_headers(self) -> None: 

2868 """Sets the content and caching headers on the response. 

2869 

2870 .. versionadded:: 3.1 

2871 """ 

2872 self.set_header("Accept-Ranges", "bytes") 

2873 self.set_etag_header() 

2874 

2875 if self.modified is not None: 

2876 self.set_header("Last-Modified", self.modified) 

2877 

2878 content_type = self.get_content_type() 

2879 if content_type: 

2880 self.set_header("Content-Type", content_type) 

2881 

2882 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2883 if cache_time > 0: 

2884 self.set_header( 

2885 "Expires", 

2886 datetime.datetime.now(datetime.timezone.utc) 

2887 + datetime.timedelta(seconds=cache_time), 

2888 ) 

2889 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2890 

2891 self.set_extra_headers(self.path) 

2892 

2893 def should_return_304(self) -> bool: 

2894 """Returns True if the headers indicate that we should return 304. 

2895 

2896 .. versionadded:: 3.1 

2897 """ 

2898 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2899 if self.request.headers.get("If-None-Match"): 

2900 return self.check_etag_header() 

2901 

2902 # Check the If-Modified-Since, and don't send the result if the 

2903 # content has not been modified 

2904 ims_value = self.request.headers.get("If-Modified-Since") 

2905 if ims_value is not None: 

2906 try: 

2907 if_since = email.utils.parsedate_to_datetime(ims_value) 

2908 except Exception: 

2909 return False 

2910 if if_since.tzinfo is None: 

2911 if_since = if_since.replace(tzinfo=datetime.timezone.utc) 

2912 assert self.modified is not None 

2913 if if_since >= self.modified: 

2914 return True 

2915 

2916 return False 

2917 

2918 @classmethod 

2919 def get_absolute_path(cls, root: str, path: str) -> str: 

2920 """Returns the absolute location of ``path`` relative to ``root``. 

2921 

2922 ``root`` is the path configured for this `StaticFileHandler` 

2923 (in most cases the ``static_path`` `Application` setting). 

2924 

2925 This class method may be overridden in subclasses. By default 

2926 it returns a filesystem path, but other strings may be used 

2927 as long as they are unique and understood by the subclass's 

2928 overridden `get_content`. 

2929 

2930 .. versionadded:: 3.1 

2931 """ 

2932 abspath = os.path.abspath(os.path.join(root, path)) 

2933 return abspath 

2934 

2935 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2936 """Validate and return the absolute path. 

2937 

2938 ``root`` is the configured path for the `StaticFileHandler`, 

2939 and ``path`` is the result of `get_absolute_path` 

2940 

2941 This is an instance method called during request processing, 

2942 so it may raise `HTTPError` or use methods like 

2943 `RequestHandler.redirect` (return None after redirecting to 

2944 halt further processing). This is where 404 errors for missing files 

2945 are generated. 

2946 

2947 This method may modify the path before returning it, but note that 

2948 any such modifications will not be understood by `make_static_url`. 

2949 

2950 In instance methods, this method's result is available as 

2951 ``self.absolute_path``. 

2952 

2953 .. versionadded:: 3.1 

2954 """ 

2955 # os.path.abspath strips a trailing /. 

2956 # We must add it back to `root` so that we only match files 

2957 # in a directory named `root` instead of files starting with 

2958 # that prefix. 

2959 root = os.path.abspath(root) 

2960 if not root.endswith(os.path.sep): 

2961 # abspath always removes a trailing slash, except when 

2962 # root is '/'. This is an unusual case, but several projects 

2963 # have independently discovered this technique to disable 

2964 # Tornado's path validation and (hopefully) do their own, 

2965 # so we need to support it. 

2966 root += os.path.sep 

2967 # The trailing slash also needs to be temporarily added back 

2968 # the requested path so a request to root/ will match. 

2969 if not (absolute_path + os.path.sep).startswith(root): 

2970 raise HTTPError(403, "%s is not in root static directory", self.path) 

2971 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2972 # need to look at the request.path here for when path is empty 

2973 # but there is some prefix to the path that was already 

2974 # trimmed by the routing 

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

2976 if self.request.path.startswith("//"): 

2977 # A redirect with two initial slashes is a "protocol-relative" URL. 

2978 # This means the next path segment is treated as a hostname instead 

2979 # of a part of the path, making this effectively an open redirect. 

2980 # Reject paths starting with two slashes to prevent this. 

2981 # This is only reachable under certain configurations. 

2982 raise HTTPError( 

2983 403, "cannot redirect path with two initial slashes" 

2984 ) 

2985 self.redirect(self.request.path + "/", permanent=True) 

2986 return None 

2987 absolute_path = os.path.join(absolute_path, self.default_filename) 

2988 if not os.path.exists(absolute_path): 

2989 raise HTTPError(404) 

2990 if not os.path.isfile(absolute_path): 

2991 raise HTTPError(403, "%s is not a file", self.path) 

2992 return absolute_path 

2993 

2994 @classmethod 

2995 def get_content( 

2996 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

2997 ) -> Generator[bytes, None, None]: 

2998 """Retrieve the content of the requested resource which is located 

2999 at the given absolute path. 

3000 

3001 This class method may be overridden by subclasses. Note that its 

3002 signature is different from other overridable class methods 

3003 (no ``settings`` argument); this is deliberate to ensure that 

3004 ``abspath`` is able to stand on its own as a cache key. 

3005 

3006 This method should either return a byte string or an iterator 

3007 of byte strings. The latter is preferred for large files 

3008 as it helps reduce memory fragmentation. 

3009 

3010 .. versionadded:: 3.1 

3011 """ 

3012 with open(abspath, "rb") as file: 

3013 if start is not None: 

3014 file.seek(start) 

3015 if end is not None: 

3016 remaining = end - (start or 0) # type: Optional[int] 

3017 else: 

3018 remaining = None 

3019 while True: 

3020 chunk_size = 64 * 1024 

3021 if remaining is not None and remaining < chunk_size: 

3022 chunk_size = remaining 

3023 chunk = file.read(chunk_size) 

3024 if chunk: 

3025 if remaining is not None: 

3026 remaining -= len(chunk) 

3027 yield chunk 

3028 else: 

3029 if remaining is not None: 

3030 assert remaining == 0 

3031 return 

3032 

3033 @classmethod 

3034 def get_content_version(cls, abspath: str) -> str: 

3035 """Returns a version string for the resource at the given path. 

3036 

3037 This class method may be overridden by subclasses. The 

3038 default implementation is a SHA-512 hash of the file's contents. 

3039 

3040 .. versionadded:: 3.1 

3041 """ 

3042 data = cls.get_content(abspath) 

3043 hasher = hashlib.sha512() 

3044 if isinstance(data, bytes): 

3045 hasher.update(data) 

3046 else: 

3047 for chunk in data: 

3048 hasher.update(chunk) 

3049 return hasher.hexdigest() 

3050 

3051 def _stat(self) -> os.stat_result: 

3052 assert self.absolute_path is not None 

3053 if not hasattr(self, "_stat_result"): 

3054 self._stat_result = os.stat(self.absolute_path) 

3055 return self._stat_result 

3056 

3057 def get_content_size(self) -> int: 

3058 """Retrieve the total size of the resource at the given path. 

3059 

3060 This method may be overridden by subclasses. 

3061 

3062 .. versionadded:: 3.1 

3063 

3064 .. versionchanged:: 4.0 

3065 This method is now always called, instead of only when 

3066 partial results are requested. 

3067 """ 

3068 stat_result = self._stat() 

3069 return stat_result.st_size 

3070 

3071 def get_modified_time(self) -> Optional[datetime.datetime]: 

3072 """Returns the time that ``self.absolute_path`` was last modified. 

3073 

3074 May be overridden in subclasses. Should return a `~datetime.datetime` 

3075 object or None. 

3076 

3077 .. versionadded:: 3.1 

3078 

3079 .. versionchanged:: 6.4 

3080 Now returns an aware datetime object instead of a naive one. 

3081 Subclasses that override this method may return either kind. 

3082 """ 

3083 stat_result = self._stat() 

3084 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3085 # which truncates the fractional portion of the timestamp. It 

3086 # was changed from that form to stat_result.st_mtime to 

3087 # satisfy mypy (which disallows the bracket operator), but the 

3088 # latter form returns a float instead of an int. For 

3089 # consistency with the past (and because we have a unit test 

3090 # that relies on this), we truncate the float here, although 

3091 # I'm not sure that's the right thing to do. 

3092 modified = datetime.datetime.fromtimestamp( 

3093 int(stat_result.st_mtime), datetime.timezone.utc 

3094 ) 

3095 return modified 

3096 

3097 def get_content_type(self) -> str: 

3098 """Returns the ``Content-Type`` header to be used for this request. 

3099 

3100 .. versionadded:: 3.1 

3101 """ 

3102 assert self.absolute_path is not None 

3103 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3104 # per RFC 6713, use the appropriate type for a gzip compressed file 

3105 if encoding == "gzip": 

3106 return "application/gzip" 

3107 # As of 2015-07-21 there is no bzip2 encoding defined at 

3108 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3109 # So for that (and any other encoding), use octet-stream. 

3110 elif encoding is not None: 

3111 return "application/octet-stream" 

3112 elif mime_type is not None: 

3113 return mime_type 

3114 # if mime_type not detected, use application/octet-stream 

3115 else: 

3116 return "application/octet-stream" 

3117 

3118 def set_extra_headers(self, path: str) -> None: 

3119 """For subclass to add extra headers to the response""" 

3120 pass 

3121 

3122 def get_cache_time( 

3123 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3124 ) -> int: 

3125 """Override to customize cache control behavior. 

3126 

3127 Return a positive number of seconds to make the result 

3128 cacheable for that amount of time or 0 to mark resource as 

3129 cacheable for an unspecified amount of time (subject to 

3130 browser heuristics). 

3131 

3132 By default returns cache expiry of 10 years for resources requested 

3133 with ``v`` argument. 

3134 """ 

3135 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3136 

3137 @classmethod 

3138 def make_static_url( 

3139 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3140 ) -> str: 

3141 """Constructs a versioned url for the given path. 

3142 

3143 This method may be overridden in subclasses (but note that it 

3144 is a class method rather than an instance method). Subclasses 

3145 are only required to implement the signature 

3146 ``make_static_url(cls, settings, path)``; other keyword 

3147 arguments may be passed through `~RequestHandler.static_url` 

3148 but are not standard. 

3149 

3150 ``settings`` is the `Application.settings` dictionary. ``path`` 

3151 is the static path being requested. The url returned should be 

3152 relative to the current host. 

3153 

3154 ``include_version`` determines whether the generated URL should 

3155 include the query string containing the version hash of the 

3156 file corresponding to the given ``path``. 

3157 

3158 """ 

3159 url = settings.get("static_url_prefix", "/static/") + path 

3160 if not include_version: 

3161 return url 

3162 

3163 version_hash = cls.get_version(settings, path) 

3164 if not version_hash: 

3165 return url 

3166 

3167 return f"{url}?v={version_hash}" 

3168 

3169 def parse_url_path(self, url_path: str) -> str: 

3170 """Converts a static URL path into a filesystem path. 

3171 

3172 ``url_path`` is the path component of the URL with 

3173 ``static_url_prefix`` removed. The return value should be 

3174 filesystem path relative to ``static_path``. 

3175 

3176 This is the inverse of `make_static_url`. 

3177 """ 

3178 if os.path.sep != "/": 

3179 url_path = url_path.replace("/", os.path.sep) 

3180 return url_path 

3181 

3182 @classmethod 

3183 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3184 """Generate the version string to be used in static URLs. 

3185 

3186 ``settings`` is the `Application.settings` dictionary and ``path`` 

3187 is the relative location of the requested asset on the filesystem. 

3188 The returned value should be a string, or ``None`` if no version 

3189 could be determined. 

3190 

3191 .. versionchanged:: 3.1 

3192 This method was previously recommended for subclasses to override; 

3193 `get_content_version` is now preferred as it allows the base 

3194 class to handle caching of the result. 

3195 """ 

3196 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3197 return cls._get_cached_version(abs_path) 

3198 

3199 @classmethod 

3200 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3201 with cls._lock: 

3202 hashes = cls._static_hashes 

3203 if abs_path not in hashes: 

3204 try: 

3205 hashes[abs_path] = cls.get_content_version(abs_path) 

3206 except Exception: 

3207 gen_log.error("Could not open static file %r", abs_path) 

3208 hashes[abs_path] = None 

3209 hsh = hashes.get(abs_path) 

3210 if hsh: 

3211 return hsh 

3212 return None 

3213 

3214 

3215class FallbackHandler(RequestHandler): 

3216 """A `RequestHandler` that wraps another HTTP server callback. 

3217 

3218 The fallback is a callable object that accepts an 

3219 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3220 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3221 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3222 usage:: 

3223 

3224 wsgi_app = tornado.wsgi.WSGIContainer( 

3225 django.core.handlers.wsgi.WSGIHandler()) 

3226 application = tornado.web.Application([ 

3227 (r"/foo", FooHandler), 

3228 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3229 ]) 

3230 """ 

3231 

3232 def initialize( 

3233 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3234 ) -> None: 

3235 self.fallback = fallback 

3236 

3237 def prepare(self) -> None: 

3238 self.fallback(self.request) 

3239 self._finished = True 

3240 self.on_finish() 

3241 

3242 

3243class OutputTransform: 

3244 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3245 

3246 Applications are not expected to create their own OutputTransforms 

3247 or interact with them directly; the framework chooses which transforms 

3248 (if any) to apply. 

3249 """ 

3250 

3251 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3252 pass 

3253 

3254 def transform_first_chunk( 

3255 self, 

3256 status_code: int, 

3257 headers: httputil.HTTPHeaders, 

3258 chunk: bytes, 

3259 finishing: bool, 

3260 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3261 return status_code, headers, chunk 

3262 

3263 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3264 return chunk 

3265 

3266 

3267class GZipContentEncoding(OutputTransform): 

3268 """Applies the gzip content encoding to the response. 

3269 

3270 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3271 

3272 .. versionchanged:: 4.0 

3273 Now compresses all mime types beginning with ``text/``, instead 

3274 of just a whitelist. (the whitelist is still used for certain 

3275 non-text mime types). 

3276 """ 

3277 

3278 # Whitelist of compressible mime types (in addition to any types 

3279 # beginning with "text/"). 

3280 CONTENT_TYPES = { 

3281 "application/javascript", 

3282 "application/x-javascript", 

3283 "application/xml", 

3284 "application/atom+xml", 

3285 "application/json", 

3286 "application/xhtml+xml", 

3287 "image/svg+xml", 

3288 } 

3289 # Python's GzipFile defaults to level 9, while most other gzip 

3290 # tools (including gzip itself) default to 6, which is probably a 

3291 # better CPU/size tradeoff. 

3292 GZIP_LEVEL = 6 

3293 # Responses that are too short are unlikely to benefit from gzipping 

3294 # after considering the "Content-Encoding: gzip" header and the header 

3295 # inside the gzip encoding. 

3296 # Note that responses written in multiple chunks will be compressed 

3297 # regardless of size. 

3298 MIN_LENGTH = 1024 

3299 

3300 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3301 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3302 

3303 def _compressible_type(self, ctype: str) -> bool: 

3304 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3305 

3306 def transform_first_chunk( 

3307 self, 

3308 status_code: int, 

3309 headers: httputil.HTTPHeaders, 

3310 chunk: bytes, 

3311 finishing: bool, 

3312 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3313 # TODO: can/should this type be inherited from the superclass? 

3314 if "Vary" in headers: 

3315 headers["Vary"] += ", Accept-Encoding" 

3316 else: 

3317 headers["Vary"] = "Accept-Encoding" 

3318 if self._gzipping: 

3319 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3320 self._gzipping = ( 

3321 self._compressible_type(ctype) 

3322 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3323 and ("Content-Encoding" not in headers) 

3324 ) 

3325 if self._gzipping: 

3326 headers["Content-Encoding"] = "gzip" 

3327 self._gzip_value = BytesIO() 

3328 self._gzip_file = gzip.GzipFile( 

3329 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3330 ) 

3331 chunk = self.transform_chunk(chunk, finishing) 

3332 if "Content-Length" in headers: 

3333 # The original content length is no longer correct. 

3334 # If this is the last (and only) chunk, we can set the new 

3335 # content-length; otherwise we remove it and fall back to 

3336 # chunked encoding. 

3337 if finishing: 

3338 headers["Content-Length"] = str(len(chunk)) 

3339 else: 

3340 del headers["Content-Length"] 

3341 return status_code, headers, chunk 

3342 

3343 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3344 if self._gzipping: 

3345 self._gzip_file.write(chunk) 

3346 if finishing: 

3347 self._gzip_file.close() 

3348 else: 

3349 self._gzip_file.flush() 

3350 chunk = self._gzip_value.getvalue() 

3351 self._gzip_value.truncate(0) 

3352 self._gzip_value.seek(0) 

3353 return chunk 

3354 

3355 

3356def authenticated( 

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

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

3359 """Decorate methods with this to require that the user be logged in. 

3360 

3361 If the user is not logged in, they will be redirected to the configured 

3362 `login url <RequestHandler.get_login_url>`. 

3363 

3364 If you configure a login url with a query parameter, Tornado will 

3365 assume you know what you're doing and use it as-is. If not, it 

3366 will add a `next` parameter so the login page knows where to send 

3367 you once you're logged in. 

3368 """ 

3369 

3370 @functools.wraps(method) 

3371 def wrapper( # type: ignore 

3372 self: RequestHandler, *args, **kwargs 

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

3374 if not self.current_user: 

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

3376 url = self.get_login_url() 

3377 if "?" not in url: 

3378 if urllib.parse.urlsplit(url).scheme: 

3379 # if login url is absolute, make next absolute too 

3380 next_url = self.request.full_url() 

3381 else: 

3382 assert self.request.uri is not None 

3383 next_url = self.request.uri 

3384 url += "?" + urlencode(dict(next=next_url)) 

3385 self.redirect(url) 

3386 return None 

3387 raise HTTPError(403) 

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

3389 

3390 return wrapper 

3391 

3392 

3393class UIModule: 

3394 """A re-usable, modular UI unit on a page. 

3395 

3396 UI modules often execute additional queries, and they can include 

3397 additional CSS and JavaScript that will be included in the output 

3398 page, which is automatically inserted on page render. 

3399 

3400 Subclasses of UIModule must override the `render` method. 

3401 """ 

3402 

3403 def __init__(self, handler: RequestHandler) -> None: 

3404 self.handler = handler 

3405 self.request = handler.request 

3406 self.ui = handler.ui 

3407 self.locale = handler.locale 

3408 

3409 @property 

3410 def current_user(self) -> Any: 

3411 return self.handler.current_user 

3412 

3413 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]: 

3414 """Override in subclasses to return this module's output.""" 

3415 raise NotImplementedError() 

3416 

3417 def embedded_javascript(self) -> Optional[str]: 

3418 """Override to return a JavaScript string 

3419 to be embedded in the page.""" 

3420 return None 

3421 

3422 def javascript_files(self) -> Optional[Iterable[str]]: 

3423 """Override to return a list of JavaScript files needed 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 embedded_css(self) -> Optional[str]: 

3431 """Override to return a CSS string 

3432 that will be embedded in the page.""" 

3433 return None 

3434 

3435 def css_files(self) -> Optional[Iterable[str]]: 

3436 """Override to returns a list of CSS files required by this module. 

3437 

3438 If the return values are relative paths, they will be passed to 

3439 `RequestHandler.static_url`; otherwise they will be used as-is. 

3440 """ 

3441 return None 

3442 

3443 def html_head(self) -> Optional[str]: 

3444 """Override to return an HTML string that will be put in the <head/> 

3445 element. 

3446 """ 

3447 return None 

3448 

3449 def html_body(self) -> Optional[str]: 

3450 """Override to return an HTML string that will be put at the end of 

3451 the <body/> element. 

3452 """ 

3453 return None 

3454 

3455 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3456 """Renders a template and returns it as a string.""" 

3457 return self.handler.render_string(path, **kwargs) 

3458 

3459 

3460class _linkify(UIModule): 

3461 def render(self, text: str, **kwargs: Any) -> str: 

3462 return escape.linkify(text, **kwargs) 

3463 

3464 

3465class _xsrf_form_html(UIModule): 

3466 def render(self) -> str: 

3467 return self.handler.xsrf_form_html() 

3468 

3469 

3470class TemplateModule(UIModule): 

3471 """UIModule that simply renders the given template. 

3472 

3473 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3474 but the module version gets its own namespace (with kwargs passed to 

3475 Template()) instead of inheriting the outer template's namespace. 

3476 

3477 Templates rendered through this module also get access to UIModule's 

3478 automatic JavaScript/CSS features. Simply call set_resources 

3479 inside the template and give it keyword arguments corresponding to 

3480 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3481 Note that these resources are output once per template file, not once 

3482 per instantiation of the template, so they must not depend on 

3483 any arguments to the template. 

3484 """ 

3485 

3486 def __init__(self, handler: RequestHandler) -> None: 

3487 super().__init__(handler) 

3488 # keep resources in both a list and a dict to preserve order 

3489 self._resource_list = [] # type: List[Dict[str, Any]] 

3490 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3491 

3492 def render(self, path: str, **kwargs: Any) -> bytes: 

3493 def set_resources(**kwargs) -> str: # type: ignore 

3494 if path not in self._resource_dict: 

3495 self._resource_list.append(kwargs) 

3496 self._resource_dict[path] = kwargs 

3497 else: 

3498 if self._resource_dict[path] != kwargs: 

3499 raise ValueError( 

3500 "set_resources called with different " 

3501 "resources for the same template" 

3502 ) 

3503 return "" 

3504 

3505 return self.render_string(path, set_resources=set_resources, **kwargs) 

3506 

3507 def _get_resources(self, key: str) -> Iterable[str]: 

3508 return (r[key] for r in self._resource_list if key in r) 

3509 

3510 def embedded_javascript(self) -> str: 

3511 return "\n".join(self._get_resources("embedded_javascript")) 

3512 

3513 def javascript_files(self) -> Iterable[str]: 

3514 result = [] 

3515 for f in self._get_resources("javascript_files"): 

3516 if isinstance(f, (unicode_type, bytes)): 

3517 result.append(f) 

3518 else: 

3519 result.extend(f) 

3520 return result 

3521 

3522 def embedded_css(self) -> str: 

3523 return "\n".join(self._get_resources("embedded_css")) 

3524 

3525 def css_files(self) -> Iterable[str]: 

3526 result = [] 

3527 for f in self._get_resources("css_files"): 

3528 if isinstance(f, (unicode_type, bytes)): 

3529 result.append(f) 

3530 else: 

3531 result.extend(f) 

3532 return result 

3533 

3534 def html_head(self) -> str: 

3535 return "".join(self._get_resources("html_head")) 

3536 

3537 def html_body(self) -> str: 

3538 return "".join(self._get_resources("html_body")) 

3539 

3540 

3541class _UIModuleNamespace: 

3542 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3543 

3544 def __init__( 

3545 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3546 ) -> None: 

3547 self.handler = handler 

3548 self.ui_modules = ui_modules 

3549 

3550 def __getitem__(self, key: str) -> Callable[..., str]: 

3551 return self.handler._ui_module(key, self.ui_modules[key]) 

3552 

3553 def __getattr__(self, key: str) -> Callable[..., str]: 

3554 try: 

3555 return self[key] 

3556 except KeyError as e: 

3557 raise AttributeError(str(e)) 

3558 

3559 

3560def create_signed_value( 

3561 secret: _CookieSecretTypes, 

3562 name: str, 

3563 value: Union[str, bytes], 

3564 version: Optional[int] = None, 

3565 clock: Optional[Callable[[], float]] = None, 

3566 key_version: Optional[int] = None, 

3567) -> bytes: 

3568 if version is None: 

3569 version = DEFAULT_SIGNED_VALUE_VERSION 

3570 if clock is None: 

3571 clock = time.time 

3572 

3573 timestamp = utf8(str(int(clock()))) 

3574 value = base64.b64encode(utf8(value)) 

3575 if version == 1: 

3576 assert not isinstance(secret, dict) 

3577 signature = _create_signature_v1(secret, name, value, timestamp) 

3578 value = b"|".join([value, timestamp, signature]) 

3579 return value 

3580 elif version == 2: 

3581 # The v2 format consists of a version number and a series of 

3582 # length-prefixed fields "%d:%s", the last of which is a 

3583 # signature, all separated by pipes. All numbers are in 

3584 # decimal format with no leading zeros. The signature is an 

3585 # HMAC-SHA256 of the whole string up to that point, including 

3586 # the final pipe. 

3587 # 

3588 # The fields are: 

3589 # - format version (i.e. 2; no length prefix) 

3590 # - key version (integer, default is 0) 

3591 # - timestamp (integer seconds since epoch) 

3592 # - name (not encoded; assumed to be ~alphanumeric) 

3593 # - value (base64-encoded) 

3594 # - signature (hex-encoded; no length prefix) 

3595 def format_field(s: Union[str, bytes]) -> bytes: 

3596 return utf8("%d:" % len(s)) + utf8(s) 

3597 

3598 to_sign = b"|".join( 

3599 [ 

3600 b"2", 

3601 format_field(str(key_version or 0)), 

3602 format_field(timestamp), 

3603 format_field(name), 

3604 format_field(value), 

3605 b"", 

3606 ] 

3607 ) 

3608 

3609 if isinstance(secret, dict): 

3610 assert ( 

3611 key_version is not None 

3612 ), "Key version must be set when sign key dict is used" 

3613 assert version >= 2, "Version must be at least 2 for key version support" 

3614 secret = secret[key_version] 

3615 

3616 signature = _create_signature_v2(secret, to_sign) 

3617 return to_sign + signature 

3618 else: 

3619 raise ValueError("Unsupported version %d" % version) 

3620 

3621 

3622# A leading version number in decimal 

3623# with no leading zeros, followed by a pipe. 

3624_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3625 

3626 

3627def _get_version(value: bytes) -> int: 

3628 # Figures out what version value is. Version 1 did not include an 

3629 # explicit version field and started with arbitrary base64 data, 

3630 # which makes this tricky. 

3631 m = _signed_value_version_re.match(value) 

3632 if m is None: 

3633 version = 1 

3634 else: 

3635 try: 

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

3637 if version > 999: 

3638 # Certain payloads from the version-less v1 format may 

3639 # be parsed as valid integers. Due to base64 padding 

3640 # restrictions, this can only happen for numbers whose 

3641 # length is a multiple of 4, so we can treat all 

3642 # numbers up to 999 as versions, and for the rest we 

3643 # fall back to v1 format. 

3644 version = 1 

3645 except ValueError: 

3646 version = 1 

3647 return version 

3648 

3649 

3650def decode_signed_value( 

3651 secret: _CookieSecretTypes, 

3652 name: str, 

3653 value: Union[None, str, bytes], 

3654 max_age_days: float = 31, 

3655 clock: Optional[Callable[[], float]] = None, 

3656 min_version: Optional[int] = None, 

3657) -> Optional[bytes]: 

3658 if clock is None: 

3659 clock = time.time 

3660 if min_version is None: 

3661 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3662 if min_version > 2: 

3663 raise ValueError("Unsupported min_version %d" % min_version) 

3664 if not value: 

3665 return None 

3666 

3667 value = utf8(value) 

3668 version = _get_version(value) 

3669 

3670 if version < min_version: 

3671 return None 

3672 if version == 1: 

3673 assert not isinstance(secret, dict) 

3674 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3675 elif version == 2: 

3676 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3677 else: 

3678 return None 

3679 

3680 

3681def _decode_signed_value_v1( 

3682 secret: Union[str, bytes], 

3683 name: str, 

3684 value: bytes, 

3685 max_age_days: float, 

3686 clock: Callable[[], float], 

3687) -> Optional[bytes]: 

3688 parts = utf8(value).split(b"|") 

3689 if len(parts) != 3: 

3690 return None 

3691 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3692 if not hmac.compare_digest(parts[2], signature): 

3693 gen_log.warning("Invalid cookie signature %r", value) 

3694 return None 

3695 timestamp = int(parts[1]) 

3696 if timestamp < clock() - max_age_days * 86400: 

3697 gen_log.warning("Expired cookie %r", value) 

3698 return None 

3699 if timestamp > clock() + 31 * 86400: 

3700 # _cookie_signature does not hash a delimiter between the 

3701 # parts of the cookie, so an attacker could transfer trailing 

3702 # digits from the payload to the timestamp without altering the 

3703 # signature. For backwards compatibility, sanity-check timestamp 

3704 # here instead of modifying _cookie_signature. 

3705 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3706 return None 

3707 if parts[1].startswith(b"0"): 

3708 gen_log.warning("Tampered cookie %r", value) 

3709 return None 

3710 try: 

3711 return base64.b64decode(parts[0]) 

3712 except Exception: 

3713 return None 

3714 

3715 

3716def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3717 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3718 length, _, rest = s.partition(b":") 

3719 n = int(length) 

3720 field_value = rest[:n] 

3721 # In python 3, indexing bytes returns small integers; we must 

3722 # use a slice to get a byte string as in python 2. 

3723 if rest[n : n + 1] != b"|": 

3724 raise ValueError("malformed v2 signed value field") 

3725 rest = rest[n + 1 :] 

3726 return field_value, rest 

3727 

3728 rest = value[2:] # remove version number 

3729 key_version, rest = _consume_field(rest) 

3730 timestamp, rest = _consume_field(rest) 

3731 name_field, rest = _consume_field(rest) 

3732 value_field, passed_sig = _consume_field(rest) 

3733 return int(key_version), timestamp, name_field, value_field, passed_sig 

3734 

3735 

3736def _decode_signed_value_v2( 

3737 secret: _CookieSecretTypes, 

3738 name: str, 

3739 value: bytes, 

3740 max_age_days: float, 

3741 clock: Callable[[], float], 

3742) -> Optional[bytes]: 

3743 try: 

3744 ( 

3745 key_version, 

3746 timestamp_bytes, 

3747 name_field, 

3748 value_field, 

3749 passed_sig, 

3750 ) = _decode_fields_v2(value) 

3751 except ValueError: 

3752 return None 

3753 signed_string = value[: -len(passed_sig)] 

3754 

3755 if isinstance(secret, dict): 

3756 try: 

3757 secret = secret[key_version] 

3758 except KeyError: 

3759 return None 

3760 

3761 expected_sig = _create_signature_v2(secret, signed_string) 

3762 if not hmac.compare_digest(passed_sig, expected_sig): 

3763 return None 

3764 if name_field != utf8(name): 

3765 return None 

3766 timestamp = int(timestamp_bytes) 

3767 if timestamp < clock() - max_age_days * 86400: 

3768 # The signature has expired. 

3769 return None 

3770 try: 

3771 return base64.b64decode(value_field) 

3772 except Exception: 

3773 return None 

3774 

3775 

3776def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3777 value = utf8(value) 

3778 version = _get_version(value) 

3779 if version < 2: 

3780 return None 

3781 try: 

3782 key_version, _, _, _, _ = _decode_fields_v2(value) 

3783 except ValueError: 

3784 return None 

3785 

3786 return key_version 

3787 

3788 

3789def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3790 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3791 for part in parts: 

3792 hash.update(utf8(part)) 

3793 return utf8(hash.hexdigest()) 

3794 

3795 

3796def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3797 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3798 hash.update(utf8(s)) 

3799 return utf8(hash.hexdigest()) 

3800 

3801 

3802def is_absolute(path: str) -> bool: 

3803 return any(path.startswith(x) for x in ["/", "http:", "https:"])