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

1467 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 if not hasattr(self, "_new_cookie"): 

721 self._new_cookie = ( 

722 http.cookies.SimpleCookie() 

723 ) # type: http.cookies.SimpleCookie 

724 if name in self._new_cookie: 

725 del self._new_cookie[name] 

726 self._new_cookie[name] = value 

727 morsel = self._new_cookie[name] 

728 if domain: 

729 morsel["domain"] = domain 

730 if expires_days is not None and not expires: 

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

732 days=expires_days 

733 ) 

734 if expires: 

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

736 if path: 

737 morsel["path"] = path 

738 if max_age: 

739 # Note change from _ to -. 

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

741 if httponly: 

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

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

744 morsel["httponly"] = True 

745 if secure: 

746 morsel["secure"] = True 

747 if samesite: 

748 morsel["samesite"] = samesite 

749 if kwargs: 

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

751 # kwargs for backwards compatibility until we can remove deprecated 

752 # features. 

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

754 morsel[k] = v 

755 warnings.warn( 

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

757 "(should be lowercase)", 

758 DeprecationWarning, 

759 ) 

760 

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

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

763 

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

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

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

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

768 arguments are ignored. 

769 

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

771 seen until the following request. 

772 

773 .. versionchanged:: 6.3 

774 

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

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

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

778 """ 

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

780 if excluded_arg in kwargs: 

781 raise TypeError( 

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

783 ) 

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

785 days=365 

786 ) 

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

788 

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

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

791 

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

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

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

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

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

797 when setting cookies. 

798 

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

800 until the following request. 

801 

802 .. versionchanged:: 3.2 

803 

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

805 

806 .. versionchanged:: 6.3 

807 

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

809 

810 .. deprecated:: 6.3 

811 

812 The increasingly complex rules governing cookies have made it 

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

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

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

816 """ 

817 for name in self.request.cookies: 

818 self.clear_cookie(name, **kwargs) 

819 

820 def set_signed_cookie( 

821 self, 

822 name: str, 

823 value: Union[str, bytes], 

824 expires_days: Optional[float] = 30, 

825 version: Optional[int] = None, 

826 **kwargs: Any, 

827 ) -> None: 

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

829 

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

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

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

833 

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

835 

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

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

838 parameter to `get_signed_cookie`. 

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

840 

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

842 strings (unlike regular cookies) 

843 

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

845 seen until the following request. 

846 

847 .. versionchanged:: 3.2.1 

848 

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

850 and made it the default. 

851 

852 .. versionchanged:: 6.3 

853 

854 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

857 """ 

858 self.set_cookie( 

859 name, 

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

861 expires_days=expires_days, 

862 **kwargs, 

863 ) 

864 

865 set_secure_cookie = set_signed_cookie 

866 

867 def create_signed_value( 

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

869 ) -> bytes: 

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

871 

872 Normally used via set_signed_cookie, but provided as a separate 

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

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

875 

876 .. versionchanged:: 3.2.1 

877 

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

879 and made it the default. 

880 """ 

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

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

883 key_version = None 

884 if isinstance(secret, dict): 

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

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

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

888 

889 return create_signed_value( 

890 secret, name, value, version=version, key_version=key_version 

891 ) 

892 

893 def get_signed_cookie( 

894 self, 

895 name: str, 

896 value: Optional[str] = None, 

897 max_age_days: float = 31, 

898 min_version: Optional[int] = None, 

899 ) -> Optional[bytes]: 

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

901 

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

903 `get_cookie`). 

904 

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

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

907 `set_signed_cookie` in this handler. 

908 

909 .. versionchanged:: 3.2.1 

910 

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

912 both versions 1 and 2 are accepted by default. 

913 

914 .. versionchanged:: 6.3 

915 

916 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

919 

920 """ 

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

922 if value is None: 

923 value = self.get_cookie(name) 

924 return decode_signed_value( 

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

926 name, 

927 value, 

928 max_age_days=max_age_days, 

929 min_version=min_version, 

930 ) 

931 

932 get_secure_cookie = get_signed_cookie 

933 

934 def get_signed_cookie_key_version( 

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

936 ) -> Optional[int]: 

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

938 

939 The version is returned as int. 

940 

941 .. versionchanged:: 6.3 

942 

943 Renamed from ``get_secure_cookie_key_version`` to 

944 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

946 remains as an alias. 

947 

948 """ 

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

950 if value is None: 

951 value = self.get_cookie(name) 

952 if value is None: 

953 return None 

954 return get_signature_key_version(value) 

955 

956 get_secure_cookie_key_version = get_signed_cookie_key_version 

957 

958 def redirect( 

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

960 ) -> None: 

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

962 

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

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

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

966 The default is 302 (temporary). 

967 """ 

968 if self._headers_written: 

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

970 if status is None: 

971 status = 301 if permanent else 302 

972 else: 

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

974 self.set_status(status) 

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

976 self.finish() 

977 

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

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

980 

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

982 

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

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

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

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

987 

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

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

990 wrapped in a dictionary. More details at 

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

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

993 """ 

994 if self._finished: 

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

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

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

998 if isinstance(chunk, list): 

999 message += ( 

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

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

1002 ) 

1003 raise TypeError(message) 

1004 if isinstance(chunk, dict): 

1005 chunk = escape.json_encode(chunk) 

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

1007 chunk = utf8(chunk) 

1008 self._write_buffer.append(chunk) 

1009 

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

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

1012 

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

1014 after it. 

1015 

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

1017 Awaiting this `.Future` is optional. 

1018 

1019 .. versionchanged:: 5.1 

1020 

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

1022 """ 

1023 if self._finished: 

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

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

1026 

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

1028 js_embed = [] 

1029 js_files = [] 

1030 css_embed = [] 

1031 css_files = [] 

1032 html_heads = [] 

1033 html_bodies = [] 

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

1035 embed_part = module.embedded_javascript() 

1036 if embed_part: 

1037 js_embed.append(utf8(embed_part)) 

1038 file_part = module.javascript_files() 

1039 if file_part: 

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

1041 js_files.append(_unicode(file_part)) 

1042 else: 

1043 js_files.extend(file_part) 

1044 embed_part = module.embedded_css() 

1045 if embed_part: 

1046 css_embed.append(utf8(embed_part)) 

1047 file_part = module.css_files() 

1048 if file_part: 

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

1050 css_files.append(_unicode(file_part)) 

1051 else: 

1052 css_files.extend(file_part) 

1053 head_part = module.html_head() 

1054 if head_part: 

1055 html_heads.append(utf8(head_part)) 

1056 body_part = module.html_body() 

1057 if body_part: 

1058 html_bodies.append(utf8(body_part)) 

1059 

1060 if js_files: 

1061 # Maintain order of JavaScript files given by modules 

1062 js = self.render_linked_js(js_files) 

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

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

1065 if js_embed: 

1066 js_bytes = self.render_embed_js(js_embed) 

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

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

1069 if css_files: 

1070 css = self.render_linked_css(css_files) 

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

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

1073 if css_embed: 

1074 css_bytes = self.render_embed_css(css_embed) 

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

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

1077 if html_heads: 

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

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

1080 if html_bodies: 

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

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

1083 return self.finish(html) 

1084 

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

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

1087 rendered webpage. 

1088 

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

1090 """ 

1091 paths = [] 

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

1093 

1094 for path in js_files: 

1095 if not is_absolute(path): 

1096 path = self.static_url(path) 

1097 if path not in unique_paths: 

1098 paths.append(path) 

1099 unique_paths.add(path) 

1100 

1101 return "".join( 

1102 '<script src="' 

1103 + escape.xhtml_escape(p) 

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

1105 for p in paths 

1106 ) 

1107 

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

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

1110 rendered webpage. 

1111 

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

1113 """ 

1114 return ( 

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

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

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

1118 ) 

1119 

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

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

1122 rendered webpage. 

1123 

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

1125 """ 

1126 paths = [] 

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

1128 

1129 for path in css_files: 

1130 if not is_absolute(path): 

1131 path = self.static_url(path) 

1132 if path not in unique_paths: 

1133 paths.append(path) 

1134 unique_paths.add(path) 

1135 

1136 return "".join( 

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

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

1139 for p in paths 

1140 ) 

1141 

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

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

1144 rendered webpage. 

1145 

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

1147 """ 

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

1149 

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

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

1152 

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

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

1155 """ 

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

1157 template_path = self.get_template_path() 

1158 if not template_path: 

1159 frame = sys._getframe(0) 

1160 web_file = frame.f_code.co_filename 

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

1162 frame = frame.f_back 

1163 assert frame.f_code.co_filename is not None 

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

1165 with RequestHandler._template_loader_lock: 

1166 if template_path not in RequestHandler._template_loaders: 

1167 loader = self.create_template_loader(template_path) 

1168 RequestHandler._template_loaders[template_path] = loader 

1169 else: 

1170 loader = RequestHandler._template_loaders[template_path] 

1171 t = loader.load(template_name) 

1172 namespace = self.get_template_namespace() 

1173 namespace.update(kwargs) 

1174 return t.generate(**namespace) 

1175 

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

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

1178 

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

1180 

1181 The results of this method will be combined with additional 

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

1183 to `render` or `render_string`. 

1184 """ 

1185 namespace = dict( 

1186 handler=self, 

1187 request=self.request, 

1188 current_user=self.current_user, 

1189 locale=self.locale, 

1190 _=self.locale.translate, 

1191 pgettext=self.locale.pgettext, 

1192 static_url=self.static_url, 

1193 xsrf_form_html=self.xsrf_form_html, 

1194 reverse_url=self.reverse_url, 

1195 ) 

1196 namespace.update(self.ui) 

1197 return namespace 

1198 

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

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

1201 

1202 May be overridden by subclasses. By default returns a 

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

1204 ``autoescape`` and ``template_whitespace`` application 

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

1206 supplied, uses that instead. 

1207 """ 

1208 settings = self.application.settings 

1209 if "template_loader" in settings: 

1210 return settings["template_loader"] 

1211 kwargs = {} 

1212 if "autoescape" in settings: 

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

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

1215 kwargs["autoescape"] = settings["autoescape"] 

1216 if "template_whitespace" in settings: 

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

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

1219 

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

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

1222 

1223 .. versionchanged:: 4.0 

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

1225 

1226 .. versionchanged:: 6.0 

1227 

1228 The ``callback`` argument was removed. 

1229 """ 

1230 assert self.request.connection is not None 

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

1232 self._write_buffer = [] 

1233 if not self._headers_written: 

1234 self._headers_written = True 

1235 for transform in self._transforms: 

1236 assert chunk is not None 

1237 ( 

1238 self._status_code, 

1239 self._headers, 

1240 chunk, 

1241 ) = transform.transform_first_chunk( 

1242 self._status_code, self._headers, chunk, include_footers 

1243 ) 

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

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

1246 chunk = b"" 

1247 

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

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

1250 # is sent). 

1251 if hasattr(self, "_new_cookie"): 

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

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

1254 

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

1256 return self.request.connection.write_headers( 

1257 start_line, self._headers, chunk 

1258 ) 

1259 else: 

1260 for transform in self._transforms: 

1261 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1265 else: 

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

1267 future.set_result(None) 

1268 return future 

1269 

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

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

1272 

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

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

1275 

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

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

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

1279 data can be sent. 

1280 

1281 .. versionchanged:: 5.1 

1282 

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

1284 """ 

1285 if self._finished: 

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

1287 

1288 if chunk is not None: 

1289 self.write(chunk) 

1290 

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

1292 # we have not flushed any content yet. 

1293 if not self._headers_written: 

1294 if ( 

1295 self._status_code == 200 

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

1297 and "Etag" not in self._headers 

1298 ): 

1299 self.set_etag_header() 

1300 if self.check_etag_header(): 

1301 self._write_buffer = [] 

1302 self.set_status(304) 

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

1304 assert not self._write_buffer, ( 

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

1306 ) 

1307 self._clear_representation_headers() 

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

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

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

1311 

1312 assert self.request.connection is not None 

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

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

1315 # garbage collection of the RequestHandler when there 

1316 # are keepalive connections) 

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

1318 

1319 future = self.flush(include_footers=True) 

1320 self.request.connection.finish() 

1321 self._log() 

1322 self._finished = True 

1323 self.on_finish() 

1324 self._break_cycles() 

1325 return future 

1326 

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

1328 """Take control of the underlying stream. 

1329 

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

1331 further HTTP processing. Intended for implementing protocols 

1332 like websockets that tunnel over an HTTP handshake. 

1333 

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

1335 

1336 .. versionadded:: 5.1 

1337 """ 

1338 self._finished = True 

1339 # TODO: add detach to HTTPConnection? 

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

1341 

1342 def _break_cycles(self) -> None: 

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

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

1345 self.ui = None # type: ignore 

1346 

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

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

1349 

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

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

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

1353 and replaced with the error page. 

1354 

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

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

1357 """ 

1358 if self._headers_written: 

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

1360 if not self._finished: 

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

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

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

1364 # socket. 

1365 try: 

1366 self.finish() 

1367 except Exception: 

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

1369 return 

1370 self.clear() 

1371 

1372 reason = kwargs.get("reason") 

1373 if "exc_info" in kwargs: 

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

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

1376 reason = exception.reason 

1377 self.set_status(status_code, reason=reason) 

1378 try: 

1379 if status_code != 304: 

1380 self.write_error(status_code, **kwargs) 

1381 except Exception: 

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

1383 if not self._finished: 

1384 self.finish() 

1385 

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

1387 """Override to implement custom error pages. 

1388 

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

1390 to produce output as usual. 

1391 

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

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

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

1395 the "current" exception for purposes of methods like 

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

1397 """ 

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

1399 # in debug mode, try to send a traceback 

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

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

1402 self.write(line) 

1403 self.finish() 

1404 else: 

1405 self.finish( 

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

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

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

1409 ) 

1410 

1411 @property 

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

1413 """The locale for the current session. 

1414 

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

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

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

1418 header. 

1419 

1420 .. versionchanged: 4.1 

1421 Added a property setter. 

1422 """ 

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

1424 loc = self.get_user_locale() 

1425 if loc is not None: 

1426 self._locale = loc 

1427 else: 

1428 self._locale = self.get_browser_locale() 

1429 assert self._locale 

1430 return self._locale 

1431 

1432 @locale.setter 

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

1434 self._locale = value 

1435 

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

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

1438 

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

1440 

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

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

1443 """ 

1444 return None 

1445 

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

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

1448 

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

1450 """ 

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

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

1453 locales = [] 

1454 for language in languages: 

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

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

1457 try: 

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

1459 if score < 0: 

1460 raise ValueError() 

1461 except (ValueError, TypeError): 

1462 score = 0.0 

1463 else: 

1464 score = 1.0 

1465 if score > 0: 

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

1467 if locales: 

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

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

1470 return locale.get(*codes) 

1471 return locale.get(default) 

1472 

1473 @property 

1474 def current_user(self) -> Any: 

1475 """The authenticated user for this request. 

1476 

1477 This is set in one of two ways: 

1478 

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

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

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

1482 and is cached for future access:: 

1483 

1484 def get_current_user(self): 

1485 user_cookie = self.get_signed_cookie("user") 

1486 if user_cookie: 

1487 return json.loads(user_cookie) 

1488 return None 

1489 

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

1491 `prepare()`:: 

1492 

1493 @gen.coroutine 

1494 def prepare(self): 

1495 user_id_cookie = self.get_signed_cookie("user_id") 

1496 if user_id_cookie: 

1497 self.current_user = yield load_user(user_id_cookie) 

1498 

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

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

1501 asynchronous operations. 

1502 

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

1504 """ 

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

1506 self._current_user = self.get_current_user() 

1507 return self._current_user 

1508 

1509 @current_user.setter 

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

1511 self._current_user = value 

1512 

1513 def get_current_user(self) -> Any: 

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

1515 

1516 This method may not be a coroutine. 

1517 """ 

1518 return None 

1519 

1520 def get_login_url(self) -> str: 

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

1522 

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

1524 """ 

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

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

1527 

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

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

1530 

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

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

1533 """ 

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

1535 

1536 @property 

1537 def xsrf_token(self) -> bytes: 

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

1539 

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

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

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

1543 as a potential forgery. 

1544 

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

1546 

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

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

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

1550 UTF-8. 

1551 

1552 .. versionchanged:: 3.2.2 

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

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

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

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

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

1558 unless the ``xsrf_cookie_version`` `Application` setting is 

1559 set to 1. 

1560 

1561 .. versionchanged:: 4.3 

1562 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1563 used to supply additional cookie options (which will be 

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

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

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

1567 ``_xsrf`` cookie. 

1568 """ 

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

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

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

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

1573 if output_version == 1: 

1574 self._xsrf_token = binascii.b2a_hex(token) 

1575 elif output_version == 2: 

1576 mask = os.urandom(4) 

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

1578 [ 

1579 b"2", 

1580 binascii.b2a_hex(mask), 

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

1582 utf8(str(int(timestamp))), 

1583 ] 

1584 ) 

1585 else: 

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

1587 if version is None: 

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

1589 cookie_kwargs["expires_days"] = 30 

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

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

1592 return self._xsrf_token 

1593 

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

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

1596 

1597 The raw_xsrf_token is a tuple containing: 

1598 

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

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

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

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

1603 for version 1 cookies) 

1604 """ 

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

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

1607 cookie = self.get_cookie(cookie_name) 

1608 if cookie: 

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

1610 else: 

1611 version, token, timestamp = None, None, None 

1612 if token is None: 

1613 version = None 

1614 token = os.urandom(16) 

1615 timestamp = time.time() 

1616 assert token is not None 

1617 assert timestamp is not None 

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

1619 return self._raw_xsrf_token 

1620 

1621 def _decode_xsrf_token( 

1622 self, cookie: str 

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

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

1625 _get_raw_xsrf_token. 

1626 """ 

1627 

1628 try: 

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

1630 

1631 if m: 

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

1633 if version == 2: 

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

1635 

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

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

1638 timestamp = int(timestamp_str) 

1639 return version, token, timestamp 

1640 else: 

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

1642 raise Exception("Unknown xsrf cookie version") 

1643 else: 

1644 version = 1 

1645 try: 

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

1647 except (binascii.Error, TypeError): 

1648 token = utf8(cookie) 

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

1650 timestamp = int(time.time()) 

1651 return (version, token, timestamp) 

1652 except Exception: 

1653 # Catch exceptions and return nothing instead of failing. 

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

1655 return None, None, None 

1656 

1657 def check_xsrf_cookie(self) -> None: 

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

1659 

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

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

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

1663 reject the form submission as a potential forgery. 

1664 

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

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

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

1668 

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

1670 

1671 .. versionchanged:: 3.2.2 

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

1673 supported. 

1674 """ 

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

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

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

1678 # information please see 

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

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

1681 input_token = ( 

1682 self.get_argument("_xsrf", None) 

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

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

1685 ) 

1686 if not input_token: 

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

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

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

1690 if not token: 

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

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

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

1694 

1695 def xsrf_form_html(self) -> str: 

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

1697 

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

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

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

1701 HTML within all of your HTML forms. 

1702 

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

1704 xsrf_form_html() %}`` 

1705 

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

1707 """ 

1708 return ( 

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

1710 + escape.xhtml_escape(self.xsrf_token) 

1711 + '"/>' 

1712 ) 

1713 

1714 def static_url( 

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

1716 ) -> str: 

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

1718 

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

1720 application (which specifies the root directory of your static 

1721 files). 

1722 

1723 This method returns a versioned url (by default appending 

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

1725 cached indefinitely. This can be disabled by passing 

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

1727 other static file implementations are not required to support 

1728 this, but they may support other options). 

1729 

1730 By default this method returns URLs relative to the current 

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

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

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

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

1735 

1736 """ 

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

1738 get_url = self.settings.get( 

1739 "static_handler_class", StaticFileHandler 

1740 ).make_static_url 

1741 

1742 if include_host is None: 

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

1744 

1745 if include_host: 

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

1747 else: 

1748 base = "" 

1749 

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

1751 

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

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

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

1755 raise Exception( 

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

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

1758 ) 

1759 

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

1761 """Alias for `Application.reverse_url`.""" 

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

1763 

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

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

1766 

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

1768 

1769 May be overridden to provide custom etag implementations, 

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

1771 """ 

1772 hasher = hashlib.sha1() 

1773 for part in self._write_buffer: 

1774 hasher.update(part) 

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

1776 

1777 def set_etag_header(self) -> None: 

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

1779 

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

1781 

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

1783 """ 

1784 etag = self.compute_etag() 

1785 if etag is not None: 

1786 self.set_header("Etag", etag) 

1787 

1788 def check_etag_header(self) -> bool: 

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

1790 

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

1792 returned. For example:: 

1793 

1794 self.set_etag_header() 

1795 if self.check_etag_header(): 

1796 self.set_status(304) 

1797 return 

1798 

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

1800 but may be called earlier for applications that override 

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

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

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

1804 """ 

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

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

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

1808 etags = re.findall( 

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

1810 ) 

1811 if not computed_etag or not etags: 

1812 return False 

1813 

1814 match = False 

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

1816 match = True 

1817 else: 

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

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

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

1821 

1822 for etag in etags: 

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

1824 match = True 

1825 break 

1826 return match 

1827 

1828 async def _execute( 

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

1830 ) -> None: 

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

1832 self._transforms = transforms 

1833 try: 

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

1835 raise HTTPError(405) 

1836 

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

1838 if not _has_stream_request_body(self.__class__): 

1839 try: 

1840 self.request._parse_body() 

1841 except httputil.HTTPInputError as e: 

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

1843 

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

1845 self.path_kwargs = { 

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

1847 } 

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

1849 # the proper cookie 

1850 if self.request.method not in ( 

1851 "GET", 

1852 "HEAD", 

1853 "OPTIONS", 

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

1855 self.check_xsrf_cookie() 

1856 

1857 result = self.prepare() 

1858 if result is not None: 

1859 result = await result # type: ignore 

1860 if self._prepared_future is not None: 

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

1862 # and are ready for the body to arrive. 

1863 future_set_result_unless_cancelled(self._prepared_future, None) 

1864 if self._finished: 

1865 return 

1866 

1867 if _has_stream_request_body(self.__class__): 

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

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

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

1871 # instead. 

1872 try: 

1873 await self.request._body_future 

1874 except iostream.StreamClosedError: 

1875 return 

1876 

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

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

1879 if result is not None: 

1880 result = await result 

1881 if self._auto_finish and not self._finished: 

1882 self.finish() 

1883 except Exception as e: 

1884 try: 

1885 self._handle_request_exception(e) 

1886 except Exception: 

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

1888 finally: 

1889 # Unset result to avoid circular references 

1890 result = None 

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

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

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

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

1895 self._prepared_future.set_result(None) 

1896 

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

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

1899 

1900 Requires the `.stream_request_body` decorator. 

1901 

1902 May be a coroutine for flow control. 

1903 """ 

1904 raise NotImplementedError() 

1905 

1906 def _log(self) -> None: 

1907 """Logs the current request. 

1908 

1909 Sort of deprecated since this functionality was moved to the 

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

1911 that have overridden this method. 

1912 """ 

1913 self.application.log_request(self) 

1914 

1915 def _request_summary(self) -> str: 

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

1917 self.request.method, 

1918 self.request.uri, 

1919 self.request.remote_ip, 

1920 ) 

1921 

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

1923 if isinstance(e, Finish): 

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

1925 if not self._finished: 

1926 self.finish(*e.args) 

1927 return 

1928 try: 

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

1930 except Exception: 

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

1932 # to avoid leaking the connection. 

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

1934 if self._finished: 

1935 # Extra errors after the request has been finished should 

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

1937 # send a response. 

1938 return 

1939 if isinstance(e, HTTPError): 

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

1941 else: 

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

1943 

1944 def log_exception( 

1945 self, 

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

1947 value: Optional[BaseException], 

1948 tb: Optional[TracebackType], 

1949 ) -> None: 

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

1951 

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

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

1954 other exceptions as errors with stack traces (on the 

1955 ``tornado.application`` logger). 

1956 

1957 .. versionadded:: 3.1 

1958 """ 

1959 if isinstance(value, HTTPError): 

1960 log_message = value.get_message() 

1961 if log_message: 

1962 format = "%d %s: %s" 

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

1964 gen_log.warning(format, *args) 

1965 else: 

1966 app_log.error( 

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

1968 self._request_summary(), 

1969 self.request, 

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

1971 ) 

1972 

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

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

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

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

1977 if name not in self._active_modules: 

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

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

1980 return _unicode(rendered) 

1981 

1982 return render 

1983 

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

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

1986 

1987 def _clear_representation_headers(self) -> None: 

1988 # 304 responses should not contain representation metadata 

1989 # headers (defined in 

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

1991 # not explicitly allowed by 

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

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

1994 for h in headers: 

1995 self.clear_header(h) 

1996 

1997 

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

1999 

2000 

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

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

2003 

2004 This decorator implies the following changes: 

2005 

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

2007 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

2015 until those futures have completed. 

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

2017 the entire body has been read. 

2018 

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

2020 for example usage. 

2021 """ # noqa: E501 

2022 if not issubclass(cls, RequestHandler): 

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

2024 cls._stream_request_body = True 

2025 return cls 

2026 

2027 

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

2029 if not issubclass(cls, RequestHandler): 

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

2031 return cls._stream_request_body 

2032 

2033 

2034def removeslash( 

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

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

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

2038 

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

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

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

2042 """ 

2043 

2044 @functools.wraps(method) 

2045 def wrapper( # type: ignore 

2046 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

2052 if self.request.query: 

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

2054 self.redirect(uri, permanent=True) 

2055 return None 

2056 else: 

2057 raise HTTPError(404) 

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

2059 

2060 return wrapper 

2061 

2062 

2063def addslash( 

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

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

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

2067 

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

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

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

2071 """ 

2072 

2073 @functools.wraps(method) 

2074 def wrapper( # type: ignore 

2075 self: RequestHandler, *args, **kwargs 

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

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

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

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

2080 if self.request.query: 

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

2082 self.redirect(uri, permanent=True) 

2083 return None 

2084 raise HTTPError(404) 

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

2086 

2087 return wrapper 

2088 

2089 

2090class _ApplicationRouter(ReversibleRuleRouter): 

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

2092 

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

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

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

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

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

2098 `_ApplicationRouter` instance. 

2099 """ 

2100 

2101 def __init__( 

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

2103 ) -> None: 

2104 assert isinstance(application, Application) 

2105 self.application = application 

2106 super().__init__(rules) 

2107 

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

2109 rule = super().process_rule(rule) 

2110 

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

2112 rule.target = _ApplicationRouter( 

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

2114 ) 

2115 

2116 return rule 

2117 

2118 def get_target_delegate( 

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

2120 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

2122 return self.application.get_handler_delegate( 

2123 request, target, **target_params 

2124 ) 

2125 

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

2127 

2128 

2129class Application(ReversibleRouter): 

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

2131 

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

2133 HTTPServer to serve the application:: 

2134 

2135 application = web.Application([ 

2136 (r"/", MainPageHandler), 

2137 ]) 

2138 http_server = httpserver.HTTPServer(application) 

2139 http_server.listen(8080) 

2140 

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

2142 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2147 

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

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

2150 

2151 application = web.Application([ 

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

2153 (r"/", MainPageHandler), 

2154 (r"/feed", FeedHandler), 

2155 ]), 

2156 ]) 

2157 

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

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

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

2161 

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

2163 instantiate an instance of the first request class whose regexp 

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

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

2166 

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

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

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

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

2171 `StaticFileHandler` can be installed automatically with the 

2172 static_path setting described below):: 

2173 

2174 application = web.Application([ 

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

2176 ]) 

2177 

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

2179 a host regular expression as the first argument:: 

2180 

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

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

2183 ]) 

2184 

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

2186 parameter value is matched against host regular expressions. 

2187 

2188 

2189 .. warning:: 

2190 

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

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

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

2194 other private networks. Appropriate host patterns must be used 

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

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

2197 may be vulnerable to DNS rebinding. 

2198 

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

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

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

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

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

2204 `StaticFileHandler` can be specified with the 

2205 ``static_handler_class`` setting. 

2206 

2207 .. versionchanged:: 4.5 

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

2209 

2210 """ 

2211 

2212 def __init__( 

2213 self, 

2214 handlers: Optional[_RuleList] = None, 

2215 default_host: Optional[str] = None, 

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

2217 **settings: Any, 

2218 ) -> None: 

2219 if transforms is None: 

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

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

2222 self.transforms.append(GZipContentEncoding) 

2223 else: 

2224 self.transforms = transforms 

2225 self.default_host = default_host 

2226 self.settings = settings 

2227 self.ui_modules = { 

2228 "linkify": _linkify, 

2229 "xsrf_form_html": _xsrf_form_html, 

2230 "Template": TemplateModule, 

2231 } 

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

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

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

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

2236 path = self.settings["static_path"] 

2237 handlers = list(handlers or []) 

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

2239 static_handler_class = settings.get( 

2240 "static_handler_class", StaticFileHandler 

2241 ) 

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

2243 static_handler_args["path"] = path 

2244 for pattern in [ 

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

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

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

2248 ]: 

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

2250 

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

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

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

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

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

2256 

2257 self.wildcard_router = _ApplicationRouter(self, handlers) 

2258 self.default_router = _ApplicationRouter( 

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

2260 ) 

2261 

2262 # Automatically reload modified modules 

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

2264 from tornado import autoreload 

2265 

2266 autoreload.start() 

2267 

2268 def listen( 

2269 self, 

2270 port: int, 

2271 address: Optional[str] = None, 

2272 *, 

2273 family: socket.AddressFamily = socket.AF_UNSPEC, 

2274 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2275 flags: Optional[int] = None, 

2276 reuse_port: bool = False, 

2277 **kwargs: Any, 

2278 ) -> HTTPServer: 

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

2280 

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

2282 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2287 

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

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

2290 the server. 

2291 

2292 Returns the `.HTTPServer` object. 

2293 

2294 .. versionchanged:: 4.3 

2295 Now returns the `.HTTPServer` object. 

2296 

2297 .. versionchanged:: 6.2 

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

2299 including ``reuse_port``. 

2300 """ 

2301 server = HTTPServer(self, **kwargs) 

2302 server.listen( 

2303 port, 

2304 address=address, 

2305 family=family, 

2306 backlog=backlog, 

2307 flags=flags, 

2308 reuse_port=reuse_port, 

2309 ) 

2310 return server 

2311 

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

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

2314 

2315 Host patterns are processed sequentially in the order they were 

2316 added. All matching patterns will be considered. 

2317 """ 

2318 host_matcher = HostMatches(host_pattern) 

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

2320 

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

2322 

2323 if self.default_host is not None: 

2324 self.wildcard_router.add_rules( 

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

2326 ) 

2327 

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

2329 self.transforms.append(transform_class) 

2330 

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

2332 if isinstance(methods, types.ModuleType): 

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

2334 elif isinstance(methods, list): 

2335 for m in methods: 

2336 self._load_ui_methods(m) 

2337 else: 

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

2339 if ( 

2340 not name.startswith("_") 

2341 and hasattr(fn, "__call__") 

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

2343 ): 

2344 self.ui_methods[name] = fn 

2345 

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

2347 if isinstance(modules, types.ModuleType): 

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

2349 elif isinstance(modules, list): 

2350 for m in modules: 

2351 self._load_ui_modules(m) 

2352 else: 

2353 assert isinstance(modules, dict) 

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

2355 try: 

2356 if issubclass(cls, UIModule): 

2357 self.ui_modules[name] = cls 

2358 except TypeError: 

2359 pass 

2360 

2361 def __call__( 

2362 self, request: httputil.HTTPServerRequest 

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

2364 # Legacy HTTPServer interface 

2365 dispatcher = self.find_handler(request) 

2366 return dispatcher.execute() 

2367 

2368 def find_handler( 

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

2370 ) -> "_HandlerDelegate": 

2371 route = self.default_router.find_handler(request) 

2372 if route is not None: 

2373 return cast("_HandlerDelegate", route) 

2374 

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

2376 return self.get_handler_delegate( 

2377 request, 

2378 self.settings["default_handler_class"], 

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

2380 ) 

2381 

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

2383 

2384 def get_handler_delegate( 

2385 self, 

2386 request: httputil.HTTPServerRequest, 

2387 target_class: Type[RequestHandler], 

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

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

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

2391 ) -> "_HandlerDelegate": 

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

2393 for application and `RequestHandler` subclass. 

2394 

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

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

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

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

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

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

2401 """ 

2402 return _HandlerDelegate( 

2403 self, request, target_class, target_kwargs, path_args, path_kwargs 

2404 ) 

2405 

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

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

2408 

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

2410 

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

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

2413 and url-escaped. 

2414 """ 

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

2416 if reversed_url is not None: 

2417 return reversed_url 

2418 

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

2420 

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

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

2423 

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

2425 this behavior either subclass Application and override this method, 

2426 or pass a function in the application settings dictionary as 

2427 ``log_function``. 

2428 """ 

2429 if "log_function" in self.settings: 

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

2431 return 

2432 if handler.get_status() < 400: 

2433 log_method = access_log.info 

2434 elif handler.get_status() < 500: 

2435 log_method = access_log.warning 

2436 else: 

2437 log_method = access_log.error 

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

2439 log_method( 

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

2441 handler.get_status(), 

2442 handler._request_summary(), 

2443 request_time, 

2444 ) 

2445 

2446 

2447class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2448 def __init__( 

2449 self, 

2450 application: Application, 

2451 request: httputil.HTTPServerRequest, 

2452 handler_class: Type[RequestHandler], 

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

2454 path_args: Optional[List[bytes]], 

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

2456 ) -> None: 

2457 self.application = application 

2458 self.connection = request.connection 

2459 self.request = request 

2460 self.handler_class = handler_class 

2461 self.handler_kwargs = handler_kwargs or {} 

2462 self.path_args = path_args or [] 

2463 self.path_kwargs = path_kwargs or {} 

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

2465 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2466 

2467 def headers_received( 

2468 self, 

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

2470 headers: httputil.HTTPHeaders, 

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

2472 if self.stream_request_body: 

2473 self.request._body_future = Future() 

2474 return self.execute() 

2475 return None 

2476 

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

2478 if self.stream_request_body: 

2479 return self.handler.data_received(data) 

2480 else: 

2481 self.chunks.append(data) 

2482 return None 

2483 

2484 def finish(self) -> None: 

2485 if self.stream_request_body: 

2486 future_set_result_unless_cancelled(self.request._body_future, None) 

2487 else: 

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

2489 # the right exception handler scope. 

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

2491 self.execute() 

2492 

2493 def on_connection_close(self) -> None: 

2494 if self.stream_request_body: 

2495 self.handler.on_connection_close() 

2496 else: 

2497 self.chunks = None # type: ignore 

2498 

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

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

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

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

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

2504 with RequestHandler._template_loader_lock: 

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

2506 loader.reset() 

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

2508 static_handler_class = self.application.settings.get( 

2509 "static_handler_class", StaticFileHandler 

2510 ) 

2511 static_handler_class.reset() 

2512 

2513 self.handler = self.handler_class( 

2514 self.application, self.request, **self.handler_kwargs 

2515 ) 

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

2517 

2518 if self.stream_request_body: 

2519 self.handler._prepared_future = Future() 

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

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

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

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

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

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

2526 # with WSGI) 

2527 fut = gen.convert_yielded( 

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

2529 ) 

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

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

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

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

2534 return self.handler._prepared_future 

2535 

2536 

2537class HTTPError(Exception): 

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

2539 

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

2541 `RequestHandler.send_error` since it automatically ends the 

2542 current function. 

2543 

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

2545 `RequestHandler.write_error`. 

2546 

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

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

2549 keyword argument is given. 

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

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

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

2553 in with remaining positional parameters. 

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

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

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

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

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

2559 error message. 

2560 """ 

2561 

2562 def __init__( 

2563 self, 

2564 status_code: int = 500, 

2565 log_message: Optional[str] = None, 

2566 *args: Any, 

2567 **kwargs: Any, 

2568 ) -> None: 

2569 self.status_code = status_code 

2570 self._log_message = log_message 

2571 self.args = args 

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

2573 

2574 @property 

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

2576 """ 

2577 A backwards compatible way of accessing log_message. 

2578 """ 

2579 if self._log_message and not self.args: 

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

2581 return self._log_message 

2582 

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

2584 if self._log_message and self.args: 

2585 return self._log_message % self.args 

2586 return self._log_message 

2587 

2588 def __str__(self) -> str: 

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

2590 self.status_code, 

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

2592 ) 

2593 log_message = self.get_message() 

2594 if log_message: 

2595 return message + " (" + log_message + ")" 

2596 else: 

2597 return message 

2598 

2599 

2600class Finish(Exception): 

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

2602 

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

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

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

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

2607 

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

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

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

2611 

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

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

2614 

2615 if self.current_user is None: 

2616 self.set_status(401) 

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

2618 raise Finish() 

2619 

2620 .. versionchanged:: 4.3 

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

2622 `RequestHandler.finish`. 

2623 """ 

2624 

2625 pass 

2626 

2627 

2628class MissingArgumentError(HTTPError): 

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

2630 

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

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

2633 

2634 .. versionadded:: 3.1 

2635 """ 

2636 

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

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

2639 self.arg_name = arg_name 

2640 

2641 

2642class ErrorHandler(RequestHandler): 

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

2644 

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

2646 self.set_status(status_code) 

2647 

2648 def prepare(self) -> None: 

2649 raise HTTPError(self._status_code) 

2650 

2651 def check_xsrf_cookie(self) -> None: 

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

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

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

2655 pass 

2656 

2657 

2658class RedirectHandler(RequestHandler): 

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

2660 

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

2662 

2663 application = web.Application([ 

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

2665 ]) 

2666 

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

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

2669 

2670 application = web.Application([ 

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

2672 ]) 

2673 

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

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

2676 formatted like:: 

2677 

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

2679 

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

2681 values are substituted. 

2682 

2683 .. versionchanged:: 4.5 

2684 Added support for substitutions into the destination URL. 

2685 

2686 .. versionchanged:: 5.0 

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

2688 destination URL. 

2689 """ 

2690 

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

2692 self._url = url 

2693 self._permanent = permanent 

2694 

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

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

2697 if self.request.query_arguments: 

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

2699 to_url = httputil.url_concat( 

2700 to_url, 

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

2702 ) 

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

2704 

2705 

2706class StaticFileHandler(RequestHandler): 

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

2708 

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

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

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

2712 and ``static_handler_args`` settings. 

2713 

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

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

2716 

2717 application = web.Application([ 

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

2719 ]) 

2720 

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

2722 local root directory of the content to be served. 

2723 

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

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

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

2727 

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

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

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

2731 argument for your ``StaticFileHandler``. 

2732 

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

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

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

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

2737 be used to construct a versioned url. 

2738 

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

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

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

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

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

2744 HTML5 audio or video). 

2745 

2746 **Subclassing notes** 

2747 

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

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

2750 instance methods, the inheritance patterns are somewhat unusual. 

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

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

2753 ``self.absolute_path``, and ``self.modified``. 

2754 

2755 Subclasses should only override methods discussed in this section; 

2756 overriding other methods is error-prone. Overriding 

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

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

2759 

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

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

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

2763 

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

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

2766 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2767 `validate_absolute_path`. 

2768 

2769 .. versionchanged:: 3.1 

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

2771 """ 

2772 

2773 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2774 

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

2776 _lock = threading.Lock() # protects _static_hashes 

2777 

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

2779 self.root = path 

2780 self.default_filename = default_filename 

2781 

2782 @classmethod 

2783 def reset(cls) -> None: 

2784 with cls._lock: 

2785 cls._static_hashes = {} 

2786 

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

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

2789 

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

2791 # Set up our path instance variables. 

2792 self.path = self.parse_url_path(path) 

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

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

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

2796 if self.absolute_path is None: 

2797 return 

2798 

2799 self.modified = self.get_modified_time() 

2800 self.set_headers() 

2801 

2802 if self.should_return_304(): 

2803 self.set_status(304) 

2804 return 

2805 

2806 request_range = None 

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

2808 if range_header: 

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

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

2811 request_range = httputil._parse_request_range(range_header) 

2812 

2813 size = self.get_content_size() 

2814 if request_range: 

2815 start, end = request_range 

2816 if start is not None and start < 0: 

2817 start += size 

2818 if start < 0: 

2819 start = 0 

2820 if ( 

2821 start is not None 

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

2823 ) or end == 0: 

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

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

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

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

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

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

2830 self.set_status(416) # Range Not Satisfiable 

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

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

2833 return 

2834 if end is not None and end > size: 

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

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

2837 end = size 

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

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

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

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

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

2843 self.set_status(206) # Partial Content 

2844 self.set_header( 

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

2846 ) 

2847 else: 

2848 start = end = None 

2849 

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

2851 content_length = end - start 

2852 elif end is not None: 

2853 content_length = end 

2854 elif start is not None: 

2855 content_length = size - start 

2856 else: 

2857 content_length = size 

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

2859 

2860 if include_body: 

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

2862 if isinstance(content, bytes): 

2863 content = [content] 

2864 for chunk in content: 

2865 try: 

2866 self.write(chunk) 

2867 await self.flush() 

2868 except iostream.StreamClosedError: 

2869 return 

2870 else: 

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

2872 

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

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

2875 

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

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

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

2879 

2880 .. versionadded:: 3.1 

2881 """ 

2882 assert self.absolute_path is not None 

2883 version_hash = self._get_cached_version(self.absolute_path) 

2884 if not version_hash: 

2885 return None 

2886 return f'"{version_hash}"' 

2887 

2888 def set_headers(self) -> None: 

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

2890 

2891 .. versionadded:: 3.1 

2892 """ 

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

2894 self.set_etag_header() 

2895 

2896 if self.modified is not None: 

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

2898 

2899 content_type = self.get_content_type() 

2900 if content_type: 

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

2902 

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

2904 if cache_time > 0: 

2905 self.set_header( 

2906 "Expires", 

2907 datetime.datetime.now(datetime.timezone.utc) 

2908 + datetime.timedelta(seconds=cache_time), 

2909 ) 

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

2911 

2912 self.set_extra_headers(self.path) 

2913 

2914 def should_return_304(self) -> bool: 

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

2916 

2917 .. versionadded:: 3.1 

2918 """ 

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

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

2921 return self.check_etag_header() 

2922 

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

2924 # content has not been modified 

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

2926 if ims_value is not None: 

2927 try: 

2928 if_since = email.utils.parsedate_to_datetime(ims_value) 

2929 except Exception: 

2930 return False 

2931 if if_since.tzinfo is None: 

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

2933 assert self.modified is not None 

2934 if if_since >= self.modified: 

2935 return True 

2936 

2937 return False 

2938 

2939 @classmethod 

2940 def get_absolute_path(cls, root: str, path: str) -> str: 

2941 """Returns the absolute location of ``path`` relative to ``root``. 

2942 

2943 ``root`` is the path configured for this `StaticFileHandler` 

2944 (in most cases the ``static_path`` `Application` setting). 

2945 

2946 This class method may be overridden in subclasses. By default 

2947 it returns a filesystem path, but other strings may be used 

2948 as long as they are unique and understood by the subclass's 

2949 overridden `get_content`. 

2950 

2951 .. versionadded:: 3.1 

2952 """ 

2953 abspath = os.path.abspath(os.path.join(root, path)) 

2954 return abspath 

2955 

2956 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2957 """Validate and return the absolute path. 

2958 

2959 ``root`` is the configured path for the `StaticFileHandler`, 

2960 and ``path`` is the result of `get_absolute_path` 

2961 

2962 This is an instance method called during request processing, 

2963 so it may raise `HTTPError` or use methods like 

2964 `RequestHandler.redirect` (return None after redirecting to 

2965 halt further processing). This is where 404 errors for missing files 

2966 are generated. 

2967 

2968 This method may modify the path before returning it, but note that 

2969 any such modifications will not be understood by `make_static_url`. 

2970 

2971 In instance methods, this method's result is available as 

2972 ``self.absolute_path``. 

2973 

2974 .. versionadded:: 3.1 

2975 """ 

2976 # os.path.abspath strips a trailing /. 

2977 # We must add it back to `root` so that we only match files 

2978 # in a directory named `root` instead of files starting with 

2979 # that prefix. 

2980 root = os.path.abspath(root) 

2981 if not root.endswith(os.path.sep): 

2982 # abspath always removes a trailing slash, except when 

2983 # root is '/'. This is an unusual case, but several projects 

2984 # have independently discovered this technique to disable 

2985 # Tornado's path validation and (hopefully) do their own, 

2986 # so we need to support it. 

2987 root += os.path.sep 

2988 # The trailing slash also needs to be temporarily added back 

2989 # the requested path so a request to root/ will match. 

2990 if not (absolute_path + os.path.sep).startswith(root): 

2991 raise HTTPError(403, "%s is not in root static directory", self.path) 

2992 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2993 # need to look at the request.path here for when path is empty 

2994 # but there is some prefix to the path that was already 

2995 # trimmed by the routing 

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

2997 if self.request.path.startswith("//"): 

2998 # A redirect with two initial slashes is a "protocol-relative" URL. 

2999 # This means the next path segment is treated as a hostname instead 

3000 # of a part of the path, making this effectively an open redirect. 

3001 # Reject paths starting with two slashes to prevent this. 

3002 # This is only reachable under certain configurations. 

3003 raise HTTPError( 

3004 403, "cannot redirect path with two initial slashes" 

3005 ) 

3006 self.redirect(self.request.path + "/", permanent=True) 

3007 return None 

3008 absolute_path = os.path.join(absolute_path, self.default_filename) 

3009 if not os.path.exists(absolute_path): 

3010 raise HTTPError(404) 

3011 if not os.path.isfile(absolute_path): 

3012 raise HTTPError(403, "%s is not a file", self.path) 

3013 return absolute_path 

3014 

3015 @classmethod 

3016 def get_content( 

3017 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

3018 ) -> Generator[bytes, None, None]: 

3019 """Retrieve the content of the requested resource which is located 

3020 at the given absolute path. 

3021 

3022 This class method may be overridden by subclasses. Note that its 

3023 signature is different from other overridable class methods 

3024 (no ``settings`` argument); this is deliberate to ensure that 

3025 ``abspath`` is able to stand on its own as a cache key. 

3026 

3027 This method should either return a byte string or an iterator 

3028 of byte strings. The latter is preferred for large files 

3029 as it helps reduce memory fragmentation. 

3030 

3031 .. versionadded:: 3.1 

3032 """ 

3033 with open(abspath, "rb") as file: 

3034 if start is not None: 

3035 file.seek(start) 

3036 if end is not None: 

3037 remaining = end - (start or 0) # type: Optional[int] 

3038 else: 

3039 remaining = None 

3040 while True: 

3041 chunk_size = 64 * 1024 

3042 if remaining is not None and remaining < chunk_size: 

3043 chunk_size = remaining 

3044 chunk = file.read(chunk_size) 

3045 if chunk: 

3046 if remaining is not None: 

3047 remaining -= len(chunk) 

3048 yield chunk 

3049 else: 

3050 if remaining is not None: 

3051 assert remaining == 0 

3052 return 

3053 

3054 @classmethod 

3055 def get_content_version(cls, abspath: str) -> str: 

3056 """Returns a version string for the resource at the given path. 

3057 

3058 This class method may be overridden by subclasses. The 

3059 default implementation is a SHA-512 hash of the file's contents. 

3060 

3061 .. versionadded:: 3.1 

3062 """ 

3063 data = cls.get_content(abspath) 

3064 hasher = hashlib.sha512() 

3065 if isinstance(data, bytes): 

3066 hasher.update(data) 

3067 else: 

3068 for chunk in data: 

3069 hasher.update(chunk) 

3070 return hasher.hexdigest() 

3071 

3072 def _stat(self) -> os.stat_result: 

3073 assert self.absolute_path is not None 

3074 if not hasattr(self, "_stat_result"): 

3075 self._stat_result = os.stat(self.absolute_path) 

3076 return self._stat_result 

3077 

3078 def get_content_size(self) -> int: 

3079 """Retrieve the total size of the resource at the given path. 

3080 

3081 This method may be overridden by subclasses. 

3082 

3083 .. versionadded:: 3.1 

3084 

3085 .. versionchanged:: 4.0 

3086 This method is now always called, instead of only when 

3087 partial results are requested. 

3088 """ 

3089 stat_result = self._stat() 

3090 return stat_result.st_size 

3091 

3092 def get_modified_time(self) -> Optional[datetime.datetime]: 

3093 """Returns the time that ``self.absolute_path`` was last modified. 

3094 

3095 May be overridden in subclasses. Should return a `~datetime.datetime` 

3096 object or None. 

3097 

3098 .. versionadded:: 3.1 

3099 

3100 .. versionchanged:: 6.4 

3101 Now returns an aware datetime object instead of a naive one. 

3102 Subclasses that override this method may return either kind. 

3103 """ 

3104 stat_result = self._stat() 

3105 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3106 # which truncates the fractional portion of the timestamp. It 

3107 # was changed from that form to stat_result.st_mtime to 

3108 # satisfy mypy (which disallows the bracket operator), but the 

3109 # latter form returns a float instead of an int. For 

3110 # consistency with the past (and because we have a unit test 

3111 # that relies on this), we truncate the float here, although 

3112 # I'm not sure that's the right thing to do. 

3113 modified = datetime.datetime.fromtimestamp( 

3114 int(stat_result.st_mtime), datetime.timezone.utc 

3115 ) 

3116 return modified 

3117 

3118 def get_content_type(self) -> str: 

3119 """Returns the ``Content-Type`` header to be used for this request. 

3120 

3121 .. versionadded:: 3.1 

3122 """ 

3123 assert self.absolute_path is not None 

3124 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3125 # per RFC 6713, use the appropriate type for a gzip compressed file 

3126 if encoding == "gzip": 

3127 return "application/gzip" 

3128 # As of 2015-07-21 there is no bzip2 encoding defined at 

3129 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3130 # So for that (and any other encoding), use octet-stream. 

3131 elif encoding is not None: 

3132 return "application/octet-stream" 

3133 elif mime_type is not None: 

3134 return mime_type 

3135 # if mime_type not detected, use application/octet-stream 

3136 else: 

3137 return "application/octet-stream" 

3138 

3139 def set_extra_headers(self, path: str) -> None: 

3140 """For subclass to add extra headers to the response""" 

3141 pass 

3142 

3143 def get_cache_time( 

3144 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3145 ) -> int: 

3146 """Override to customize cache control behavior. 

3147 

3148 Return a positive number of seconds to make the result 

3149 cacheable for that amount of time or 0 to mark resource as 

3150 cacheable for an unspecified amount of time (subject to 

3151 browser heuristics). 

3152 

3153 By default returns cache expiry of 10 years for resources requested 

3154 with ``v`` argument. 

3155 """ 

3156 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3157 

3158 @classmethod 

3159 def make_static_url( 

3160 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3161 ) -> str: 

3162 """Constructs a versioned url for the given path. 

3163 

3164 This method may be overridden in subclasses (but note that it 

3165 is a class method rather than an instance method). Subclasses 

3166 are only required to implement the signature 

3167 ``make_static_url(cls, settings, path)``; other keyword 

3168 arguments may be passed through `~RequestHandler.static_url` 

3169 but are not standard. 

3170 

3171 ``settings`` is the `Application.settings` dictionary. ``path`` 

3172 is the static path being requested. The url returned should be 

3173 relative to the current host. 

3174 

3175 ``include_version`` determines whether the generated URL should 

3176 include the query string containing the version hash of the 

3177 file corresponding to the given ``path``. 

3178 

3179 """ 

3180 url = settings.get("static_url_prefix", "/static/") + path 

3181 if not include_version: 

3182 return url 

3183 

3184 version_hash = cls.get_version(settings, path) 

3185 if not version_hash: 

3186 return url 

3187 

3188 return f"{url}?v={version_hash}" 

3189 

3190 def parse_url_path(self, url_path: str) -> str: 

3191 """Converts a static URL path into a filesystem path. 

3192 

3193 ``url_path`` is the path component of the URL with 

3194 ``static_url_prefix`` removed. The return value should be 

3195 filesystem path relative to ``static_path``. 

3196 

3197 This is the inverse of `make_static_url`. 

3198 """ 

3199 if os.path.sep != "/": 

3200 url_path = url_path.replace("/", os.path.sep) 

3201 return url_path 

3202 

3203 @classmethod 

3204 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3205 """Generate the version string to be used in static URLs. 

3206 

3207 ``settings`` is the `Application.settings` dictionary and ``path`` 

3208 is the relative location of the requested asset on the filesystem. 

3209 The returned value should be a string, or ``None`` if no version 

3210 could be determined. 

3211 

3212 .. versionchanged:: 3.1 

3213 This method was previously recommended for subclasses to override; 

3214 `get_content_version` is now preferred as it allows the base 

3215 class to handle caching of the result. 

3216 """ 

3217 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3218 return cls._get_cached_version(abs_path) 

3219 

3220 @classmethod 

3221 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3222 with cls._lock: 

3223 hashes = cls._static_hashes 

3224 if abs_path not in hashes: 

3225 try: 

3226 hashes[abs_path] = cls.get_content_version(abs_path) 

3227 except Exception: 

3228 gen_log.error("Could not open static file %r", abs_path) 

3229 hashes[abs_path] = None 

3230 hsh = hashes.get(abs_path) 

3231 if hsh: 

3232 return hsh 

3233 return None 

3234 

3235 

3236class FallbackHandler(RequestHandler): 

3237 """A `RequestHandler` that wraps another HTTP server callback. 

3238 

3239 The fallback is a callable object that accepts an 

3240 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3241 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3242 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3243 usage:: 

3244 

3245 wsgi_app = tornado.wsgi.WSGIContainer( 

3246 django.core.handlers.wsgi.WSGIHandler()) 

3247 application = tornado.web.Application([ 

3248 (r"/foo", FooHandler), 

3249 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3250 ]) 

3251 """ 

3252 

3253 def initialize( 

3254 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3255 ) -> None: 

3256 self.fallback = fallback 

3257 

3258 def prepare(self) -> None: 

3259 self.fallback(self.request) 

3260 self._finished = True 

3261 self.on_finish() 

3262 

3263 

3264class OutputTransform: 

3265 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3266 

3267 Applications are not expected to create their own OutputTransforms 

3268 or interact with them directly; the framework chooses which transforms 

3269 (if any) to apply. 

3270 """ 

3271 

3272 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3273 pass 

3274 

3275 def transform_first_chunk( 

3276 self, 

3277 status_code: int, 

3278 headers: httputil.HTTPHeaders, 

3279 chunk: bytes, 

3280 finishing: bool, 

3281 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3282 return status_code, headers, chunk 

3283 

3284 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3285 return chunk 

3286 

3287 

3288class GZipContentEncoding(OutputTransform): 

3289 """Applies the gzip content encoding to the response. 

3290 

3291 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3292 

3293 .. versionchanged:: 4.0 

3294 Now compresses all mime types beginning with ``text/``, instead 

3295 of just a whitelist. (the whitelist is still used for certain 

3296 non-text mime types). 

3297 """ 

3298 

3299 # Whitelist of compressible mime types (in addition to any types 

3300 # beginning with "text/"). 

3301 CONTENT_TYPES = { 

3302 "application/javascript", 

3303 "application/x-javascript", 

3304 "application/xml", 

3305 "application/atom+xml", 

3306 "application/json", 

3307 "application/xhtml+xml", 

3308 "image/svg+xml", 

3309 } 

3310 # Python's GzipFile defaults to level 9, while most other gzip 

3311 # tools (including gzip itself) default to 6, which is probably a 

3312 # better CPU/size tradeoff. 

3313 GZIP_LEVEL = 6 

3314 # Responses that are too short are unlikely to benefit from gzipping 

3315 # after considering the "Content-Encoding: gzip" header and the header 

3316 # inside the gzip encoding. 

3317 # Note that responses written in multiple chunks will be compressed 

3318 # regardless of size. 

3319 MIN_LENGTH = 1024 

3320 

3321 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3322 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3323 

3324 def _compressible_type(self, ctype: str) -> bool: 

3325 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3326 

3327 def transform_first_chunk( 

3328 self, 

3329 status_code: int, 

3330 headers: httputil.HTTPHeaders, 

3331 chunk: bytes, 

3332 finishing: bool, 

3333 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3334 # TODO: can/should this type be inherited from the superclass? 

3335 if "Vary" in headers: 

3336 headers["Vary"] += ", Accept-Encoding" 

3337 else: 

3338 headers["Vary"] = "Accept-Encoding" 

3339 if self._gzipping: 

3340 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3341 self._gzipping = ( 

3342 self._compressible_type(ctype) 

3343 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3344 and ("Content-Encoding" not in headers) 

3345 ) 

3346 if self._gzipping: 

3347 headers["Content-Encoding"] = "gzip" 

3348 self._gzip_value = BytesIO() 

3349 self._gzip_file = gzip.GzipFile( 

3350 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3351 ) 

3352 chunk = self.transform_chunk(chunk, finishing) 

3353 if "Content-Length" in headers: 

3354 # The original content length is no longer correct. 

3355 # If this is the last (and only) chunk, we can set the new 

3356 # content-length; otherwise we remove it and fall back to 

3357 # chunked encoding. 

3358 if finishing: 

3359 headers["Content-Length"] = str(len(chunk)) 

3360 else: 

3361 del headers["Content-Length"] 

3362 return status_code, headers, chunk 

3363 

3364 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3365 if self._gzipping: 

3366 self._gzip_file.write(chunk) 

3367 if finishing: 

3368 self._gzip_file.close() 

3369 else: 

3370 self._gzip_file.flush() 

3371 chunk = self._gzip_value.getvalue() 

3372 self._gzip_value.truncate(0) 

3373 self._gzip_value.seek(0) 

3374 return chunk 

3375 

3376 

3377def authenticated( 

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

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

3380 """Decorate methods with this to require that the user be logged in. 

3381 

3382 If the user is not logged in, they will be redirected to the configured 

3383 `login url <RequestHandler.get_login_url>`. 

3384 

3385 If you configure a login url with a query parameter, Tornado will 

3386 assume you know what you're doing and use it as-is. If not, it 

3387 will add a `next` parameter so the login page knows where to send 

3388 you once you're logged in. 

3389 """ 

3390 

3391 @functools.wraps(method) 

3392 def wrapper( # type: ignore 

3393 self: RequestHandler, *args, **kwargs 

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

3395 if not self.current_user: 

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

3397 url = self.get_login_url() 

3398 if "?" not in url: 

3399 if urllib.parse.urlsplit(url).scheme: 

3400 # if login url is absolute, make next absolute too 

3401 next_url = self.request.full_url() 

3402 else: 

3403 assert self.request.uri is not None 

3404 next_url = self.request.uri 

3405 url += "?" + urlencode(dict(next=next_url)) 

3406 self.redirect(url) 

3407 return None 

3408 raise HTTPError(403) 

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

3410 

3411 return wrapper 

3412 

3413 

3414class UIModule: 

3415 """A re-usable, modular UI unit on a page. 

3416 

3417 UI modules often execute additional queries, and they can include 

3418 additional CSS and JavaScript that will be included in the output 

3419 page, which is automatically inserted on page render. 

3420 

3421 Subclasses of UIModule must override the `render` method. 

3422 """ 

3423 

3424 def __init__(self, handler: RequestHandler) -> None: 

3425 self.handler = handler 

3426 self.request = handler.request 

3427 self.ui = handler.ui 

3428 self.locale = handler.locale 

3429 

3430 @property 

3431 def current_user(self) -> Any: 

3432 return self.handler.current_user 

3433 

3434 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]: 

3435 """Override in subclasses to return this module's output.""" 

3436 raise NotImplementedError() 

3437 

3438 def embedded_javascript(self) -> Optional[str]: 

3439 """Override to return a JavaScript string 

3440 to be embedded in the page.""" 

3441 return None 

3442 

3443 def javascript_files(self) -> Optional[Iterable[str]]: 

3444 """Override to return a list of JavaScript files needed by this module. 

3445 

3446 If the return values are relative paths, they will be passed to 

3447 `RequestHandler.static_url`; otherwise they will be used as-is. 

3448 """ 

3449 return None 

3450 

3451 def embedded_css(self) -> Optional[str]: 

3452 """Override to return a CSS string 

3453 that will be embedded in the page.""" 

3454 return None 

3455 

3456 def css_files(self) -> Optional[Iterable[str]]: 

3457 """Override to returns a list of CSS files required by this module. 

3458 

3459 If the return values are relative paths, they will be passed to 

3460 `RequestHandler.static_url`; otherwise they will be used as-is. 

3461 """ 

3462 return None 

3463 

3464 def html_head(self) -> Optional[str]: 

3465 """Override to return an HTML string that will be put in the <head/> 

3466 element. 

3467 """ 

3468 return None 

3469 

3470 def html_body(self) -> Optional[str]: 

3471 """Override to return an HTML string that will be put at the end of 

3472 the <body/> element. 

3473 """ 

3474 return None 

3475 

3476 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3477 """Renders a template and returns it as a string.""" 

3478 return self.handler.render_string(path, **kwargs) 

3479 

3480 

3481class _linkify(UIModule): 

3482 def render(self, text: str, **kwargs: Any) -> str: 

3483 return escape.linkify(text, **kwargs) 

3484 

3485 

3486class _xsrf_form_html(UIModule): 

3487 def render(self) -> str: 

3488 return self.handler.xsrf_form_html() 

3489 

3490 

3491class TemplateModule(UIModule): 

3492 """UIModule that simply renders the given template. 

3493 

3494 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3495 but the module version gets its own namespace (with kwargs passed to 

3496 Template()) instead of inheriting the outer template's namespace. 

3497 

3498 Templates rendered through this module also get access to UIModule's 

3499 automatic JavaScript/CSS features. Simply call set_resources 

3500 inside the template and give it keyword arguments corresponding to 

3501 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3502 Note that these resources are output once per template file, not once 

3503 per instantiation of the template, so they must not depend on 

3504 any arguments to the template. 

3505 """ 

3506 

3507 def __init__(self, handler: RequestHandler) -> None: 

3508 super().__init__(handler) 

3509 # keep resources in both a list and a dict to preserve order 

3510 self._resource_list = [] # type: List[Dict[str, Any]] 

3511 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3512 

3513 def render(self, path: str, **kwargs: Any) -> bytes: 

3514 def set_resources(**kwargs) -> str: # type: ignore 

3515 if path not in self._resource_dict: 

3516 self._resource_list.append(kwargs) 

3517 self._resource_dict[path] = kwargs 

3518 else: 

3519 if self._resource_dict[path] != kwargs: 

3520 raise ValueError( 

3521 "set_resources called with different " 

3522 "resources for the same template" 

3523 ) 

3524 return "" 

3525 

3526 return self.render_string(path, set_resources=set_resources, **kwargs) 

3527 

3528 def _get_resources(self, key: str) -> Iterable[str]: 

3529 return (r[key] for r in self._resource_list if key in r) 

3530 

3531 def embedded_javascript(self) -> str: 

3532 return "\n".join(self._get_resources("embedded_javascript")) 

3533 

3534 def javascript_files(self) -> Iterable[str]: 

3535 result = [] 

3536 for f in self._get_resources("javascript_files"): 

3537 if isinstance(f, (unicode_type, bytes)): 

3538 result.append(f) 

3539 else: 

3540 result.extend(f) 

3541 return result 

3542 

3543 def embedded_css(self) -> str: 

3544 return "\n".join(self._get_resources("embedded_css")) 

3545 

3546 def css_files(self) -> Iterable[str]: 

3547 result = [] 

3548 for f in self._get_resources("css_files"): 

3549 if isinstance(f, (unicode_type, bytes)): 

3550 result.append(f) 

3551 else: 

3552 result.extend(f) 

3553 return result 

3554 

3555 def html_head(self) -> str: 

3556 return "".join(self._get_resources("html_head")) 

3557 

3558 def html_body(self) -> str: 

3559 return "".join(self._get_resources("html_body")) 

3560 

3561 

3562class _UIModuleNamespace: 

3563 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3564 

3565 def __init__( 

3566 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3567 ) -> None: 

3568 self.handler = handler 

3569 self.ui_modules = ui_modules 

3570 

3571 def __getitem__(self, key: str) -> Callable[..., str]: 

3572 return self.handler._ui_module(key, self.ui_modules[key]) 

3573 

3574 def __getattr__(self, key: str) -> Callable[..., str]: 

3575 try: 

3576 return self[key] 

3577 except KeyError as e: 

3578 raise AttributeError(str(e)) 

3579 

3580 

3581def create_signed_value( 

3582 secret: _CookieSecretTypes, 

3583 name: str, 

3584 value: Union[str, bytes], 

3585 version: Optional[int] = None, 

3586 clock: Optional[Callable[[], float]] = None, 

3587 key_version: Optional[int] = None, 

3588) -> bytes: 

3589 if version is None: 

3590 version = DEFAULT_SIGNED_VALUE_VERSION 

3591 if clock is None: 

3592 clock = time.time 

3593 

3594 timestamp = utf8(str(int(clock()))) 

3595 value = base64.b64encode(utf8(value)) 

3596 if version == 1: 

3597 assert not isinstance(secret, dict) 

3598 signature = _create_signature_v1(secret, name, value, timestamp) 

3599 value = b"|".join([value, timestamp, signature]) 

3600 return value 

3601 elif version == 2: 

3602 # The v2 format consists of a version number and a series of 

3603 # length-prefixed fields "%d:%s", the last of which is a 

3604 # signature, all separated by pipes. All numbers are in 

3605 # decimal format with no leading zeros. The signature is an 

3606 # HMAC-SHA256 of the whole string up to that point, including 

3607 # the final pipe. 

3608 # 

3609 # The fields are: 

3610 # - format version (i.e. 2; no length prefix) 

3611 # - key version (integer, default is 0) 

3612 # - timestamp (integer seconds since epoch) 

3613 # - name (not encoded; assumed to be ~alphanumeric) 

3614 # - value (base64-encoded) 

3615 # - signature (hex-encoded; no length prefix) 

3616 def format_field(s: Union[str, bytes]) -> bytes: 

3617 return utf8("%d:" % len(s)) + utf8(s) 

3618 

3619 to_sign = b"|".join( 

3620 [ 

3621 b"2", 

3622 format_field(str(key_version or 0)), 

3623 format_field(timestamp), 

3624 format_field(name), 

3625 format_field(value), 

3626 b"", 

3627 ] 

3628 ) 

3629 

3630 if isinstance(secret, dict): 

3631 assert ( 

3632 key_version is not None 

3633 ), "Key version must be set when sign key dict is used" 

3634 assert version >= 2, "Version must be at least 2 for key version support" 

3635 secret = secret[key_version] 

3636 

3637 signature = _create_signature_v2(secret, to_sign) 

3638 return to_sign + signature 

3639 else: 

3640 raise ValueError("Unsupported version %d" % version) 

3641 

3642 

3643# A leading version number in decimal 

3644# with no leading zeros, followed by a pipe. 

3645_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3646 

3647 

3648def _get_version(value: bytes) -> int: 

3649 # Figures out what version value is. Version 1 did not include an 

3650 # explicit version field and started with arbitrary base64 data, 

3651 # which makes this tricky. 

3652 m = _signed_value_version_re.match(value) 

3653 if m is None: 

3654 version = 1 

3655 else: 

3656 try: 

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

3658 if version > 999: 

3659 # Certain payloads from the version-less v1 format may 

3660 # be parsed as valid integers. Due to base64 padding 

3661 # restrictions, this can only happen for numbers whose 

3662 # length is a multiple of 4, so we can treat all 

3663 # numbers up to 999 as versions, and for the rest we 

3664 # fall back to v1 format. 

3665 version = 1 

3666 except ValueError: 

3667 version = 1 

3668 return version 

3669 

3670 

3671def decode_signed_value( 

3672 secret: _CookieSecretTypes, 

3673 name: str, 

3674 value: Union[None, str, bytes], 

3675 max_age_days: float = 31, 

3676 clock: Optional[Callable[[], float]] = None, 

3677 min_version: Optional[int] = None, 

3678) -> Optional[bytes]: 

3679 if clock is None: 

3680 clock = time.time 

3681 if min_version is None: 

3682 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3683 if min_version > 2: 

3684 raise ValueError("Unsupported min_version %d" % min_version) 

3685 if not value: 

3686 return None 

3687 

3688 value = utf8(value) 

3689 version = _get_version(value) 

3690 

3691 if version < min_version: 

3692 return None 

3693 if version == 1: 

3694 assert not isinstance(secret, dict) 

3695 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3696 elif version == 2: 

3697 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3698 else: 

3699 return None 

3700 

3701 

3702def _decode_signed_value_v1( 

3703 secret: Union[str, bytes], 

3704 name: str, 

3705 value: bytes, 

3706 max_age_days: float, 

3707 clock: Callable[[], float], 

3708) -> Optional[bytes]: 

3709 parts = utf8(value).split(b"|") 

3710 if len(parts) != 3: 

3711 return None 

3712 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3713 if not hmac.compare_digest(parts[2], signature): 

3714 gen_log.warning("Invalid cookie signature %r", value) 

3715 return None 

3716 timestamp = int(parts[1]) 

3717 if timestamp < clock() - max_age_days * 86400: 

3718 gen_log.warning("Expired cookie %r", value) 

3719 return None 

3720 if timestamp > clock() + 31 * 86400: 

3721 # _cookie_signature does not hash a delimiter between the 

3722 # parts of the cookie, so an attacker could transfer trailing 

3723 # digits from the payload to the timestamp without altering the 

3724 # signature. For backwards compatibility, sanity-check timestamp 

3725 # here instead of modifying _cookie_signature. 

3726 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3727 return None 

3728 if parts[1].startswith(b"0"): 

3729 gen_log.warning("Tampered cookie %r", value) 

3730 return None 

3731 try: 

3732 return base64.b64decode(parts[0]) 

3733 except Exception: 

3734 return None 

3735 

3736 

3737def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3738 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3739 length, _, rest = s.partition(b":") 

3740 n = int(length) 

3741 field_value = rest[:n] 

3742 # In python 3, indexing bytes returns small integers; we must 

3743 # use a slice to get a byte string as in python 2. 

3744 if rest[n : n + 1] != b"|": 

3745 raise ValueError("malformed v2 signed value field") 

3746 rest = rest[n + 1 :] 

3747 return field_value, rest 

3748 

3749 rest = value[2:] # remove version number 

3750 key_version, rest = _consume_field(rest) 

3751 timestamp, rest = _consume_field(rest) 

3752 name_field, rest = _consume_field(rest) 

3753 value_field, passed_sig = _consume_field(rest) 

3754 return int(key_version), timestamp, name_field, value_field, passed_sig 

3755 

3756 

3757def _decode_signed_value_v2( 

3758 secret: _CookieSecretTypes, 

3759 name: str, 

3760 value: bytes, 

3761 max_age_days: float, 

3762 clock: Callable[[], float], 

3763) -> Optional[bytes]: 

3764 try: 

3765 ( 

3766 key_version, 

3767 timestamp_bytes, 

3768 name_field, 

3769 value_field, 

3770 passed_sig, 

3771 ) = _decode_fields_v2(value) 

3772 except ValueError: 

3773 return None 

3774 signed_string = value[: -len(passed_sig)] 

3775 

3776 if isinstance(secret, dict): 

3777 try: 

3778 secret = secret[key_version] 

3779 except KeyError: 

3780 return None 

3781 

3782 expected_sig = _create_signature_v2(secret, signed_string) 

3783 if not hmac.compare_digest(passed_sig, expected_sig): 

3784 return None 

3785 if name_field != utf8(name): 

3786 return None 

3787 timestamp = int(timestamp_bytes) 

3788 if timestamp < clock() - max_age_days * 86400: 

3789 # The signature has expired. 

3790 return None 

3791 try: 

3792 return base64.b64decode(value_field) 

3793 except Exception: 

3794 return None 

3795 

3796 

3797def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3798 value = utf8(value) 

3799 version = _get_version(value) 

3800 if version < 2: 

3801 return None 

3802 try: 

3803 key_version, _, _, _, _ = _decode_fields_v2(value) 

3804 except ValueError: 

3805 return None 

3806 

3807 return key_version 

3808 

3809 

3810def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3811 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3812 for part in parts: 

3813 hash.update(utf8(part)) 

3814 return utf8(hash.hexdigest()) 

3815 

3816 

3817def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3818 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3819 hash.update(utf8(s)) 

3820 return utf8(hash.hexdigest()) 

3821 

3822 

3823def is_absolute(path: str) -> bool: 

3824 return any(path.startswith(x) for x in ["/", "http:", "https:"])