Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/web.py: 29%

137 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-26 06:16 +0000

1import asyncio 

2import logging 

3import os 

4import socket 

5import sys 

6import warnings 

7from argparse import ArgumentParser 

8from collections.abc import Iterable 

9from contextlib import suppress 

10from functools import partial 

11from importlib import import_module 

12from typing import ( 

13 Any, 

14 Awaitable, 

15 Callable, 

16 Iterable as TypingIterable, 

17 List, 

18 Optional, 

19 Set, 

20 Type, 

21 Union, 

22 cast, 

23) 

24from weakref import WeakSet 

25 

26from .abc import AbstractAccessLogger 

27from .helpers import AppKey 

28from .log import access_logger 

29from .typedefs import PathLike 

30from .web_app import Application, CleanupError 

31from .web_exceptions import ( 

32 HTTPAccepted, 

33 HTTPBadGateway, 

34 HTTPBadRequest, 

35 HTTPClientError, 

36 HTTPConflict, 

37 HTTPCreated, 

38 HTTPError, 

39 HTTPException, 

40 HTTPExpectationFailed, 

41 HTTPFailedDependency, 

42 HTTPForbidden, 

43 HTTPFound, 

44 HTTPGatewayTimeout, 

45 HTTPGone, 

46 HTTPInsufficientStorage, 

47 HTTPInternalServerError, 

48 HTTPLengthRequired, 

49 HTTPMethodNotAllowed, 

50 HTTPMisdirectedRequest, 

51 HTTPMove, 

52 HTTPMovedPermanently, 

53 HTTPMultipleChoices, 

54 HTTPNetworkAuthenticationRequired, 

55 HTTPNoContent, 

56 HTTPNonAuthoritativeInformation, 

57 HTTPNotAcceptable, 

58 HTTPNotExtended, 

59 HTTPNotFound, 

60 HTTPNotImplemented, 

61 HTTPNotModified, 

62 HTTPOk, 

63 HTTPPartialContent, 

64 HTTPPaymentRequired, 

65 HTTPPermanentRedirect, 

66 HTTPPreconditionFailed, 

67 HTTPPreconditionRequired, 

68 HTTPProxyAuthenticationRequired, 

69 HTTPRedirection, 

70 HTTPRequestEntityTooLarge, 

71 HTTPRequestHeaderFieldsTooLarge, 

72 HTTPRequestRangeNotSatisfiable, 

73 HTTPRequestTimeout, 

74 HTTPRequestURITooLong, 

75 HTTPResetContent, 

76 HTTPSeeOther, 

77 HTTPServerError, 

78 HTTPServiceUnavailable, 

79 HTTPSuccessful, 

80 HTTPTemporaryRedirect, 

81 HTTPTooManyRequests, 

82 HTTPUnauthorized, 

83 HTTPUnavailableForLegalReasons, 

84 HTTPUnprocessableEntity, 

85 HTTPUnsupportedMediaType, 

86 HTTPUpgradeRequired, 

87 HTTPUseProxy, 

88 HTTPVariantAlsoNegotiates, 

89 HTTPVersionNotSupported, 

90 NotAppKeyWarning, 

91) 

92from .web_fileresponse import FileResponse 

93from .web_log import AccessLogger 

94from .web_middlewares import middleware, normalize_path_middleware 

95from .web_protocol import PayloadAccessError, RequestHandler, RequestPayloadError 

96from .web_request import BaseRequest, FileField, Request 

97from .web_response import ContentCoding, Response, StreamResponse, json_response 

98from .web_routedef import ( 

99 AbstractRouteDef, 

100 RouteDef, 

101 RouteTableDef, 

102 StaticDef, 

103 delete, 

104 get, 

105 head, 

106 options, 

107 patch, 

108 post, 

109 put, 

110 route, 

111 static, 

112 view, 

113) 

114from .web_runner import ( 

115 AppRunner, 

116 BaseRunner, 

117 BaseSite, 

118 GracefulExit, 

119 NamedPipeSite, 

120 ServerRunner, 

121 SockSite, 

122 TCPSite, 

123 UnixSite, 

124) 

125from .web_server import Server 

126from .web_urldispatcher import ( 

127 AbstractResource, 

128 AbstractRoute, 

129 DynamicResource, 

130 PlainResource, 

131 PrefixedSubAppResource, 

132 Resource, 

133 ResourceRoute, 

134 StaticResource, 

135 UrlDispatcher, 

136 UrlMappingMatchInfo, 

137 View, 

138) 

139from .web_ws import WebSocketReady, WebSocketResponse, WSMsgType 

140 

141__all__ = ( 

142 # web_app 

143 "AppKey", 

144 "Application", 

145 "CleanupError", 

146 # web_exceptions 

147 "NotAppKeyWarning", 

148 "HTTPAccepted", 

149 "HTTPBadGateway", 

150 "HTTPBadRequest", 

151 "HTTPClientError", 

152 "HTTPConflict", 

153 "HTTPCreated", 

154 "HTTPError", 

155 "HTTPException", 

156 "HTTPExpectationFailed", 

157 "HTTPFailedDependency", 

158 "HTTPForbidden", 

159 "HTTPFound", 

160 "HTTPGatewayTimeout", 

161 "HTTPGone", 

162 "HTTPInsufficientStorage", 

163 "HTTPInternalServerError", 

164 "HTTPLengthRequired", 

165 "HTTPMethodNotAllowed", 

166 "HTTPMisdirectedRequest", 

167 "HTTPMove", 

168 "HTTPMovedPermanently", 

169 "HTTPMultipleChoices", 

170 "HTTPNetworkAuthenticationRequired", 

171 "HTTPNoContent", 

172 "HTTPNonAuthoritativeInformation", 

173 "HTTPNotAcceptable", 

174 "HTTPNotExtended", 

175 "HTTPNotFound", 

176 "HTTPNotImplemented", 

177 "HTTPNotModified", 

178 "HTTPOk", 

179 "HTTPPartialContent", 

180 "HTTPPaymentRequired", 

181 "HTTPPermanentRedirect", 

182 "HTTPPreconditionFailed", 

183 "HTTPPreconditionRequired", 

184 "HTTPProxyAuthenticationRequired", 

185 "HTTPRedirection", 

186 "HTTPRequestEntityTooLarge", 

187 "HTTPRequestHeaderFieldsTooLarge", 

188 "HTTPRequestRangeNotSatisfiable", 

189 "HTTPRequestTimeout", 

190 "HTTPRequestURITooLong", 

191 "HTTPResetContent", 

192 "HTTPSeeOther", 

193 "HTTPServerError", 

194 "HTTPServiceUnavailable", 

195 "HTTPSuccessful", 

196 "HTTPTemporaryRedirect", 

197 "HTTPTooManyRequests", 

198 "HTTPUnauthorized", 

199 "HTTPUnavailableForLegalReasons", 

200 "HTTPUnprocessableEntity", 

201 "HTTPUnsupportedMediaType", 

202 "HTTPUpgradeRequired", 

203 "HTTPUseProxy", 

204 "HTTPVariantAlsoNegotiates", 

205 "HTTPVersionNotSupported", 

206 # web_fileresponse 

207 "FileResponse", 

208 # web_middlewares 

209 "middleware", 

210 "normalize_path_middleware", 

211 # web_protocol 

212 "PayloadAccessError", 

213 "RequestHandler", 

214 "RequestPayloadError", 

215 # web_request 

216 "BaseRequest", 

217 "FileField", 

218 "Request", 

219 # web_response 

220 "ContentCoding", 

221 "Response", 

222 "StreamResponse", 

223 "json_response", 

224 # web_routedef 

225 "AbstractRouteDef", 

226 "RouteDef", 

227 "RouteTableDef", 

228 "StaticDef", 

229 "delete", 

230 "get", 

231 "head", 

232 "options", 

233 "patch", 

234 "post", 

235 "put", 

236 "route", 

237 "static", 

238 "view", 

239 # web_runner 

240 "AppRunner", 

241 "BaseRunner", 

242 "BaseSite", 

243 "GracefulExit", 

244 "ServerRunner", 

245 "SockSite", 

246 "TCPSite", 

247 "UnixSite", 

248 "NamedPipeSite", 

249 # web_server 

250 "Server", 

251 # web_urldispatcher 

252 "AbstractResource", 

253 "AbstractRoute", 

254 "DynamicResource", 

255 "PlainResource", 

256 "PrefixedSubAppResource", 

257 "Resource", 

258 "ResourceRoute", 

259 "StaticResource", 

260 "UrlDispatcher", 

261 "UrlMappingMatchInfo", 

262 "View", 

263 # web_ws 

264 "WebSocketReady", 

265 "WebSocketResponse", 

266 "WSMsgType", 

267 # web 

268 "run_app", 

269) 

270 

271 

272try: 

273 from ssl import SSLContext 

274except ImportError: # pragma: no cover 

275 SSLContext = Any # type: ignore[misc,assignment] 

276 

277# Only display warning when using -Wdefault, -We, -X dev or similar. 

278warnings.filterwarnings("ignore", category=NotAppKeyWarning, append=True) 

279 

280HostSequence = TypingIterable[str] 

281 

282 

283async def _run_app( 

284 app: Union[Application, Awaitable[Application]], 

285 *, 

286 host: Optional[Union[str, HostSequence]] = None, 

287 port: Optional[int] = None, 

288 path: Union[PathLike, TypingIterable[PathLike], None] = None, 

289 sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, 

290 shutdown_timeout: float = 60.0, 

291 keepalive_timeout: float = 75.0, 

292 ssl_context: Optional[SSLContext] = None, 

293 print: Optional[Callable[..., None]] = print, 

294 backlog: int = 128, 

295 access_log_class: Type[AbstractAccessLogger] = AccessLogger, 

296 access_log_format: str = AccessLogger.LOG_FORMAT, 

297 access_log: Optional[logging.Logger] = access_logger, 

298 handle_signals: bool = True, 

299 reuse_address: Optional[bool] = None, 

300 reuse_port: Optional[bool] = None, 

301 handler_cancellation: bool = False, 

302) -> None: 

303 async def wait( 

304 starting_tasks: "WeakSet[asyncio.Task[object]]", shutdown_timeout: float 

305 ) -> None: 

306 # Wait for pending tasks for a given time limit. 

307 t = asyncio.current_task() 

308 assert t is not None 

309 starting_tasks.add(t) 

310 with suppress(asyncio.TimeoutError): 

311 await asyncio.wait_for(_wait(starting_tasks), timeout=shutdown_timeout) 

312 

313 async def _wait(exclude: "WeakSet[asyncio.Task[object]]") -> None: 

314 t = asyncio.current_task() 

315 assert t is not None 

316 exclude.add(t) 

317 while tasks := asyncio.all_tasks().difference(exclude): 

318 await asyncio.wait(tasks) 

319 

320 # An internal function to actually do all dirty job for application running 

321 if asyncio.iscoroutine(app): 

322 app = await app 

323 

324 app = cast(Application, app) 

325 

326 runner = AppRunner( 

327 app, 

328 handle_signals=handle_signals, 

329 access_log_class=access_log_class, 

330 access_log_format=access_log_format, 

331 access_log=access_log, 

332 keepalive_timeout=keepalive_timeout, 

333 shutdown_timeout=shutdown_timeout, 

334 handler_cancellation=handler_cancellation, 

335 ) 

336 

337 await runner.setup() 

338 # On shutdown we want to avoid waiting on tasks which run forever. 

339 # It's very likely that all tasks which run forever will have been created by 

340 # the time we have completed the application startup (in runner.setup()), 

341 # so we just record all running tasks here and exclude them later. 

342 starting_tasks: "WeakSet[asyncio.Task[object]]" = WeakSet(asyncio.all_tasks()) 

343 runner.shutdown_callback = partial(wait, starting_tasks, shutdown_timeout) 

344 

345 sites: List[BaseSite] = [] 

346 

347 try: 

348 if host is not None: 

349 if isinstance(host, (str, bytes, bytearray, memoryview)): 

350 sites.append( 

351 TCPSite( 

352 runner, 

353 host, 

354 port, 

355 ssl_context=ssl_context, 

356 backlog=backlog, 

357 reuse_address=reuse_address, 

358 reuse_port=reuse_port, 

359 ) 

360 ) 

361 else: 

362 for h in host: 

363 sites.append( 

364 TCPSite( 

365 runner, 

366 h, 

367 port, 

368 ssl_context=ssl_context, 

369 backlog=backlog, 

370 reuse_address=reuse_address, 

371 reuse_port=reuse_port, 

372 ) 

373 ) 

374 elif path is None and sock is None or port is not None: 

375 sites.append( 

376 TCPSite( 

377 runner, 

378 port=port, 

379 ssl_context=ssl_context, 

380 backlog=backlog, 

381 reuse_address=reuse_address, 

382 reuse_port=reuse_port, 

383 ) 

384 ) 

385 

386 if path is not None: 

387 if isinstance(path, (str, os.PathLike)): 

388 sites.append( 

389 UnixSite( 

390 runner, 

391 path, 

392 ssl_context=ssl_context, 

393 backlog=backlog, 

394 ) 

395 ) 

396 else: 

397 for p in path: 

398 sites.append( 

399 UnixSite( 

400 runner, 

401 p, 

402 ssl_context=ssl_context, 

403 backlog=backlog, 

404 ) 

405 ) 

406 

407 if sock is not None: 

408 if not isinstance(sock, Iterable): 

409 sites.append( 

410 SockSite( 

411 runner, 

412 sock, 

413 ssl_context=ssl_context, 

414 backlog=backlog, 

415 ) 

416 ) 

417 else: 

418 for s in sock: 

419 sites.append( 

420 SockSite( 

421 runner, 

422 s, 

423 ssl_context=ssl_context, 

424 backlog=backlog, 

425 ) 

426 ) 

427 for site in sites: 

428 await site.start() 

429 

430 if print: # pragma: no branch 

431 names = sorted(str(s.name) for s in runner.sites) 

432 print( 

433 "======== Running on {} ========\n" 

434 "(Press CTRL+C to quit)".format(", ".join(names)) 

435 ) 

436 

437 # sleep forever by 1 hour intervals, 

438 while True: 

439 await asyncio.sleep(3600) 

440 finally: 

441 await runner.cleanup() 

442 

443 

444def _cancel_tasks( 

445 to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop 

446) -> None: 

447 if not to_cancel: 

448 return 

449 

450 for task in to_cancel: 

451 task.cancel() 

452 

453 loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) 

454 

455 for task in to_cancel: 

456 if task.cancelled(): 

457 continue 

458 if task.exception() is not None: 

459 loop.call_exception_handler( 

460 { 

461 "message": "unhandled exception during asyncio.run() shutdown", 

462 "exception": task.exception(), 

463 "task": task, 

464 } 

465 ) 

466 

467 

468def run_app( 

469 app: Union[Application, Awaitable[Application]], 

470 *, 

471 debug: bool = False, 

472 host: Optional[Union[str, HostSequence]] = None, 

473 port: Optional[int] = None, 

474 path: Union[PathLike, TypingIterable[PathLike], None] = None, 

475 sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, 

476 shutdown_timeout: float = 60.0, 

477 keepalive_timeout: float = 75.0, 

478 ssl_context: Optional[SSLContext] = None, 

479 print: Optional[Callable[..., None]] = print, 

480 backlog: int = 128, 

481 access_log_class: Type[AbstractAccessLogger] = AccessLogger, 

482 access_log_format: str = AccessLogger.LOG_FORMAT, 

483 access_log: Optional[logging.Logger] = access_logger, 

484 handle_signals: bool = True, 

485 reuse_address: Optional[bool] = None, 

486 reuse_port: Optional[bool] = None, 

487 handler_cancellation: bool = False, 

488 loop: Optional[asyncio.AbstractEventLoop] = None, 

489) -> None: 

490 """Run an app locally""" 

491 if loop is None: 

492 loop = asyncio.new_event_loop() 

493 loop.set_debug(debug) 

494 

495 # Configure if and only if in debugging mode and using the default logger 

496 if loop.get_debug() and access_log and access_log.name == "aiohttp.access": 

497 if access_log.level == logging.NOTSET: 

498 access_log.setLevel(logging.DEBUG) 

499 if not access_log.hasHandlers(): 

500 access_log.addHandler(logging.StreamHandler()) 

501 

502 main_task = loop.create_task( 

503 _run_app( 

504 app, 

505 host=host, 

506 port=port, 

507 path=path, 

508 sock=sock, 

509 shutdown_timeout=shutdown_timeout, 

510 keepalive_timeout=keepalive_timeout, 

511 ssl_context=ssl_context, 

512 print=print, 

513 backlog=backlog, 

514 access_log_class=access_log_class, 

515 access_log_format=access_log_format, 

516 access_log=access_log, 

517 handle_signals=handle_signals, 

518 reuse_address=reuse_address, 

519 reuse_port=reuse_port, 

520 handler_cancellation=handler_cancellation, 

521 ) 

522 ) 

523 

524 try: 

525 asyncio.set_event_loop(loop) 

526 loop.run_until_complete(main_task) 

527 except (GracefulExit, KeyboardInterrupt): # pragma: no cover 

528 pass 

529 finally: 

530 _cancel_tasks({main_task}, loop) 

531 _cancel_tasks(asyncio.all_tasks(loop), loop) 

532 loop.run_until_complete(loop.shutdown_asyncgens()) 

533 loop.close() 

534 asyncio.set_event_loop(None) 

535 

536 

537def main(argv: List[str]) -> None: 

538 arg_parser = ArgumentParser( 

539 description="aiohttp.web Application server", prog="aiohttp.web" 

540 ) 

541 arg_parser.add_argument( 

542 "entry_func", 

543 help=( 

544 "Callable returning the `aiohttp.web.Application` instance to " 

545 "run. Should be specified in the 'module:function' syntax." 

546 ), 

547 metavar="entry-func", 

548 ) 

549 arg_parser.add_argument( 

550 "-H", 

551 "--hostname", 

552 help="TCP/IP hostname to serve on (default: %(default)r)", 

553 default="localhost", 

554 ) 

555 arg_parser.add_argument( 

556 "-P", 

557 "--port", 

558 help="TCP/IP port to serve on (default: %(default)r)", 

559 type=int, 

560 default="8080", 

561 ) 

562 arg_parser.add_argument( 

563 "-U", 

564 "--path", 

565 help="Unix file system path to serve on. Specifying a path will cause " 

566 "hostname and port arguments to be ignored.", 

567 ) 

568 args, extra_argv = arg_parser.parse_known_args(argv) 

569 

570 # Import logic 

571 mod_str, _, func_str = args.entry_func.partition(":") 

572 if not func_str or not mod_str: 

573 arg_parser.error("'entry-func' not in 'module:function' syntax") 

574 if mod_str.startswith("."): 

575 arg_parser.error("relative module names not supported") 

576 try: 

577 module = import_module(mod_str) 

578 except ImportError as ex: 

579 arg_parser.error(f"unable to import {mod_str}: {ex}") 

580 try: 

581 func = getattr(module, func_str) 

582 except AttributeError: 

583 arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") 

584 

585 # Compatibility logic 

586 if args.path is not None and not hasattr(socket, "AF_UNIX"): 

587 arg_parser.error( 

588 "file system paths not supported by your operating" " environment" 

589 ) 

590 

591 logging.basicConfig(level=logging.DEBUG) 

592 

593 app = func(extra_argv) 

594 run_app(app, host=args.hostname, port=args.port, path=args.path) 

595 arg_parser.exit(message="Stopped\n") 

596 

597 

598if __name__ == "__main__": # pragma: no branch 

599 main(sys.argv[1:]) # pragma: no cover