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

1470 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]", value): 

697 # Legacy check for control characters in cookie values. This check is no longer needed 

698 # since the cookie library escapes these characters correctly now. It will be removed 

699 # in the next feature release. 

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

701 for attr_name, attr_value in [ 

702 ("name", name), 

703 ("domain", domain), 

704 ("path", path), 

705 ("samesite", samesite), 

706 ]: 

707 # Cookie attributes may not contain control characters or semicolons (except when 

708 # escaped in the value). A check for control characters was added to the http.cookies 

709 # library in a Feb 2026 security release; as of March it still does not check for 

710 # semicolons. 

711 # 

712 # When a semicolon check is added to the standard library (and the release has had time 

713 # for adoption), this check may be removed, but be mindful of the fact that this may 

714 # change the timing of the exception (to the generation of the Set-Cookie header in 

715 # flush()). We m 

716 if attr_value is not None and re.search(r"[\x00-\x20\x3b\x7f]", attr_value): 

717 raise http.cookies.CookieError( 

718 f"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}" 

719 ) 

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

721 # Also check for disallowed characters in deprecated kwargs. 

722 if re.search(r"[\x00-\x20\x3b\x7f]", str(v)): 

723 raise http.cookies.CookieError( 

724 f"Invalid cookie attribute {k}={v!r} for cookie {name!r}" 

725 ) 

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

727 self._new_cookie = ( 

728 http.cookies.SimpleCookie() 

729 ) # type: http.cookies.SimpleCookie 

730 if name in self._new_cookie: 

731 del self._new_cookie[name] 

732 self._new_cookie[name] = value 

733 morsel = self._new_cookie[name] 

734 if domain: 

735 morsel["domain"] = domain 

736 if expires_days is not None and not expires: 

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

738 days=expires_days 

739 ) 

740 if expires: 

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

742 if path: 

743 morsel["path"] = path 

744 if max_age: 

745 # Note change from _ to -. 

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

747 if httponly: 

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

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

750 morsel["httponly"] = True 

751 if secure: 

752 morsel["secure"] = True 

753 if samesite: 

754 morsel["samesite"] = samesite 

755 if kwargs: 

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

757 # kwargs for backwards compatibility until we can remove deprecated 

758 # features. 

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

760 morsel[k] = v 

761 warnings.warn( 

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

763 "(should be lowercase)", 

764 DeprecationWarning, 

765 ) 

766 

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

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

769 

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

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

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

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

774 arguments are ignored. 

775 

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

777 seen until the following request. 

778 

779 .. versionchanged:: 6.3 

780 

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

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

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

784 """ 

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

786 if excluded_arg in kwargs: 

787 raise TypeError( 

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

789 ) 

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

791 days=365 

792 ) 

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

794 

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

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

797 

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

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

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

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

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

803 when setting cookies. 

804 

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

806 until the following request. 

807 

808 .. versionchanged:: 3.2 

809 

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

811 

812 .. versionchanged:: 6.3 

813 

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

815 

816 .. deprecated:: 6.3 

817 

818 The increasingly complex rules governing cookies have made it 

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

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

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

822 """ 

823 for name in self.request.cookies: 

824 self.clear_cookie(name, **kwargs) 

825 

826 def set_signed_cookie( 

827 self, 

828 name: str, 

829 value: Union[str, bytes], 

830 expires_days: Optional[float] = 30, 

831 version: Optional[int] = None, 

832 **kwargs: Any, 

833 ) -> None: 

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

835 

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

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

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

839 

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

841 

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

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

844 parameter to `get_signed_cookie`. 

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

846 

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

848 strings (unlike regular cookies) 

849 

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

851 seen until the following request. 

852 

853 .. versionchanged:: 3.2.1 

854 

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

856 and made it the default. 

857 

858 .. versionchanged:: 6.3 

859 

860 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

863 """ 

864 self.set_cookie( 

865 name, 

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

867 expires_days=expires_days, 

868 **kwargs, 

869 ) 

870 

871 set_secure_cookie = set_signed_cookie 

872 

873 def create_signed_value( 

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

875 ) -> bytes: 

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

877 

878 Normally used via set_signed_cookie, but provided as a separate 

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

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

881 

882 .. versionchanged:: 3.2.1 

883 

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

885 and made it the default. 

886 """ 

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

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

889 key_version = None 

890 if isinstance(secret, dict): 

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

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

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

894 

895 return create_signed_value( 

896 secret, name, value, version=version, key_version=key_version 

897 ) 

898 

899 def get_signed_cookie( 

900 self, 

901 name: str, 

902 value: Optional[str] = None, 

903 max_age_days: float = 31, 

904 min_version: Optional[int] = None, 

905 ) -> Optional[bytes]: 

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

907 

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

909 `get_cookie`). 

910 

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

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

913 `set_signed_cookie` in this handler. 

914 

915 .. versionchanged:: 3.2.1 

916 

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

918 both versions 1 and 2 are accepted by default. 

919 

920 .. versionchanged:: 6.3 

921 

922 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

925 

926 """ 

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

928 if value is None: 

929 value = self.get_cookie(name) 

930 return decode_signed_value( 

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

932 name, 

933 value, 

934 max_age_days=max_age_days, 

935 min_version=min_version, 

936 ) 

937 

938 get_secure_cookie = get_signed_cookie 

939 

940 def get_signed_cookie_key_version( 

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

942 ) -> Optional[int]: 

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

944 

945 The version is returned as int. 

946 

947 .. versionchanged:: 6.3 

948 

949 Renamed from ``get_secure_cookie_key_version`` to 

950 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

952 remains as an alias. 

953 

954 """ 

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

956 if value is None: 

957 value = self.get_cookie(name) 

958 if value is None: 

959 return None 

960 return get_signature_key_version(value) 

961 

962 get_secure_cookie_key_version = get_signed_cookie_key_version 

963 

964 def redirect( 

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

966 ) -> None: 

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

968 

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

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

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

972 The default is 302 (temporary). 

973 """ 

974 if self._headers_written: 

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

976 if status is None: 

977 status = 301 if permanent else 302 

978 else: 

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

980 self.set_status(status) 

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

982 self.finish() 

983 

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

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

986 

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

988 

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

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

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

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

993 

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

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

996 wrapped in a dictionary. More details at 

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

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

999 """ 

1000 if self._finished: 

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

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

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

1004 if isinstance(chunk, list): 

1005 message += ( 

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

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

1008 ) 

1009 raise TypeError(message) 

1010 if isinstance(chunk, dict): 

1011 chunk = escape.json_encode(chunk) 

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

1013 chunk = utf8(chunk) 

1014 self._write_buffer.append(chunk) 

1015 

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

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

1018 

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

1020 after it. 

1021 

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

1023 Awaiting this `.Future` is optional. 

1024 

1025 .. versionchanged:: 5.1 

1026 

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

1028 """ 

1029 if self._finished: 

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

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

1032 

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

1034 js_embed = [] 

1035 js_files = [] 

1036 css_embed = [] 

1037 css_files = [] 

1038 html_heads = [] 

1039 html_bodies = [] 

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

1041 embed_part = module.embedded_javascript() 

1042 if embed_part: 

1043 js_embed.append(utf8(embed_part)) 

1044 file_part = module.javascript_files() 

1045 if file_part: 

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

1047 js_files.append(_unicode(file_part)) 

1048 else: 

1049 js_files.extend(file_part) 

1050 embed_part = module.embedded_css() 

1051 if embed_part: 

1052 css_embed.append(utf8(embed_part)) 

1053 file_part = module.css_files() 

1054 if file_part: 

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

1056 css_files.append(_unicode(file_part)) 

1057 else: 

1058 css_files.extend(file_part) 

1059 head_part = module.html_head() 

1060 if head_part: 

1061 html_heads.append(utf8(head_part)) 

1062 body_part = module.html_body() 

1063 if body_part: 

1064 html_bodies.append(utf8(body_part)) 

1065 

1066 if js_files: 

1067 # Maintain order of JavaScript files given by modules 

1068 js = self.render_linked_js(js_files) 

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

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

1071 if js_embed: 

1072 js_bytes = self.render_embed_js(js_embed) 

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

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

1075 if css_files: 

1076 css = self.render_linked_css(css_files) 

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

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

1079 if css_embed: 

1080 css_bytes = self.render_embed_css(css_embed) 

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

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

1083 if html_heads: 

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

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

1086 if html_bodies: 

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

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

1089 return self.finish(html) 

1090 

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

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

1093 rendered webpage. 

1094 

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

1096 """ 

1097 paths = [] 

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

1099 

1100 for path in js_files: 

1101 if not is_absolute(path): 

1102 path = self.static_url(path) 

1103 if path not in unique_paths: 

1104 paths.append(path) 

1105 unique_paths.add(path) 

1106 

1107 return "".join( 

1108 '<script src="' 

1109 + escape.xhtml_escape(p) 

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

1111 for p in paths 

1112 ) 

1113 

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

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

1116 rendered webpage. 

1117 

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

1119 """ 

1120 return ( 

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

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

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

1124 ) 

1125 

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

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

1128 rendered webpage. 

1129 

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

1131 """ 

1132 paths = [] 

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

1134 

1135 for path in css_files: 

1136 if not is_absolute(path): 

1137 path = self.static_url(path) 

1138 if path not in unique_paths: 

1139 paths.append(path) 

1140 unique_paths.add(path) 

1141 

1142 return "".join( 

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

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

1145 for p in paths 

1146 ) 

1147 

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

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

1150 rendered webpage. 

1151 

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

1153 """ 

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

1155 

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

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

1158 

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

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

1161 """ 

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

1163 template_path = self.get_template_path() 

1164 if not template_path: 

1165 frame = sys._getframe(0) 

1166 web_file = frame.f_code.co_filename 

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

1168 frame = frame.f_back 

1169 assert frame.f_code.co_filename is not None 

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

1171 with RequestHandler._template_loader_lock: 

1172 if template_path not in RequestHandler._template_loaders: 

1173 loader = self.create_template_loader(template_path) 

1174 RequestHandler._template_loaders[template_path] = loader 

1175 else: 

1176 loader = RequestHandler._template_loaders[template_path] 

1177 t = loader.load(template_name) 

1178 namespace = self.get_template_namespace() 

1179 namespace.update(kwargs) 

1180 return t.generate(**namespace) 

1181 

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

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

1184 

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

1186 

1187 The results of this method will be combined with additional 

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

1189 to `render` or `render_string`. 

1190 """ 

1191 namespace = dict( 

1192 handler=self, 

1193 request=self.request, 

1194 current_user=self.current_user, 

1195 locale=self.locale, 

1196 _=self.locale.translate, 

1197 pgettext=self.locale.pgettext, 

1198 static_url=self.static_url, 

1199 xsrf_form_html=self.xsrf_form_html, 

1200 reverse_url=self.reverse_url, 

1201 ) 

1202 namespace.update(self.ui) 

1203 return namespace 

1204 

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

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

1207 

1208 May be overridden by subclasses. By default returns a 

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

1210 ``autoescape`` and ``template_whitespace`` application 

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

1212 supplied, uses that instead. 

1213 """ 

1214 settings = self.application.settings 

1215 if "template_loader" in settings: 

1216 return settings["template_loader"] 

1217 kwargs = {} 

1218 if "autoescape" in settings: 

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

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

1221 kwargs["autoescape"] = settings["autoescape"] 

1222 if "template_whitespace" in settings: 

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

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

1225 

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

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

1228 

1229 .. versionchanged:: 4.0 

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

1231 

1232 .. versionchanged:: 6.0 

1233 

1234 The ``callback`` argument was removed. 

1235 """ 

1236 assert self.request.connection is not None 

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

1238 self._write_buffer = [] 

1239 if not self._headers_written: 

1240 self._headers_written = True 

1241 for transform in self._transforms: 

1242 assert chunk is not None 

1243 ( 

1244 self._status_code, 

1245 self._headers, 

1246 chunk, 

1247 ) = transform.transform_first_chunk( 

1248 self._status_code, self._headers, chunk, include_footers 

1249 ) 

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

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

1252 chunk = b"" 

1253 

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

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

1256 # is sent). 

1257 if hasattr(self, "_new_cookie"): 

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

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

1260 

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

1262 return self.request.connection.write_headers( 

1263 start_line, self._headers, chunk 

1264 ) 

1265 else: 

1266 for transform in self._transforms: 

1267 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1271 else: 

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

1273 future.set_result(None) 

1274 return future 

1275 

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

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

1278 

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

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

1281 

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

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

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

1285 data can be sent. 

1286 

1287 .. versionchanged:: 5.1 

1288 

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

1290 """ 

1291 if self._finished: 

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

1293 

1294 if chunk is not None: 

1295 self.write(chunk) 

1296 

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

1298 # we have not flushed any content yet. 

1299 if not self._headers_written: 

1300 if ( 

1301 self._status_code == 200 

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

1303 and "Etag" not in self._headers 

1304 ): 

1305 self.set_etag_header() 

1306 if self.check_etag_header(): 

1307 self._write_buffer = [] 

1308 self.set_status(304) 

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

1310 assert not self._write_buffer, ( 

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

1312 ) 

1313 self._clear_representation_headers() 

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

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

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

1317 

1318 assert self.request.connection is not None 

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

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

1321 # garbage collection of the RequestHandler when there 

1322 # are keepalive connections) 

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

1324 

1325 future = self.flush(include_footers=True) 

1326 self.request.connection.finish() 

1327 self._log() 

1328 self._finished = True 

1329 self.on_finish() 

1330 self._break_cycles() 

1331 return future 

1332 

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

1334 """Take control of the underlying stream. 

1335 

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

1337 further HTTP processing. Intended for implementing protocols 

1338 like websockets that tunnel over an HTTP handshake. 

1339 

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

1341 

1342 .. versionadded:: 5.1 

1343 """ 

1344 self._finished = True 

1345 # TODO: add detach to HTTPConnection? 

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

1347 

1348 def _break_cycles(self) -> None: 

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

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

1351 self.ui = None # type: ignore 

1352 

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

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

1355 

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

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

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

1359 and replaced with the error page. 

1360 

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

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

1363 """ 

1364 if self._headers_written: 

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

1366 if not self._finished: 

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

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

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

1370 # socket. 

1371 try: 

1372 self.finish() 

1373 except Exception: 

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

1375 return 

1376 self.clear() 

1377 

1378 reason = kwargs.get("reason") 

1379 if "exc_info" in kwargs: 

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

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

1382 reason = exception.reason 

1383 self.set_status(status_code, reason=reason) 

1384 try: 

1385 if status_code != 304: 

1386 self.write_error(status_code, **kwargs) 

1387 except Exception: 

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

1389 if not self._finished: 

1390 self.finish() 

1391 

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

1393 """Override to implement custom error pages. 

1394 

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

1396 to produce output as usual. 

1397 

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

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

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

1401 the "current" exception for purposes of methods like 

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

1403 """ 

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

1405 # in debug mode, try to send a traceback 

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

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

1408 self.write(line) 

1409 self.finish() 

1410 else: 

1411 self.finish( 

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

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

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

1415 ) 

1416 

1417 @property 

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

1419 """The locale for the current session. 

1420 

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

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

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

1424 header. 

1425 

1426 .. versionchanged: 4.1 

1427 Added a property setter. 

1428 """ 

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

1430 loc = self.get_user_locale() 

1431 if loc is not None: 

1432 self._locale = loc 

1433 else: 

1434 self._locale = self.get_browser_locale() 

1435 assert self._locale 

1436 return self._locale 

1437 

1438 @locale.setter 

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

1440 self._locale = value 

1441 

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

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

1444 

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

1446 

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

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

1449 """ 

1450 return None 

1451 

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

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

1454 

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

1456 """ 

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

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

1459 locales = [] 

1460 for language in languages: 

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

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

1463 try: 

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

1465 if score < 0: 

1466 raise ValueError() 

1467 except (ValueError, TypeError): 

1468 score = 0.0 

1469 else: 

1470 score = 1.0 

1471 if score > 0: 

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

1473 if locales: 

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

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

1476 return locale.get(*codes) 

1477 return locale.get(default) 

1478 

1479 @property 

1480 def current_user(self) -> Any: 

1481 """The authenticated user for this request. 

1482 

1483 This is set in one of two ways: 

1484 

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

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

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

1488 and is cached for future access:: 

1489 

1490 def get_current_user(self): 

1491 user_cookie = self.get_signed_cookie("user") 

1492 if user_cookie: 

1493 return json.loads(user_cookie) 

1494 return None 

1495 

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

1497 `prepare()`:: 

1498 

1499 @gen.coroutine 

1500 def prepare(self): 

1501 user_id_cookie = self.get_signed_cookie("user_id") 

1502 if user_id_cookie: 

1503 self.current_user = yield load_user(user_id_cookie) 

1504 

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

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

1507 asynchronous operations. 

1508 

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

1510 """ 

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

1512 self._current_user = self.get_current_user() 

1513 return self._current_user 

1514 

1515 @current_user.setter 

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

1517 self._current_user = value 

1518 

1519 def get_current_user(self) -> Any: 

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

1521 

1522 This method may not be a coroutine. 

1523 """ 

1524 return None 

1525 

1526 def get_login_url(self) -> str: 

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

1528 

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

1530 """ 

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

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

1533 

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

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

1536 

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

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

1539 """ 

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

1541 

1542 @property 

1543 def xsrf_token(self) -> bytes: 

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

1545 

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

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

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

1549 as a potential forgery. 

1550 

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

1552 

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

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

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

1556 UTF-8. 

1557 

1558 .. versionchanged:: 3.2.2 

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

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

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

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

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

1564 unless the ``xsrf_cookie_version`` `Application` setting is 

1565 set to 1. 

1566 

1567 .. versionchanged:: 4.3 

1568 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1569 used to supply additional cookie options (which will be 

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

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

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

1573 ``_xsrf`` cookie. 

1574 """ 

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

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

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

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

1579 if output_version == 1: 

1580 self._xsrf_token = binascii.b2a_hex(token) 

1581 elif output_version == 2: 

1582 mask = os.urandom(4) 

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

1584 [ 

1585 b"2", 

1586 binascii.b2a_hex(mask), 

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

1588 utf8(str(int(timestamp))), 

1589 ] 

1590 ) 

1591 else: 

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

1593 if version is None: 

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

1595 cookie_kwargs["expires_days"] = 30 

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

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

1598 return self._xsrf_token 

1599 

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

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

1602 

1603 The raw_xsrf_token is a tuple containing: 

1604 

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

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

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

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

1609 for version 1 cookies) 

1610 """ 

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

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

1613 cookie = self.get_cookie(cookie_name) 

1614 if cookie: 

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

1616 else: 

1617 version, token, timestamp = None, None, None 

1618 if token is None: 

1619 version = None 

1620 token = os.urandom(16) 

1621 timestamp = time.time() 

1622 assert token is not None 

1623 assert timestamp is not None 

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

1625 return self._raw_xsrf_token 

1626 

1627 def _decode_xsrf_token( 

1628 self, cookie: str 

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

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

1631 _get_raw_xsrf_token. 

1632 """ 

1633 

1634 try: 

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

1636 

1637 if m: 

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

1639 if version == 2: 

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

1641 

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

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

1644 timestamp = int(timestamp_str) 

1645 return version, token, timestamp 

1646 else: 

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

1648 raise Exception("Unknown xsrf cookie version") 

1649 else: 

1650 version = 1 

1651 try: 

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

1653 except (binascii.Error, TypeError): 

1654 token = utf8(cookie) 

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

1656 timestamp = int(time.time()) 

1657 return (version, token, timestamp) 

1658 except Exception: 

1659 # Catch exceptions and return nothing instead of failing. 

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

1661 return None, None, None 

1662 

1663 def check_xsrf_cookie(self) -> None: 

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

1665 

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

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

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

1669 reject the form submission as a potential forgery. 

1670 

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

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

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

1674 

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

1676 

1677 .. versionchanged:: 3.2.2 

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

1679 supported. 

1680 """ 

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

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

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

1684 # information please see 

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

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

1687 input_token = ( 

1688 self.get_argument("_xsrf", None) 

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

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

1691 ) 

1692 if not input_token: 

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

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

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

1696 if not token: 

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

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

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

1700 

1701 def xsrf_form_html(self) -> str: 

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

1703 

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

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

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

1707 HTML within all of your HTML forms. 

1708 

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

1710 xsrf_form_html() %}`` 

1711 

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

1713 """ 

1714 return ( 

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

1716 + escape.xhtml_escape(self.xsrf_token) 

1717 + '"/>' 

1718 ) 

1719 

1720 def static_url( 

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

1722 ) -> str: 

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

1724 

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

1726 application (which specifies the root directory of your static 

1727 files). 

1728 

1729 This method returns a versioned url (by default appending 

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

1731 cached indefinitely. This can be disabled by passing 

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

1733 other static file implementations are not required to support 

1734 this, but they may support other options). 

1735 

1736 By default this method returns URLs relative to the current 

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

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

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

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

1741 

1742 """ 

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

1744 get_url = self.settings.get( 

1745 "static_handler_class", StaticFileHandler 

1746 ).make_static_url 

1747 

1748 if include_host is None: 

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

1750 

1751 if include_host: 

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

1753 else: 

1754 base = "" 

1755 

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

1757 

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

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

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

1761 raise Exception( 

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

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

1764 ) 

1765 

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

1767 """Alias for `Application.reverse_url`.""" 

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

1769 

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

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

1772 

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

1774 

1775 May be overridden to provide custom etag implementations, 

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

1777 """ 

1778 hasher = hashlib.sha1() 

1779 for part in self._write_buffer: 

1780 hasher.update(part) 

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

1782 

1783 def set_etag_header(self) -> None: 

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

1785 

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

1787 

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

1789 """ 

1790 etag = self.compute_etag() 

1791 if etag is not None: 

1792 self.set_header("Etag", etag) 

1793 

1794 def check_etag_header(self) -> bool: 

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

1796 

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

1798 returned. For example:: 

1799 

1800 self.set_etag_header() 

1801 if self.check_etag_header(): 

1802 self.set_status(304) 

1803 return 

1804 

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

1806 but may be called earlier for applications that override 

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

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

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

1810 """ 

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

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

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

1814 etags = re.findall( 

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

1816 ) 

1817 if not computed_etag or not etags: 

1818 return False 

1819 

1820 match = False 

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

1822 match = True 

1823 else: 

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

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

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

1827 

1828 for etag in etags: 

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

1830 match = True 

1831 break 

1832 return match 

1833 

1834 async def _execute( 

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

1836 ) -> None: 

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

1838 self._transforms = transforms 

1839 try: 

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

1841 raise HTTPError(405) 

1842 

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

1844 if not _has_stream_request_body(self.__class__): 

1845 try: 

1846 self.request._parse_body() 

1847 except httputil.HTTPInputError as e: 

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

1849 

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

1851 self.path_kwargs = { 

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

1853 } 

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

1855 # the proper cookie 

1856 if self.request.method not in ( 

1857 "GET", 

1858 "HEAD", 

1859 "OPTIONS", 

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

1861 self.check_xsrf_cookie() 

1862 

1863 result = self.prepare() 

1864 if result is not None: 

1865 result = await result # type: ignore 

1866 if self._prepared_future is not None: 

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

1868 # and are ready for the body to arrive. 

1869 future_set_result_unless_cancelled(self._prepared_future, None) 

1870 if self._finished: 

1871 return 

1872 

1873 if _has_stream_request_body(self.__class__): 

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

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

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

1877 # instead. 

1878 try: 

1879 await self.request._body_future 

1880 except iostream.StreamClosedError: 

1881 return 

1882 

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

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

1885 if result is not None: 

1886 result = await result 

1887 if self._auto_finish and not self._finished: 

1888 self.finish() 

1889 except Exception as e: 

1890 try: 

1891 self._handle_request_exception(e) 

1892 except Exception: 

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

1894 finally: 

1895 # Unset result to avoid circular references 

1896 result = None 

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

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

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

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

1901 self._prepared_future.set_result(None) 

1902 

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

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

1905 

1906 Requires the `.stream_request_body` decorator. 

1907 

1908 May be a coroutine for flow control. 

1909 """ 

1910 raise NotImplementedError() 

1911 

1912 def _log(self) -> None: 

1913 """Logs the current request. 

1914 

1915 Sort of deprecated since this functionality was moved to the 

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

1917 that have overridden this method. 

1918 """ 

1919 self.application.log_request(self) 

1920 

1921 def _request_summary(self) -> str: 

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

1923 self.request.method, 

1924 self.request.uri, 

1925 self.request.remote_ip, 

1926 ) 

1927 

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

1929 if isinstance(e, Finish): 

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

1931 if not self._finished: 

1932 self.finish(*e.args) 

1933 return 

1934 try: 

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

1936 except Exception: 

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

1938 # to avoid leaking the connection. 

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

1940 if self._finished: 

1941 # Extra errors after the request has been finished should 

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

1943 # send a response. 

1944 return 

1945 if isinstance(e, HTTPError): 

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

1947 else: 

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

1949 

1950 def log_exception( 

1951 self, 

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

1953 value: Optional[BaseException], 

1954 tb: Optional[TracebackType], 

1955 ) -> None: 

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

1957 

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

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

1960 other exceptions as errors with stack traces (on the 

1961 ``tornado.application`` logger). 

1962 

1963 .. versionadded:: 3.1 

1964 """ 

1965 if isinstance(value, HTTPError): 

1966 log_message = value.get_message() 

1967 if log_message: 

1968 format = "%d %s: %s" 

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

1970 gen_log.warning(format, *args) 

1971 else: 

1972 app_log.error( 

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

1974 self._request_summary(), 

1975 self.request, 

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

1977 ) 

1978 

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

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

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

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

1983 if name not in self._active_modules: 

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

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

1986 return _unicode(rendered) 

1987 

1988 return render 

1989 

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

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

1992 

1993 def _clear_representation_headers(self) -> None: 

1994 # 304 responses should not contain representation metadata 

1995 # headers (defined in 

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

1997 # not explicitly allowed by 

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

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

2000 for h in headers: 

2001 self.clear_header(h) 

2002 

2003 

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

2005 

2006 

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

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

2009 

2010 This decorator implies the following changes: 

2011 

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

2013 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

2021 until those futures have completed. 

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

2023 the entire body has been read. 

2024 

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

2026 for example usage. 

2027 """ # noqa: E501 

2028 if not issubclass(cls, RequestHandler): 

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

2030 cls._stream_request_body = True 

2031 return cls 

2032 

2033 

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

2035 if not issubclass(cls, RequestHandler): 

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

2037 return cls._stream_request_body 

2038 

2039 

2040def removeslash( 

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

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

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

2044 

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

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

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

2048 """ 

2049 

2050 @functools.wraps(method) 

2051 def wrapper( # type: ignore 

2052 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

2058 if self.request.query: 

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

2060 self.redirect(uri, permanent=True) 

2061 return None 

2062 else: 

2063 raise HTTPError(404) 

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

2065 

2066 return wrapper 

2067 

2068 

2069def addslash( 

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

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

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

2073 

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

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

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

2077 """ 

2078 

2079 @functools.wraps(method) 

2080 def wrapper( # type: ignore 

2081 self: RequestHandler, *args, **kwargs 

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

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

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

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

2086 if self.request.query: 

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

2088 self.redirect(uri, permanent=True) 

2089 return None 

2090 raise HTTPError(404) 

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

2092 

2093 return wrapper 

2094 

2095 

2096class _ApplicationRouter(ReversibleRuleRouter): 

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

2098 

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

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

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

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

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

2104 `_ApplicationRouter` instance. 

2105 """ 

2106 

2107 def __init__( 

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

2109 ) -> None: 

2110 assert isinstance(application, Application) 

2111 self.application = application 

2112 super().__init__(rules) 

2113 

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

2115 rule = super().process_rule(rule) 

2116 

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

2118 rule.target = _ApplicationRouter( 

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

2120 ) 

2121 

2122 return rule 

2123 

2124 def get_target_delegate( 

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

2126 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

2128 return self.application.get_handler_delegate( 

2129 request, target, **target_params 

2130 ) 

2131 

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

2133 

2134 

2135class Application(ReversibleRouter): 

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

2137 

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

2139 HTTPServer to serve the application:: 

2140 

2141 application = web.Application([ 

2142 (r"/", MainPageHandler), 

2143 ]) 

2144 http_server = httpserver.HTTPServer(application) 

2145 http_server.listen(8080) 

2146 

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

2148 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2153 

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

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

2156 

2157 application = web.Application([ 

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

2159 (r"/", MainPageHandler), 

2160 (r"/feed", FeedHandler), 

2161 ]), 

2162 ]) 

2163 

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

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

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

2167 

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

2169 instantiate an instance of the first request class whose regexp 

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

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

2172 

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

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

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

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

2177 `StaticFileHandler` can be installed automatically with the 

2178 static_path setting described below):: 

2179 

2180 application = web.Application([ 

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

2182 ]) 

2183 

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

2185 a host regular expression as the first argument:: 

2186 

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

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

2189 ]) 

2190 

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

2192 parameter value is matched against host regular expressions. 

2193 

2194 

2195 .. warning:: 

2196 

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

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

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

2200 other private networks. Appropriate host patterns must be used 

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

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

2203 may be vulnerable to DNS rebinding. 

2204 

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

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

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

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

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

2210 `StaticFileHandler` can be specified with the 

2211 ``static_handler_class`` setting. 

2212 

2213 .. versionchanged:: 4.5 

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

2215 

2216 """ 

2217 

2218 def __init__( 

2219 self, 

2220 handlers: Optional[_RuleList] = None, 

2221 default_host: Optional[str] = None, 

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

2223 **settings: Any, 

2224 ) -> None: 

2225 if transforms is None: 

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

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

2228 self.transforms.append(GZipContentEncoding) 

2229 else: 

2230 self.transforms = transforms 

2231 self.default_host = default_host 

2232 self.settings = settings 

2233 self.ui_modules = { 

2234 "linkify": _linkify, 

2235 "xsrf_form_html": _xsrf_form_html, 

2236 "Template": TemplateModule, 

2237 } 

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

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

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

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

2242 path = self.settings["static_path"] 

2243 handlers = list(handlers or []) 

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

2245 static_handler_class = settings.get( 

2246 "static_handler_class", StaticFileHandler 

2247 ) 

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

2249 static_handler_args["path"] = path 

2250 for pattern in [ 

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

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

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

2254 ]: 

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

2256 

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

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

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

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

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

2262 

2263 self.wildcard_router = _ApplicationRouter(self, handlers) 

2264 self.default_router = _ApplicationRouter( 

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

2266 ) 

2267 

2268 # Automatically reload modified modules 

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

2270 from tornado import autoreload 

2271 

2272 autoreload.start() 

2273 

2274 def listen( 

2275 self, 

2276 port: int, 

2277 address: Optional[str] = None, 

2278 *, 

2279 family: socket.AddressFamily = socket.AF_UNSPEC, 

2280 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2281 flags: Optional[int] = None, 

2282 reuse_port: bool = False, 

2283 **kwargs: Any, 

2284 ) -> HTTPServer: 

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

2286 

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

2288 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2293 

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

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

2296 the server. 

2297 

2298 Returns the `.HTTPServer` object. 

2299 

2300 .. versionchanged:: 4.3 

2301 Now returns the `.HTTPServer` object. 

2302 

2303 .. versionchanged:: 6.2 

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

2305 including ``reuse_port``. 

2306 """ 

2307 server = HTTPServer(self, **kwargs) 

2308 server.listen( 

2309 port, 

2310 address=address, 

2311 family=family, 

2312 backlog=backlog, 

2313 flags=flags, 

2314 reuse_port=reuse_port, 

2315 ) 

2316 return server 

2317 

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

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

2320 

2321 Host patterns are processed sequentially in the order they were 

2322 added. All matching patterns will be considered. 

2323 """ 

2324 host_matcher = HostMatches(host_pattern) 

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

2326 

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

2328 

2329 if self.default_host is not None: 

2330 self.wildcard_router.add_rules( 

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

2332 ) 

2333 

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

2335 self.transforms.append(transform_class) 

2336 

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

2338 if isinstance(methods, types.ModuleType): 

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

2340 elif isinstance(methods, list): 

2341 for m in methods: 

2342 self._load_ui_methods(m) 

2343 else: 

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

2345 if ( 

2346 not name.startswith("_") 

2347 and hasattr(fn, "__call__") 

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

2349 ): 

2350 self.ui_methods[name] = fn 

2351 

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

2353 if isinstance(modules, types.ModuleType): 

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

2355 elif isinstance(modules, list): 

2356 for m in modules: 

2357 self._load_ui_modules(m) 

2358 else: 

2359 assert isinstance(modules, dict) 

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

2361 try: 

2362 if issubclass(cls, UIModule): 

2363 self.ui_modules[name] = cls 

2364 except TypeError: 

2365 pass 

2366 

2367 def __call__( 

2368 self, request: httputil.HTTPServerRequest 

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

2370 # Legacy HTTPServer interface 

2371 dispatcher = self.find_handler(request) 

2372 return dispatcher.execute() 

2373 

2374 def find_handler( 

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

2376 ) -> "_HandlerDelegate": 

2377 route = self.default_router.find_handler(request) 

2378 if route is not None: 

2379 return cast("_HandlerDelegate", route) 

2380 

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

2382 return self.get_handler_delegate( 

2383 request, 

2384 self.settings["default_handler_class"], 

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

2386 ) 

2387 

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

2389 

2390 def get_handler_delegate( 

2391 self, 

2392 request: httputil.HTTPServerRequest, 

2393 target_class: Type[RequestHandler], 

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

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

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

2397 ) -> "_HandlerDelegate": 

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

2399 for application and `RequestHandler` subclass. 

2400 

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

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

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

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

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

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

2407 """ 

2408 return _HandlerDelegate( 

2409 self, request, target_class, target_kwargs, path_args, path_kwargs 

2410 ) 

2411 

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

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

2414 

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

2416 

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

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

2419 and url-escaped. 

2420 """ 

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

2422 if reversed_url is not None: 

2423 return reversed_url 

2424 

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

2426 

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

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

2429 

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

2431 this behavior either subclass Application and override this method, 

2432 or pass a function in the application settings dictionary as 

2433 ``log_function``. 

2434 """ 

2435 if "log_function" in self.settings: 

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

2437 return 

2438 if handler.get_status() < 400: 

2439 log_method = access_log.info 

2440 elif handler.get_status() < 500: 

2441 log_method = access_log.warning 

2442 else: 

2443 log_method = access_log.error 

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

2445 log_method( 

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

2447 handler.get_status(), 

2448 handler._request_summary(), 

2449 request_time, 

2450 ) 

2451 

2452 

2453class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2454 def __init__( 

2455 self, 

2456 application: Application, 

2457 request: httputil.HTTPServerRequest, 

2458 handler_class: Type[RequestHandler], 

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

2460 path_args: Optional[List[bytes]], 

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

2462 ) -> None: 

2463 self.application = application 

2464 self.connection = request.connection 

2465 self.request = request 

2466 self.handler_class = handler_class 

2467 self.handler_kwargs = handler_kwargs or {} 

2468 self.path_args = path_args or [] 

2469 self.path_kwargs = path_kwargs or {} 

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

2471 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2472 

2473 def headers_received( 

2474 self, 

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

2476 headers: httputil.HTTPHeaders, 

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

2478 if self.stream_request_body: 

2479 self.request._body_future = Future() 

2480 return self.execute() 

2481 return None 

2482 

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

2484 if self.stream_request_body: 

2485 return self.handler.data_received(data) 

2486 else: 

2487 self.chunks.append(data) 

2488 return None 

2489 

2490 def finish(self) -> None: 

2491 if self.stream_request_body: 

2492 future_set_result_unless_cancelled(self.request._body_future, None) 

2493 else: 

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

2495 # the right exception handler scope. 

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

2497 self.execute() 

2498 

2499 def on_connection_close(self) -> None: 

2500 if self.stream_request_body: 

2501 self.handler.on_connection_close() 

2502 else: 

2503 self.chunks = None # type: ignore 

2504 

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

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

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

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

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

2510 with RequestHandler._template_loader_lock: 

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

2512 loader.reset() 

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

2514 static_handler_class = self.application.settings.get( 

2515 "static_handler_class", StaticFileHandler 

2516 ) 

2517 static_handler_class.reset() 

2518 

2519 self.handler = self.handler_class( 

2520 self.application, self.request, **self.handler_kwargs 

2521 ) 

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

2523 

2524 if self.stream_request_body: 

2525 self.handler._prepared_future = Future() 

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

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

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

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

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

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

2532 # with WSGI) 

2533 fut = gen.convert_yielded( 

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

2535 ) 

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

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

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

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

2540 return self.handler._prepared_future 

2541 

2542 

2543class HTTPError(Exception): 

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

2545 

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

2547 `RequestHandler.send_error` since it automatically ends the 

2548 current function. 

2549 

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

2551 `RequestHandler.write_error`. 

2552 

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

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

2555 keyword argument is given. 

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

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

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

2559 in with remaining positional parameters. 

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

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

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

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

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

2565 error message. 

2566 """ 

2567 

2568 def __init__( 

2569 self, 

2570 status_code: int = 500, 

2571 log_message: Optional[str] = None, 

2572 *args: Any, 

2573 **kwargs: Any, 

2574 ) -> None: 

2575 self.status_code = status_code 

2576 self._log_message = log_message 

2577 self.args = args 

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

2579 

2580 @property 

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

2582 """ 

2583 A backwards compatible way of accessing log_message. 

2584 """ 

2585 if self._log_message and not self.args: 

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

2587 return self._log_message 

2588 

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

2590 if self._log_message and self.args: 

2591 return self._log_message % self.args 

2592 return self._log_message 

2593 

2594 def __str__(self) -> str: 

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

2596 self.status_code, 

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

2598 ) 

2599 log_message = self.get_message() 

2600 if log_message: 

2601 return message + " (" + log_message + ")" 

2602 else: 

2603 return message 

2604 

2605 

2606class Finish(Exception): 

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

2608 

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

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

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

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

2613 

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

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

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

2617 

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

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

2620 

2621 if self.current_user is None: 

2622 self.set_status(401) 

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

2624 raise Finish() 

2625 

2626 .. versionchanged:: 4.3 

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

2628 `RequestHandler.finish`. 

2629 """ 

2630 

2631 pass 

2632 

2633 

2634class MissingArgumentError(HTTPError): 

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

2636 

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

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

2639 

2640 .. versionadded:: 3.1 

2641 """ 

2642 

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

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

2645 self.arg_name = arg_name 

2646 

2647 

2648class ErrorHandler(RequestHandler): 

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

2650 

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

2652 self.set_status(status_code) 

2653 

2654 def prepare(self) -> None: 

2655 raise HTTPError(self._status_code) 

2656 

2657 def check_xsrf_cookie(self) -> None: 

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

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

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

2661 pass 

2662 

2663 

2664class RedirectHandler(RequestHandler): 

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

2666 

2667 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2668 

2669 application = web.Application([ 

2670 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2671 ]) 

2672 

2673 `RedirectHandler` supports regular expression substitutions. E.g., to 

2674 swap the first and second parts of a path while preserving the remainder:: 

2675 

2676 application = web.Application([ 

2677 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2678 ]) 

2679 

2680 The final URL is formatted with `str.format` and the substrings that match 

2681 the capturing groups. In the above example, a request to "/a/b/c" would be 

2682 formatted like:: 

2683 

2684 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2685 

2686 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2687 values are substituted. 

2688 

2689 .. versionchanged:: 4.5 

2690 Added support for substitutions into the destination URL. 

2691 

2692 .. versionchanged:: 5.0 

2693 If any query arguments are present, they will be copied to the 

2694 destination URL. 

2695 """ 

2696 

2697 def initialize(self, url: str, permanent: bool = True) -> None: 

2698 self._url = url 

2699 self._permanent = permanent 

2700 

2701 def get(self, *args: Any, **kwargs: Any) -> None: 

2702 to_url = self._url.format(*args, **kwargs) 

2703 if self.request.query_arguments: 

2704 # TODO: figure out typing for the next line. 

2705 to_url = httputil.url_concat( 

2706 to_url, 

2707 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2708 ) 

2709 self.redirect(to_url, permanent=self._permanent) 

2710 

2711 

2712class StaticFileHandler(RequestHandler): 

2713 """A simple handler that can serve static content from a directory. 

2714 

2715 A `StaticFileHandler` is configured automatically if you pass the 

2716 ``static_path`` keyword argument to `Application`. This handler 

2717 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2718 and ``static_handler_args`` settings. 

2719 

2720 To map an additional path to this handler for a static data directory 

2721 you would add a line to your application like:: 

2722 

2723 application = web.Application([ 

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

2725 ]) 

2726 

2727 The handler constructor requires a ``path`` argument, which specifies the 

2728 local root directory of the content to be served. 

2729 

2730 Note that a capture group in the regex is required to parse the value for 

2731 the ``path`` argument to the get() method (different than the constructor 

2732 argument above); see `URLSpec` for details. 

2733 

2734 To serve a file like ``index.html`` automatically when a directory is 

2735 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2736 in your application settings, or add ``default_filename`` as an initializer 

2737 argument for your ``StaticFileHandler``. 

2738 

2739 To maximize the effectiveness of browser caching, this class supports 

2740 versioned urls (by default using the argument ``?v=``). If a version 

2741 is given, we instruct the browser to cache this file indefinitely. 

2742 `make_static_url` (also available as `RequestHandler.static_url`) can 

2743 be used to construct a versioned url. 

2744 

2745 This handler is intended primarily for use in development and light-duty 

2746 file serving; for heavy traffic it will be more efficient to use 

2747 a dedicated static file server (such as nginx or Apache). We support 

2748 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2749 some browsers require this functionality to be present to seek in 

2750 HTML5 audio or video). 

2751 

2752 **Subclassing notes** 

2753 

2754 This class is designed to be extensible by subclassing, but because 

2755 of the way static urls are generated with class methods rather than 

2756 instance methods, the inheritance patterns are somewhat unusual. 

2757 Be sure to use the ``@classmethod`` decorator when overriding a 

2758 class method. Instance methods may use the attributes ``self.path`` 

2759 ``self.absolute_path``, and ``self.modified``. 

2760 

2761 Subclasses should only override methods discussed in this section; 

2762 overriding other methods is error-prone. Overriding 

2763 ``StaticFileHandler.get`` is particularly problematic due to the 

2764 tight coupling with ``compute_etag`` and other methods. 

2765 

2766 To change the way static urls are generated (e.g. to match the behavior 

2767 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2768 `get_cache_time`, and/or `get_version`. 

2769 

2770 To replace all interaction with the filesystem (e.g. to serve 

2771 static content from a database), override `get_content`, 

2772 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2773 `validate_absolute_path`. 

2774 

2775 .. versionchanged:: 3.1 

2776 Many of the methods for subclasses were added in Tornado 3.1. 

2777 """ 

2778 

2779 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2780 

2781 _static_hashes = {} # type: Dict[str, Optional[str]] 

2782 _lock = threading.Lock() # protects _static_hashes 

2783 

2784 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2785 self.root = path 

2786 self.default_filename = default_filename 

2787 

2788 @classmethod 

2789 def reset(cls) -> None: 

2790 with cls._lock: 

2791 cls._static_hashes = {} 

2792 

2793 def head(self, path: str) -> Awaitable[None]: 

2794 return self.get(path, include_body=False) 

2795 

2796 async def get(self, path: str, include_body: bool = True) -> None: 

2797 # Set up our path instance variables. 

2798 self.path = self.parse_url_path(path) 

2799 del path # make sure we don't refer to path instead of self.path again 

2800 absolute_path = self.get_absolute_path(self.root, self.path) 

2801 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2802 if self.absolute_path is None: 

2803 return 

2804 

2805 self.modified = self.get_modified_time() 

2806 self.set_headers() 

2807 

2808 if self.should_return_304(): 

2809 self.set_status(304) 

2810 return 

2811 

2812 request_range = None 

2813 range_header = self.request.headers.get("Range") 

2814 if range_header: 

2815 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2816 # the request will be treated as if the header didn't exist. 

2817 request_range = httputil._parse_request_range(range_header) 

2818 

2819 size = self.get_content_size() 

2820 if request_range: 

2821 start, end = request_range 

2822 if start is not None and start < 0: 

2823 start += size 

2824 if start < 0: 

2825 start = 0 

2826 if ( 

2827 start is not None 

2828 and (start >= size or (end is not None and start >= end)) 

2829 ) or end == 0: 

2830 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2831 # the first requested byte is equal to or greater than the 

2832 # content, or when a suffix with length 0 is specified. 

2833 # https://tools.ietf.org/html/rfc7233#section-2.1 

2834 # A byte-range-spec is invalid if the last-byte-pos value is present 

2835 # and less than the first-byte-pos. 

2836 self.set_status(416) # Range Not Satisfiable 

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

2838 self.set_header("Content-Range", f"bytes */{size}") 

2839 return 

2840 if end is not None and end > size: 

2841 # Clients sometimes blindly use a large range to limit their 

2842 # download size; cap the endpoint at the actual file size. 

2843 end = size 

2844 # Note: only return HTTP 206 if less than the entire range has been 

2845 # requested. Not only is this semantically correct, but Chrome 

2846 # refuses to play audio if it gets an HTTP 206 in response to 

2847 # ``Range: bytes=0-``. 

2848 if size != (end or size) - (start or 0): 

2849 self.set_status(206) # Partial Content 

2850 self.set_header( 

2851 "Content-Range", httputil._get_content_range(start, end, size) 

2852 ) 

2853 else: 

2854 start = end = None 

2855 

2856 if start is not None and end is not None: 

2857 content_length = end - start 

2858 elif end is not None: 

2859 content_length = end 

2860 elif start is not None: 

2861 content_length = size - start 

2862 else: 

2863 content_length = size 

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

2865 

2866 if include_body: 

2867 content = self.get_content(self.absolute_path, start, end) 

2868 if isinstance(content, bytes): 

2869 content = [content] 

2870 for chunk in content: 

2871 try: 

2872 self.write(chunk) 

2873 await self.flush() 

2874 except iostream.StreamClosedError: 

2875 return 

2876 else: 

2877 assert self.request.method == "HEAD" 

2878 

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

2880 """Sets the ``Etag`` header based on static url version. 

2881 

2882 This allows efficient ``If-None-Match`` checks against cached 

2883 versions, and sends the correct ``Etag`` for a partial response 

2884 (i.e. the same ``Etag`` as the full file). 

2885 

2886 .. versionadded:: 3.1 

2887 """ 

2888 assert self.absolute_path is not None 

2889 version_hash = self._get_cached_version(self.absolute_path) 

2890 if not version_hash: 

2891 return None 

2892 return f'"{version_hash}"' 

2893 

2894 def set_headers(self) -> None: 

2895 """Sets the content and caching headers on the response. 

2896 

2897 .. versionadded:: 3.1 

2898 """ 

2899 self.set_header("Accept-Ranges", "bytes") 

2900 self.set_etag_header() 

2901 

2902 if self.modified is not None: 

2903 self.set_header("Last-Modified", self.modified) 

2904 

2905 content_type = self.get_content_type() 

2906 if content_type: 

2907 self.set_header("Content-Type", content_type) 

2908 

2909 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2910 if cache_time > 0: 

2911 self.set_header( 

2912 "Expires", 

2913 datetime.datetime.now(datetime.timezone.utc) 

2914 + datetime.timedelta(seconds=cache_time), 

2915 ) 

2916 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2917 

2918 self.set_extra_headers(self.path) 

2919 

2920 def should_return_304(self) -> bool: 

2921 """Returns True if the headers indicate that we should return 304. 

2922 

2923 .. versionadded:: 3.1 

2924 """ 

2925 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2926 if self.request.headers.get("If-None-Match"): 

2927 return self.check_etag_header() 

2928 

2929 # Check the If-Modified-Since, and don't send the result if the 

2930 # content has not been modified 

2931 ims_value = self.request.headers.get("If-Modified-Since") 

2932 if ims_value is not None: 

2933 try: 

2934 if_since = email.utils.parsedate_to_datetime(ims_value) 

2935 except Exception: 

2936 return False 

2937 if if_since.tzinfo is None: 

2938 if_since = if_since.replace(tzinfo=datetime.timezone.utc) 

2939 assert self.modified is not None 

2940 if if_since >= self.modified: 

2941 return True 

2942 

2943 return False 

2944 

2945 @classmethod 

2946 def get_absolute_path(cls, root: str, path: str) -> str: 

2947 """Returns the absolute location of ``path`` relative to ``root``. 

2948 

2949 ``root`` is the path configured for this `StaticFileHandler` 

2950 (in most cases the ``static_path`` `Application` setting). 

2951 

2952 This class method may be overridden in subclasses. By default 

2953 it returns a filesystem path, but other strings may be used 

2954 as long as they are unique and understood by the subclass's 

2955 overridden `get_content`. 

2956 

2957 .. versionadded:: 3.1 

2958 """ 

2959 abspath = os.path.abspath(os.path.join(root, path)) 

2960 return abspath 

2961 

2962 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2963 """Validate and return the absolute path. 

2964 

2965 ``root`` is the configured path for the `StaticFileHandler`, 

2966 and ``path`` is the result of `get_absolute_path` 

2967 

2968 This is an instance method called during request processing, 

2969 so it may raise `HTTPError` or use methods like 

2970 `RequestHandler.redirect` (return None after redirecting to 

2971 halt further processing). This is where 404 errors for missing files 

2972 are generated. 

2973 

2974 This method may modify the path before returning it, but note that 

2975 any such modifications will not be understood by `make_static_url`. 

2976 

2977 In instance methods, this method's result is available as 

2978 ``self.absolute_path``. 

2979 

2980 .. versionadded:: 3.1 

2981 """ 

2982 # os.path.abspath strips a trailing /. 

2983 # We must add it back to `root` so that we only match files 

2984 # in a directory named `root` instead of files starting with 

2985 # that prefix. 

2986 root = os.path.abspath(root) 

2987 if not root.endswith(os.path.sep): 

2988 # abspath always removes a trailing slash, except when 

2989 # root is '/'. This is an unusual case, but several projects 

2990 # have independently discovered this technique to disable 

2991 # Tornado's path validation and (hopefully) do their own, 

2992 # so we need to support it. 

2993 root += os.path.sep 

2994 # The trailing slash also needs to be temporarily added back 

2995 # the requested path so a request to root/ will match. 

2996 if not (absolute_path + os.path.sep).startswith(root): 

2997 raise HTTPError(403, "%s is not in root static directory", self.path) 

2998 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2999 # need to look at the request.path here for when path is empty 

3000 # but there is some prefix to the path that was already 

3001 # trimmed by the routing 

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

3003 if self.request.path.startswith("//"): 

3004 # A redirect with two initial slashes is a "protocol-relative" URL. 

3005 # This means the next path segment is treated as a hostname instead 

3006 # of a part of the path, making this effectively an open redirect. 

3007 # Reject paths starting with two slashes to prevent this. 

3008 # This is only reachable under certain configurations. 

3009 raise HTTPError( 

3010 403, "cannot redirect path with two initial slashes" 

3011 ) 

3012 self.redirect(self.request.path + "/", permanent=True) 

3013 return None 

3014 absolute_path = os.path.join(absolute_path, self.default_filename) 

3015 if not os.path.exists(absolute_path): 

3016 raise HTTPError(404) 

3017 if not os.path.isfile(absolute_path): 

3018 raise HTTPError(403, "%s is not a file", self.path) 

3019 return absolute_path 

3020 

3021 @classmethod 

3022 def get_content( 

3023 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

3024 ) -> Generator[bytes, None, None]: 

3025 """Retrieve the content of the requested resource which is located 

3026 at the given absolute path. 

3027 

3028 This class method may be overridden by subclasses. Note that its 

3029 signature is different from other overridable class methods 

3030 (no ``settings`` argument); this is deliberate to ensure that 

3031 ``abspath`` is able to stand on its own as a cache key. 

3032 

3033 This method should either return a byte string or an iterator 

3034 of byte strings. The latter is preferred for large files 

3035 as it helps reduce memory fragmentation. 

3036 

3037 .. versionadded:: 3.1 

3038 """ 

3039 with open(abspath, "rb") as file: 

3040 if start is not None: 

3041 file.seek(start) 

3042 if end is not None: 

3043 remaining = end - (start or 0) # type: Optional[int] 

3044 else: 

3045 remaining = None 

3046 while True: 

3047 chunk_size = 64 * 1024 

3048 if remaining is not None and remaining < chunk_size: 

3049 chunk_size = remaining 

3050 chunk = file.read(chunk_size) 

3051 if chunk: 

3052 if remaining is not None: 

3053 remaining -= len(chunk) 

3054 yield chunk 

3055 else: 

3056 if remaining is not None: 

3057 assert remaining == 0 

3058 return 

3059 

3060 @classmethod 

3061 def get_content_version(cls, abspath: str) -> str: 

3062 """Returns a version string for the resource at the given path. 

3063 

3064 This class method may be overridden by subclasses. The 

3065 default implementation is a SHA-512 hash of the file's contents. 

3066 

3067 .. versionadded:: 3.1 

3068 """ 

3069 data = cls.get_content(abspath) 

3070 hasher = hashlib.sha512() 

3071 if isinstance(data, bytes): 

3072 hasher.update(data) 

3073 else: 

3074 for chunk in data: 

3075 hasher.update(chunk) 

3076 return hasher.hexdigest() 

3077 

3078 def _stat(self) -> os.stat_result: 

3079 assert self.absolute_path is not None 

3080 if not hasattr(self, "_stat_result"): 

3081 self._stat_result = os.stat(self.absolute_path) 

3082 return self._stat_result 

3083 

3084 def get_content_size(self) -> int: 

3085 """Retrieve the total size of the resource at the given path. 

3086 

3087 This method may be overridden by subclasses. 

3088 

3089 .. versionadded:: 3.1 

3090 

3091 .. versionchanged:: 4.0 

3092 This method is now always called, instead of only when 

3093 partial results are requested. 

3094 """ 

3095 stat_result = self._stat() 

3096 return stat_result.st_size 

3097 

3098 def get_modified_time(self) -> Optional[datetime.datetime]: 

3099 """Returns the time that ``self.absolute_path`` was last modified. 

3100 

3101 May be overridden in subclasses. Should return a `~datetime.datetime` 

3102 object or None. 

3103 

3104 .. versionadded:: 3.1 

3105 

3106 .. versionchanged:: 6.4 

3107 Now returns an aware datetime object instead of a naive one. 

3108 Subclasses that override this method may return either kind. 

3109 """ 

3110 stat_result = self._stat() 

3111 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3112 # which truncates the fractional portion of the timestamp. It 

3113 # was changed from that form to stat_result.st_mtime to 

3114 # satisfy mypy (which disallows the bracket operator), but the 

3115 # latter form returns a float instead of an int. For 

3116 # consistency with the past (and because we have a unit test 

3117 # that relies on this), we truncate the float here, although 

3118 # I'm not sure that's the right thing to do. 

3119 modified = datetime.datetime.fromtimestamp( 

3120 int(stat_result.st_mtime), datetime.timezone.utc 

3121 ) 

3122 return modified 

3123 

3124 def get_content_type(self) -> str: 

3125 """Returns the ``Content-Type`` header to be used for this request. 

3126 

3127 .. versionadded:: 3.1 

3128 """ 

3129 assert self.absolute_path is not None 

3130 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3131 # per RFC 6713, use the appropriate type for a gzip compressed file 

3132 if encoding == "gzip": 

3133 return "application/gzip" 

3134 # As of 2015-07-21 there is no bzip2 encoding defined at 

3135 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3136 # So for that (and any other encoding), use octet-stream. 

3137 elif encoding is not None: 

3138 return "application/octet-stream" 

3139 elif mime_type is not None: 

3140 return mime_type 

3141 # if mime_type not detected, use application/octet-stream 

3142 else: 

3143 return "application/octet-stream" 

3144 

3145 def set_extra_headers(self, path: str) -> None: 

3146 """For subclass to add extra headers to the response""" 

3147 pass 

3148 

3149 def get_cache_time( 

3150 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3151 ) -> int: 

3152 """Override to customize cache control behavior. 

3153 

3154 Return a positive number of seconds to make the result 

3155 cacheable for that amount of time or 0 to mark resource as 

3156 cacheable for an unspecified amount of time (subject to 

3157 browser heuristics). 

3158 

3159 By default returns cache expiry of 10 years for resources requested 

3160 with ``v`` argument. 

3161 """ 

3162 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3163 

3164 @classmethod 

3165 def make_static_url( 

3166 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3167 ) -> str: 

3168 """Constructs a versioned url for the given path. 

3169 

3170 This method may be overridden in subclasses (but note that it 

3171 is a class method rather than an instance method). Subclasses 

3172 are only required to implement the signature 

3173 ``make_static_url(cls, settings, path)``; other keyword 

3174 arguments may be passed through `~RequestHandler.static_url` 

3175 but are not standard. 

3176 

3177 ``settings`` is the `Application.settings` dictionary. ``path`` 

3178 is the static path being requested. The url returned should be 

3179 relative to the current host. 

3180 

3181 ``include_version`` determines whether the generated URL should 

3182 include the query string containing the version hash of the 

3183 file corresponding to the given ``path``. 

3184 

3185 """ 

3186 url = settings.get("static_url_prefix", "/static/") + path 

3187 if not include_version: 

3188 return url 

3189 

3190 version_hash = cls.get_version(settings, path) 

3191 if not version_hash: 

3192 return url 

3193 

3194 return f"{url}?v={version_hash}" 

3195 

3196 def parse_url_path(self, url_path: str) -> str: 

3197 """Converts a static URL path into a filesystem path. 

3198 

3199 ``url_path`` is the path component of the URL with 

3200 ``static_url_prefix`` removed. The return value should be 

3201 filesystem path relative to ``static_path``. 

3202 

3203 This is the inverse of `make_static_url`. 

3204 """ 

3205 if os.path.sep != "/": 

3206 url_path = url_path.replace("/", os.path.sep) 

3207 return url_path 

3208 

3209 @classmethod 

3210 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3211 """Generate the version string to be used in static URLs. 

3212 

3213 ``settings`` is the `Application.settings` dictionary and ``path`` 

3214 is the relative location of the requested asset on the filesystem. 

3215 The returned value should be a string, or ``None`` if no version 

3216 could be determined. 

3217 

3218 .. versionchanged:: 3.1 

3219 This method was previously recommended for subclasses to override; 

3220 `get_content_version` is now preferred as it allows the base 

3221 class to handle caching of the result. 

3222 """ 

3223 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3224 return cls._get_cached_version(abs_path) 

3225 

3226 @classmethod 

3227 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3228 with cls._lock: 

3229 hashes = cls._static_hashes 

3230 if abs_path not in hashes: 

3231 try: 

3232 hashes[abs_path] = cls.get_content_version(abs_path) 

3233 except Exception: 

3234 gen_log.error("Could not open static file %r", abs_path) 

3235 hashes[abs_path] = None 

3236 hsh = hashes.get(abs_path) 

3237 if hsh: 

3238 return hsh 

3239 return None 

3240 

3241 

3242class FallbackHandler(RequestHandler): 

3243 """A `RequestHandler` that wraps another HTTP server callback. 

3244 

3245 The fallback is a callable object that accepts an 

3246 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3247 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3248 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3249 usage:: 

3250 

3251 wsgi_app = tornado.wsgi.WSGIContainer( 

3252 django.core.handlers.wsgi.WSGIHandler()) 

3253 application = tornado.web.Application([ 

3254 (r"/foo", FooHandler), 

3255 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3256 ]) 

3257 """ 

3258 

3259 def initialize( 

3260 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3261 ) -> None: 

3262 self.fallback = fallback 

3263 

3264 def prepare(self) -> None: 

3265 self.fallback(self.request) 

3266 self._finished = True 

3267 self.on_finish() 

3268 

3269 

3270class OutputTransform: 

3271 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3272 

3273 Applications are not expected to create their own OutputTransforms 

3274 or interact with them directly; the framework chooses which transforms 

3275 (if any) to apply. 

3276 """ 

3277 

3278 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3279 pass 

3280 

3281 def transform_first_chunk( 

3282 self, 

3283 status_code: int, 

3284 headers: httputil.HTTPHeaders, 

3285 chunk: bytes, 

3286 finishing: bool, 

3287 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3288 return status_code, headers, chunk 

3289 

3290 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3291 return chunk 

3292 

3293 

3294class GZipContentEncoding(OutputTransform): 

3295 """Applies the gzip content encoding to the response. 

3296 

3297 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3298 

3299 .. versionchanged:: 4.0 

3300 Now compresses all mime types beginning with ``text/``, instead 

3301 of just a whitelist. (the whitelist is still used for certain 

3302 non-text mime types). 

3303 """ 

3304 

3305 # Whitelist of compressible mime types (in addition to any types 

3306 # beginning with "text/"). 

3307 CONTENT_TYPES = { 

3308 "application/javascript", 

3309 "application/x-javascript", 

3310 "application/xml", 

3311 "application/atom+xml", 

3312 "application/json", 

3313 "application/xhtml+xml", 

3314 "image/svg+xml", 

3315 } 

3316 # Python's GzipFile defaults to level 9, while most other gzip 

3317 # tools (including gzip itself) default to 6, which is probably a 

3318 # better CPU/size tradeoff. 

3319 GZIP_LEVEL = 6 

3320 # Responses that are too short are unlikely to benefit from gzipping 

3321 # after considering the "Content-Encoding: gzip" header and the header 

3322 # inside the gzip encoding. 

3323 # Note that responses written in multiple chunks will be compressed 

3324 # regardless of size. 

3325 MIN_LENGTH = 1024 

3326 

3327 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3328 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3329 

3330 def _compressible_type(self, ctype: str) -> bool: 

3331 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3332 

3333 def transform_first_chunk( 

3334 self, 

3335 status_code: int, 

3336 headers: httputil.HTTPHeaders, 

3337 chunk: bytes, 

3338 finishing: bool, 

3339 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3340 # TODO: can/should this type be inherited from the superclass? 

3341 if "Vary" in headers: 

3342 headers["Vary"] += ", Accept-Encoding" 

3343 else: 

3344 headers["Vary"] = "Accept-Encoding" 

3345 if self._gzipping: 

3346 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3347 self._gzipping = ( 

3348 self._compressible_type(ctype) 

3349 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3350 and ("Content-Encoding" not in headers) 

3351 ) 

3352 if self._gzipping: 

3353 headers["Content-Encoding"] = "gzip" 

3354 self._gzip_value = BytesIO() 

3355 self._gzip_file = gzip.GzipFile( 

3356 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3357 ) 

3358 chunk = self.transform_chunk(chunk, finishing) 

3359 if "Content-Length" in headers: 

3360 # The original content length is no longer correct. 

3361 # If this is the last (and only) chunk, we can set the new 

3362 # content-length; otherwise we remove it and fall back to 

3363 # chunked encoding. 

3364 if finishing: 

3365 headers["Content-Length"] = str(len(chunk)) 

3366 else: 

3367 del headers["Content-Length"] 

3368 return status_code, headers, chunk 

3369 

3370 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3371 if self._gzipping: 

3372 self._gzip_file.write(chunk) 

3373 if finishing: 

3374 self._gzip_file.close() 

3375 else: 

3376 self._gzip_file.flush() 

3377 chunk = self._gzip_value.getvalue() 

3378 self._gzip_value.truncate(0) 

3379 self._gzip_value.seek(0) 

3380 return chunk 

3381 

3382 

3383def authenticated( 

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

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

3386 """Decorate methods with this to require that the user be logged in. 

3387 

3388 If the user is not logged in, they will be redirected to the configured 

3389 `login url <RequestHandler.get_login_url>`. 

3390 

3391 If you configure a login url with a query parameter, Tornado will 

3392 assume you know what you're doing and use it as-is. If not, it 

3393 will add a `next` parameter so the login page knows where to send 

3394 you once you're logged in. 

3395 """ 

3396 

3397 @functools.wraps(method) 

3398 def wrapper( # type: ignore 

3399 self: RequestHandler, *args, **kwargs 

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

3401 if not self.current_user: 

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

3403 url = self.get_login_url() 

3404 if "?" not in url: 

3405 if urllib.parse.urlsplit(url).scheme: 

3406 # if login url is absolute, make next absolute too 

3407 next_url = self.request.full_url() 

3408 else: 

3409 assert self.request.uri is not None 

3410 next_url = self.request.uri 

3411 url += "?" + urlencode(dict(next=next_url)) 

3412 self.redirect(url) 

3413 return None 

3414 raise HTTPError(403) 

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

3416 

3417 return wrapper 

3418 

3419 

3420class UIModule: 

3421 """A re-usable, modular UI unit on a page. 

3422 

3423 UI modules often execute additional queries, and they can include 

3424 additional CSS and JavaScript that will be included in the output 

3425 page, which is automatically inserted on page render. 

3426 

3427 Subclasses of UIModule must override the `render` method. 

3428 """ 

3429 

3430 def __init__(self, handler: RequestHandler) -> None: 

3431 self.handler = handler 

3432 self.request = handler.request 

3433 self.ui = handler.ui 

3434 self.locale = handler.locale 

3435 

3436 @property 

3437 def current_user(self) -> Any: 

3438 return self.handler.current_user 

3439 

3440 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]: 

3441 """Override in subclasses to return this module's output.""" 

3442 raise NotImplementedError() 

3443 

3444 def embedded_javascript(self) -> Optional[str]: 

3445 """Override to return a JavaScript string 

3446 to be embedded in the page.""" 

3447 return None 

3448 

3449 def javascript_files(self) -> Optional[Iterable[str]]: 

3450 """Override to return a list of JavaScript files needed by this module. 

3451 

3452 If the return values are relative paths, they will be passed to 

3453 `RequestHandler.static_url`; otherwise they will be used as-is. 

3454 """ 

3455 return None 

3456 

3457 def embedded_css(self) -> Optional[str]: 

3458 """Override to return a CSS string 

3459 that will be embedded in the page.""" 

3460 return None 

3461 

3462 def css_files(self) -> Optional[Iterable[str]]: 

3463 """Override to returns a list of CSS files required by this module. 

3464 

3465 If the return values are relative paths, they will be passed to 

3466 `RequestHandler.static_url`; otherwise they will be used as-is. 

3467 """ 

3468 return None 

3469 

3470 def html_head(self) -> Optional[str]: 

3471 """Override to return an HTML string that will be put in the <head/> 

3472 element. 

3473 """ 

3474 return None 

3475 

3476 def html_body(self) -> Optional[str]: 

3477 """Override to return an HTML string that will be put at the end of 

3478 the <body/> element. 

3479 """ 

3480 return None 

3481 

3482 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3483 """Renders a template and returns it as a string.""" 

3484 return self.handler.render_string(path, **kwargs) 

3485 

3486 

3487class _linkify(UIModule): 

3488 def render(self, text: str, **kwargs: Any) -> str: 

3489 return escape.linkify(text, **kwargs) 

3490 

3491 

3492class _xsrf_form_html(UIModule): 

3493 def render(self) -> str: 

3494 return self.handler.xsrf_form_html() 

3495 

3496 

3497class TemplateModule(UIModule): 

3498 """UIModule that simply renders the given template. 

3499 

3500 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3501 but the module version gets its own namespace (with kwargs passed to 

3502 Template()) instead of inheriting the outer template's namespace. 

3503 

3504 Templates rendered through this module also get access to UIModule's 

3505 automatic JavaScript/CSS features. Simply call set_resources 

3506 inside the template and give it keyword arguments corresponding to 

3507 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3508 Note that these resources are output once per template file, not once 

3509 per instantiation of the template, so they must not depend on 

3510 any arguments to the template. 

3511 """ 

3512 

3513 def __init__(self, handler: RequestHandler) -> None: 

3514 super().__init__(handler) 

3515 # keep resources in both a list and a dict to preserve order 

3516 self._resource_list = [] # type: List[Dict[str, Any]] 

3517 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3518 

3519 def render(self, path: str, **kwargs: Any) -> bytes: 

3520 def set_resources(**kwargs) -> str: # type: ignore 

3521 if path not in self._resource_dict: 

3522 self._resource_list.append(kwargs) 

3523 self._resource_dict[path] = kwargs 

3524 else: 

3525 if self._resource_dict[path] != kwargs: 

3526 raise ValueError( 

3527 "set_resources called with different " 

3528 "resources for the same template" 

3529 ) 

3530 return "" 

3531 

3532 return self.render_string(path, set_resources=set_resources, **kwargs) 

3533 

3534 def _get_resources(self, key: str) -> Iterable[str]: 

3535 return (r[key] for r in self._resource_list if key in r) 

3536 

3537 def embedded_javascript(self) -> str: 

3538 return "\n".join(self._get_resources("embedded_javascript")) 

3539 

3540 def javascript_files(self) -> Iterable[str]: 

3541 result = [] 

3542 for f in self._get_resources("javascript_files"): 

3543 if isinstance(f, (unicode_type, bytes)): 

3544 result.append(f) 

3545 else: 

3546 result.extend(f) 

3547 return result 

3548 

3549 def embedded_css(self) -> str: 

3550 return "\n".join(self._get_resources("embedded_css")) 

3551 

3552 def css_files(self) -> Iterable[str]: 

3553 result = [] 

3554 for f in self._get_resources("css_files"): 

3555 if isinstance(f, (unicode_type, bytes)): 

3556 result.append(f) 

3557 else: 

3558 result.extend(f) 

3559 return result 

3560 

3561 def html_head(self) -> str: 

3562 return "".join(self._get_resources("html_head")) 

3563 

3564 def html_body(self) -> str: 

3565 return "".join(self._get_resources("html_body")) 

3566 

3567 

3568class _UIModuleNamespace: 

3569 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3570 

3571 def __init__( 

3572 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3573 ) -> None: 

3574 self.handler = handler 

3575 self.ui_modules = ui_modules 

3576 

3577 def __getitem__(self, key: str) -> Callable[..., str]: 

3578 return self.handler._ui_module(key, self.ui_modules[key]) 

3579 

3580 def __getattr__(self, key: str) -> Callable[..., str]: 

3581 try: 

3582 return self[key] 

3583 except KeyError as e: 

3584 raise AttributeError(str(e)) 

3585 

3586 

3587def create_signed_value( 

3588 secret: _CookieSecretTypes, 

3589 name: str, 

3590 value: Union[str, bytes], 

3591 version: Optional[int] = None, 

3592 clock: Optional[Callable[[], float]] = None, 

3593 key_version: Optional[int] = None, 

3594) -> bytes: 

3595 if version is None: 

3596 version = DEFAULT_SIGNED_VALUE_VERSION 

3597 if clock is None: 

3598 clock = time.time 

3599 

3600 timestamp = utf8(str(int(clock()))) 

3601 value = base64.b64encode(utf8(value)) 

3602 if version == 1: 

3603 assert not isinstance(secret, dict) 

3604 signature = _create_signature_v1(secret, name, value, timestamp) 

3605 value = b"|".join([value, timestamp, signature]) 

3606 return value 

3607 elif version == 2: 

3608 # The v2 format consists of a version number and a series of 

3609 # length-prefixed fields "%d:%s", the last of which is a 

3610 # signature, all separated by pipes. All numbers are in 

3611 # decimal format with no leading zeros. The signature is an 

3612 # HMAC-SHA256 of the whole string up to that point, including 

3613 # the final pipe. 

3614 # 

3615 # The fields are: 

3616 # - format version (i.e. 2; no length prefix) 

3617 # - key version (integer, default is 0) 

3618 # - timestamp (integer seconds since epoch) 

3619 # - name (not encoded; assumed to be ~alphanumeric) 

3620 # - value (base64-encoded) 

3621 # - signature (hex-encoded; no length prefix) 

3622 def format_field(s: Union[str, bytes]) -> bytes: 

3623 return utf8("%d:" % len(s)) + utf8(s) 

3624 

3625 to_sign = b"|".join( 

3626 [ 

3627 b"2", 

3628 format_field(str(key_version or 0)), 

3629 format_field(timestamp), 

3630 format_field(name), 

3631 format_field(value), 

3632 b"", 

3633 ] 

3634 ) 

3635 

3636 if isinstance(secret, dict): 

3637 assert ( 

3638 key_version is not None 

3639 ), "Key version must be set when sign key dict is used" 

3640 assert version >= 2, "Version must be at least 2 for key version support" 

3641 secret = secret[key_version] 

3642 

3643 signature = _create_signature_v2(secret, to_sign) 

3644 return to_sign + signature 

3645 else: 

3646 raise ValueError("Unsupported version %d" % version) 

3647 

3648 

3649# A leading version number in decimal 

3650# with no leading zeros, followed by a pipe. 

3651_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3652 

3653 

3654def _get_version(value: bytes) -> int: 

3655 # Figures out what version value is. Version 1 did not include an 

3656 # explicit version field and started with arbitrary base64 data, 

3657 # which makes this tricky. 

3658 m = _signed_value_version_re.match(value) 

3659 if m is None: 

3660 version = 1 

3661 else: 

3662 try: 

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

3664 if version > 999: 

3665 # Certain payloads from the version-less v1 format may 

3666 # be parsed as valid integers. Due to base64 padding 

3667 # restrictions, this can only happen for numbers whose 

3668 # length is a multiple of 4, so we can treat all 

3669 # numbers up to 999 as versions, and for the rest we 

3670 # fall back to v1 format. 

3671 version = 1 

3672 except ValueError: 

3673 version = 1 

3674 return version 

3675 

3676 

3677def decode_signed_value( 

3678 secret: _CookieSecretTypes, 

3679 name: str, 

3680 value: Union[None, str, bytes], 

3681 max_age_days: float = 31, 

3682 clock: Optional[Callable[[], float]] = None, 

3683 min_version: Optional[int] = None, 

3684) -> Optional[bytes]: 

3685 if clock is None: 

3686 clock = time.time 

3687 if min_version is None: 

3688 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3689 if min_version > 2: 

3690 raise ValueError("Unsupported min_version %d" % min_version) 

3691 if not value: 

3692 return None 

3693 

3694 value = utf8(value) 

3695 version = _get_version(value) 

3696 

3697 if version < min_version: 

3698 return None 

3699 if version == 1: 

3700 assert not isinstance(secret, dict) 

3701 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3702 elif version == 2: 

3703 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3704 else: 

3705 return None 

3706 

3707 

3708def _decode_signed_value_v1( 

3709 secret: Union[str, bytes], 

3710 name: str, 

3711 value: bytes, 

3712 max_age_days: float, 

3713 clock: Callable[[], float], 

3714) -> Optional[bytes]: 

3715 parts = utf8(value).split(b"|") 

3716 if len(parts) != 3: 

3717 return None 

3718 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3719 if not hmac.compare_digest(parts[2], signature): 

3720 gen_log.warning("Invalid cookie signature %r", value) 

3721 return None 

3722 timestamp = int(parts[1]) 

3723 if timestamp < clock() - max_age_days * 86400: 

3724 gen_log.warning("Expired cookie %r", value) 

3725 return None 

3726 if timestamp > clock() + 31 * 86400: 

3727 # _cookie_signature does not hash a delimiter between the 

3728 # parts of the cookie, so an attacker could transfer trailing 

3729 # digits from the payload to the timestamp without altering the 

3730 # signature. For backwards compatibility, sanity-check timestamp 

3731 # here instead of modifying _cookie_signature. 

3732 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3733 return None 

3734 if parts[1].startswith(b"0"): 

3735 gen_log.warning("Tampered cookie %r", value) 

3736 return None 

3737 try: 

3738 return base64.b64decode(parts[0]) 

3739 except Exception: 

3740 return None 

3741 

3742 

3743def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3744 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3745 length, _, rest = s.partition(b":") 

3746 n = int(length) 

3747 field_value = rest[:n] 

3748 # In python 3, indexing bytes returns small integers; we must 

3749 # use a slice to get a byte string as in python 2. 

3750 if rest[n : n + 1] != b"|": 

3751 raise ValueError("malformed v2 signed value field") 

3752 rest = rest[n + 1 :] 

3753 return field_value, rest 

3754 

3755 rest = value[2:] # remove version number 

3756 key_version, rest = _consume_field(rest) 

3757 timestamp, rest = _consume_field(rest) 

3758 name_field, rest = _consume_field(rest) 

3759 value_field, passed_sig = _consume_field(rest) 

3760 return int(key_version), timestamp, name_field, value_field, passed_sig 

3761 

3762 

3763def _decode_signed_value_v2( 

3764 secret: _CookieSecretTypes, 

3765 name: str, 

3766 value: bytes, 

3767 max_age_days: float, 

3768 clock: Callable[[], float], 

3769) -> Optional[bytes]: 

3770 try: 

3771 ( 

3772 key_version, 

3773 timestamp_bytes, 

3774 name_field, 

3775 value_field, 

3776 passed_sig, 

3777 ) = _decode_fields_v2(value) 

3778 except ValueError: 

3779 return None 

3780 signed_string = value[: -len(passed_sig)] 

3781 

3782 if isinstance(secret, dict): 

3783 try: 

3784 secret = secret[key_version] 

3785 except KeyError: 

3786 return None 

3787 

3788 expected_sig = _create_signature_v2(secret, signed_string) 

3789 if not hmac.compare_digest(passed_sig, expected_sig): 

3790 return None 

3791 if name_field != utf8(name): 

3792 return None 

3793 timestamp = int(timestamp_bytes) 

3794 if timestamp < clock() - max_age_days * 86400: 

3795 # The signature has expired. 

3796 return None 

3797 try: 

3798 return base64.b64decode(value_field) 

3799 except Exception: 

3800 return None 

3801 

3802 

3803def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3804 value = utf8(value) 

3805 version = _get_version(value) 

3806 if version < 2: 

3807 return None 

3808 try: 

3809 key_version, _, _, _, _ = _decode_fields_v2(value) 

3810 except ValueError: 

3811 return None 

3812 

3813 return key_version 

3814 

3815 

3816def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3817 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3818 for part in parts: 

3819 hash.update(utf8(part)) 

3820 return utf8(hash.hexdigest()) 

3821 

3822 

3823def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3824 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3825 hash.update(utf8(s)) 

3826 return utf8(hash.hexdigest()) 

3827 

3828 

3829def is_absolute(path: str) -> bool: 

3830 return any(path.startswith(x) for x in ["/", "http:", "https:"])