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

1399 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-10 06:20 +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.web 

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 tornado 

83import traceback 

84import types 

85import urllib.parse 

86from urllib.parse import urlencode 

87 

88from tornado.concurrent import Future, future_set_result_unless_cancelled 

89from tornado import escape 

90from tornado import gen 

91from tornado.httpserver import HTTPServer 

92from tornado import httputil 

93from tornado import iostream 

94import tornado.locale 

95from tornado import locale 

96from tornado.log import access_log, app_log, gen_log 

97import tornado.netutil 

98from tornado import template 

99from tornado.escape import utf8, _unicode 

100from tornado.routing import ( 

101 AnyMatches, 

102 DefaultHostMatches, 

103 HostMatches, 

104 ReversibleRouter, 

105 Rule, 

106 ReversibleRuleRouter, 

107 URLSpec, 

108 _RuleList, 

109) 

110from tornado.util import ObjectDict, unicode_type, _websocket_mask 

111 

112url = URLSpec 

113 

114from typing import ( 

115 Dict, 

116 Any, 

117 Union, 

118 Optional, 

119 Awaitable, 

120 Tuple, 

121 List, 

122 Callable, 

123 Iterable, 

124 Generator, 

125 Type, 

126 TypeVar, 

127 cast, 

128 overload, 

129) 

130from types import TracebackType 

131import typing 

132 

133if typing.TYPE_CHECKING: 

134 from typing import Set # noqa: F401 

135 

136 

137# The following types are accepted by RequestHandler.set_header 

138# and related methods. 

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

140 

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

142 

143 

144MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

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

146 

147Signed values older than this version cannot be decoded. 

148 

149.. versionadded:: 3.2.1 

150""" 

151 

152MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

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

154 

155Signed values newer than this version cannot be decoded. 

156 

157.. versionadded:: 3.2.1 

158""" 

159 

160DEFAULT_SIGNED_VALUE_VERSION = 2 

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

162 

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

164 

165.. versionadded:: 3.2.1 

166""" 

167 

168DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

169"""The oldest signed value accepted by `.RequestHandler.get_secure_cookie`. 

170 

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

172 

173.. versionadded:: 3.2.1 

174""" 

175 

176 

177class _ArgDefaultMarker: 

178 pass 

179 

180 

181_ARG_DEFAULT = _ArgDefaultMarker() 

182 

183 

184class RequestHandler(object): 

185 """Base class for HTTP request handlers. 

186 

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

188 "Entry points" section below. 

189 

190 Applications should not construct `RequestHandler` objects 

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

192 `~RequestHandler.initialize` instead). 

193 

194 """ 

195 

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

197 

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

199 _template_loader_lock = threading.Lock() 

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

201 

202 _stream_request_body = False 

203 

204 # Will be set in _execute. 

205 _transforms = None # type: List[OutputTransform] 

206 path_args = None # type: List[str] 

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

208 

209 def __init__( 

210 self, 

211 application: "Application", 

212 request: httputil.HTTPServerRequest, 

213 **kwargs: Any 

214 ) -> None: 

215 super().__init__() 

216 

217 self.application = application 

218 self.request = request 

219 self._headers_written = False 

220 self._finished = False 

221 self._auto_finish = True 

222 self._prepared_future = None 

223 self.ui = ObjectDict( 

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

225 ) 

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

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

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

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

230 # possible conflicts. 

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

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

233 self.clear() 

234 assert self.request.connection is not None 

235 # TODO: need to add set_close_callback to HTTPConnection interface 

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

237 self.on_connection_close 

238 ) 

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

240 

241 def _initialize(self) -> None: 

242 pass 

243 

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

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

246 

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

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

249 

250 Example:: 

251 

252 class ProfileHandler(RequestHandler): 

253 def initialize(self, database): 

254 self.database = database 

255 

256 def get(self, username): 

257 ... 

258 

259 app = Application([ 

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

261 ]) 

262 """ 

263 

264 @property 

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

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

267 return self.application.settings 

268 

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

270 raise HTTPError(405) 

271 

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

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

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

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

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

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

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

279 

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

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

282 

283 Override this method to perform common initialization regardless 

284 of the request method. 

285 

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

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

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

289 until the ``Awaitable`` is done. 

290 

291 .. versionadded:: 3.1 

292 Asynchronous support. 

293 """ 

294 pass 

295 

296 def on_finish(self) -> None: 

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

298 

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

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

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

302 has been sent to the client. 

303 """ 

304 pass 

305 

306 def on_connection_close(self) -> None: 

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

308 

309 Override this to clean up resources associated with 

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

311 the connection was closed during asynchronous processing; if you 

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

313 instead. 

314 

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

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

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

318 connection. 

319 """ 

320 if _has_stream_request_body(self.__class__): 

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

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

323 self.request._body_future.exception() 

324 

325 def clear(self) -> None: 

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

327 self._headers = httputil.HTTPHeaders( 

328 { 

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

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

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

332 } 

333 ) 

334 self.set_default_headers() 

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

336 self._status_code = 200 

337 self._reason = httputil.responses[200] 

338 

339 def set_default_headers(self) -> None: 

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

341 

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

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

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

345 during error handling. 

346 """ 

347 pass 

348 

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

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

351 

352 :arg int status_code: Response status code. 

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

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

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

356 

357 .. versionchanged:: 5.0 

358 

359 No longer validates that the response code is in 

360 `http.client.responses`. 

361 """ 

362 self._status_code = status_code 

363 if reason is not None: 

364 self._reason = escape.native_str(reason) 

365 else: 

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

367 

368 def get_status(self) -> int: 

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

370 return self._status_code 

371 

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

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

374 

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

376 are formatted according to the HTTP specification for the 

377 ``Date`` header). 

378 

379 """ 

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

381 

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

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

384 

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

386 to return multiple values for the same header. 

387 """ 

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

389 

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

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

392 

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

394 set by `add_header`. 

395 """ 

396 if name in self._headers: 

397 del self._headers[name] 

398 

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

400 

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

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

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

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

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

406 if isinstance(value, str): 

407 retval = value 

408 elif isinstance(value, bytes): 

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

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

411 retval = value.decode("latin1") 

412 elif isinstance(value, numbers.Integral): 

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

414 return str(value) 

415 elif isinstance(value, datetime.datetime): 

416 return httputil.format_timestamp(value) 

417 else: 

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

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

420 # additional headers or split the request. 

421 if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval): 

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

423 return retval 

424 

425 @overload 

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

427 pass 

428 

429 @overload 

430 def get_argument( # noqa: F811 

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

432 ) -> str: 

433 pass 

434 

435 @overload 

436 def get_argument( # noqa: F811 

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

438 ) -> Optional[str]: 

439 pass 

440 

441 def get_argument( # noqa: F811 

442 self, 

443 name: str, 

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

445 strip: bool = True, 

446 ) -> Optional[str]: 

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

448 

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

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

451 

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

453 last value. 

454 

455 This method searches both the query and body arguments. 

456 """ 

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

458 

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

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

461 

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

463 

464 This method searches both the query and body arguments. 

465 """ 

466 

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

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

469 # `get_argument`.) 

470 assert isinstance(strip, bool) 

471 

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

473 

474 def get_body_argument( 

475 self, 

476 name: str, 

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

478 strip: bool = True, 

479 ) -> Optional[str]: 

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

481 from the request body. 

482 

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

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

485 

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

487 last value. 

488 

489 .. versionadded:: 3.2 

490 """ 

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

492 

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

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

495 

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

497 

498 .. versionadded:: 3.2 

499 """ 

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

501 

502 def get_query_argument( 

503 self, 

504 name: str, 

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

506 strip: bool = True, 

507 ) -> Optional[str]: 

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

509 from the request query string. 

510 

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

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

513 

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

515 last value. 

516 

517 .. versionadded:: 3.2 

518 """ 

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

520 

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

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

523 

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

525 

526 .. versionadded:: 3.2 

527 """ 

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

529 

530 def _get_argument( 

531 self, 

532 name: str, 

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

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

535 strip: bool = True, 

536 ) -> Optional[str]: 

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

538 if not args: 

539 if isinstance(default, _ArgDefaultMarker): 

540 raise MissingArgumentError(name) 

541 return default 

542 return args[-1] 

543 

544 def _get_arguments( 

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

546 ) -> List[str]: 

547 values = [] 

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

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

550 if isinstance(s, unicode_type): 

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

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

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

554 if strip: 

555 s = s.strip() 

556 values.append(s) 

557 return values 

558 

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

560 """Decodes an argument from the request. 

561 

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

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

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

565 

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

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

568 

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

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

571 """ 

572 try: 

573 return _unicode(value) 

574 except UnicodeDecodeError: 

575 raise HTTPError( 

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

577 ) 

578 

579 @property 

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

581 """An alias for 

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

583 return self.request.cookies 

584 

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

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

587 

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

589 

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

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

592 handler. 

593 """ 

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

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

596 return default 

597 

598 def set_cookie( 

599 self, 

600 name: str, 

601 value: Union[str, bytes], 

602 domain: Optional[str] = None, 

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

604 path: str = "/", 

605 expires_days: Optional[float] = None, 

606 **kwargs: Any 

607 ) -> None: 

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

609 

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

611 they are not present until the next request. 

612 

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

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

615 `datetime.datetime` object. 

616 

617 Additional keyword arguments are set on the cookies.Morsel 

618 directly. 

619 See https://docs.python.org/3/library/http.cookies.html#http.cookies.Morsel 

620 for available attributes. 

621 """ 

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

623 name = escape.native_str(name) 

624 value = escape.native_str(value) 

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

626 # Don't let us accidentally inject bad stuff 

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

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

629 self._new_cookie = ( 

630 http.cookies.SimpleCookie() 

631 ) # type: http.cookies.SimpleCookie 

632 if name in self._new_cookie: 

633 del self._new_cookie[name] 

634 self._new_cookie[name] = value 

635 morsel = self._new_cookie[name] 

636 if domain: 

637 morsel["domain"] = domain 

638 if expires_days is not None and not expires: 

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

640 if expires: 

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

642 if path: 

643 morsel["path"] = path 

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

645 if k == "max_age": 

646 k = "max-age" 

647 

648 # skip falsy values for httponly and secure flags because 

649 # SimpleCookie sets them regardless 

650 if k in ["httponly", "secure"] and not v: 

651 continue 

652 

653 morsel[k] = v 

654 

655 def clear_cookie( 

656 self, name: str, path: str = "/", domain: Optional[str] = None 

657 ) -> None: 

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

659 

660 Due to limitations of the cookie protocol, you must pass the same 

661 path and domain to clear a cookie as were used when that cookie 

662 was set (but there is no way to find out on the server side 

663 which values were used for a given cookie). 

664 

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

666 seen until the following request. 

667 """ 

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

669 self.set_cookie(name, value="", path=path, expires=expires, domain=domain) 

670 

671 def clear_all_cookies(self, path: str = "/", domain: Optional[str] = None) -> None: 

672 """Deletes all the cookies the user sent with this request. 

673 

674 See `clear_cookie` for more information on the path and domain 

675 parameters. 

676 

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

678 seen until the following request. 

679 

680 .. versionchanged:: 3.2 

681 

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

683 """ 

684 for name in self.request.cookies: 

685 self.clear_cookie(name, path=path, domain=domain) 

686 

687 def set_secure_cookie( 

688 self, 

689 name: str, 

690 value: Union[str, bytes], 

691 expires_days: Optional[float] = 30, 

692 version: Optional[int] = None, 

693 **kwargs: Any 

694 ) -> None: 

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

696 

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

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

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

700 

701 To read a cookie set with this method, use `get_secure_cookie()`. 

702 

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

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

705 parameter to `get_secure_cookie`. 

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

707 

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

709 strings (unlike regular cookies) 

710 

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

712 seen until the following request. 

713 

714 .. versionchanged:: 3.2.1 

715 

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

717 and made it the default. 

718 """ 

719 self.set_cookie( 

720 name, 

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

722 expires_days=expires_days, 

723 **kwargs 

724 ) 

725 

726 def create_signed_value( 

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

728 ) -> bytes: 

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

730 

731 Normally used via set_secure_cookie, but provided as a separate 

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

733 as a cookie use the optional value argument to get_secure_cookie. 

734 

735 .. versionchanged:: 3.2.1 

736 

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

738 and made it the default. 

739 """ 

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

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

742 key_version = None 

743 if isinstance(secret, dict): 

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

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

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

747 

748 return create_signed_value( 

749 secret, name, value, version=version, key_version=key_version 

750 ) 

751 

752 def get_secure_cookie( 

753 self, 

754 name: str, 

755 value: Optional[str] = None, 

756 max_age_days: float = 31, 

757 min_version: Optional[int] = None, 

758 ) -> Optional[bytes]: 

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

760 

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

762 `get_cookie`). 

763 

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

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

766 `set_secure_cookie` in this handler. 

767 

768 .. versionchanged:: 3.2.1 

769 

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

771 both versions 1 and 2 are accepted by default. 

772 """ 

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

774 if value is None: 

775 value = self.get_cookie(name) 

776 return decode_signed_value( 

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

778 name, 

779 value, 

780 max_age_days=max_age_days, 

781 min_version=min_version, 

782 ) 

783 

784 def get_secure_cookie_key_version( 

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

786 ) -> Optional[int]: 

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

788 

789 The version is returned as int. 

790 """ 

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

792 if value is None: 

793 value = self.get_cookie(name) 

794 if value is None: 

795 return None 

796 return get_signature_key_version(value) 

797 

798 def redirect( 

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

800 ) -> None: 

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

802 

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

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

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

806 The default is 302 (temporary). 

807 """ 

808 if self._headers_written: 

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

810 if status is None: 

811 status = 301 if permanent else 302 

812 else: 

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

814 self.set_status(status) 

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

816 self.finish() 

817 

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

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

820 

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

822 

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

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

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

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

827 

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

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

830 wrapped in a dictionary. More details at 

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

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

833 """ 

834 if self._finished: 

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

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

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

838 if isinstance(chunk, list): 

839 message += ( 

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

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

842 ) 

843 raise TypeError(message) 

844 if isinstance(chunk, dict): 

845 chunk = escape.json_encode(chunk) 

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

847 chunk = utf8(chunk) 

848 self._write_buffer.append(chunk) 

849 

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

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

852 

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

854 after it. 

855 

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

857 Awaiting this `.Future` is optional. 

858 

859 .. versionchanged:: 5.1 

860 

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

862 """ 

863 if self._finished: 

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

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

866 

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

868 js_embed = [] 

869 js_files = [] 

870 css_embed = [] 

871 css_files = [] 

872 html_heads = [] 

873 html_bodies = [] 

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

875 embed_part = module.embedded_javascript() 

876 if embed_part: 

877 js_embed.append(utf8(embed_part)) 

878 file_part = module.javascript_files() 

879 if file_part: 

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

881 js_files.append(_unicode(file_part)) 

882 else: 

883 js_files.extend(file_part) 

884 embed_part = module.embedded_css() 

885 if embed_part: 

886 css_embed.append(utf8(embed_part)) 

887 file_part = module.css_files() 

888 if file_part: 

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

890 css_files.append(_unicode(file_part)) 

891 else: 

892 css_files.extend(file_part) 

893 head_part = module.html_head() 

894 if head_part: 

895 html_heads.append(utf8(head_part)) 

896 body_part = module.html_body() 

897 if body_part: 

898 html_bodies.append(utf8(body_part)) 

899 

900 if js_files: 

901 # Maintain order of JavaScript files given by modules 

902 js = self.render_linked_js(js_files) 

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

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

905 if js_embed: 

906 js_bytes = self.render_embed_js(js_embed) 

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

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

909 if css_files: 

910 css = self.render_linked_css(css_files) 

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

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

913 if css_embed: 

914 css_bytes = self.render_embed_css(css_embed) 

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

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

917 if html_heads: 

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

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

920 if html_bodies: 

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

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

923 return self.finish(html) 

924 

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

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

927 rendered webpage. 

928 

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

930 """ 

931 paths = [] 

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

933 

934 for path in js_files: 

935 if not is_absolute(path): 

936 path = self.static_url(path) 

937 if path not in unique_paths: 

938 paths.append(path) 

939 unique_paths.add(path) 

940 

941 return "".join( 

942 '<script src="' 

943 + escape.xhtml_escape(p) 

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

945 for p in paths 

946 ) 

947 

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

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

950 rendered webpage. 

951 

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

953 """ 

954 return ( 

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

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

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

958 ) 

959 

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

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

962 rendered webpage. 

963 

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

965 """ 

966 paths = [] 

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

968 

969 for path in css_files: 

970 if not is_absolute(path): 

971 path = self.static_url(path) 

972 if path not in unique_paths: 

973 paths.append(path) 

974 unique_paths.add(path) 

975 

976 return "".join( 

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

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

979 for p in paths 

980 ) 

981 

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

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

984 rendered webpage. 

985 

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

987 """ 

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

989 

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

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

992 

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

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

995 """ 

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

997 template_path = self.get_template_path() 

998 if not template_path: 

999 frame = sys._getframe(0) 

1000 web_file = frame.f_code.co_filename 

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

1002 frame = frame.f_back 

1003 assert frame.f_code.co_filename is not None 

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

1005 with RequestHandler._template_loader_lock: 

1006 if template_path not in RequestHandler._template_loaders: 

1007 loader = self.create_template_loader(template_path) 

1008 RequestHandler._template_loaders[template_path] = loader 

1009 else: 

1010 loader = RequestHandler._template_loaders[template_path] 

1011 t = loader.load(template_name) 

1012 namespace = self.get_template_namespace() 

1013 namespace.update(kwargs) 

1014 return t.generate(**namespace) 

1015 

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

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

1018 

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

1020 

1021 The results of this method will be combined with additional 

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

1023 to `render` or `render_string`. 

1024 """ 

1025 namespace = dict( 

1026 handler=self, 

1027 request=self.request, 

1028 current_user=self.current_user, 

1029 locale=self.locale, 

1030 _=self.locale.translate, 

1031 pgettext=self.locale.pgettext, 

1032 static_url=self.static_url, 

1033 xsrf_form_html=self.xsrf_form_html, 

1034 reverse_url=self.reverse_url, 

1035 ) 

1036 namespace.update(self.ui) 

1037 return namespace 

1038 

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

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

1041 

1042 May be overridden by subclasses. By default returns a 

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

1044 ``autoescape`` and ``template_whitespace`` application 

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

1046 supplied, uses that instead. 

1047 """ 

1048 settings = self.application.settings 

1049 if "template_loader" in settings: 

1050 return settings["template_loader"] 

1051 kwargs = {} 

1052 if "autoescape" in settings: 

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

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

1055 kwargs["autoescape"] = settings["autoescape"] 

1056 if "template_whitespace" in settings: 

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

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

1059 

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

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

1062 

1063 .. versionchanged:: 4.0 

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

1065 

1066 .. versionchanged:: 6.0 

1067 

1068 The ``callback`` argument was removed. 

1069 """ 

1070 assert self.request.connection is not None 

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

1072 self._write_buffer = [] 

1073 if not self._headers_written: 

1074 self._headers_written = True 

1075 for transform in self._transforms: 

1076 assert chunk is not None 

1077 ( 

1078 self._status_code, 

1079 self._headers, 

1080 chunk, 

1081 ) = transform.transform_first_chunk( 

1082 self._status_code, self._headers, chunk, include_footers 

1083 ) 

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

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

1086 chunk = b"" 

1087 

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

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

1090 # is sent). 

1091 if hasattr(self, "_new_cookie"): 

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

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

1094 

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

1096 return self.request.connection.write_headers( 

1097 start_line, self._headers, chunk 

1098 ) 

1099 else: 

1100 for transform in self._transforms: 

1101 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1105 else: 

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

1107 future.set_result(None) 

1108 return future 

1109 

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

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

1112 

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

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

1115 

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

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

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

1119 data can be sent. 

1120 

1121 .. versionchanged:: 5.1 

1122 

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

1124 """ 

1125 if self._finished: 

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

1127 

1128 if chunk is not None: 

1129 self.write(chunk) 

1130 

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

1132 # we have not flushed any content yet. 

1133 if not self._headers_written: 

1134 if ( 

1135 self._status_code == 200 

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

1137 and "Etag" not in self._headers 

1138 ): 

1139 self.set_etag_header() 

1140 if self.check_etag_header(): 

1141 self._write_buffer = [] 

1142 self.set_status(304) 

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

1144 assert not self._write_buffer, ( 

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

1146 ) 

1147 self._clear_representation_headers() 

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

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

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

1151 

1152 assert self.request.connection is not None 

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

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

1155 # garbage collection of the RequestHandler when there 

1156 # are keepalive connections) 

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

1158 

1159 future = self.flush(include_footers=True) 

1160 self.request.connection.finish() 

1161 self._log() 

1162 self._finished = True 

1163 self.on_finish() 

1164 self._break_cycles() 

1165 return future 

1166 

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

1168 """Take control of the underlying stream. 

1169 

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

1171 further HTTP processing. Intended for implementing protocols 

1172 like websockets that tunnel over an HTTP handshake. 

1173 

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

1175 

1176 .. versionadded:: 5.1 

1177 """ 

1178 self._finished = True 

1179 # TODO: add detach to HTTPConnection? 

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

1181 

1182 def _break_cycles(self) -> None: 

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

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

1185 self.ui = None # type: ignore 

1186 

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

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

1189 

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

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

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

1193 and replaced with the error page. 

1194 

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

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

1197 """ 

1198 if self._headers_written: 

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

1200 if not self._finished: 

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

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

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

1204 # socket. 

1205 try: 

1206 self.finish() 

1207 except Exception: 

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

1209 return 

1210 self.clear() 

1211 

1212 reason = kwargs.get("reason") 

1213 if "exc_info" in kwargs: 

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

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

1216 reason = exception.reason 

1217 self.set_status(status_code, reason=reason) 

1218 try: 

1219 self.write_error(status_code, **kwargs) 

1220 except Exception: 

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

1222 if not self._finished: 

1223 self.finish() 

1224 

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

1226 """Override to implement custom error pages. 

1227 

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

1229 to produce output as usual. 

1230 

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

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

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

1234 the "current" exception for purposes of methods like 

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

1236 """ 

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

1238 # in debug mode, try to send a traceback 

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

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

1241 self.write(line) 

1242 self.finish() 

1243 else: 

1244 self.finish( 

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

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

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

1248 ) 

1249 

1250 @property 

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

1252 """The locale for the current session. 

1253 

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

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

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

1257 header. 

1258 

1259 .. versionchanged: 4.1 

1260 Added a property setter. 

1261 """ 

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

1263 loc = self.get_user_locale() 

1264 if loc is not None: 

1265 self._locale = loc 

1266 else: 

1267 self._locale = self.get_browser_locale() 

1268 assert self._locale 

1269 return self._locale 

1270 

1271 @locale.setter 

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

1273 self._locale = value 

1274 

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

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

1277 

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

1279 

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

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

1282 """ 

1283 return None 

1284 

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

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

1287 

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

1289 """ 

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

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

1292 locales = [] 

1293 for language in languages: 

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

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

1296 try: 

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

1298 if score < 0: 

1299 raise ValueError() 

1300 except (ValueError, TypeError): 

1301 score = 0.0 

1302 else: 

1303 score = 1.0 

1304 if score > 0: 

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

1306 if locales: 

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

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

1309 return locale.get(*codes) 

1310 return locale.get(default) 

1311 

1312 @property 

1313 def current_user(self) -> Any: 

1314 """The authenticated user for this request. 

1315 

1316 This is set in one of two ways: 

1317 

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

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

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

1321 and is cached for future access:: 

1322 

1323 def get_current_user(self): 

1324 user_cookie = self.get_secure_cookie("user") 

1325 if user_cookie: 

1326 return json.loads(user_cookie) 

1327 return None 

1328 

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

1330 `prepare()`:: 

1331 

1332 @gen.coroutine 

1333 def prepare(self): 

1334 user_id_cookie = self.get_secure_cookie("user_id") 

1335 if user_id_cookie: 

1336 self.current_user = yield load_user(user_id_cookie) 

1337 

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

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

1340 asynchronous operations. 

1341 

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

1343 """ 

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

1345 self._current_user = self.get_current_user() 

1346 return self._current_user 

1347 

1348 @current_user.setter 

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

1350 self._current_user = value 

1351 

1352 def get_current_user(self) -> Any: 

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

1354 

1355 This method may not be a coroutine. 

1356 """ 

1357 return None 

1358 

1359 def get_login_url(self) -> str: 

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

1361 

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

1363 """ 

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

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

1366 

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

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

1369 

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

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

1372 """ 

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

1374 

1375 @property 

1376 def xsrf_token(self) -> bytes: 

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

1378 

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

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

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

1382 as a potential forgery. 

1383 

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

1385 

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

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

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

1389 UTF-8. 

1390 

1391 .. versionchanged:: 3.2.2 

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

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

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

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

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

1397 unless the ``xsrf_cookie_version`` `Application` setting is 

1398 set to 1. 

1399 

1400 .. versionchanged:: 4.3 

1401 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1402 used to supply additional cookie options (which will be 

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

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

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

1406 ``_xsrf`` cookie. 

1407 """ 

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

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

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

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

1412 if output_version == 1: 

1413 self._xsrf_token = binascii.b2a_hex(token) 

1414 elif output_version == 2: 

1415 mask = os.urandom(4) 

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

1417 [ 

1418 b"2", 

1419 binascii.b2a_hex(mask), 

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

1421 utf8(str(int(timestamp))), 

1422 ] 

1423 ) 

1424 else: 

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

1426 if version is None: 

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

1428 cookie_kwargs["expires_days"] = 30 

1429 self.set_cookie("_xsrf", self._xsrf_token, **cookie_kwargs) 

1430 return self._xsrf_token 

1431 

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

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

1434 

1435 The raw_xsrf_token is a tuple containing: 

1436 

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

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

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

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

1441 for version 1 cookies) 

1442 """ 

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

1444 cookie = self.get_cookie("_xsrf") 

1445 if cookie: 

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

1447 else: 

1448 version, token, timestamp = None, None, None 

1449 if token is None: 

1450 version = None 

1451 token = os.urandom(16) 

1452 timestamp = time.time() 

1453 assert token is not None 

1454 assert timestamp is not None 

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

1456 return self._raw_xsrf_token 

1457 

1458 def _decode_xsrf_token( 

1459 self, cookie: str 

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

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

1462 _get_raw_xsrf_token. 

1463 """ 

1464 

1465 try: 

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

1467 

1468 if m: 

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

1470 if version == 2: 

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

1472 

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

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

1475 timestamp = int(timestamp_str) 

1476 return version, token, timestamp 

1477 else: 

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

1479 raise Exception("Unknown xsrf cookie version") 

1480 else: 

1481 version = 1 

1482 try: 

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

1484 except (binascii.Error, TypeError): 

1485 token = utf8(cookie) 

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

1487 timestamp = int(time.time()) 

1488 return (version, token, timestamp) 

1489 except Exception: 

1490 # Catch exceptions and return nothing instead of failing. 

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

1492 return None, None, None 

1493 

1494 def check_xsrf_cookie(self) -> None: 

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

1496 

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

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

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

1500 reject the form submission as a potential forgery. 

1501 

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

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

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

1505 

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

1507 

1508 .. versionchanged:: 3.2.2 

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

1510 supported. 

1511 """ 

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

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

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

1515 # information please see 

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

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

1518 token = ( 

1519 self.get_argument("_xsrf", None) 

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

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

1522 ) 

1523 if not token: 

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

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

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

1527 if not token: 

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

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

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

1531 

1532 def xsrf_form_html(self) -> str: 

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

1534 

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

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

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

1538 HTML within all of your HTML forms. 

1539 

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

1541 xsrf_form_html() %}`` 

1542 

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

1544 """ 

1545 return ( 

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

1547 + escape.xhtml_escape(self.xsrf_token) 

1548 + '"/>' 

1549 ) 

1550 

1551 def static_url( 

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

1553 ) -> str: 

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

1555 

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

1557 application (which specifies the root directory of your static 

1558 files). 

1559 

1560 This method returns a versioned url (by default appending 

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

1562 cached indefinitely. This can be disabled by passing 

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

1564 other static file implementations are not required to support 

1565 this, but they may support other options). 

1566 

1567 By default this method returns URLs relative to the current 

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

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

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

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

1572 

1573 """ 

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

1575 get_url = self.settings.get( 

1576 "static_handler_class", StaticFileHandler 

1577 ).make_static_url 

1578 

1579 if include_host is None: 

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

1581 

1582 if include_host: 

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

1584 else: 

1585 base = "" 

1586 

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

1588 

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

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

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

1592 raise Exception( 

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

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

1595 ) 

1596 

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

1598 """Alias for `Application.reverse_url`.""" 

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

1600 

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

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

1603 

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

1605 

1606 May be overridden to provide custom etag implementations, 

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

1608 """ 

1609 hasher = hashlib.sha1() 

1610 for part in self._write_buffer: 

1611 hasher.update(part) 

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

1613 

1614 def set_etag_header(self) -> None: 

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

1616 

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

1618 

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

1620 """ 

1621 etag = self.compute_etag() 

1622 if etag is not None: 

1623 self.set_header("Etag", etag) 

1624 

1625 def check_etag_header(self) -> bool: 

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

1627 

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

1629 returned. For example:: 

1630 

1631 self.set_etag_header() 

1632 if self.check_etag_header(): 

1633 self.set_status(304) 

1634 return 

1635 

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

1637 but may be called earlier for applications that override 

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

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

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

1641 """ 

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

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

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

1645 etags = re.findall( 

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

1647 ) 

1648 if not computed_etag or not etags: 

1649 return False 

1650 

1651 match = False 

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

1653 match = True 

1654 else: 

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

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

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

1658 

1659 for etag in etags: 

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

1661 match = True 

1662 break 

1663 return match 

1664 

1665 async def _execute( 

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

1667 ) -> None: 

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

1669 self._transforms = transforms 

1670 try: 

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

1672 raise HTTPError(405) 

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

1674 self.path_kwargs = dict( 

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

1676 ) 

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

1678 # the proper cookie 

1679 if ( 

1680 self.request.method 

1681 not in ( 

1682 "GET", 

1683 "HEAD", 

1684 "OPTIONS", 

1685 ) 

1686 and self.application.settings.get("xsrf_cookies") 

1687 ): 

1688 self.check_xsrf_cookie() 

1689 

1690 result = self.prepare() 

1691 if result is not None: 

1692 result = await result 

1693 if self._prepared_future is not None: 

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

1695 # and are ready for the body to arrive. 

1696 future_set_result_unless_cancelled(self._prepared_future, None) 

1697 if self._finished: 

1698 return 

1699 

1700 if _has_stream_request_body(self.__class__): 

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

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

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

1704 # instead. 

1705 try: 

1706 await self.request._body_future 

1707 except iostream.StreamClosedError: 

1708 return 

1709 

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

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

1712 if result is not None: 

1713 result = await result 

1714 if self._auto_finish and not self._finished: 

1715 self.finish() 

1716 except Exception as e: 

1717 try: 

1718 self._handle_request_exception(e) 

1719 except Exception: 

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

1721 finally: 

1722 # Unset result to avoid circular references 

1723 result = None 

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

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

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

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

1728 self._prepared_future.set_result(None) 

1729 

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

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

1732 

1733 Requires the `.stream_request_body` decorator. 

1734 

1735 May be a coroutine for flow control. 

1736 """ 

1737 raise NotImplementedError() 

1738 

1739 def _log(self) -> None: 

1740 """Logs the current request. 

1741 

1742 Sort of deprecated since this functionality was moved to the 

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

1744 that have overridden this method. 

1745 """ 

1746 self.application.log_request(self) 

1747 

1748 def _request_summary(self) -> str: 

1749 return "%s %s (%s)" % ( 

1750 self.request.method, 

1751 self.request.uri, 

1752 self.request.remote_ip, 

1753 ) 

1754 

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

1756 if isinstance(e, Finish): 

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

1758 if not self._finished: 

1759 self.finish(*e.args) 

1760 return 

1761 try: 

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

1763 except Exception: 

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

1765 # to avoid leaking the connection. 

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

1767 if self._finished: 

1768 # Extra errors after the request has been finished should 

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

1770 # send a response. 

1771 return 

1772 if isinstance(e, HTTPError): 

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

1774 else: 

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

1776 

1777 def log_exception( 

1778 self, 

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

1780 value: Optional[BaseException], 

1781 tb: Optional[TracebackType], 

1782 ) -> None: 

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

1784 

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

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

1787 other exceptions as errors with stack traces (on the 

1788 ``tornado.application`` logger). 

1789 

1790 .. versionadded:: 3.1 

1791 """ 

1792 if isinstance(value, HTTPError): 

1793 if value.log_message: 

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

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

1796 gen_log.warning(format, *args) 

1797 else: 

1798 app_log.error( 

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

1800 self._request_summary(), 

1801 self.request, 

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

1803 ) 

1804 

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

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

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

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

1809 if name not in self._active_modules: 

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

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

1812 return rendered 

1813 

1814 return render 

1815 

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

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

1818 

1819 def _clear_representation_headers(self) -> None: 

1820 # 304 responses should not contain representation metadata 

1821 # headers (defined in 

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

1823 # not explicitly allowed by 

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

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

1826 for h in headers: 

1827 self.clear_header(h) 

1828 

1829 

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

1831 

1832 

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

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

1835 

1836 This decorator implies the following changes: 

1837 

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

1839 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

1847 until those futures have completed. 

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

1849 the entire body has been read. 

1850 

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

1852 for example usage. 

1853 """ # noqa: E501 

1854 if not issubclass(cls, RequestHandler): 

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

1856 cls._stream_request_body = True 

1857 return cls 

1858 

1859 

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

1861 if not issubclass(cls, RequestHandler): 

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

1863 return cls._stream_request_body 

1864 

1865 

1866def removeslash( 

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

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

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

1870 

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

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

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

1874 """ 

1875 

1876 @functools.wraps(method) 

1877 def wrapper( # type: ignore 

1878 self: RequestHandler, *args, **kwargs 

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

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

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

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

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

1884 if self.request.query: 

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

1886 self.redirect(uri, permanent=True) 

1887 return None 

1888 else: 

1889 raise HTTPError(404) 

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

1891 

1892 return wrapper 

1893 

1894 

1895def addslash( 

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

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

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

1899 

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

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

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

1903 """ 

1904 

1905 @functools.wraps(method) 

1906 def wrapper( # type: ignore 

1907 self: RequestHandler, *args, **kwargs 

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

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

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

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

1912 if self.request.query: 

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

1914 self.redirect(uri, permanent=True) 

1915 return None 

1916 raise HTTPError(404) 

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

1918 

1919 return wrapper 

1920 

1921 

1922class _ApplicationRouter(ReversibleRuleRouter): 

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

1924 

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

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

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

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

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

1930 `_ApplicationRouter` instance. 

1931 """ 

1932 

1933 def __init__( 

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

1935 ) -> None: 

1936 assert isinstance(application, Application) 

1937 self.application = application 

1938 super().__init__(rules) 

1939 

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

1941 rule = super().process_rule(rule) 

1942 

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

1944 rule.target = _ApplicationRouter( 

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

1946 ) 

1947 

1948 return rule 

1949 

1950 def get_target_delegate( 

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

1952 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

1954 return self.application.get_handler_delegate( 

1955 request, target, **target_params 

1956 ) 

1957 

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

1959 

1960 

1961class Application(ReversibleRouter): 

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

1963 

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

1965 HTTPServer to serve the application:: 

1966 

1967 application = web.Application([ 

1968 (r"/", MainPageHandler), 

1969 ]) 

1970 http_server = httpserver.HTTPServer(application) 

1971 http_server.listen(8080) 

1972 

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

1974 objects or tuples of values corresponding to the arguments of 

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

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

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

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

1979 

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

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

1982 

1983 application = web.Application([ 

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

1985 (r"/", MainPageHandler), 

1986 (r"/feed", FeedHandler), 

1987 ]), 

1988 ]) 

1989 

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

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

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

1993 

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

1995 instantiate an instance of the first request class whose regexp 

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

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

1998 

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

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

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

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

2003 `StaticFileHandler` can be installed automatically with the 

2004 static_path setting described below):: 

2005 

2006 application = web.Application([ 

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

2008 ]) 

2009 

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

2011 a host regular expression as the first argument:: 

2012 

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

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

2015 ]) 

2016 

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

2018 parameter value is matched against host regular expressions. 

2019 

2020 

2021 .. warning:: 

2022 

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

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

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

2026 other private networks. Appropriate host patterns must be used 

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

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

2029 may be vulnerable to DNS rebinding. 

2030 

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

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

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

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

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

2036 `StaticFileHandler` can be specified with the 

2037 ``static_handler_class`` setting. 

2038 

2039 .. versionchanged:: 4.5 

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

2041 

2042 """ 

2043 

2044 def __init__( 

2045 self, 

2046 handlers: Optional[_RuleList] = None, 

2047 default_host: Optional[str] = None, 

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

2049 **settings: Any 

2050 ) -> None: 

2051 if transforms is None: 

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

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

2054 self.transforms.append(GZipContentEncoding) 

2055 else: 

2056 self.transforms = transforms 

2057 self.default_host = default_host 

2058 self.settings = settings 

2059 self.ui_modules = { 

2060 "linkify": _linkify, 

2061 "xsrf_form_html": _xsrf_form_html, 

2062 "Template": TemplateModule, 

2063 } 

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

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

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

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

2068 path = self.settings["static_path"] 

2069 handlers = list(handlers or []) 

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

2071 static_handler_class = settings.get( 

2072 "static_handler_class", StaticFileHandler 

2073 ) 

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

2075 static_handler_args["path"] = path 

2076 for pattern in [ 

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

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

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

2080 ]: 

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

2082 

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

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

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

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

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

2088 

2089 self.wildcard_router = _ApplicationRouter(self, handlers) 

2090 self.default_router = _ApplicationRouter( 

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

2092 ) 

2093 

2094 # Automatically reload modified modules 

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

2096 from tornado import autoreload 

2097 

2098 autoreload.start() 

2099 

2100 def listen( 

2101 self, 

2102 port: int, 

2103 address: Optional[str] = None, 

2104 *, 

2105 family: socket.AddressFamily = socket.AF_UNSPEC, 

2106 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2107 flags: Optional[int] = None, 

2108 reuse_port: bool = False, 

2109 **kwargs: Any 

2110 ) -> HTTPServer: 

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

2112 

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

2114 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2119 

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

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

2122 the server. 

2123 

2124 Returns the `.HTTPServer` object. 

2125 

2126 .. versionchanged:: 4.3 

2127 Now returns the `.HTTPServer` object. 

2128 

2129 .. versionchanged:: 6.2 

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

2131 including ``reuse_port``. 

2132 """ 

2133 server = HTTPServer(self, **kwargs) 

2134 server.listen( 

2135 port, 

2136 address=address, 

2137 family=family, 

2138 backlog=backlog, 

2139 flags=flags, 

2140 reuse_port=reuse_port, 

2141 ) 

2142 return server 

2143 

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

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

2146 

2147 Host patterns are processed sequentially in the order they were 

2148 added. All matching patterns will be considered. 

2149 """ 

2150 host_matcher = HostMatches(host_pattern) 

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

2152 

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

2154 

2155 if self.default_host is not None: 

2156 self.wildcard_router.add_rules( 

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

2158 ) 

2159 

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

2161 self.transforms.append(transform_class) 

2162 

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

2164 if isinstance(methods, types.ModuleType): 

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

2166 elif isinstance(methods, list): 

2167 for m in methods: 

2168 self._load_ui_methods(m) 

2169 else: 

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

2171 if ( 

2172 not name.startswith("_") 

2173 and hasattr(fn, "__call__") 

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

2175 ): 

2176 self.ui_methods[name] = fn 

2177 

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

2179 if isinstance(modules, types.ModuleType): 

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

2181 elif isinstance(modules, list): 

2182 for m in modules: 

2183 self._load_ui_modules(m) 

2184 else: 

2185 assert isinstance(modules, dict) 

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

2187 try: 

2188 if issubclass(cls, UIModule): 

2189 self.ui_modules[name] = cls 

2190 except TypeError: 

2191 pass 

2192 

2193 def __call__( 

2194 self, request: httputil.HTTPServerRequest 

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

2196 # Legacy HTTPServer interface 

2197 dispatcher = self.find_handler(request) 

2198 return dispatcher.execute() 

2199 

2200 def find_handler( 

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

2202 ) -> "_HandlerDelegate": 

2203 route = self.default_router.find_handler(request) 

2204 if route is not None: 

2205 return cast("_HandlerDelegate", route) 

2206 

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

2208 return self.get_handler_delegate( 

2209 request, 

2210 self.settings["default_handler_class"], 

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

2212 ) 

2213 

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

2215 

2216 def get_handler_delegate( 

2217 self, 

2218 request: httputil.HTTPServerRequest, 

2219 target_class: Type[RequestHandler], 

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

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

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

2223 ) -> "_HandlerDelegate": 

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

2225 for application and `RequestHandler` subclass. 

2226 

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

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

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

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

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

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

2233 """ 

2234 return _HandlerDelegate( 

2235 self, request, target_class, target_kwargs, path_args, path_kwargs 

2236 ) 

2237 

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

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

2240 

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

2242 

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

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

2245 and url-escaped. 

2246 """ 

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

2248 if reversed_url is not None: 

2249 return reversed_url 

2250 

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

2252 

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

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

2255 

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

2257 this behavior either subclass Application and override this method, 

2258 or pass a function in the application settings dictionary as 

2259 ``log_function``. 

2260 """ 

2261 if "log_function" in self.settings: 

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

2263 return 

2264 if handler.get_status() < 400: 

2265 log_method = access_log.info 

2266 elif handler.get_status() < 500: 

2267 log_method = access_log.warning 

2268 else: 

2269 log_method = access_log.error 

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

2271 log_method( 

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

2273 handler.get_status(), 

2274 handler._request_summary(), 

2275 request_time, 

2276 ) 

2277 

2278 

2279class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2280 def __init__( 

2281 self, 

2282 application: Application, 

2283 request: httputil.HTTPServerRequest, 

2284 handler_class: Type[RequestHandler], 

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

2286 path_args: Optional[List[bytes]], 

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

2288 ) -> None: 

2289 self.application = application 

2290 self.connection = request.connection 

2291 self.request = request 

2292 self.handler_class = handler_class 

2293 self.handler_kwargs = handler_kwargs or {} 

2294 self.path_args = path_args or [] 

2295 self.path_kwargs = path_kwargs or {} 

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

2297 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2298 

2299 def headers_received( 

2300 self, 

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

2302 headers: httputil.HTTPHeaders, 

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

2304 if self.stream_request_body: 

2305 self.request._body_future = Future() 

2306 return self.execute() 

2307 return None 

2308 

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

2310 if self.stream_request_body: 

2311 return self.handler.data_received(data) 

2312 else: 

2313 self.chunks.append(data) 

2314 return None 

2315 

2316 def finish(self) -> None: 

2317 if self.stream_request_body: 

2318 future_set_result_unless_cancelled(self.request._body_future, None) 

2319 else: 

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

2321 self.request._parse_body() 

2322 self.execute() 

2323 

2324 def on_connection_close(self) -> None: 

2325 if self.stream_request_body: 

2326 self.handler.on_connection_close() 

2327 else: 

2328 self.chunks = None # type: ignore 

2329 

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

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

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

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

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

2335 with RequestHandler._template_loader_lock: 

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

2337 loader.reset() 

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

2339 static_handler_class = self.application.settings.get( 

2340 "static_handler_class", StaticFileHandler 

2341 ) 

2342 static_handler_class.reset() 

2343 

2344 self.handler = self.handler_class( 

2345 self.application, self.request, **self.handler_kwargs 

2346 ) 

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

2348 

2349 if self.stream_request_body: 

2350 self.handler._prepared_future = Future() 

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

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

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

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

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

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

2357 # with WSGI) 

2358 fut = gen.convert_yielded( 

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

2360 ) 

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

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

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

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

2365 return self.handler._prepared_future 

2366 

2367 

2368class HTTPError(Exception): 

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

2370 

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

2372 `RequestHandler.send_error` since it automatically ends the 

2373 current function. 

2374 

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

2376 `RequestHandler.write_error`. 

2377 

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

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

2380 keyword argument is given. 

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

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

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

2384 in with remaining positional parameters. 

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

2386 to pass in the status line along with ``status_code``. Normally 

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

2388 to use a non-standard numeric code. 

2389 """ 

2390 

2391 def __init__( 

2392 self, 

2393 status_code: int = 500, 

2394 log_message: Optional[str] = None, 

2395 *args: Any, 

2396 **kwargs: Any 

2397 ) -> None: 

2398 self.status_code = status_code 

2399 self.log_message = log_message 

2400 self.args = args 

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

2402 if log_message and not args: 

2403 self.log_message = log_message.replace("%", "%%") 

2404 

2405 def __str__(self) -> str: 

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

2407 self.status_code, 

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

2409 ) 

2410 if self.log_message: 

2411 return message + " (" + (self.log_message % self.args) + ")" 

2412 else: 

2413 return message 

2414 

2415 

2416class Finish(Exception): 

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

2418 

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

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

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

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

2423 

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

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

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

2427 

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

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

2430 

2431 if self.current_user is None: 

2432 self.set_status(401) 

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

2434 raise Finish() 

2435 

2436 .. versionchanged:: 4.3 

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

2438 `RequestHandler.finish`. 

2439 """ 

2440 

2441 pass 

2442 

2443 

2444class MissingArgumentError(HTTPError): 

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

2446 

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

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

2449 

2450 .. versionadded:: 3.1 

2451 """ 

2452 

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

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

2455 self.arg_name = arg_name 

2456 

2457 

2458class ErrorHandler(RequestHandler): 

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

2460 

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

2462 self.set_status(status_code) 

2463 

2464 def prepare(self) -> None: 

2465 raise HTTPError(self._status_code) 

2466 

2467 def check_xsrf_cookie(self) -> None: 

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

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

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

2471 pass 

2472 

2473 

2474class RedirectHandler(RequestHandler): 

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

2476 

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

2478 

2479 application = web.Application([ 

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

2481 ]) 

2482 

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

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

2485 

2486 application = web.Application([ 

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

2488 ]) 

2489 

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

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

2492 formatted like:: 

2493 

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

2495 

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

2497 values are substituted. 

2498 

2499 .. versionchanged:: 4.5 

2500 Added support for substitutions into the destination URL. 

2501 

2502 .. versionchanged:: 5.0 

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

2504 destination URL. 

2505 """ 

2506 

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

2508 self._url = url 

2509 self._permanent = permanent 

2510 

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

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

2513 if self.request.query_arguments: 

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

2515 to_url = httputil.url_concat( 

2516 to_url, 

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

2518 ) 

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

2520 

2521 

2522class StaticFileHandler(RequestHandler): 

2523 """A simple handler that can serve static content from a directory. 

2524 

2525 A `StaticFileHandler` is configured automatically if you pass the 

2526 ``static_path`` keyword argument to `Application`. This handler 

2527 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2528 and ``static_handler_args`` settings. 

2529 

2530 To map an additional path to this handler for a static data directory 

2531 you would add a line to your application like:: 

2532 

2533 application = web.Application([ 

2534 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2535 ]) 

2536 

2537 The handler constructor requires a ``path`` argument, which specifies the 

2538 local root directory of the content to be served. 

2539 

2540 Note that a capture group in the regex is required to parse the value for 

2541 the ``path`` argument to the get() method (different than the constructor 

2542 argument above); see `URLSpec` for details. 

2543 

2544 To serve a file like ``index.html`` automatically when a directory is 

2545 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2546 in your application settings, or add ``default_filename`` as an initializer 

2547 argument for your ``StaticFileHandler``. 

2548 

2549 To maximize the effectiveness of browser caching, this class supports 

2550 versioned urls (by default using the argument ``?v=``). If a version 

2551 is given, we instruct the browser to cache this file indefinitely. 

2552 `make_static_url` (also available as `RequestHandler.static_url`) can 

2553 be used to construct a versioned url. 

2554 

2555 This handler is intended primarily for use in development and light-duty 

2556 file serving; for heavy traffic it will be more efficient to use 

2557 a dedicated static file server (such as nginx or Apache). We support 

2558 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2559 some browsers require this functionality to be present to seek in 

2560 HTML5 audio or video). 

2561 

2562 **Subclassing notes** 

2563 

2564 This class is designed to be extensible by subclassing, but because 

2565 of the way static urls are generated with class methods rather than 

2566 instance methods, the inheritance patterns are somewhat unusual. 

2567 Be sure to use the ``@classmethod`` decorator when overriding a 

2568 class method. Instance methods may use the attributes ``self.path`` 

2569 ``self.absolute_path``, and ``self.modified``. 

2570 

2571 Subclasses should only override methods discussed in this section; 

2572 overriding other methods is error-prone. Overriding 

2573 ``StaticFileHandler.get`` is particularly problematic due to the 

2574 tight coupling with ``compute_etag`` and other methods. 

2575 

2576 To change the way static urls are generated (e.g. to match the behavior 

2577 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2578 `get_cache_time`, and/or `get_version`. 

2579 

2580 To replace all interaction with the filesystem (e.g. to serve 

2581 static content from a database), override `get_content`, 

2582 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2583 `validate_absolute_path`. 

2584 

2585 .. versionchanged:: 3.1 

2586 Many of the methods for subclasses were added in Tornado 3.1. 

2587 """ 

2588 

2589 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2590 

2591 _static_hashes = {} # type: Dict[str, Optional[str]] 

2592 _lock = threading.Lock() # protects _static_hashes 

2593 

2594 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 

2595 self.root = path 

2596 self.default_filename = default_filename 

2597 

2598 @classmethod 

2599 def reset(cls) -> None: 

2600 with cls._lock: 

2601 cls._static_hashes = {} 

2602 

2603 def head(self, path: str) -> Awaitable[None]: 

2604 return self.get(path, include_body=False) 

2605 

2606 async def get(self, path: str, include_body: bool = True) -> None: 

2607 # Set up our path instance variables. 

2608 self.path = self.parse_url_path(path) 

2609 del path # make sure we don't refer to path instead of self.path again 

2610 absolute_path = self.get_absolute_path(self.root, self.path) 

2611 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2612 if self.absolute_path is None: 

2613 return 

2614 

2615 self.modified = self.get_modified_time() 

2616 self.set_headers() 

2617 

2618 if self.should_return_304(): 

2619 self.set_status(304) 

2620 return 

2621 

2622 request_range = None 

2623 range_header = self.request.headers.get("Range") 

2624 if range_header: 

2625 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2626 # the request will be treated as if the header didn't exist. 

2627 request_range = httputil._parse_request_range(range_header) 

2628 

2629 size = self.get_content_size() 

2630 if request_range: 

2631 start, end = request_range 

2632 if start is not None and start < 0: 

2633 start += size 

2634 if start < 0: 

2635 start = 0 

2636 if ( 

2637 start is not None 

2638 and (start >= size or (end is not None and start >= end)) 

2639 ) or end == 0: 

2640 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2641 # the first requested byte is equal to or greater than the 

2642 # content, or when a suffix with length 0 is specified. 

2643 # https://tools.ietf.org/html/rfc7233#section-2.1 

2644 # A byte-range-spec is invalid if the last-byte-pos value is present 

2645 # and less than the first-byte-pos. 

2646 self.set_status(416) # Range Not Satisfiable 

2647 self.set_header("Content-Type", "text/plain") 

2648 self.set_header("Content-Range", "bytes */%s" % (size,)) 

2649 return 

2650 if end is not None and end > size: 

2651 # Clients sometimes blindly use a large range to limit their 

2652 # download size; cap the endpoint at the actual file size. 

2653 end = size 

2654 # Note: only return HTTP 206 if less than the entire range has been 

2655 # requested. Not only is this semantically correct, but Chrome 

2656 # refuses to play audio if it gets an HTTP 206 in response to 

2657 # ``Range: bytes=0-``. 

2658 if size != (end or size) - (start or 0): 

2659 self.set_status(206) # Partial Content 

2660 self.set_header( 

2661 "Content-Range", httputil._get_content_range(start, end, size) 

2662 ) 

2663 else: 

2664 start = end = None 

2665 

2666 if start is not None and end is not None: 

2667 content_length = end - start 

2668 elif end is not None: 

2669 content_length = end 

2670 elif start is not None: 

2671 content_length = size - start 

2672 else: 

2673 content_length = size 

2674 self.set_header("Content-Length", content_length) 

2675 

2676 if include_body: 

2677 content = self.get_content(self.absolute_path, start, end) 

2678 if isinstance(content, bytes): 

2679 content = [content] 

2680 for chunk in content: 

2681 try: 

2682 self.write(chunk) 

2683 await self.flush() 

2684 except iostream.StreamClosedError: 

2685 return 

2686 else: 

2687 assert self.request.method == "HEAD" 

2688 

2689 def compute_etag(self) -> Optional[str]: 

2690 """Sets the ``Etag`` header based on static url version. 

2691 

2692 This allows efficient ``If-None-Match`` checks against cached 

2693 versions, and sends the correct ``Etag`` for a partial response 

2694 (i.e. the same ``Etag`` as the full file). 

2695 

2696 .. versionadded:: 3.1 

2697 """ 

2698 assert self.absolute_path is not None 

2699 version_hash = self._get_cached_version(self.absolute_path) 

2700 if not version_hash: 

2701 return None 

2702 return '"%s"' % (version_hash,) 

2703 

2704 def set_headers(self) -> None: 

2705 """Sets the content and caching headers on the response. 

2706 

2707 .. versionadded:: 3.1 

2708 """ 

2709 self.set_header("Accept-Ranges", "bytes") 

2710 self.set_etag_header() 

2711 

2712 if self.modified is not None: 

2713 self.set_header("Last-Modified", self.modified) 

2714 

2715 content_type = self.get_content_type() 

2716 if content_type: 

2717 self.set_header("Content-Type", content_type) 

2718 

2719 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2720 if cache_time > 0: 

2721 self.set_header( 

2722 "Expires", 

2723 datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time), 

2724 ) 

2725 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2726 

2727 self.set_extra_headers(self.path) 

2728 

2729 def should_return_304(self) -> bool: 

2730 """Returns True if the headers indicate that we should return 304. 

2731 

2732 .. versionadded:: 3.1 

2733 """ 

2734 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2735 if self.request.headers.get("If-None-Match"): 

2736 return self.check_etag_header() 

2737 

2738 # Check the If-Modified-Since, and don't send the result if the 

2739 # content has not been modified 

2740 ims_value = self.request.headers.get("If-Modified-Since") 

2741 if ims_value is not None: 

2742 date_tuple = email.utils.parsedate(ims_value) 

2743 if date_tuple is not None: 

2744 if_since = datetime.datetime(*date_tuple[:6]) 

2745 assert self.modified is not None 

2746 if if_since >= self.modified: 

2747 return True 

2748 

2749 return False 

2750 

2751 @classmethod 

2752 def get_absolute_path(cls, root: str, path: str) -> str: 

2753 """Returns the absolute location of ``path`` relative to ``root``. 

2754 

2755 ``root`` is the path configured for this `StaticFileHandler` 

2756 (in most cases the ``static_path`` `Application` setting). 

2757 

2758 This class method may be overridden in subclasses. By default 

2759 it returns a filesystem path, but other strings may be used 

2760 as long as they are unique and understood by the subclass's 

2761 overridden `get_content`. 

2762 

2763 .. versionadded:: 3.1 

2764 """ 

2765 abspath = os.path.abspath(os.path.join(root, path)) 

2766 return abspath 

2767 

2768 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2769 """Validate and return the absolute path. 

2770 

2771 ``root`` is the configured path for the `StaticFileHandler`, 

2772 and ``path`` is the result of `get_absolute_path` 

2773 

2774 This is an instance method called during request processing, 

2775 so it may raise `HTTPError` or use methods like 

2776 `RequestHandler.redirect` (return None after redirecting to 

2777 halt further processing). This is where 404 errors for missing files 

2778 are generated. 

2779 

2780 This method may modify the path before returning it, but note that 

2781 any such modifications will not be understood by `make_static_url`. 

2782 

2783 In instance methods, this method's result is available as 

2784 ``self.absolute_path``. 

2785 

2786 .. versionadded:: 3.1 

2787 """ 

2788 # os.path.abspath strips a trailing /. 

2789 # We must add it back to `root` so that we only match files 

2790 # in a directory named `root` instead of files starting with 

2791 # that prefix. 

2792 root = os.path.abspath(root) 

2793 if not root.endswith(os.path.sep): 

2794 # abspath always removes a trailing slash, except when 

2795 # root is '/'. This is an unusual case, but several projects 

2796 # have independently discovered this technique to disable 

2797 # Tornado's path validation and (hopefully) do their own, 

2798 # so we need to support it. 

2799 root += os.path.sep 

2800 # The trailing slash also needs to be temporarily added back 

2801 # the requested path so a request to root/ will match. 

2802 if not (absolute_path + os.path.sep).startswith(root): 

2803 raise HTTPError(403, "%s is not in root static directory", self.path) 

2804 if os.path.isdir(absolute_path) and self.default_filename is not None: 

2805 # need to look at the request.path here for when path is empty 

2806 # but there is some prefix to the path that was already 

2807 # trimmed by the routing 

2808 if not self.request.path.endswith("/"): 

2809 self.redirect(self.request.path + "/", permanent=True) 

2810 return None 

2811 absolute_path = os.path.join(absolute_path, self.default_filename) 

2812 if not os.path.exists(absolute_path): 

2813 raise HTTPError(404) 

2814 if not os.path.isfile(absolute_path): 

2815 raise HTTPError(403, "%s is not a file", self.path) 

2816 return absolute_path 

2817 

2818 @classmethod 

2819 def get_content( 

2820 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

2821 ) -> Generator[bytes, None, None]: 

2822 """Retrieve the content of the requested resource which is located 

2823 at the given absolute path. 

2824 

2825 This class method may be overridden by subclasses. Note that its 

2826 signature is different from other overridable class methods 

2827 (no ``settings`` argument); this is deliberate to ensure that 

2828 ``abspath`` is able to stand on its own as a cache key. 

2829 

2830 This method should either return a byte string or an iterator 

2831 of byte strings. The latter is preferred for large files 

2832 as it helps reduce memory fragmentation. 

2833 

2834 .. versionadded:: 3.1 

2835 """ 

2836 with open(abspath, "rb") as file: 

2837 if start is not None: 

2838 file.seek(start) 

2839 if end is not None: 

2840 remaining = end - (start or 0) # type: Optional[int] 

2841 else: 

2842 remaining = None 

2843 while True: 

2844 chunk_size = 64 * 1024 

2845 if remaining is not None and remaining < chunk_size: 

2846 chunk_size = remaining 

2847 chunk = file.read(chunk_size) 

2848 if chunk: 

2849 if remaining is not None: 

2850 remaining -= len(chunk) 

2851 yield chunk 

2852 else: 

2853 if remaining is not None: 

2854 assert remaining == 0 

2855 return 

2856 

2857 @classmethod 

2858 def get_content_version(cls, abspath: str) -> str: 

2859 """Returns a version string for the resource at the given path. 

2860 

2861 This class method may be overridden by subclasses. The 

2862 default implementation is a SHA-512 hash of the file's contents. 

2863 

2864 .. versionadded:: 3.1 

2865 """ 

2866 data = cls.get_content(abspath) 

2867 hasher = hashlib.sha512() 

2868 if isinstance(data, bytes): 

2869 hasher.update(data) 

2870 else: 

2871 for chunk in data: 

2872 hasher.update(chunk) 

2873 return hasher.hexdigest() 

2874 

2875 def _stat(self) -> os.stat_result: 

2876 assert self.absolute_path is not None 

2877 if not hasattr(self, "_stat_result"): 

2878 self._stat_result = os.stat(self.absolute_path) 

2879 return self._stat_result 

2880 

2881 def get_content_size(self) -> int: 

2882 """Retrieve the total size of the resource at the given path. 

2883 

2884 This method may be overridden by subclasses. 

2885 

2886 .. versionadded:: 3.1 

2887 

2888 .. versionchanged:: 4.0 

2889 This method is now always called, instead of only when 

2890 partial results are requested. 

2891 """ 

2892 stat_result = self._stat() 

2893 return stat_result.st_size 

2894 

2895 def get_modified_time(self) -> Optional[datetime.datetime]: 

2896 """Returns the time that ``self.absolute_path`` was last modified. 

2897 

2898 May be overridden in subclasses. Should return a `~datetime.datetime` 

2899 object or None. 

2900 

2901 .. versionadded:: 3.1 

2902 """ 

2903 stat_result = self._stat() 

2904 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

2905 # which truncates the fractional portion of the timestamp. It 

2906 # was changed from that form to stat_result.st_mtime to 

2907 # satisfy mypy (which disallows the bracket operator), but the 

2908 # latter form returns a float instead of an int. For 

2909 # consistency with the past (and because we have a unit test 

2910 # that relies on this), we truncate the float here, although 

2911 # I'm not sure that's the right thing to do. 

2912 modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime)) 

2913 return modified 

2914 

2915 def get_content_type(self) -> str: 

2916 """Returns the ``Content-Type`` header to be used for this request. 

2917 

2918 .. versionadded:: 3.1 

2919 """ 

2920 assert self.absolute_path is not None 

2921 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

2922 # per RFC 6713, use the appropriate type for a gzip compressed file 

2923 if encoding == "gzip": 

2924 return "application/gzip" 

2925 # As of 2015-07-21 there is no bzip2 encoding defined at 

2926 # http://www.iana.org/assignments/media-types/media-types.xhtml 

2927 # So for that (and any other encoding), use octet-stream. 

2928 elif encoding is not None: 

2929 return "application/octet-stream" 

2930 elif mime_type is not None: 

2931 return mime_type 

2932 # if mime_type not detected, use application/octet-stream 

2933 else: 

2934 return "application/octet-stream" 

2935 

2936 def set_extra_headers(self, path: str) -> None: 

2937 """For subclass to add extra headers to the response""" 

2938 pass 

2939 

2940 def get_cache_time( 

2941 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

2942 ) -> int: 

2943 """Override to customize cache control behavior. 

2944 

2945 Return a positive number of seconds to make the result 

2946 cacheable for that amount of time or 0 to mark resource as 

2947 cacheable for an unspecified amount of time (subject to 

2948 browser heuristics). 

2949 

2950 By default returns cache expiry of 10 years for resources requested 

2951 with ``v`` argument. 

2952 """ 

2953 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

2954 

2955 @classmethod 

2956 def make_static_url( 

2957 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

2958 ) -> str: 

2959 """Constructs a versioned url for the given path. 

2960 

2961 This method may be overridden in subclasses (but note that it 

2962 is a class method rather than an instance method). Subclasses 

2963 are only required to implement the signature 

2964 ``make_static_url(cls, settings, path)``; other keyword 

2965 arguments may be passed through `~RequestHandler.static_url` 

2966 but are not standard. 

2967 

2968 ``settings`` is the `Application.settings` dictionary. ``path`` 

2969 is the static path being requested. The url returned should be 

2970 relative to the current host. 

2971 

2972 ``include_version`` determines whether the generated URL should 

2973 include the query string containing the version hash of the 

2974 file corresponding to the given ``path``. 

2975 

2976 """ 

2977 url = settings.get("static_url_prefix", "/static/") + path 

2978 if not include_version: 

2979 return url 

2980 

2981 version_hash = cls.get_version(settings, path) 

2982 if not version_hash: 

2983 return url 

2984 

2985 return "%s?v=%s" % (url, version_hash) 

2986 

2987 def parse_url_path(self, url_path: str) -> str: 

2988 """Converts a static URL path into a filesystem path. 

2989 

2990 ``url_path`` is the path component of the URL with 

2991 ``static_url_prefix`` removed. The return value should be 

2992 filesystem path relative to ``static_path``. 

2993 

2994 This is the inverse of `make_static_url`. 

2995 """ 

2996 if os.path.sep != "/": 

2997 url_path = url_path.replace("/", os.path.sep) 

2998 return url_path 

2999 

3000 @classmethod 

3001 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3002 """Generate the version string to be used in static URLs. 

3003 

3004 ``settings`` is the `Application.settings` dictionary and ``path`` 

3005 is the relative location of the requested asset on the filesystem. 

3006 The returned value should be a string, or ``None`` if no version 

3007 could be determined. 

3008 

3009 .. versionchanged:: 3.1 

3010 This method was previously recommended for subclasses to override; 

3011 `get_content_version` is now preferred as it allows the base 

3012 class to handle caching of the result. 

3013 """ 

3014 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3015 return cls._get_cached_version(abs_path) 

3016 

3017 @classmethod 

3018 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3019 with cls._lock: 

3020 hashes = cls._static_hashes 

3021 if abs_path not in hashes: 

3022 try: 

3023 hashes[abs_path] = cls.get_content_version(abs_path) 

3024 except Exception: 

3025 gen_log.error("Could not open static file %r", abs_path) 

3026 hashes[abs_path] = None 

3027 hsh = hashes.get(abs_path) 

3028 if hsh: 

3029 return hsh 

3030 return None 

3031 

3032 

3033class FallbackHandler(RequestHandler): 

3034 """A `RequestHandler` that wraps another HTTP server callback. 

3035 

3036 The fallback is a callable object that accepts an 

3037 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3038 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3039 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3040 usage:: 

3041 

3042 wsgi_app = tornado.wsgi.WSGIContainer( 

3043 django.core.handlers.wsgi.WSGIHandler()) 

3044 application = tornado.web.Application([ 

3045 (r"/foo", FooHandler), 

3046 (r".*", FallbackHandler, dict(fallback=wsgi_app), 

3047 ]) 

3048 """ 

3049 

3050 def initialize( 

3051 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3052 ) -> None: 

3053 self.fallback = fallback 

3054 

3055 def prepare(self) -> None: 

3056 self.fallback(self.request) 

3057 self._finished = True 

3058 self.on_finish() 

3059 

3060 

3061class OutputTransform(object): 

3062 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3063 

3064 Applications are not expected to create their own OutputTransforms 

3065 or interact with them directly; the framework chooses which transforms 

3066 (if any) to apply. 

3067 """ 

3068 

3069 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3070 pass 

3071 

3072 def transform_first_chunk( 

3073 self, 

3074 status_code: int, 

3075 headers: httputil.HTTPHeaders, 

3076 chunk: bytes, 

3077 finishing: bool, 

3078 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3079 return status_code, headers, chunk 

3080 

3081 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3082 return chunk 

3083 

3084 

3085class GZipContentEncoding(OutputTransform): 

3086 """Applies the gzip content encoding to the response. 

3087 

3088 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3089 

3090 .. versionchanged:: 4.0 

3091 Now compresses all mime types beginning with ``text/``, instead 

3092 of just a whitelist. (the whitelist is still used for certain 

3093 non-text mime types). 

3094 """ 

3095 

3096 # Whitelist of compressible mime types (in addition to any types 

3097 # beginning with "text/"). 

3098 CONTENT_TYPES = set( 

3099 [ 

3100 "application/javascript", 

3101 "application/x-javascript", 

3102 "application/xml", 

3103 "application/atom+xml", 

3104 "application/json", 

3105 "application/xhtml+xml", 

3106 "image/svg+xml", 

3107 ] 

3108 ) 

3109 # Python's GzipFile defaults to level 9, while most other gzip 

3110 # tools (including gzip itself) default to 6, which is probably a 

3111 # better CPU/size tradeoff. 

3112 GZIP_LEVEL = 6 

3113 # Responses that are too short are unlikely to benefit from gzipping 

3114 # after considering the "Content-Encoding: gzip" header and the header 

3115 # inside the gzip encoding. 

3116 # Note that responses written in multiple chunks will be compressed 

3117 # regardless of size. 

3118 MIN_LENGTH = 1024 

3119 

3120 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3121 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3122 

3123 def _compressible_type(self, ctype: str) -> bool: 

3124 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3125 

3126 def transform_first_chunk( 

3127 self, 

3128 status_code: int, 

3129 headers: httputil.HTTPHeaders, 

3130 chunk: bytes, 

3131 finishing: bool, 

3132 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3133 # TODO: can/should this type be inherited from the superclass? 

3134 if "Vary" in headers: 

3135 headers["Vary"] += ", Accept-Encoding" 

3136 else: 

3137 headers["Vary"] = "Accept-Encoding" 

3138 if self._gzipping: 

3139 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3140 self._gzipping = ( 

3141 self._compressible_type(ctype) 

3142 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3143 and ("Content-Encoding" not in headers) 

3144 ) 

3145 if self._gzipping: 

3146 headers["Content-Encoding"] = "gzip" 

3147 self._gzip_value = BytesIO() 

3148 self._gzip_file = gzip.GzipFile( 

3149 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3150 ) 

3151 chunk = self.transform_chunk(chunk, finishing) 

3152 if "Content-Length" in headers: 

3153 # The original content length is no longer correct. 

3154 # If this is the last (and only) chunk, we can set the new 

3155 # content-length; otherwise we remove it and fall back to 

3156 # chunked encoding. 

3157 if finishing: 

3158 headers["Content-Length"] = str(len(chunk)) 

3159 else: 

3160 del headers["Content-Length"] 

3161 return status_code, headers, chunk 

3162 

3163 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3164 if self._gzipping: 

3165 self._gzip_file.write(chunk) 

3166 if finishing: 

3167 self._gzip_file.close() 

3168 else: 

3169 self._gzip_file.flush() 

3170 chunk = self._gzip_value.getvalue() 

3171 self._gzip_value.truncate(0) 

3172 self._gzip_value.seek(0) 

3173 return chunk 

3174 

3175 

3176def authenticated( 

3177 method: Callable[..., Optional[Awaitable[None]]] 

3178) -> Callable[..., Optional[Awaitable[None]]]: 

3179 """Decorate methods with this to require that the user be logged in. 

3180 

3181 If the user is not logged in, they will be redirected to the configured 

3182 `login url <RequestHandler.get_login_url>`. 

3183 

3184 If you configure a login url with a query parameter, Tornado will 

3185 assume you know what you're doing and use it as-is. If not, it 

3186 will add a `next` parameter so the login page knows where to send 

3187 you once you're logged in. 

3188 """ 

3189 

3190 @functools.wraps(method) 

3191 def wrapper( # type: ignore 

3192 self: RequestHandler, *args, **kwargs 

3193 ) -> Optional[Awaitable[None]]: 

3194 if not self.current_user: 

3195 if self.request.method in ("GET", "HEAD"): 

3196 url = self.get_login_url() 

3197 if "?" not in url: 

3198 if urllib.parse.urlsplit(url).scheme: 

3199 # if login url is absolute, make next absolute too 

3200 next_url = self.request.full_url() 

3201 else: 

3202 assert self.request.uri is not None 

3203 next_url = self.request.uri 

3204 url += "?" + urlencode(dict(next=next_url)) 

3205 self.redirect(url) 

3206 return None 

3207 raise HTTPError(403) 

3208 return method(self, *args, **kwargs) 

3209 

3210 return wrapper 

3211 

3212 

3213class UIModule(object): 

3214 """A re-usable, modular UI unit on a page. 

3215 

3216 UI modules often execute additional queries, and they can include 

3217 additional CSS and JavaScript that will be included in the output 

3218 page, which is automatically inserted on page render. 

3219 

3220 Subclasses of UIModule must override the `render` method. 

3221 """ 

3222 

3223 def __init__(self, handler: RequestHandler) -> None: 

3224 self.handler = handler 

3225 self.request = handler.request 

3226 self.ui = handler.ui 

3227 self.locale = handler.locale 

3228 

3229 @property 

3230 def current_user(self) -> Any: 

3231 return self.handler.current_user 

3232 

3233 def render(self, *args: Any, **kwargs: Any) -> str: 

3234 """Override in subclasses to return this module's output.""" 

3235 raise NotImplementedError() 

3236 

3237 def embedded_javascript(self) -> Optional[str]: 

3238 """Override to return a JavaScript string 

3239 to be embedded in the page.""" 

3240 return None 

3241 

3242 def javascript_files(self) -> Optional[Iterable[str]]: 

3243 """Override to return a list of JavaScript files needed by this module. 

3244 

3245 If the return values are relative paths, they will be passed to 

3246 `RequestHandler.static_url`; otherwise they will be used as-is. 

3247 """ 

3248 return None 

3249 

3250 def embedded_css(self) -> Optional[str]: 

3251 """Override to return a CSS string 

3252 that will be embedded in the page.""" 

3253 return None 

3254 

3255 def css_files(self) -> Optional[Iterable[str]]: 

3256 """Override to returns a list of CSS files required by this module. 

3257 

3258 If the return values are relative paths, they will be passed to 

3259 `RequestHandler.static_url`; otherwise they will be used as-is. 

3260 """ 

3261 return None 

3262 

3263 def html_head(self) -> Optional[str]: 

3264 """Override to return an HTML string that will be put in the <head/> 

3265 element. 

3266 """ 

3267 return None 

3268 

3269 def html_body(self) -> Optional[str]: 

3270 """Override to return an HTML string that will be put at the end of 

3271 the <body/> element. 

3272 """ 

3273 return None 

3274 

3275 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3276 """Renders a template and returns it as a string.""" 

3277 return self.handler.render_string(path, **kwargs) 

3278 

3279 

3280class _linkify(UIModule): 

3281 def render(self, text: str, **kwargs: Any) -> str: # type: ignore 

3282 return escape.linkify(text, **kwargs) 

3283 

3284 

3285class _xsrf_form_html(UIModule): 

3286 def render(self) -> str: # type: ignore 

3287 return self.handler.xsrf_form_html() 

3288 

3289 

3290class TemplateModule(UIModule): 

3291 """UIModule that simply renders the given template. 

3292 

3293 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3294 but the module version gets its own namespace (with kwargs passed to 

3295 Template()) instead of inheriting the outer template's namespace. 

3296 

3297 Templates rendered through this module also get access to UIModule's 

3298 automatic JavaScript/CSS features. Simply call set_resources 

3299 inside the template and give it keyword arguments corresponding to 

3300 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3301 Note that these resources are output once per template file, not once 

3302 per instantiation of the template, so they must not depend on 

3303 any arguments to the template. 

3304 """ 

3305 

3306 def __init__(self, handler: RequestHandler) -> None: 

3307 super().__init__(handler) 

3308 # keep resources in both a list and a dict to preserve order 

3309 self._resource_list = [] # type: List[Dict[str, Any]] 

3310 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3311 

3312 def render(self, path: str, **kwargs: Any) -> bytes: # type: ignore 

3313 def set_resources(**kwargs) -> str: # type: ignore 

3314 if path not in self._resource_dict: 

3315 self._resource_list.append(kwargs) 

3316 self._resource_dict[path] = kwargs 

3317 else: 

3318 if self._resource_dict[path] != kwargs: 

3319 raise ValueError( 

3320 "set_resources called with different " 

3321 "resources for the same template" 

3322 ) 

3323 return "" 

3324 

3325 return self.render_string(path, set_resources=set_resources, **kwargs) 

3326 

3327 def _get_resources(self, key: str) -> Iterable[str]: 

3328 return (r[key] for r in self._resource_list if key in r) 

3329 

3330 def embedded_javascript(self) -> str: 

3331 return "\n".join(self._get_resources("embedded_javascript")) 

3332 

3333 def javascript_files(self) -> Iterable[str]: 

3334 result = [] 

3335 for f in self._get_resources("javascript_files"): 

3336 if isinstance(f, (unicode_type, bytes)): 

3337 result.append(f) 

3338 else: 

3339 result.extend(f) 

3340 return result 

3341 

3342 def embedded_css(self) -> str: 

3343 return "\n".join(self._get_resources("embedded_css")) 

3344 

3345 def css_files(self) -> Iterable[str]: 

3346 result = [] 

3347 for f in self._get_resources("css_files"): 

3348 if isinstance(f, (unicode_type, bytes)): 

3349 result.append(f) 

3350 else: 

3351 result.extend(f) 

3352 return result 

3353 

3354 def html_head(self) -> str: 

3355 return "".join(self._get_resources("html_head")) 

3356 

3357 def html_body(self) -> str: 

3358 return "".join(self._get_resources("html_body")) 

3359 

3360 

3361class _UIModuleNamespace(object): 

3362 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3363 

3364 def __init__( 

3365 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3366 ) -> None: 

3367 self.handler = handler 

3368 self.ui_modules = ui_modules 

3369 

3370 def __getitem__(self, key: str) -> Callable[..., str]: 

3371 return self.handler._ui_module(key, self.ui_modules[key]) 

3372 

3373 def __getattr__(self, key: str) -> Callable[..., str]: 

3374 try: 

3375 return self[key] 

3376 except KeyError as e: 

3377 raise AttributeError(str(e)) 

3378 

3379 

3380def create_signed_value( 

3381 secret: _CookieSecretTypes, 

3382 name: str, 

3383 value: Union[str, bytes], 

3384 version: Optional[int] = None, 

3385 clock: Optional[Callable[[], float]] = None, 

3386 key_version: Optional[int] = None, 

3387) -> bytes: 

3388 if version is None: 

3389 version = DEFAULT_SIGNED_VALUE_VERSION 

3390 if clock is None: 

3391 clock = time.time 

3392 

3393 timestamp = utf8(str(int(clock()))) 

3394 value = base64.b64encode(utf8(value)) 

3395 if version == 1: 

3396 assert not isinstance(secret, dict) 

3397 signature = _create_signature_v1(secret, name, value, timestamp) 

3398 value = b"|".join([value, timestamp, signature]) 

3399 return value 

3400 elif version == 2: 

3401 # The v2 format consists of a version number and a series of 

3402 # length-prefixed fields "%d:%s", the last of which is a 

3403 # signature, all separated by pipes. All numbers are in 

3404 # decimal format with no leading zeros. The signature is an 

3405 # HMAC-SHA256 of the whole string up to that point, including 

3406 # the final pipe. 

3407 # 

3408 # The fields are: 

3409 # - format version (i.e. 2; no length prefix) 

3410 # - key version (integer, default is 0) 

3411 # - timestamp (integer seconds since epoch) 

3412 # - name (not encoded; assumed to be ~alphanumeric) 

3413 # - value (base64-encoded) 

3414 # - signature (hex-encoded; no length prefix) 

3415 def format_field(s: Union[str, bytes]) -> bytes: 

3416 return utf8("%d:" % len(s)) + utf8(s) 

3417 

3418 to_sign = b"|".join( 

3419 [ 

3420 b"2", 

3421 format_field(str(key_version or 0)), 

3422 format_field(timestamp), 

3423 format_field(name), 

3424 format_field(value), 

3425 b"", 

3426 ] 

3427 ) 

3428 

3429 if isinstance(secret, dict): 

3430 assert ( 

3431 key_version is not None 

3432 ), "Key version must be set when sign key dict is used" 

3433 assert version >= 2, "Version must be at least 2 for key version support" 

3434 secret = secret[key_version] 

3435 

3436 signature = _create_signature_v2(secret, to_sign) 

3437 return to_sign + signature 

3438 else: 

3439 raise ValueError("Unsupported version %d" % version) 

3440 

3441 

3442# A leading version number in decimal 

3443# with no leading zeros, followed by a pipe. 

3444_signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$") 

3445 

3446 

3447def _get_version(value: bytes) -> int: 

3448 # Figures out what version value is. Version 1 did not include an 

3449 # explicit version field and started with arbitrary base64 data, 

3450 # which makes this tricky. 

3451 m = _signed_value_version_re.match(value) 

3452 if m is None: 

3453 version = 1 

3454 else: 

3455 try: 

3456 version = int(m.group(1)) 

3457 if version > 999: 

3458 # Certain payloads from the version-less v1 format may 

3459 # be parsed as valid integers. Due to base64 padding 

3460 # restrictions, this can only happen for numbers whose 

3461 # length is a multiple of 4, so we can treat all 

3462 # numbers up to 999 as versions, and for the rest we 

3463 # fall back to v1 format. 

3464 version = 1 

3465 except ValueError: 

3466 version = 1 

3467 return version 

3468 

3469 

3470def decode_signed_value( 

3471 secret: _CookieSecretTypes, 

3472 name: str, 

3473 value: Union[None, str, bytes], 

3474 max_age_days: float = 31, 

3475 clock: Optional[Callable[[], float]] = None, 

3476 min_version: Optional[int] = None, 

3477) -> Optional[bytes]: 

3478 if clock is None: 

3479 clock = time.time 

3480 if min_version is None: 

3481 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3482 if min_version > 2: 

3483 raise ValueError("Unsupported min_version %d" % min_version) 

3484 if not value: 

3485 return None 

3486 

3487 value = utf8(value) 

3488 version = _get_version(value) 

3489 

3490 if version < min_version: 

3491 return None 

3492 if version == 1: 

3493 assert not isinstance(secret, dict) 

3494 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3495 elif version == 2: 

3496 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3497 else: 

3498 return None 

3499 

3500 

3501def _decode_signed_value_v1( 

3502 secret: Union[str, bytes], 

3503 name: str, 

3504 value: bytes, 

3505 max_age_days: float, 

3506 clock: Callable[[], float], 

3507) -> Optional[bytes]: 

3508 parts = utf8(value).split(b"|") 

3509 if len(parts) != 3: 

3510 return None 

3511 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3512 if not hmac.compare_digest(parts[2], signature): 

3513 gen_log.warning("Invalid cookie signature %r", value) 

3514 return None 

3515 timestamp = int(parts[1]) 

3516 if timestamp < clock() - max_age_days * 86400: 

3517 gen_log.warning("Expired cookie %r", value) 

3518 return None 

3519 if timestamp > clock() + 31 * 86400: 

3520 # _cookie_signature does not hash a delimiter between the 

3521 # parts of the cookie, so an attacker could transfer trailing 

3522 # digits from the payload to the timestamp without altering the 

3523 # signature. For backwards compatibility, sanity-check timestamp 

3524 # here instead of modifying _cookie_signature. 

3525 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3526 return None 

3527 if parts[1].startswith(b"0"): 

3528 gen_log.warning("Tampered cookie %r", value) 

3529 return None 

3530 try: 

3531 return base64.b64decode(parts[0]) 

3532 except Exception: 

3533 return None 

3534 

3535 

3536def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3537 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3538 length, _, rest = s.partition(b":") 

3539 n = int(length) 

3540 field_value = rest[:n] 

3541 # In python 3, indexing bytes returns small integers; we must 

3542 # use a slice to get a byte string as in python 2. 

3543 if rest[n : n + 1] != b"|": 

3544 raise ValueError("malformed v2 signed value field") 

3545 rest = rest[n + 1 :] 

3546 return field_value, rest 

3547 

3548 rest = value[2:] # remove version number 

3549 key_version, rest = _consume_field(rest) 

3550 timestamp, rest = _consume_field(rest) 

3551 name_field, rest = _consume_field(rest) 

3552 value_field, passed_sig = _consume_field(rest) 

3553 return int(key_version), timestamp, name_field, value_field, passed_sig 

3554 

3555 

3556def _decode_signed_value_v2( 

3557 secret: _CookieSecretTypes, 

3558 name: str, 

3559 value: bytes, 

3560 max_age_days: float, 

3561 clock: Callable[[], float], 

3562) -> Optional[bytes]: 

3563 try: 

3564 ( 

3565 key_version, 

3566 timestamp_bytes, 

3567 name_field, 

3568 value_field, 

3569 passed_sig, 

3570 ) = _decode_fields_v2(value) 

3571 except ValueError: 

3572 return None 

3573 signed_string = value[: -len(passed_sig)] 

3574 

3575 if isinstance(secret, dict): 

3576 try: 

3577 secret = secret[key_version] 

3578 except KeyError: 

3579 return None 

3580 

3581 expected_sig = _create_signature_v2(secret, signed_string) 

3582 if not hmac.compare_digest(passed_sig, expected_sig): 

3583 return None 

3584 if name_field != utf8(name): 

3585 return None 

3586 timestamp = int(timestamp_bytes) 

3587 if timestamp < clock() - max_age_days * 86400: 

3588 # The signature has expired. 

3589 return None 

3590 try: 

3591 return base64.b64decode(value_field) 

3592 except Exception: 

3593 return None 

3594 

3595 

3596def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3597 value = utf8(value) 

3598 version = _get_version(value) 

3599 if version < 2: 

3600 return None 

3601 try: 

3602 key_version, _, _, _, _ = _decode_fields_v2(value) 

3603 except ValueError: 

3604 return None 

3605 

3606 return key_version 

3607 

3608 

3609def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3610 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3611 for part in parts: 

3612 hash.update(utf8(part)) 

3613 return utf8(hash.hexdigest()) 

3614 

3615 

3616def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3617 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3618 hash.update(utf8(s)) 

3619 return utf8(hash.hexdigest()) 

3620 

3621 

3622def is_absolute(path: str) -> bool: 

3623 return any(path.startswith(x) for x in ["/", "http:", "https:"])