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

1414 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1# 

2# Copyright 2009 Facebook 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16"""``tornado.web`` provides a simple web framework with asynchronous 

17features that allow it to scale to large numbers of open connections, 

18making it ideal for `long polling 

19<http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_. 

20 

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

22 

23.. testcode:: 

24 

25 import asyncio 

26 import tornado 

27 

28 class MainHandler(tornado.web.RequestHandler): 

29 def get(self): 

30 self.write("Hello, world") 

31 

32 async def main(): 

33 application = tornado.web.Application([ 

34 (r"/", MainHandler), 

35 ]) 

36 application.listen(8888) 

37 await asyncio.Event().wait() 

38 

39 if __name__ == "__main__": 

40 asyncio.run(main()) 

41 

42.. testoutput:: 

43 :hide: 

44 

45 

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

47 

48Thread-safety notes 

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

50 

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

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

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

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

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

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

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

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

59the executor do not refer to Tornado objects. 

60 

61""" 

62 

63import base64 

64import binascii 

65import datetime 

66import email.utils 

67import functools 

68import gzip 

69import hashlib 

70import hmac 

71import http.cookies 

72from inspect import isclass 

73from io import BytesIO 

74import mimetypes 

75import numbers 

76import os.path 

77import re 

78import socket 

79import sys 

80import threading 

81import time 

82import warnings 

83import tornado 

84import traceback 

85import types 

86import urllib.parse 

87from urllib.parse import urlencode 

88 

89from tornado.concurrent import Future, future_set_result_unless_cancelled 

90from tornado import escape 

91from tornado import gen 

92from tornado.httpserver import HTTPServer 

93from tornado import httputil 

94from tornado import iostream 

95from tornado import locale 

96from tornado.log import access_log, app_log, gen_log 

97from tornado import template 

98from tornado.escape import utf8, _unicode 

99from tornado.routing import ( 

100 AnyMatches, 

101 DefaultHostMatches, 

102 HostMatches, 

103 ReversibleRouter, 

104 Rule, 

105 ReversibleRuleRouter, 

106 URLSpec, 

107 _RuleList, 

108) 

109from tornado.util import ObjectDict, unicode_type, _websocket_mask 

110 

111url = URLSpec 

112 

113from typing import ( 

114 Dict, 

115 Any, 

116 Union, 

117 Optional, 

118 Awaitable, 

119 Tuple, 

120 List, 

121 Callable, 

122 Iterable, 

123 Generator, 

124 Type, 

125 TypeVar, 

126 cast, 

127 overload, 

128) 

129from types import TracebackType 

130import typing 

131 

132if typing.TYPE_CHECKING: 

133 from typing import Set # noqa: F401 

134 

135 

136# The following types are accepted by RequestHandler.set_header 

137# and related methods. 

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

139 

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

141 

142 

143MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

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

145 

146Signed values older than this version cannot be decoded. 

147 

148.. versionadded:: 3.2.1 

149""" 

150 

151MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

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

153 

154Signed values newer than this version cannot be decoded. 

155 

156.. versionadded:: 3.2.1 

157""" 

158 

159DEFAULT_SIGNED_VALUE_VERSION = 2 

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

161 

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

163 

164.. versionadded:: 3.2.1 

165""" 

166 

167DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

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

169 

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

171 

172.. versionadded:: 3.2.1 

173""" 

174 

175 

176class _ArgDefaultMarker: 

177 pass 

178 

179 

180_ARG_DEFAULT = _ArgDefaultMarker() 

181 

182 

183class RequestHandler(object): 

184 """Base class for HTTP request handlers. 

185 

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

187 "Entry points" section below. 

188 

189 Applications should not construct `RequestHandler` objects 

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

191 `~RequestHandler.initialize` instead). 

192 

193 """ 

194 

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

196 

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

198 _template_loader_lock = threading.Lock() 

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

200 

201 _stream_request_body = False 

202 

203 # Will be set in _execute. 

204 _transforms = None # type: List[OutputTransform] 

205 path_args = None # type: List[str] 

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

207 

208 def __init__( 

209 self, 

210 application: "Application", 

211 request: httputil.HTTPServerRequest, 

212 **kwargs: Any, 

213 ) -> None: 

214 super().__init__() 

215 

216 self.application = application 

217 self.request = request 

218 self._headers_written = False 

219 self._finished = False 

220 self._auto_finish = True 

221 self._prepared_future = None 

222 self.ui = ObjectDict( 

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

224 ) 

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

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

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

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

229 # possible conflicts. 

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

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

232 self.clear() 

233 assert self.request.connection is not None 

234 # TODO: need to add set_close_callback to HTTPConnection interface 

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

236 self.on_connection_close 

237 ) 

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

239 

240 def _initialize(self) -> None: 

241 pass 

242 

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

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

245 

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

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

248 

249 Example:: 

250 

251 class ProfileHandler(RequestHandler): 

252 def initialize(self, database): 

253 self.database = database 

254 

255 def get(self, username): 

256 ... 

257 

258 app = Application([ 

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

260 ]) 

261 """ 

262 

263 @property 

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

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

266 return self.application.settings 

267 

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

269 raise HTTPError(405) 

270 

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

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

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

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

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

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

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

278 

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

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

281 

282 Override this method to perform common initialization regardless 

283 of the request method. 

284 

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

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

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

288 until the ``Awaitable`` is done. 

289 

290 .. versionadded:: 3.1 

291 Asynchronous support. 

292 """ 

293 pass 

294 

295 def on_finish(self) -> None: 

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

297 

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

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

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

301 has been sent to the client. 

302 """ 

303 pass 

304 

305 def on_connection_close(self) -> None: 

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

307 

308 Override this to clean up resources associated with 

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

310 the connection was closed during asynchronous processing; if you 

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

312 instead. 

313 

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

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

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

317 connection. 

318 """ 

319 if _has_stream_request_body(self.__class__): 

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

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

322 self.request._body_future.exception() 

323 

324 def clear(self) -> None: 

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

326 self._headers = httputil.HTTPHeaders( 

327 { 

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

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

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

331 } 

332 ) 

333 self.set_default_headers() 

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

335 self._status_code = 200 

336 self._reason = httputil.responses[200] 

337 

338 def set_default_headers(self) -> None: 

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

340 

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

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

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

344 during error handling. 

345 """ 

346 pass 

347 

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

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

350 

351 :arg int status_code: Response status code. 

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

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

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

355 

356 .. versionchanged:: 5.0 

357 

358 No longer validates that the response code is in 

359 `http.client.responses`. 

360 """ 

361 self._status_code = status_code 

362 if reason is not None: 

363 self._reason = escape.native_str(reason) 

364 else: 

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

366 

367 def get_status(self) -> int: 

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

369 return self._status_code 

370 

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

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

373 

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

375 are formatted according to the HTTP specification for the 

376 ``Date`` header). 

377 

378 """ 

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

380 

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

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

383 

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

385 to return multiple values for the same header. 

386 """ 

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

388 

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

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

391 

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

393 set by `add_header`. 

394 """ 

395 if name in self._headers: 

396 del self._headers[name] 

397 

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

399 

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

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

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

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

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

405 if isinstance(value, str): 

406 retval = value 

407 elif isinstance(value, bytes): 

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

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

410 retval = value.decode("latin1") 

411 elif isinstance(value, numbers.Integral): 

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

413 return str(value) 

414 elif isinstance(value, datetime.datetime): 

415 return httputil.format_timestamp(value) 

416 else: 

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

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

419 # additional headers or split the request. 

420 if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval): 

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

422 return retval 

423 

424 @overload 

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

426 pass 

427 

428 @overload 

429 def get_argument( # noqa: F811 

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

431 ) -> str: 

432 pass 

433 

434 @overload 

435 def get_argument( # noqa: F811 

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

437 ) -> Optional[str]: 

438 pass 

439 

440 def get_argument( # noqa: F811 

441 self, 

442 name: str, 

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

444 strip: bool = True, 

445 ) -> Optional[str]: 

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

447 

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

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

450 

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

452 last value. 

453 

454 This method searches both the query and body arguments. 

455 """ 

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

457 

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

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

460 

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

462 

463 This method searches both the query and body arguments. 

464 """ 

465 

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

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

468 # `get_argument`.) 

469 assert isinstance(strip, bool) 

470 

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

472 

473 def get_body_argument( 

474 self, 

475 name: str, 

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

477 strip: bool = True, 

478 ) -> Optional[str]: 

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

480 from the request body. 

481 

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

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

484 

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

486 last value. 

487 

488 .. versionadded:: 3.2 

489 """ 

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

491 

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

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

494 

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

496 

497 .. versionadded:: 3.2 

498 """ 

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

500 

501 def get_query_argument( 

502 self, 

503 name: str, 

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

505 strip: bool = True, 

506 ) -> Optional[str]: 

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

508 from the request query string. 

509 

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

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

512 

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

514 last value. 

515 

516 .. versionadded:: 3.2 

517 """ 

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

519 

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

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

522 

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

524 

525 .. versionadded:: 3.2 

526 """ 

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

528 

529 def _get_argument( 

530 self, 

531 name: str, 

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

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

534 strip: bool = True, 

535 ) -> Optional[str]: 

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

537 if not args: 

538 if isinstance(default, _ArgDefaultMarker): 

539 raise MissingArgumentError(name) 

540 return default 

541 return args[-1] 

542 

543 def _get_arguments( 

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

545 ) -> List[str]: 

546 values = [] 

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

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

549 if isinstance(s, unicode_type): 

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

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

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

553 if strip: 

554 s = s.strip() 

555 values.append(s) 

556 return values 

557 

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

559 """Decodes an argument from the request. 

560 

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

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

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

564 

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

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

567 

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

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

570 """ 

571 try: 

572 return _unicode(value) 

573 except UnicodeDecodeError: 

574 raise HTTPError( 

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

576 ) 

577 

578 @property 

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

580 """An alias for 

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

582 return self.request.cookies 

583 

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

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

586 

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

588 

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

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

591 handler. 

592 """ 

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

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

595 return default 

596 

597 def set_cookie( 

598 self, 

599 name: str, 

600 value: Union[str, bytes], 

601 domain: Optional[str] = None, 

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

603 path: str = "/", 

604 expires_days: Optional[float] = None, 

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

606 *, 

607 max_age: Optional[int] = None, 

608 httponly: bool = False, 

609 secure: bool = False, 

610 samesite: Optional[str] = None, 

611 **kwargs: Any, 

612 ) -> None: 

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

614 

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

616 they are not present until the next request. 

617 

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

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

620 for more information. 

621 

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

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

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

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

626 is used). 

627 

628 .. deprecated:: 6.3 

629 Keyword arguments are currently accepted case-insensitively. 

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

631 arguments. 

632 """ 

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

634 name = escape.native_str(name) 

635 value = escape.native_str(value) 

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

637 # Don't let us accidentally inject bad stuff 

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

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

640 self._new_cookie = ( 

641 http.cookies.SimpleCookie() 

642 ) # type: http.cookies.SimpleCookie 

643 if name in self._new_cookie: 

644 del self._new_cookie[name] 

645 self._new_cookie[name] = value 

646 morsel = self._new_cookie[name] 

647 if domain: 

648 morsel["domain"] = domain 

649 if expires_days is not None and not expires: 

650 expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days) 

651 if expires: 

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

653 if path: 

654 morsel["path"] = path 

655 if max_age: 

656 # Note change from _ to -. 

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

658 if httponly: 

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

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

661 morsel["httponly"] = True 

662 if secure: 

663 morsel["secure"] = True 

664 if samesite: 

665 morsel["samesite"] = samesite 

666 if kwargs: 

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

668 # kwargs for backwards compatibility until we can remove deprecated 

669 # features. 

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

671 morsel[k] = v 

672 warnings.warn( 

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

674 "(should be lowercase)", 

675 DeprecationWarning, 

676 ) 

677 

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

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

680 

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

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

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

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

685 arguments are ignored. 

686 

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

688 seen until the following request. 

689 

690 .. versionchanged:: 6.3 

691 

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

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

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

695 """ 

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

697 if excluded_arg in kwargs: 

698 raise TypeError( 

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

700 ) 

701 expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) 

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

703 

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

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

706 

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

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

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

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

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

712 when setting cookies. 

713 

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

715 until the following request. 

716 

717 .. versionchanged:: 3.2 

718 

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

720 

721 .. versionchanged:: 6.3 

722 

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

724 

725 .. deprecated:: 6.3 

726 

727 The increasingly complex rules governing cookies have made it 

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

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

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

731 """ 

732 for name in self.request.cookies: 

733 self.clear_cookie(name, **kwargs) 

734 

735 def set_signed_cookie( 

736 self, 

737 name: str, 

738 value: Union[str, bytes], 

739 expires_days: Optional[float] = 30, 

740 version: Optional[int] = None, 

741 **kwargs: Any, 

742 ) -> None: 

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

744 

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

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

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

748 

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

750 

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

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

753 parameter to `get_signed_cookie`. 

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

755 

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

757 strings (unlike regular cookies) 

758 

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

760 seen until the following request. 

761 

762 .. versionchanged:: 3.2.1 

763 

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

765 and made it the default. 

766 

767 .. versionchanged:: 6.3 

768 

769 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

772 """ 

773 self.set_cookie( 

774 name, 

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

776 expires_days=expires_days, 

777 **kwargs, 

778 ) 

779 

780 set_secure_cookie = set_signed_cookie 

781 

782 def create_signed_value( 

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

784 ) -> bytes: 

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

786 

787 Normally used via set_signed_cookie, but provided as a separate 

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

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

790 

791 .. versionchanged:: 3.2.1 

792 

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

794 and made it the default. 

795 """ 

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

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

798 key_version = None 

799 if isinstance(secret, dict): 

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

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

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

803 

804 return create_signed_value( 

805 secret, name, value, version=version, key_version=key_version 

806 ) 

807 

808 def get_signed_cookie( 

809 self, 

810 name: str, 

811 value: Optional[str] = None, 

812 max_age_days: float = 31, 

813 min_version: Optional[int] = None, 

814 ) -> Optional[bytes]: 

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

816 

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

818 `get_cookie`). 

819 

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

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

822 `set_signed_cookie` in this handler. 

823 

824 .. versionchanged:: 3.2.1 

825 

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

827 both versions 1 and 2 are accepted by default. 

828 

829 .. versionchanged:: 6.3 

830 

831 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

834 

835 """ 

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

837 if value is None: 

838 value = self.get_cookie(name) 

839 return decode_signed_value( 

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

841 name, 

842 value, 

843 max_age_days=max_age_days, 

844 min_version=min_version, 

845 ) 

846 

847 get_secure_cookie = get_signed_cookie 

848 

849 def get_signed_cookie_key_version( 

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

851 ) -> Optional[int]: 

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

853 

854 The version is returned as int. 

855 

856 .. versionchanged:: 6.3 

857 

858 Renamed from ``get_secure_cookie_key_version`` to 

859 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

861 remains as an alias. 

862 

863 """ 

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

865 if value is None: 

866 value = self.get_cookie(name) 

867 if value is None: 

868 return None 

869 return get_signature_key_version(value) 

870 

871 get_secure_cookie_key_version = get_signed_cookie_key_version 

872 

873 def redirect( 

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

875 ) -> None: 

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

877 

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

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

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

881 The default is 302 (temporary). 

882 """ 

883 if self._headers_written: 

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

885 if status is None: 

886 status = 301 if permanent else 302 

887 else: 

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

889 self.set_status(status) 

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

891 self.finish() 

892 

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

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

895 

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

897 

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

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

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

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

902 

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

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

905 wrapped in a dictionary. More details at 

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

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

908 """ 

909 if self._finished: 

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

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

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

913 if isinstance(chunk, list): 

914 message += ( 

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

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

917 ) 

918 raise TypeError(message) 

919 if isinstance(chunk, dict): 

920 chunk = escape.json_encode(chunk) 

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

922 chunk = utf8(chunk) 

923 self._write_buffer.append(chunk) 

924 

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

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

927 

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

929 after it. 

930 

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

932 Awaiting this `.Future` is optional. 

933 

934 .. versionchanged:: 5.1 

935 

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

937 """ 

938 if self._finished: 

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

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

941 

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

943 js_embed = [] 

944 js_files = [] 

945 css_embed = [] 

946 css_files = [] 

947 html_heads = [] 

948 html_bodies = [] 

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

950 embed_part = module.embedded_javascript() 

951 if embed_part: 

952 js_embed.append(utf8(embed_part)) 

953 file_part = module.javascript_files() 

954 if file_part: 

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

956 js_files.append(_unicode(file_part)) 

957 else: 

958 js_files.extend(file_part) 

959 embed_part = module.embedded_css() 

960 if embed_part: 

961 css_embed.append(utf8(embed_part)) 

962 file_part = module.css_files() 

963 if file_part: 

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

965 css_files.append(_unicode(file_part)) 

966 else: 

967 css_files.extend(file_part) 

968 head_part = module.html_head() 

969 if head_part: 

970 html_heads.append(utf8(head_part)) 

971 body_part = module.html_body() 

972 if body_part: 

973 html_bodies.append(utf8(body_part)) 

974 

975 if js_files: 

976 # Maintain order of JavaScript files given by modules 

977 js = self.render_linked_js(js_files) 

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

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

980 if js_embed: 

981 js_bytes = self.render_embed_js(js_embed) 

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

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

984 if css_files: 

985 css = self.render_linked_css(css_files) 

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

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

988 if css_embed: 

989 css_bytes = self.render_embed_css(css_embed) 

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

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

992 if html_heads: 

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

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

995 if html_bodies: 

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

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

998 return self.finish(html) 

999 

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

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

1002 rendered webpage. 

1003 

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

1005 """ 

1006 paths = [] 

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

1008 

1009 for path in js_files: 

1010 if not is_absolute(path): 

1011 path = self.static_url(path) 

1012 if path not in unique_paths: 

1013 paths.append(path) 

1014 unique_paths.add(path) 

1015 

1016 return "".join( 

1017 '<script src="' 

1018 + escape.xhtml_escape(p) 

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

1020 for p in paths 

1021 ) 

1022 

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

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

1025 rendered webpage. 

1026 

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

1028 """ 

1029 return ( 

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

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

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

1033 ) 

1034 

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

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

1037 rendered webpage. 

1038 

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

1040 """ 

1041 paths = [] 

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

1043 

1044 for path in css_files: 

1045 if not is_absolute(path): 

1046 path = self.static_url(path) 

1047 if path not in unique_paths: 

1048 paths.append(path) 

1049 unique_paths.add(path) 

1050 

1051 return "".join( 

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

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

1054 for p in paths 

1055 ) 

1056 

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

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

1059 rendered webpage. 

1060 

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

1062 """ 

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

1064 

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

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

1067 

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

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

1070 """ 

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

1072 template_path = self.get_template_path() 

1073 if not template_path: 

1074 frame = sys._getframe(0) 

1075 web_file = frame.f_code.co_filename 

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

1077 frame = frame.f_back 

1078 assert frame.f_code.co_filename is not None 

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

1080 with RequestHandler._template_loader_lock: 

1081 if template_path not in RequestHandler._template_loaders: 

1082 loader = self.create_template_loader(template_path) 

1083 RequestHandler._template_loaders[template_path] = loader 

1084 else: 

1085 loader = RequestHandler._template_loaders[template_path] 

1086 t = loader.load(template_name) 

1087 namespace = self.get_template_namespace() 

1088 namespace.update(kwargs) 

1089 return t.generate(**namespace) 

1090 

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

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

1093 

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

1095 

1096 The results of this method will be combined with additional 

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

1098 to `render` or `render_string`. 

1099 """ 

1100 namespace = dict( 

1101 handler=self, 

1102 request=self.request, 

1103 current_user=self.current_user, 

1104 locale=self.locale, 

1105 _=self.locale.translate, 

1106 pgettext=self.locale.pgettext, 

1107 static_url=self.static_url, 

1108 xsrf_form_html=self.xsrf_form_html, 

1109 reverse_url=self.reverse_url, 

1110 ) 

1111 namespace.update(self.ui) 

1112 return namespace 

1113 

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

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

1116 

1117 May be overridden by subclasses. By default returns a 

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

1119 ``autoescape`` and ``template_whitespace`` application 

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

1121 supplied, uses that instead. 

1122 """ 

1123 settings = self.application.settings 

1124 if "template_loader" in settings: 

1125 return settings["template_loader"] 

1126 kwargs = {} 

1127 if "autoescape" in settings: 

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

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

1130 kwargs["autoescape"] = settings["autoescape"] 

1131 if "template_whitespace" in settings: 

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

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

1134 

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

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

1137 

1138 .. versionchanged:: 4.0 

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

1140 

1141 .. versionchanged:: 6.0 

1142 

1143 The ``callback`` argument was removed. 

1144 """ 

1145 assert self.request.connection is not None 

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

1147 self._write_buffer = [] 

1148 if not self._headers_written: 

1149 self._headers_written = True 

1150 for transform in self._transforms: 

1151 assert chunk is not None 

1152 ( 

1153 self._status_code, 

1154 self._headers, 

1155 chunk, 

1156 ) = transform.transform_first_chunk( 

1157 self._status_code, self._headers, chunk, include_footers 

1158 ) 

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

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

1161 chunk = b"" 

1162 

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

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

1165 # is sent). 

1166 if hasattr(self, "_new_cookie"): 

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

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

1169 

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

1171 return self.request.connection.write_headers( 

1172 start_line, self._headers, chunk 

1173 ) 

1174 else: 

1175 for transform in self._transforms: 

1176 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1180 else: 

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

1182 future.set_result(None) 

1183 return future 

1184 

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

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

1187 

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

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

1190 

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

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

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

1194 data can be sent. 

1195 

1196 .. versionchanged:: 5.1 

1197 

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

1199 """ 

1200 if self._finished: 

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

1202 

1203 if chunk is not None: 

1204 self.write(chunk) 

1205 

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

1207 # we have not flushed any content yet. 

1208 if not self._headers_written: 

1209 if ( 

1210 self._status_code == 200 

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

1212 and "Etag" not in self._headers 

1213 ): 

1214 self.set_etag_header() 

1215 if self.check_etag_header(): 

1216 self._write_buffer = [] 

1217 self.set_status(304) 

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

1219 assert not self._write_buffer, ( 

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

1221 ) 

1222 self._clear_representation_headers() 

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

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

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

1226 

1227 assert self.request.connection is not None 

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

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

1230 # garbage collection of the RequestHandler when there 

1231 # are keepalive connections) 

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

1233 

1234 future = self.flush(include_footers=True) 

1235 self.request.connection.finish() 

1236 self._log() 

1237 self._finished = True 

1238 self.on_finish() 

1239 self._break_cycles() 

1240 return future 

1241 

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

1243 """Take control of the underlying stream. 

1244 

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

1246 further HTTP processing. Intended for implementing protocols 

1247 like websockets that tunnel over an HTTP handshake. 

1248 

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

1250 

1251 .. versionadded:: 5.1 

1252 """ 

1253 self._finished = True 

1254 # TODO: add detach to HTTPConnection? 

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

1256 

1257 def _break_cycles(self) -> None: 

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

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

1260 self.ui = None # type: ignore 

1261 

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

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

1264 

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

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

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

1268 and replaced with the error page. 

1269 

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

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

1272 """ 

1273 if self._headers_written: 

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

1275 if not self._finished: 

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

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

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

1279 # socket. 

1280 try: 

1281 self.finish() 

1282 except Exception: 

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

1284 return 

1285 self.clear() 

1286 

1287 reason = kwargs.get("reason") 

1288 if "exc_info" in kwargs: 

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

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

1291 reason = exception.reason 

1292 self.set_status(status_code, reason=reason) 

1293 try: 

1294 self.write_error(status_code, **kwargs) 

1295 except Exception: 

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

1297 if not self._finished: 

1298 self.finish() 

1299 

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

1301 """Override to implement custom error pages. 

1302 

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

1304 to produce output as usual. 

1305 

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

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

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

1309 the "current" exception for purposes of methods like 

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

1311 """ 

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

1313 # in debug mode, try to send a traceback 

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

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

1316 self.write(line) 

1317 self.finish() 

1318 else: 

1319 self.finish( 

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

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

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

1323 ) 

1324 

1325 @property 

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

1327 """The locale for the current session. 

1328 

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

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

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

1332 header. 

1333 

1334 .. versionchanged: 4.1 

1335 Added a property setter. 

1336 """ 

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

1338 loc = self.get_user_locale() 

1339 if loc is not None: 

1340 self._locale = loc 

1341 else: 

1342 self._locale = self.get_browser_locale() 

1343 assert self._locale 

1344 return self._locale 

1345 

1346 @locale.setter 

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

1348 self._locale = value 

1349 

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

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

1352 

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

1354 

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

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

1357 """ 

1358 return None 

1359 

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

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

1362 

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

1364 """ 

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

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

1367 locales = [] 

1368 for language in languages: 

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

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

1371 try: 

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

1373 if score < 0: 

1374 raise ValueError() 

1375 except (ValueError, TypeError): 

1376 score = 0.0 

1377 else: 

1378 score = 1.0 

1379 if score > 0: 

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

1381 if locales: 

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

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

1384 return locale.get(*codes) 

1385 return locale.get(default) 

1386 

1387 @property 

1388 def current_user(self) -> Any: 

1389 """The authenticated user for this request. 

1390 

1391 This is set in one of two ways: 

1392 

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

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

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

1396 and is cached for future access:: 

1397 

1398 def get_current_user(self): 

1399 user_cookie = self.get_signed_cookie("user") 

1400 if user_cookie: 

1401 return json.loads(user_cookie) 

1402 return None 

1403 

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

1405 `prepare()`:: 

1406 

1407 @gen.coroutine 

1408 def prepare(self): 

1409 user_id_cookie = self.get_signed_cookie("user_id") 

1410 if user_id_cookie: 

1411 self.current_user = yield load_user(user_id_cookie) 

1412 

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

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

1415 asynchronous operations. 

1416 

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

1418 """ 

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

1420 self._current_user = self.get_current_user() 

1421 return self._current_user 

1422 

1423 @current_user.setter 

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

1425 self._current_user = value 

1426 

1427 def get_current_user(self) -> Any: 

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

1429 

1430 This method may not be a coroutine. 

1431 """ 

1432 return None 

1433 

1434 def get_login_url(self) -> str: 

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

1436 

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

1438 """ 

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

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

1441 

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

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

1444 

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

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

1447 """ 

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

1449 

1450 @property 

1451 def xsrf_token(self) -> bytes: 

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

1453 

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

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

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

1457 as a potential forgery. 

1458 

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

1460 

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

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

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

1464 UTF-8. 

1465 

1466 .. versionchanged:: 3.2.2 

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

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

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

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

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

1472 unless the ``xsrf_cookie_version`` `Application` setting is 

1473 set to 1. 

1474 

1475 .. versionchanged:: 4.3 

1476 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1477 used to supply additional cookie options (which will be 

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

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

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

1481 ``_xsrf`` cookie. 

1482 """ 

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

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

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

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

1487 if output_version == 1: 

1488 self._xsrf_token = binascii.b2a_hex(token) 

1489 elif output_version == 2: 

1490 mask = os.urandom(4) 

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

1492 [ 

1493 b"2", 

1494 binascii.b2a_hex(mask), 

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

1496 utf8(str(int(timestamp))), 

1497 ] 

1498 ) 

1499 else: 

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

1501 if version is None: 

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

1503 cookie_kwargs["expires_days"] = 30 

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

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

1506 return self._xsrf_token 

1507 

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

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

1510 

1511 The raw_xsrf_token is a tuple containing: 

1512 

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

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

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

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

1517 for version 1 cookies) 

1518 """ 

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

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

1521 cookie = self.get_cookie(cookie_name) 

1522 if cookie: 

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

1524 else: 

1525 version, token, timestamp = None, None, None 

1526 if token is None: 

1527 version = None 

1528 token = os.urandom(16) 

1529 timestamp = time.time() 

1530 assert token is not None 

1531 assert timestamp is not None 

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

1533 return self._raw_xsrf_token 

1534 

1535 def _decode_xsrf_token( 

1536 self, cookie: str 

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

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

1539 _get_raw_xsrf_token. 

1540 """ 

1541 

1542 try: 

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

1544 

1545 if m: 

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

1547 if version == 2: 

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

1549 

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

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

1552 timestamp = int(timestamp_str) 

1553 return version, token, timestamp 

1554 else: 

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

1556 raise Exception("Unknown xsrf cookie version") 

1557 else: 

1558 version = 1 

1559 try: 

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

1561 except (binascii.Error, TypeError): 

1562 token = utf8(cookie) 

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

1564 timestamp = int(time.time()) 

1565 return (version, token, timestamp) 

1566 except Exception: 

1567 # Catch exceptions and return nothing instead of failing. 

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

1569 return None, None, None 

1570 

1571 def check_xsrf_cookie(self) -> None: 

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

1573 

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

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

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

1577 reject the form submission as a potential forgery. 

1578 

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

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

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

1582 

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

1584 

1585 .. versionchanged:: 3.2.2 

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

1587 supported. 

1588 """ 

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

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

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

1592 # information please see 

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

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

1595 token = ( 

1596 self.get_argument("_xsrf", None) 

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

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

1599 ) 

1600 if not token: 

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

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

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

1604 if not token: 

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

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

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

1608 

1609 def xsrf_form_html(self) -> str: 

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

1611 

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

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

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

1615 HTML within all of your HTML forms. 

1616 

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

1618 xsrf_form_html() %}`` 

1619 

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

1621 """ 

1622 return ( 

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

1624 + escape.xhtml_escape(self.xsrf_token) 

1625 + '"/>' 

1626 ) 

1627 

1628 def static_url( 

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

1630 ) -> str: 

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

1632 

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

1634 application (which specifies the root directory of your static 

1635 files). 

1636 

1637 This method returns a versioned url (by default appending 

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

1639 cached indefinitely. This can be disabled by passing 

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

1641 other static file implementations are not required to support 

1642 this, but they may support other options). 

1643 

1644 By default this method returns URLs relative to the current 

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

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

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

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

1649 

1650 """ 

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

1652 get_url = self.settings.get( 

1653 "static_handler_class", StaticFileHandler 

1654 ).make_static_url 

1655 

1656 if include_host is None: 

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

1658 

1659 if include_host: 

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

1661 else: 

1662 base = "" 

1663 

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

1665 

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

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

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

1669 raise Exception( 

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

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

1672 ) 

1673 

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

1675 """Alias for `Application.reverse_url`.""" 

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

1677 

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

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

1680 

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

1682 

1683 May be overridden to provide custom etag implementations, 

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

1685 """ 

1686 hasher = hashlib.sha1() 

1687 for part in self._write_buffer: 

1688 hasher.update(part) 

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

1690 

1691 def set_etag_header(self) -> None: 

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

1693 

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

1695 

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

1697 """ 

1698 etag = self.compute_etag() 

1699 if etag is not None: 

1700 self.set_header("Etag", etag) 

1701 

1702 def check_etag_header(self) -> bool: 

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

1704 

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

1706 returned. For example:: 

1707 

1708 self.set_etag_header() 

1709 if self.check_etag_header(): 

1710 self.set_status(304) 

1711 return 

1712 

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

1714 but may be called earlier for applications that override 

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

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

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

1718 """ 

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

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

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

1722 etags = re.findall( 

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

1724 ) 

1725 if not computed_etag or not etags: 

1726 return False 

1727 

1728 match = False 

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

1730 match = True 

1731 else: 

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

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

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

1735 

1736 for etag in etags: 

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

1738 match = True 

1739 break 

1740 return match 

1741 

1742 async def _execute( 

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

1744 ) -> None: 

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

1746 self._transforms = transforms 

1747 try: 

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

1749 raise HTTPError(405) 

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

1751 self.path_kwargs = dict( 

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

1753 ) 

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

1755 # the proper cookie 

1756 if self.request.method not in ( 

1757 "GET", 

1758 "HEAD", 

1759 "OPTIONS", 

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

1761 self.check_xsrf_cookie() 

1762 

1763 result = self.prepare() 

1764 if result is not None: 

1765 result = await result # type: ignore 

1766 if self._prepared_future is not None: 

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

1768 # and are ready for the body to arrive. 

1769 future_set_result_unless_cancelled(self._prepared_future, None) 

1770 if self._finished: 

1771 return 

1772 

1773 if _has_stream_request_body(self.__class__): 

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

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

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

1777 # instead. 

1778 try: 

1779 await self.request._body_future 

1780 except iostream.StreamClosedError: 

1781 return 

1782 

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

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

1785 if result is not None: 

1786 result = await result 

1787 if self._auto_finish and not self._finished: 

1788 self.finish() 

1789 except Exception as e: 

1790 try: 

1791 self._handle_request_exception(e) 

1792 except Exception: 

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

1794 finally: 

1795 # Unset result to avoid circular references 

1796 result = None 

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

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

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

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

1801 self._prepared_future.set_result(None) 

1802 

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

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

1805 

1806 Requires the `.stream_request_body` decorator. 

1807 

1808 May be a coroutine for flow control. 

1809 """ 

1810 raise NotImplementedError() 

1811 

1812 def _log(self) -> None: 

1813 """Logs the current request. 

1814 

1815 Sort of deprecated since this functionality was moved to the 

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

1817 that have overridden this method. 

1818 """ 

1819 self.application.log_request(self) 

1820 

1821 def _request_summary(self) -> str: 

1822 return "%s %s (%s)" % ( 

1823 self.request.method, 

1824 self.request.uri, 

1825 self.request.remote_ip, 

1826 ) 

1827 

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

1829 if isinstance(e, Finish): 

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

1831 if not self._finished: 

1832 self.finish(*e.args) 

1833 return 

1834 try: 

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

1836 except Exception: 

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

1838 # to avoid leaking the connection. 

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

1840 if self._finished: 

1841 # Extra errors after the request has been finished should 

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

1843 # send a response. 

1844 return 

1845 if isinstance(e, HTTPError): 

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

1847 else: 

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

1849 

1850 def log_exception( 

1851 self, 

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

1853 value: Optional[BaseException], 

1854 tb: Optional[TracebackType], 

1855 ) -> None: 

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

1857 

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

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

1860 other exceptions as errors with stack traces (on the 

1861 ``tornado.application`` logger). 

1862 

1863 .. versionadded:: 3.1 

1864 """ 

1865 if isinstance(value, HTTPError): 

1866 if value.log_message: 

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

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

1869 gen_log.warning(format, *args) 

1870 else: 

1871 app_log.error( 

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

1873 self._request_summary(), 

1874 self.request, 

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

1876 ) 

1877 

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

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

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

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

1882 if name not in self._active_modules: 

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

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

1885 return rendered 

1886 

1887 return render 

1888 

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

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

1891 

1892 def _clear_representation_headers(self) -> None: 

1893 # 304 responses should not contain representation metadata 

1894 # headers (defined in 

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

1896 # not explicitly allowed by 

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

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

1899 for h in headers: 

1900 self.clear_header(h) 

1901 

1902 

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

1904 

1905 

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

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

1908 

1909 This decorator implies the following changes: 

1910 

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

1912 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

1920 until those futures have completed. 

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

1922 the entire body has been read. 

1923 

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

1925 for example usage. 

1926 """ # noqa: E501 

1927 if not issubclass(cls, RequestHandler): 

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

1929 cls._stream_request_body = True 

1930 return cls 

1931 

1932 

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

1934 if not issubclass(cls, RequestHandler): 

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

1936 return cls._stream_request_body 

1937 

1938 

1939def removeslash( 

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

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

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

1943 

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

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

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

1947 """ 

1948 

1949 @functools.wraps(method) 

1950 def wrapper( # type: ignore 

1951 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

1957 if self.request.query: 

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

1959 self.redirect(uri, permanent=True) 

1960 return None 

1961 else: 

1962 raise HTTPError(404) 

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

1964 

1965 return wrapper 

1966 

1967 

1968def addslash( 

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

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

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

1972 

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

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

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

1976 """ 

1977 

1978 @functools.wraps(method) 

1979 def wrapper( # type: ignore 

1980 self: RequestHandler, *args, **kwargs 

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

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

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

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

1985 if self.request.query: 

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

1987 self.redirect(uri, permanent=True) 

1988 return None 

1989 raise HTTPError(404) 

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

1991 

1992 return wrapper 

1993 

1994 

1995class _ApplicationRouter(ReversibleRuleRouter): 

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

1997 

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

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

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

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

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

2003 `_ApplicationRouter` instance. 

2004 """ 

2005 

2006 def __init__( 

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

2008 ) -> None: 

2009 assert isinstance(application, Application) 

2010 self.application = application 

2011 super().__init__(rules) 

2012 

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

2014 rule = super().process_rule(rule) 

2015 

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

2017 rule.target = _ApplicationRouter( 

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

2019 ) 

2020 

2021 return rule 

2022 

2023 def get_target_delegate( 

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

2025 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

2027 return self.application.get_handler_delegate( 

2028 request, target, **target_params 

2029 ) 

2030 

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

2032 

2033 

2034class Application(ReversibleRouter): 

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

2036 

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

2038 HTTPServer to serve the application:: 

2039 

2040 application = web.Application([ 

2041 (r"/", MainPageHandler), 

2042 ]) 

2043 http_server = httpserver.HTTPServer(application) 

2044 http_server.listen(8080) 

2045 

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

2047 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2052 

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

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

2055 

2056 application = web.Application([ 

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

2058 (r"/", MainPageHandler), 

2059 (r"/feed", FeedHandler), 

2060 ]), 

2061 ]) 

2062 

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

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

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

2066 

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

2068 instantiate an instance of the first request class whose regexp 

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

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

2071 

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

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

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

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

2076 `StaticFileHandler` can be installed automatically with the 

2077 static_path setting described below):: 

2078 

2079 application = web.Application([ 

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

2081 ]) 

2082 

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

2084 a host regular expression as the first argument:: 

2085 

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

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

2088 ]) 

2089 

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

2091 parameter value is matched against host regular expressions. 

2092 

2093 

2094 .. warning:: 

2095 

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

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

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

2099 other private networks. Appropriate host patterns must be used 

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

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

2102 may be vulnerable to DNS rebinding. 

2103 

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

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

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

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

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

2109 `StaticFileHandler` can be specified with the 

2110 ``static_handler_class`` setting. 

2111 

2112 .. versionchanged:: 4.5 

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

2114 

2115 """ 

2116 

2117 def __init__( 

2118 self, 

2119 handlers: Optional[_RuleList] = None, 

2120 default_host: Optional[str] = None, 

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

2122 **settings: Any, 

2123 ) -> None: 

2124 if transforms is None: 

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

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

2127 self.transforms.append(GZipContentEncoding) 

2128 else: 

2129 self.transforms = transforms 

2130 self.default_host = default_host 

2131 self.settings = settings 

2132 self.ui_modules = { 

2133 "linkify": _linkify, 

2134 "xsrf_form_html": _xsrf_form_html, 

2135 "Template": TemplateModule, 

2136 } 

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

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

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

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

2141 path = self.settings["static_path"] 

2142 handlers = list(handlers or []) 

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

2144 static_handler_class = settings.get( 

2145 "static_handler_class", StaticFileHandler 

2146 ) 

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

2148 static_handler_args["path"] = path 

2149 for pattern in [ 

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

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

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

2153 ]: 

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

2155 

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

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

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

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

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

2161 

2162 self.wildcard_router = _ApplicationRouter(self, handlers) 

2163 self.default_router = _ApplicationRouter( 

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

2165 ) 

2166 

2167 # Automatically reload modified modules 

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

2169 from tornado import autoreload 

2170 

2171 autoreload.start() 

2172 

2173 def listen( 

2174 self, 

2175 port: int, 

2176 address: Optional[str] = None, 

2177 *, 

2178 family: socket.AddressFamily = socket.AF_UNSPEC, 

2179 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2180 flags: Optional[int] = None, 

2181 reuse_port: bool = False, 

2182 **kwargs: Any, 

2183 ) -> HTTPServer: 

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

2185 

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

2187 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2192 

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

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

2195 the server. 

2196 

2197 Returns the `.HTTPServer` object. 

2198 

2199 .. versionchanged:: 4.3 

2200 Now returns the `.HTTPServer` object. 

2201 

2202 .. versionchanged:: 6.2 

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

2204 including ``reuse_port``. 

2205 """ 

2206 server = HTTPServer(self, **kwargs) 

2207 server.listen( 

2208 port, 

2209 address=address, 

2210 family=family, 

2211 backlog=backlog, 

2212 flags=flags, 

2213 reuse_port=reuse_port, 

2214 ) 

2215 return server 

2216 

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

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

2219 

2220 Host patterns are processed sequentially in the order they were 

2221 added. All matching patterns will be considered. 

2222 """ 

2223 host_matcher = HostMatches(host_pattern) 

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

2225 

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

2227 

2228 if self.default_host is not None: 

2229 self.wildcard_router.add_rules( 

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

2231 ) 

2232 

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

2234 self.transforms.append(transform_class) 

2235 

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

2237 if isinstance(methods, types.ModuleType): 

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

2239 elif isinstance(methods, list): 

2240 for m in methods: 

2241 self._load_ui_methods(m) 

2242 else: 

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

2244 if ( 

2245 not name.startswith("_") 

2246 and hasattr(fn, "__call__") 

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

2248 ): 

2249 self.ui_methods[name] = fn 

2250 

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

2252 if isinstance(modules, types.ModuleType): 

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

2254 elif isinstance(modules, list): 

2255 for m in modules: 

2256 self._load_ui_modules(m) 

2257 else: 

2258 assert isinstance(modules, dict) 

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

2260 try: 

2261 if issubclass(cls, UIModule): 

2262 self.ui_modules[name] = cls 

2263 except TypeError: 

2264 pass 

2265 

2266 def __call__( 

2267 self, request: httputil.HTTPServerRequest 

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

2269 # Legacy HTTPServer interface 

2270 dispatcher = self.find_handler(request) 

2271 return dispatcher.execute() 

2272 

2273 def find_handler( 

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

2275 ) -> "_HandlerDelegate": 

2276 route = self.default_router.find_handler(request) 

2277 if route is not None: 

2278 return cast("_HandlerDelegate", route) 

2279 

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

2281 return self.get_handler_delegate( 

2282 request, 

2283 self.settings["default_handler_class"], 

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

2285 ) 

2286 

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

2288 

2289 def get_handler_delegate( 

2290 self, 

2291 request: httputil.HTTPServerRequest, 

2292 target_class: Type[RequestHandler], 

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

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

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

2296 ) -> "_HandlerDelegate": 

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

2298 for application and `RequestHandler` subclass. 

2299 

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

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

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

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

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

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

2306 """ 

2307 return _HandlerDelegate( 

2308 self, request, target_class, target_kwargs, path_args, path_kwargs 

2309 ) 

2310 

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

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

2313 

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

2315 

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

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

2318 and url-escaped. 

2319 """ 

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

2321 if reversed_url is not None: 

2322 return reversed_url 

2323 

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

2325 

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

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

2328 

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

2330 this behavior either subclass Application and override this method, 

2331 or pass a function in the application settings dictionary as 

2332 ``log_function``. 

2333 """ 

2334 if "log_function" in self.settings: 

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

2336 return 

2337 if handler.get_status() < 400: 

2338 log_method = access_log.info 

2339 elif handler.get_status() < 500: 

2340 log_method = access_log.warning 

2341 else: 

2342 log_method = access_log.error 

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

2344 log_method( 

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

2346 handler.get_status(), 

2347 handler._request_summary(), 

2348 request_time, 

2349 ) 

2350 

2351 

2352class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2353 def __init__( 

2354 self, 

2355 application: Application, 

2356 request: httputil.HTTPServerRequest, 

2357 handler_class: Type[RequestHandler], 

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

2359 path_args: Optional[List[bytes]], 

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

2361 ) -> None: 

2362 self.application = application 

2363 self.connection = request.connection 

2364 self.request = request 

2365 self.handler_class = handler_class 

2366 self.handler_kwargs = handler_kwargs or {} 

2367 self.path_args = path_args or [] 

2368 self.path_kwargs = path_kwargs or {} 

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

2370 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2371 

2372 def headers_received( 

2373 self, 

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

2375 headers: httputil.HTTPHeaders, 

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

2377 if self.stream_request_body: 

2378 self.request._body_future = Future() 

2379 return self.execute() 

2380 return None 

2381 

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

2383 if self.stream_request_body: 

2384 return self.handler.data_received(data) 

2385 else: 

2386 self.chunks.append(data) 

2387 return None 

2388 

2389 def finish(self) -> None: 

2390 if self.stream_request_body: 

2391 future_set_result_unless_cancelled(self.request._body_future, None) 

2392 else: 

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

2394 self.request._parse_body() 

2395 self.execute() 

2396 

2397 def on_connection_close(self) -> None: 

2398 if self.stream_request_body: 

2399 self.handler.on_connection_close() 

2400 else: 

2401 self.chunks = None # type: ignore 

2402 

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

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

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

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

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

2408 with RequestHandler._template_loader_lock: 

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

2410 loader.reset() 

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

2412 static_handler_class = self.application.settings.get( 

2413 "static_handler_class", StaticFileHandler 

2414 ) 

2415 static_handler_class.reset() 

2416 

2417 self.handler = self.handler_class( 

2418 self.application, self.request, **self.handler_kwargs 

2419 ) 

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

2421 

2422 if self.stream_request_body: 

2423 self.handler._prepared_future = Future() 

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

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

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

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

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

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

2430 # with WSGI) 

2431 fut = gen.convert_yielded( 

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

2433 ) 

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

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

2436 # when the handler has prepared to receive the body. If not, 

2437 # it doesn't matter when execute() finishes (so we return None) 

2438 return self.handler._prepared_future 

2439 

2440 

2441class HTTPError(Exception): 

2442 """An exception that will turn into an HTTP error response. 

2443 

2444 Raising an `HTTPError` is a convenient alternative to calling 

2445 `RequestHandler.send_error` since it automatically ends the 

2446 current function. 

2447 

2448 To customize the response sent with an `HTTPError`, override 

2449 `RequestHandler.write_error`. 

2450 

2451 :arg int status_code: HTTP status code. Must be listed in 

2452 `httplib.responses <http.client.responses>` unless the ``reason`` 

2453 keyword argument is given. 

2454 :arg str log_message: Message to be written to the log for this error 

2455 (will not be shown to the user unless the `Application` is in debug 

2456 mode). May contain ``%s``-style placeholders, which will be filled 

2457 in with remaining positional parameters. 

2458 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2459 to pass in the status line along with ``status_code``. Normally 

2460 determined automatically from ``status_code``, but can be used 

2461 to use a non-standard numeric code. 

2462 """ 

2463 

2464 def __init__( 

2465 self, 

2466 status_code: int = 500, 

2467 log_message: Optional[str] = None, 

2468 *args: Any, 

2469 **kwargs: Any, 

2470 ) -> None: 

2471 self.status_code = status_code 

2472 self.log_message = log_message 

2473 self.args = args 

2474 self.reason = kwargs.get("reason", None) 

2475 if log_message and not args: 

2476 self.log_message = log_message.replace("%", "%%") 

2477 

2478 def __str__(self) -> str: 

2479 message = "HTTP %d: %s" % ( 

2480 self.status_code, 

2481 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2482 ) 

2483 if self.log_message: 

2484 return message + " (" + (self.log_message % self.args) + ")" 

2485 else: 

2486 return message 

2487 

2488 

2489class Finish(Exception): 

2490 """An exception that ends the request without producing an error response. 

2491 

2492 When `Finish` is raised in a `RequestHandler`, the request will 

2493 end (calling `RequestHandler.finish` if it hasn't already been 

2494 called), but the error-handling methods (including 

2495 `RequestHandler.write_error`) will not be called. 

2496 

2497 If `Finish()` was created with no arguments, the pending response 

2498 will be sent as-is. If `Finish()` was given an argument, that 

2499 argument will be passed to `RequestHandler.finish()`. 

2500 

2501 This can be a more convenient way to implement custom error pages 

2502 than overriding ``write_error`` (especially in library code):: 

2503 

2504 if self.current_user is None: 

2505 self.set_status(401) 

2506 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2507 raise Finish() 

2508 

2509 .. versionchanged:: 4.3 

2510 Arguments passed to ``Finish()`` will be passed on to 

2511 `RequestHandler.finish`. 

2512 """ 

2513 

2514 pass 

2515 

2516 

2517class MissingArgumentError(HTTPError): 

2518 """Exception raised by `RequestHandler.get_argument`. 

2519 

2520 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2521 code will be used instead of 500 (and a stack trace will not be logged). 

2522 

2523 .. versionadded:: 3.1 

2524 """ 

2525 

2526 def __init__(self, arg_name: str) -> None: 

2527 super().__init__(400, "Missing argument %s" % arg_name) 

2528 self.arg_name = arg_name 

2529 

2530 

2531class ErrorHandler(RequestHandler): 

2532 """Generates an error response with ``status_code`` for all requests.""" 

2533 

2534 def initialize(self, status_code: int) -> None: 

2535 self.set_status(status_code) 

2536 

2537 def prepare(self) -> None: 

2538 raise HTTPError(self._status_code) 

2539 

2540 def check_xsrf_cookie(self) -> None: 

2541 # POSTs to an ErrorHandler don't actually have side effects, 

2542 # so we don't need to check the xsrf token. This allows POSTs 

2543 # to the wrong url to return a 404 instead of 403. 

2544 pass 

2545 

2546 

2547class RedirectHandler(RequestHandler): 

2548 """Redirects the client to the given URL for all GET requests. 

2549 

2550 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2551 

2552 application = web.Application([ 

2553 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2554 ]) 

2555 

2556 `RedirectHandler` supports regular expression substitutions. E.g., to 

2557 swap the first and second parts of a path while preserving the remainder:: 

2558 

2559 application = web.Application([ 

2560 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2561 ]) 

2562 

2563 The final URL is formatted with `str.format` and the substrings that match 

2564 the capturing groups. In the above example, a request to "/a/b/c" would be 

2565 formatted like:: 

2566 

2567 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2568 

2569 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2570 values are substituted. 

2571 

2572 .. versionchanged:: 4.5 

2573 Added support for substitutions into the destination URL. 

2574 

2575 .. versionchanged:: 5.0 

2576 If any query arguments are present, they will be copied to the 

2577 destination URL. 

2578 """ 

2579 

2580 def initialize(self, url: str, permanent: bool = True) -> None: 

2581 self._url = url 

2582 self._permanent = permanent 

2583 

2584 def get(self, *args: Any, **kwargs: Any) -> None: 

2585 to_url = self._url.format(*args, **kwargs) 

2586 if self.request.query_arguments: 

2587 # TODO: figure out typing for the next line. 

2588 to_url = httputil.url_concat( 

2589 to_url, 

2590 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2591 ) 

2592 self.redirect(to_url, permanent=self._permanent) 

2593 

2594 

2595class StaticFileHandler(RequestHandler): 

2596 """A simple handler that can serve static content from a directory. 

2597 

2598 A `StaticFileHandler` is configured automatically if you pass the 

2599 ``static_path`` keyword argument to `Application`. This handler 

2600 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2601 and ``static_handler_args`` settings. 

2602 

2603 To map an additional path to this handler for a static data directory 

2604 you would add a line to your application like:: 

2605 

2606 application = web.Application([ 

2607 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2608 ]) 

2609 

2610 The handler constructor requires a ``path`` argument, which specifies the 

2611 local root directory of the content to be served. 

2612 

2613 Note that a capture group in the regex is required to parse the value for 

2614 the ``path`` argument to the get() method (different than the constructor 

2615 argument above); see `URLSpec` for details. 

2616 

2617 To serve a file like ``index.html`` automatically when a directory is 

2618 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2619 in your application settings, or add ``default_filename`` as an initializer 

2620 argument for your ``StaticFileHandler``. 

2621 

2622 To maximize the effectiveness of browser caching, this class supports 

2623 versioned urls (by default using the argument ``?v=``). If a version 

2624 is given, we instruct the browser to cache this file indefinitely. 

2625 `make_static_url` (also available as `RequestHandler.static_url`) can 

2626 be used to construct a versioned url. 

2627 

2628 This handler is intended primarily for use in development and light-duty 

2629 file serving; for heavy traffic it will be more efficient to use 

2630 a dedicated static file server (such as nginx or Apache). We support 

2631 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2632 some browsers require this functionality to be present to seek in 

2633 HTML5 audio or video). 

2634 

2635 **Subclassing notes** 

2636 

2637 This class is designed to be extensible by subclassing, but because 

2638 of the way static urls are generated with class methods rather than 

2639 instance methods, the inheritance patterns are somewhat unusual. 

2640 Be sure to use the ``@classmethod`` decorator when overriding a 

2641 class method. Instance methods may use the attributes ``self.path`` 

2642 ``self.absolute_path``, and ``self.modified``. 

2643 

2644 Subclasses should only override methods discussed in this section; 

2645 overriding other methods is error-prone. Overriding 

2646 ``StaticFileHandler.get`` is particularly problematic due to the 

2647 tight coupling with ``compute_etag`` and other methods. 

2648 

2649 To change the way static urls are generated (e.g. to match the behavior 

2650 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2651 `get_cache_time`, and/or `get_version`. 

2652 

2653 To replace all interaction with the filesystem (e.g. to serve 

2654 static content from a database), override `get_content`, 

2655 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2656 `validate_absolute_path`. 

2657 

2658 .. versionchanged:: 3.1 

2659 Many of the methods for subclasses were added in Tornado 3.1. 

2660 """ 

2661 

2662 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2663 

2664 _static_hashes = {} # type: Dict[str, Optional[str]] 

2665 _lock = threading.Lock() # protects _static_hashes 

2666 

2667 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2668 self.root = path 

2669 self.default_filename = default_filename 

2670 

2671 @classmethod 

2672 def reset(cls) -> None: 

2673 with cls._lock: 

2674 cls._static_hashes = {} 

2675 

2676 def head(self, path: str) -> Awaitable[None]: 

2677 return self.get(path, include_body=False) 

2678 

2679 async def get(self, path: str, include_body: bool = True) -> None: 

2680 # Set up our path instance variables. 

2681 self.path = self.parse_url_path(path) 

2682 del path # make sure we don't refer to path instead of self.path again 

2683 absolute_path = self.get_absolute_path(self.root, self.path) 

2684 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2685 if self.absolute_path is None: 

2686 return 

2687 

2688 self.modified = self.get_modified_time() 

2689 self.set_headers() 

2690 

2691 if self.should_return_304(): 

2692 self.set_status(304) 

2693 return 

2694 

2695 request_range = None 

2696 range_header = self.request.headers.get("Range") 

2697 if range_header: 

2698 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2699 # the request will be treated as if the header didn't exist. 

2700 request_range = httputil._parse_request_range(range_header) 

2701 

2702 size = self.get_content_size() 

2703 if request_range: 

2704 start, end = request_range 

2705 if start is not None and start < 0: 

2706 start += size 

2707 if start < 0: 

2708 start = 0 

2709 if ( 

2710 start is not None 

2711 and (start >= size or (end is not None and start >= end)) 

2712 ) or end == 0: 

2713 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2714 # the first requested byte is equal to or greater than the 

2715 # content, or when a suffix with length 0 is specified. 

2716 # https://tools.ietf.org/html/rfc7233#section-2.1 

2717 # A byte-range-spec is invalid if the last-byte-pos value is present 

2718 # and less than the first-byte-pos. 

2719 self.set_status(416) # Range Not Satisfiable 

2720 self.set_header("Content-Type", "text/plain") 

2721 self.set_header("Content-Range", "bytes */%s" % (size,)) 

2722 return 

2723 if end is not None and end > size: 

2724 # Clients sometimes blindly use a large range to limit their 

2725 # download size; cap the endpoint at the actual file size. 

2726 end = size 

2727 # Note: only return HTTP 206 if less than the entire range has been 

2728 # requested. Not only is this semantically correct, but Chrome 

2729 # refuses to play audio if it gets an HTTP 206 in response to 

2730 # ``Range: bytes=0-``. 

2731 if size != (end or size) - (start or 0): 

2732 self.set_status(206) # Partial Content 

2733 self.set_header( 

2734 "Content-Range", httputil._get_content_range(start, end, size) 

2735 ) 

2736 else: 

2737 start = end = None 

2738 

2739 if start is not None and end is not None: 

2740 content_length = end - start 

2741 elif end is not None: 

2742 content_length = end 

2743 elif start is not None: 

2744 content_length = size - start 

2745 else: 

2746 content_length = size 

2747 self.set_header("Content-Length", content_length) 

2748 

2749 if include_body: 

2750 content = self.get_content(self.absolute_path, start, end) 

2751 if isinstance(content, bytes): 

2752 content = [content] 

2753 for chunk in content: 

2754 try: 

2755 self.write(chunk) 

2756 await self.flush() 

2757 except iostream.StreamClosedError: 

2758 return 

2759 else: 

2760 assert self.request.method == "HEAD" 

2761 

2762 def compute_etag(self) -> Optional[str]: 

2763 """Sets the ``Etag`` header based on static url version. 

2764 

2765 This allows efficient ``If-None-Match`` checks against cached 

2766 versions, and sends the correct ``Etag`` for a partial response 

2767 (i.e. the same ``Etag`` as the full file). 

2768 

2769 .. versionadded:: 3.1 

2770 """ 

2771 assert self.absolute_path is not None 

2772 version_hash = self._get_cached_version(self.absolute_path) 

2773 if not version_hash: 

2774 return None 

2775 return '"%s"' % (version_hash,) 

2776 

2777 def set_headers(self) -> None: 

2778 """Sets the content and caching headers on the response. 

2779 

2780 .. versionadded:: 3.1 

2781 """ 

2782 self.set_header("Accept-Ranges", "bytes") 

2783 self.set_etag_header() 

2784 

2785 if self.modified is not None: 

2786 self.set_header("Last-Modified", self.modified) 

2787 

2788 content_type = self.get_content_type() 

2789 if content_type: 

2790 self.set_header("Content-Type", content_type) 

2791 

2792 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2793 if cache_time > 0: 

2794 self.set_header( 

2795 "Expires", 

2796 datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time), 

2797 ) 

2798 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2799 

2800 self.set_extra_headers(self.path) 

2801 

2802 def should_return_304(self) -> bool: 

2803 """Returns True if the headers indicate that we should return 304. 

2804 

2805 .. versionadded:: 3.1 

2806 """ 

2807 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2808 if self.request.headers.get("If-None-Match"): 

2809 return self.check_etag_header() 

2810 

2811 # Check the If-Modified-Since, and don't send the result if the 

2812 # content has not been modified 

2813 ims_value = self.request.headers.get("If-Modified-Since") 

2814 if ims_value is not None: 

2815 date_tuple = email.utils.parsedate(ims_value) 

2816 if date_tuple is not None: 

2817 if_since = datetime.datetime(*date_tuple[:6]) 

2818 assert self.modified is not None 

2819 if if_since >= self.modified: 

2820 return True 

2821 

2822 return False 

2823 

2824 @classmethod 

2825 def get_absolute_path(cls, root: str, path: str) -> str: 

2826 """Returns the absolute location of ``path`` relative to ``root``. 

2827 

2828 ``root`` is the path configured for this `StaticFileHandler` 

2829 (in most cases the ``static_path`` `Application` setting). 

2830 

2831 This class method may be overridden in subclasses. By default 

2832 it returns a filesystem path, but other strings may be used 

2833 as long as they are unique and understood by the subclass's 

2834 overridden `get_content`. 

2835 

2836 .. versionadded:: 3.1 

2837 """ 

2838 abspath = os.path.abspath(os.path.join(root, path)) 

2839 return abspath 

2840 

2841 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2842 """Validate and return the absolute path. 

2843 

2844 ``root`` is the configured path for the `StaticFileHandler`, 

2845 and ``path`` is the result of `get_absolute_path` 

2846 

2847 This is an instance method called during request processing, 

2848 so it may raise `HTTPError` or use methods like 

2849 `RequestHandler.redirect` (return None after redirecting to 

2850 halt further processing). This is where 404 errors for missing files 

2851 are generated. 

2852 

2853 This method may modify the path before returning it, but note that 

2854 any such modifications will not be understood by `make_static_url`. 

2855 

2856 In instance methods, this method's result is available as 

2857 ``self.absolute_path``. 

2858 

2859 .. versionadded:: 3.1 

2860 """ 

2861 # os.path.abspath strips a trailing /. 

2862 # We must add it back to `root` so that we only match files 

2863 # in a directory named `root` instead of files starting with 

2864 # that prefix. 

2865 root = os.path.abspath(root) 

2866 if not root.endswith(os.path.sep): 

2867 # abspath always removes a trailing slash, except when 

2868 # root is '/'. This is an unusual case, but several projects 

2869 # have independently discovered this technique to disable 

2870 # Tornado's path validation and (hopefully) do their own, 

2871 # so we need to support it. 

2872 root += os.path.sep 

2873 # The trailing slash also needs to be temporarily added back 

2874 # the requested path so a request to root/ will match. 

2875 if not (absolute_path + os.path.sep).startswith(root): 

2876 raise HTTPError(403, "%s is not in root static directory", self.path) 

2877 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2878 # need to look at the request.path here for when path is empty 

2879 # but there is some prefix to the path that was already 

2880 # trimmed by the routing 

2881 if not self.request.path.endswith("/"): 

2882 if self.request.path.startswith("//"): 

2883 # A redirect with two initial slashes is a "protocol-relative" URL. 

2884 # This means the next path segment is treated as a hostname instead 

2885 # of a part of the path, making this effectively an open redirect. 

2886 # Reject paths starting with two slashes to prevent this. 

2887 # This is only reachable under certain configurations. 

2888 raise HTTPError( 

2889 403, "cannot redirect path with two initial slashes" 

2890 ) 

2891 self.redirect(self.request.path + "/", permanent=True) 

2892 return None 

2893 absolute_path = os.path.join(absolute_path, self.default_filename) 

2894 if not os.path.exists(absolute_path): 

2895 raise HTTPError(404) 

2896 if not os.path.isfile(absolute_path): 

2897 raise HTTPError(403, "%s is not a file", self.path) 

2898 return absolute_path 

2899 

2900 @classmethod 

2901 def get_content( 

2902 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

2903 ) -> Generator[bytes, None, None]: 

2904 """Retrieve the content of the requested resource which is located 

2905 at the given absolute path. 

2906 

2907 This class method may be overridden by subclasses. Note that its 

2908 signature is different from other overridable class methods 

2909 (no ``settings`` argument); this is deliberate to ensure that 

2910 ``abspath`` is able to stand on its own as a cache key. 

2911 

2912 This method should either return a byte string or an iterator 

2913 of byte strings. The latter is preferred for large files 

2914 as it helps reduce memory fragmentation. 

2915 

2916 .. versionadded:: 3.1 

2917 """ 

2918 with open(abspath, "rb") as file: 

2919 if start is not None: 

2920 file.seek(start) 

2921 if end is not None: 

2922 remaining = end - (start or 0) # type: Optional[int] 

2923 else: 

2924 remaining = None 

2925 while True: 

2926 chunk_size = 64 * 1024 

2927 if remaining is not None and remaining < chunk_size: 

2928 chunk_size = remaining 

2929 chunk = file.read(chunk_size) 

2930 if chunk: 

2931 if remaining is not None: 

2932 remaining -= len(chunk) 

2933 yield chunk 

2934 else: 

2935 if remaining is not None: 

2936 assert remaining == 0 

2937 return 

2938 

2939 @classmethod 

2940 def get_content_version(cls, abspath: str) -> str: 

2941 """Returns a version string for the resource at the given path. 

2942 

2943 This class method may be overridden by subclasses. The 

2944 default implementation is a SHA-512 hash of the file's contents. 

2945 

2946 .. versionadded:: 3.1 

2947 """ 

2948 data = cls.get_content(abspath) 

2949 hasher = hashlib.sha512() 

2950 if isinstance(data, bytes): 

2951 hasher.update(data) 

2952 else: 

2953 for chunk in data: 

2954 hasher.update(chunk) 

2955 return hasher.hexdigest() 

2956 

2957 def _stat(self) -> os.stat_result: 

2958 assert self.absolute_path is not None 

2959 if not hasattr(self, "_stat_result"): 

2960 self._stat_result = os.stat(self.absolute_path) 

2961 return self._stat_result 

2962 

2963 def get_content_size(self) -> int: 

2964 """Retrieve the total size of the resource at the given path. 

2965 

2966 This method may be overridden by subclasses. 

2967 

2968 .. versionadded:: 3.1 

2969 

2970 .. versionchanged:: 4.0 

2971 This method is now always called, instead of only when 

2972 partial results are requested. 

2973 """ 

2974 stat_result = self._stat() 

2975 return stat_result.st_size 

2976 

2977 def get_modified_time(self) -> Optional[datetime.datetime]: 

2978 """Returns the time that ``self.absolute_path`` was last modified. 

2979 

2980 May be overridden in subclasses. Should return a `~datetime.datetime` 

2981 object or None. 

2982 

2983 .. versionadded:: 3.1 

2984 """ 

2985 stat_result = self._stat() 

2986 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

2987 # which truncates the fractional portion of the timestamp. It 

2988 # was changed from that form to stat_result.st_mtime to 

2989 # satisfy mypy (which disallows the bracket operator), but the 

2990 # latter form returns a float instead of an int. For 

2991 # consistency with the past (and because we have a unit test 

2992 # that relies on this), we truncate the float here, although 

2993 # I'm not sure that's the right thing to do. 

2994 modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime)) 

2995 return modified 

2996 

2997 def get_content_type(self) -> str: 

2998 """Returns the ``Content-Type`` header to be used for this request. 

2999 

3000 .. versionadded:: 3.1 

3001 """ 

3002 assert self.absolute_path is not None 

3003 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3004 # per RFC 6713, use the appropriate type for a gzip compressed file 

3005 if encoding == "gzip": 

3006 return "application/gzip" 

3007 # As of 2015-07-21 there is no bzip2 encoding defined at 

3008 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3009 # So for that (and any other encoding), use octet-stream. 

3010 elif encoding is not None: 

3011 return "application/octet-stream" 

3012 elif mime_type is not None: 

3013 return mime_type 

3014 # if mime_type not detected, use application/octet-stream 

3015 else: 

3016 return "application/octet-stream" 

3017 

3018 def set_extra_headers(self, path: str) -> None: 

3019 """For subclass to add extra headers to the response""" 

3020 pass 

3021 

3022 def get_cache_time( 

3023 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3024 ) -> int: 

3025 """Override to customize cache control behavior. 

3026 

3027 Return a positive number of seconds to make the result 

3028 cacheable for that amount of time or 0 to mark resource as 

3029 cacheable for an unspecified amount of time (subject to 

3030 browser heuristics). 

3031 

3032 By default returns cache expiry of 10 years for resources requested 

3033 with ``v`` argument. 

3034 """ 

3035 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3036 

3037 @classmethod 

3038 def make_static_url( 

3039 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3040 ) -> str: 

3041 """Constructs a versioned url for the given path. 

3042 

3043 This method may be overridden in subclasses (but note that it 

3044 is a class method rather than an instance method). Subclasses 

3045 are only required to implement the signature 

3046 ``make_static_url(cls, settings, path)``; other keyword 

3047 arguments may be passed through `~RequestHandler.static_url` 

3048 but are not standard. 

3049 

3050 ``settings`` is the `Application.settings` dictionary. ``path`` 

3051 is the static path being requested. The url returned should be 

3052 relative to the current host. 

3053 

3054 ``include_version`` determines whether the generated URL should 

3055 include the query string containing the version hash of the 

3056 file corresponding to the given ``path``. 

3057 

3058 """ 

3059 url = settings.get("static_url_prefix", "/static/") + path 

3060 if not include_version: 

3061 return url 

3062 

3063 version_hash = cls.get_version(settings, path) 

3064 if not version_hash: 

3065 return url 

3066 

3067 return "%s?v=%s" % (url, version_hash) 

3068 

3069 def parse_url_path(self, url_path: str) -> str: 

3070 """Converts a static URL path into a filesystem path. 

3071 

3072 ``url_path`` is the path component of the URL with 

3073 ``static_url_prefix`` removed. The return value should be 

3074 filesystem path relative to ``static_path``. 

3075 

3076 This is the inverse of `make_static_url`. 

3077 """ 

3078 if os.path.sep != "/": 

3079 url_path = url_path.replace("/", os.path.sep) 

3080 return url_path 

3081 

3082 @classmethod 

3083 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3084 """Generate the version string to be used in static URLs. 

3085 

3086 ``settings`` is the `Application.settings` dictionary and ``path`` 

3087 is the relative location of the requested asset on the filesystem. 

3088 The returned value should be a string, or ``None`` if no version 

3089 could be determined. 

3090 

3091 .. versionchanged:: 3.1 

3092 This method was previously recommended for subclasses to override; 

3093 `get_content_version` is now preferred as it allows the base 

3094 class to handle caching of the result. 

3095 """ 

3096 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3097 return cls._get_cached_version(abs_path) 

3098 

3099 @classmethod 

3100 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3101 with cls._lock: 

3102 hashes = cls._static_hashes 

3103 if abs_path not in hashes: 

3104 try: 

3105 hashes[abs_path] = cls.get_content_version(abs_path) 

3106 except Exception: 

3107 gen_log.error("Could not open static file %r", abs_path) 

3108 hashes[abs_path] = None 

3109 hsh = hashes.get(abs_path) 

3110 if hsh: 

3111 return hsh 

3112 return None 

3113 

3114 

3115class FallbackHandler(RequestHandler): 

3116 """A `RequestHandler` that wraps another HTTP server callback. 

3117 

3118 The fallback is a callable object that accepts an 

3119 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3120 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3121 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3122 usage:: 

3123 

3124 wsgi_app = tornado.wsgi.WSGIContainer( 

3125 django.core.handlers.wsgi.WSGIHandler()) 

3126 application = tornado.web.Application([ 

3127 (r"/foo", FooHandler), 

3128 (r".*", FallbackHandler, dict(fallback=wsgi_app), 

3129 ]) 

3130 """ 

3131 

3132 def initialize( 

3133 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3134 ) -> None: 

3135 self.fallback = fallback 

3136 

3137 def prepare(self) -> None: 

3138 self.fallback(self.request) 

3139 self._finished = True 

3140 self.on_finish() 

3141 

3142 

3143class OutputTransform(object): 

3144 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3145 

3146 Applications are not expected to create their own OutputTransforms 

3147 or interact with them directly; the framework chooses which transforms 

3148 (if any) to apply. 

3149 """ 

3150 

3151 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3152 pass 

3153 

3154 def transform_first_chunk( 

3155 self, 

3156 status_code: int, 

3157 headers: httputil.HTTPHeaders, 

3158 chunk: bytes, 

3159 finishing: bool, 

3160 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3161 return status_code, headers, chunk 

3162 

3163 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3164 return chunk 

3165 

3166 

3167class GZipContentEncoding(OutputTransform): 

3168 """Applies the gzip content encoding to the response. 

3169 

3170 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3171 

3172 .. versionchanged:: 4.0 

3173 Now compresses all mime types beginning with ``text/``, instead 

3174 of just a whitelist. (the whitelist is still used for certain 

3175 non-text mime types). 

3176 """ 

3177 

3178 # Whitelist of compressible mime types (in addition to any types 

3179 # beginning with "text/"). 

3180 CONTENT_TYPES = set( 

3181 [ 

3182 "application/javascript", 

3183 "application/x-javascript", 

3184 "application/xml", 

3185 "application/atom+xml", 

3186 "application/json", 

3187 "application/xhtml+xml", 

3188 "image/svg+xml", 

3189 ] 

3190 ) 

3191 # Python's GzipFile defaults to level 9, while most other gzip 

3192 # tools (including gzip itself) default to 6, which is probably a 

3193 # better CPU/size tradeoff. 

3194 GZIP_LEVEL = 6 

3195 # Responses that are too short are unlikely to benefit from gzipping 

3196 # after considering the "Content-Encoding: gzip" header and the header 

3197 # inside the gzip encoding. 

3198 # Note that responses written in multiple chunks will be compressed 

3199 # regardless of size. 

3200 MIN_LENGTH = 1024 

3201 

3202 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3203 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3204 

3205 def _compressible_type(self, ctype: str) -> bool: 

3206 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3207 

3208 def transform_first_chunk( 

3209 self, 

3210 status_code: int, 

3211 headers: httputil.HTTPHeaders, 

3212 chunk: bytes, 

3213 finishing: bool, 

3214 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3215 # TODO: can/should this type be inherited from the superclass? 

3216 if "Vary" in headers: 

3217 headers["Vary"] += ", Accept-Encoding" 

3218 else: 

3219 headers["Vary"] = "Accept-Encoding" 

3220 if self._gzipping: 

3221 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3222 self._gzipping = ( 

3223 self._compressible_type(ctype) 

3224 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3225 and ("Content-Encoding" not in headers) 

3226 ) 

3227 if self._gzipping: 

3228 headers["Content-Encoding"] = "gzip" 

3229 self._gzip_value = BytesIO() 

3230 self._gzip_file = gzip.GzipFile( 

3231 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3232 ) 

3233 chunk = self.transform_chunk(chunk, finishing) 

3234 if "Content-Length" in headers: 

3235 # The original content length is no longer correct. 

3236 # If this is the last (and only) chunk, we can set the new 

3237 # content-length; otherwise we remove it and fall back to 

3238 # chunked encoding. 

3239 if finishing: 

3240 headers["Content-Length"] = str(len(chunk)) 

3241 else: 

3242 del headers["Content-Length"] 

3243 return status_code, headers, chunk 

3244 

3245 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3246 if self._gzipping: 

3247 self._gzip_file.write(chunk) 

3248 if finishing: 

3249 self._gzip_file.close() 

3250 else: 

3251 self._gzip_file.flush() 

3252 chunk = self._gzip_value.getvalue() 

3253 self._gzip_value.truncate(0) 

3254 self._gzip_value.seek(0) 

3255 return chunk 

3256 

3257 

3258def authenticated( 

3259 method: Callable[..., Optional[Awaitable[None]]] 

3260) -> Callable[..., Optional[Awaitable[None]]]: 

3261 """Decorate methods with this to require that the user be logged in. 

3262 

3263 If the user is not logged in, they will be redirected to the configured 

3264 `login url <RequestHandler.get_login_url>`. 

3265 

3266 If you configure a login url with a query parameter, Tornado will 

3267 assume you know what you're doing and use it as-is. If not, it 

3268 will add a `next` parameter so the login page knows where to send 

3269 you once you're logged in. 

3270 """ 

3271 

3272 @functools.wraps(method) 

3273 def wrapper( # type: ignore 

3274 self: RequestHandler, *args, **kwargs 

3275 ) -> Optional[Awaitable[None]]: 

3276 if not self.current_user: 

3277 if self.request.method in ("GET", "HEAD"): 

3278 url = self.get_login_url() 

3279 if "?" not in url: 

3280 if urllib.parse.urlsplit(url).scheme: 

3281 # if login url is absolute, make next absolute too 

3282 next_url = self.request.full_url() 

3283 else: 

3284 assert self.request.uri is not None 

3285 next_url = self.request.uri 

3286 url += "?" + urlencode(dict(next=next_url)) 

3287 self.redirect(url) 

3288 return None 

3289 raise HTTPError(403) 

3290 return method(self, *args, **kwargs) 

3291 

3292 return wrapper 

3293 

3294 

3295class UIModule(object): 

3296 """A re-usable, modular UI unit on a page. 

3297 

3298 UI modules often execute additional queries, and they can include 

3299 additional CSS and JavaScript that will be included in the output 

3300 page, which is automatically inserted on page render. 

3301 

3302 Subclasses of UIModule must override the `render` method. 

3303 """ 

3304 

3305 def __init__(self, handler: RequestHandler) -> None: 

3306 self.handler = handler 

3307 self.request = handler.request 

3308 self.ui = handler.ui 

3309 self.locale = handler.locale 

3310 

3311 @property 

3312 def current_user(self) -> Any: 

3313 return self.handler.current_user 

3314 

3315 def render(self, *args: Any, **kwargs: Any) -> str: 

3316 """Override in subclasses to return this module's output.""" 

3317 raise NotImplementedError() 

3318 

3319 def embedded_javascript(self) -> Optional[str]: 

3320 """Override to return a JavaScript string 

3321 to be embedded in the page.""" 

3322 return None 

3323 

3324 def javascript_files(self) -> Optional[Iterable[str]]: 

3325 """Override to return a list of JavaScript files needed by this module. 

3326 

3327 If the return values are relative paths, they will be passed to 

3328 `RequestHandler.static_url`; otherwise they will be used as-is. 

3329 """ 

3330 return None 

3331 

3332 def embedded_css(self) -> Optional[str]: 

3333 """Override to return a CSS string 

3334 that will be embedded in the page.""" 

3335 return None 

3336 

3337 def css_files(self) -> Optional[Iterable[str]]: 

3338 """Override to returns a list of CSS files required by this module. 

3339 

3340 If the return values are relative paths, they will be passed to 

3341 `RequestHandler.static_url`; otherwise they will be used as-is. 

3342 """ 

3343 return None 

3344 

3345 def html_head(self) -> Optional[str]: 

3346 """Override to return an HTML string that will be put in the <head/> 

3347 element. 

3348 """ 

3349 return None 

3350 

3351 def html_body(self) -> Optional[str]: 

3352 """Override to return an HTML string that will be put at the end of 

3353 the <body/> element. 

3354 """ 

3355 return None 

3356 

3357 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3358 """Renders a template and returns it as a string.""" 

3359 return self.handler.render_string(path, **kwargs) 

3360 

3361 

3362class _linkify(UIModule): 

3363 def render(self, text: str, **kwargs: Any) -> str: # type: ignore 

3364 return escape.linkify(text, **kwargs) 

3365 

3366 

3367class _xsrf_form_html(UIModule): 

3368 def render(self) -> str: # type: ignore 

3369 return self.handler.xsrf_form_html() 

3370 

3371 

3372class TemplateModule(UIModule): 

3373 """UIModule that simply renders the given template. 

3374 

3375 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3376 but the module version gets its own namespace (with kwargs passed to 

3377 Template()) instead of inheriting the outer template's namespace. 

3378 

3379 Templates rendered through this module also get access to UIModule's 

3380 automatic JavaScript/CSS features. Simply call set_resources 

3381 inside the template and give it keyword arguments corresponding to 

3382 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3383 Note that these resources are output once per template file, not once 

3384 per instantiation of the template, so they must not depend on 

3385 any arguments to the template. 

3386 """ 

3387 

3388 def __init__(self, handler: RequestHandler) -> None: 

3389 super().__init__(handler) 

3390 # keep resources in both a list and a dict to preserve order 

3391 self._resource_list = [] # type: List[Dict[str, Any]] 

3392 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3393 

3394 def render(self, path: str, **kwargs: Any) -> bytes: # type: ignore 

3395 def set_resources(**kwargs) -> str: # type: ignore 

3396 if path not in self._resource_dict: 

3397 self._resource_list.append(kwargs) 

3398 self._resource_dict[path] = kwargs 

3399 else: 

3400 if self._resource_dict[path] != kwargs: 

3401 raise ValueError( 

3402 "set_resources called with different " 

3403 "resources for the same template" 

3404 ) 

3405 return "" 

3406 

3407 return self.render_string(path, set_resources=set_resources, **kwargs) 

3408 

3409 def _get_resources(self, key: str) -> Iterable[str]: 

3410 return (r[key] for r in self._resource_list if key in r) 

3411 

3412 def embedded_javascript(self) -> str: 

3413 return "\n".join(self._get_resources("embedded_javascript")) 

3414 

3415 def javascript_files(self) -> Iterable[str]: 

3416 result = [] 

3417 for f in self._get_resources("javascript_files"): 

3418 if isinstance(f, (unicode_type, bytes)): 

3419 result.append(f) 

3420 else: 

3421 result.extend(f) 

3422 return result 

3423 

3424 def embedded_css(self) -> str: 

3425 return "\n".join(self._get_resources("embedded_css")) 

3426 

3427 def css_files(self) -> Iterable[str]: 

3428 result = [] 

3429 for f in self._get_resources("css_files"): 

3430 if isinstance(f, (unicode_type, bytes)): 

3431 result.append(f) 

3432 else: 

3433 result.extend(f) 

3434 return result 

3435 

3436 def html_head(self) -> str: 

3437 return "".join(self._get_resources("html_head")) 

3438 

3439 def html_body(self) -> str: 

3440 return "".join(self._get_resources("html_body")) 

3441 

3442 

3443class _UIModuleNamespace(object): 

3444 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3445 

3446 def __init__( 

3447 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3448 ) -> None: 

3449 self.handler = handler 

3450 self.ui_modules = ui_modules 

3451 

3452 def __getitem__(self, key: str) -> Callable[..., str]: 

3453 return self.handler._ui_module(key, self.ui_modules[key]) 

3454 

3455 def __getattr__(self, key: str) -> Callable[..., str]: 

3456 try: 

3457 return self[key] 

3458 except KeyError as e: 

3459 raise AttributeError(str(e)) 

3460 

3461 

3462def create_signed_value( 

3463 secret: _CookieSecretTypes, 

3464 name: str, 

3465 value: Union[str, bytes], 

3466 version: Optional[int] = None, 

3467 clock: Optional[Callable[[], float]] = None, 

3468 key_version: Optional[int] = None, 

3469) -> bytes: 

3470 if version is None: 

3471 version = DEFAULT_SIGNED_VALUE_VERSION 

3472 if clock is None: 

3473 clock = time.time 

3474 

3475 timestamp = utf8(str(int(clock()))) 

3476 value = base64.b64encode(utf8(value)) 

3477 if version == 1: 

3478 assert not isinstance(secret, dict) 

3479 signature = _create_signature_v1(secret, name, value, timestamp) 

3480 value = b"|".join([value, timestamp, signature]) 

3481 return value 

3482 elif version == 2: 

3483 # The v2 format consists of a version number and a series of 

3484 # length-prefixed fields "%d:%s", the last of which is a 

3485 # signature, all separated by pipes. All numbers are in 

3486 # decimal format with no leading zeros. The signature is an 

3487 # HMAC-SHA256 of the whole string up to that point, including 

3488 # the final pipe. 

3489 # 

3490 # The fields are: 

3491 # - format version (i.e. 2; no length prefix) 

3492 # - key version (integer, default is 0) 

3493 # - timestamp (integer seconds since epoch) 

3494 # - name (not encoded; assumed to be ~alphanumeric) 

3495 # - value (base64-encoded) 

3496 # - signature (hex-encoded; no length prefix) 

3497 def format_field(s: Union[str, bytes]) -> bytes: 

3498 return utf8("%d:" % len(s)) + utf8(s) 

3499 

3500 to_sign = b"|".join( 

3501 [ 

3502 b"2", 

3503 format_field(str(key_version or 0)), 

3504 format_field(timestamp), 

3505 format_field(name), 

3506 format_field(value), 

3507 b"", 

3508 ] 

3509 ) 

3510 

3511 if isinstance(secret, dict): 

3512 assert ( 

3513 key_version is not None 

3514 ), "Key version must be set when sign key dict is used" 

3515 assert version >= 2, "Version must be at least 2 for key version support" 

3516 secret = secret[key_version] 

3517 

3518 signature = _create_signature_v2(secret, to_sign) 

3519 return to_sign + signature 

3520 else: 

3521 raise ValueError("Unsupported version %d" % version) 

3522 

3523 

3524# A leading version number in decimal 

3525# with no leading zeros, followed by a pipe. 

3526_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3527 

3528 

3529def _get_version(value: bytes) -> int: 

3530 # Figures out what version value is. Version 1 did not include an 

3531 # explicit version field and started with arbitrary base64 data, 

3532 # which makes this tricky. 

3533 m = _signed_value_version_re.match(value) 

3534 if m is None: 

3535 version = 1 

3536 else: 

3537 try: 

3538 version = int(m.group(1)) 

3539 if version > 999: 

3540 # Certain payloads from the version-less v1 format may 

3541 # be parsed as valid integers. Due to base64 padding 

3542 # restrictions, this can only happen for numbers whose 

3543 # length is a multiple of 4, so we can treat all 

3544 # numbers up to 999 as versions, and for the rest we 

3545 # fall back to v1 format. 

3546 version = 1 

3547 except ValueError: 

3548 version = 1 

3549 return version 

3550 

3551 

3552def decode_signed_value( 

3553 secret: _CookieSecretTypes, 

3554 name: str, 

3555 value: Union[None, str, bytes], 

3556 max_age_days: float = 31, 

3557 clock: Optional[Callable[[], float]] = None, 

3558 min_version: Optional[int] = None, 

3559) -> Optional[bytes]: 

3560 if clock is None: 

3561 clock = time.time 

3562 if min_version is None: 

3563 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3564 if min_version > 2: 

3565 raise ValueError("Unsupported min_version %d" % min_version) 

3566 if not value: 

3567 return None 

3568 

3569 value = utf8(value) 

3570 version = _get_version(value) 

3571 

3572 if version < min_version: 

3573 return None 

3574 if version == 1: 

3575 assert not isinstance(secret, dict) 

3576 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3577 elif version == 2: 

3578 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3579 else: 

3580 return None 

3581 

3582 

3583def _decode_signed_value_v1( 

3584 secret: Union[str, bytes], 

3585 name: str, 

3586 value: bytes, 

3587 max_age_days: float, 

3588 clock: Callable[[], float], 

3589) -> Optional[bytes]: 

3590 parts = utf8(value).split(b"|") 

3591 if len(parts) != 3: 

3592 return None 

3593 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3594 if not hmac.compare_digest(parts[2], signature): 

3595 gen_log.warning("Invalid cookie signature %r", value) 

3596 return None 

3597 timestamp = int(parts[1]) 

3598 if timestamp < clock() - max_age_days * 86400: 

3599 gen_log.warning("Expired cookie %r", value) 

3600 return None 

3601 if timestamp > clock() + 31 * 86400: 

3602 # _cookie_signature does not hash a delimiter between the 

3603 # parts of the cookie, so an attacker could transfer trailing 

3604 # digits from the payload to the timestamp without altering the 

3605 # signature. For backwards compatibility, sanity-check timestamp 

3606 # here instead of modifying _cookie_signature. 

3607 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3608 return None 

3609 if parts[1].startswith(b"0"): 

3610 gen_log.warning("Tampered cookie %r", value) 

3611 return None 

3612 try: 

3613 return base64.b64decode(parts[0]) 

3614 except Exception: 

3615 return None 

3616 

3617 

3618def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3619 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3620 length, _, rest = s.partition(b":") 

3621 n = int(length) 

3622 field_value = rest[:n] 

3623 # In python 3, indexing bytes returns small integers; we must 

3624 # use a slice to get a byte string as in python 2. 

3625 if rest[n : n + 1] != b"|": 

3626 raise ValueError("malformed v2 signed value field") 

3627 rest = rest[n + 1 :] 

3628 return field_value, rest 

3629 

3630 rest = value[2:] # remove version number 

3631 key_version, rest = _consume_field(rest) 

3632 timestamp, rest = _consume_field(rest) 

3633 name_field, rest = _consume_field(rest) 

3634 value_field, passed_sig = _consume_field(rest) 

3635 return int(key_version), timestamp, name_field, value_field, passed_sig 

3636 

3637 

3638def _decode_signed_value_v2( 

3639 secret: _CookieSecretTypes, 

3640 name: str, 

3641 value: bytes, 

3642 max_age_days: float, 

3643 clock: Callable[[], float], 

3644) -> Optional[bytes]: 

3645 try: 

3646 ( 

3647 key_version, 

3648 timestamp_bytes, 

3649 name_field, 

3650 value_field, 

3651 passed_sig, 

3652 ) = _decode_fields_v2(value) 

3653 except ValueError: 

3654 return None 

3655 signed_string = value[: -len(passed_sig)] 

3656 

3657 if isinstance(secret, dict): 

3658 try: 

3659 secret = secret[key_version] 

3660 except KeyError: 

3661 return None 

3662 

3663 expected_sig = _create_signature_v2(secret, signed_string) 

3664 if not hmac.compare_digest(passed_sig, expected_sig): 

3665 return None 

3666 if name_field != utf8(name): 

3667 return None 

3668 timestamp = int(timestamp_bytes) 

3669 if timestamp < clock() - max_age_days * 86400: 

3670 # The signature has expired. 

3671 return None 

3672 try: 

3673 return base64.b64decode(value_field) 

3674 except Exception: 

3675 return None 

3676 

3677 

3678def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3679 value = utf8(value) 

3680 version = _get_version(value) 

3681 if version < 2: 

3682 return None 

3683 try: 

3684 key_version, _, _, _, _ = _decode_fields_v2(value) 

3685 except ValueError: 

3686 return None 

3687 

3688 return key_version 

3689 

3690 

3691def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3692 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3693 for part in parts: 

3694 hash.update(utf8(part)) 

3695 return utf8(hash.hexdigest()) 

3696 

3697 

3698def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3699 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3700 hash.update(utf8(s)) 

3701 return utf8(hash.hexdigest()) 

3702 

3703 

3704def is_absolute(path: str) -> bool: 

3705 return any(path.startswith(x) for x in ["/", "http:", "https:"])