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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

131 statements  

1import asyncio 

2import logging 

3import os 

4import socket 

5import sys 

6import warnings 

7from argparse import ArgumentParser 

8from collections.abc import Awaitable, Callable, Iterable, Iterable as TypingIterable 

9from contextlib import suppress 

10from importlib import import_module 

11from typing import Any, cast 

12 

13from .abc import AbstractAccessLogger 

14from .helpers import AppKey, RequestKey, ResponseKey 

15from .log import access_logger 

16from .typedefs import PathLike 

17from .web_app import Application, CleanupError 

18from .web_exceptions import ( 

19 HTTPAccepted, 

20 HTTPBadGateway, 

21 HTTPBadRequest, 

22 HTTPClientError, 

23 HTTPConflict, 

24 HTTPCreated, 

25 HTTPError, 

26 HTTPException, 

27 HTTPExpectationFailed, 

28 HTTPFailedDependency, 

29 HTTPForbidden, 

30 HTTPFound, 

31 HTTPGatewayTimeout, 

32 HTTPGone, 

33 HTTPInsufficientStorage, 

34 HTTPInternalServerError, 

35 HTTPLengthRequired, 

36 HTTPMethodNotAllowed, 

37 HTTPMisdirectedRequest, 

38 HTTPMove, 

39 HTTPMovedPermanently, 

40 HTTPMultipleChoices, 

41 HTTPNetworkAuthenticationRequired, 

42 HTTPNoContent, 

43 HTTPNonAuthoritativeInformation, 

44 HTTPNotAcceptable, 

45 HTTPNotExtended, 

46 HTTPNotFound, 

47 HTTPNotImplemented, 

48 HTTPNotModified, 

49 HTTPOk, 

50 HTTPPartialContent, 

51 HTTPPaymentRequired, 

52 HTTPPermanentRedirect, 

53 HTTPPreconditionFailed, 

54 HTTPPreconditionRequired, 

55 HTTPProxyAuthenticationRequired, 

56 HTTPRedirection, 

57 HTTPRequestEntityTooLarge, 

58 HTTPRequestHeaderFieldsTooLarge, 

59 HTTPRequestRangeNotSatisfiable, 

60 HTTPRequestTimeout, 

61 HTTPRequestURITooLong, 

62 HTTPResetContent, 

63 HTTPSeeOther, 

64 HTTPServerError, 

65 HTTPServiceUnavailable, 

66 HTTPSuccessful, 

67 HTTPTemporaryRedirect, 

68 HTTPTooManyRequests, 

69 HTTPUnauthorized, 

70 HTTPUnavailableForLegalReasons, 

71 HTTPUnprocessableEntity, 

72 HTTPUnsupportedMediaType, 

73 HTTPUpgradeRequired, 

74 HTTPUseProxy, 

75 HTTPVariantAlsoNegotiates, 

76 HTTPVersionNotSupported, 

77 NotAppKeyWarning, 

78) 

79from .web_fileresponse import FileResponse 

80from .web_log import AccessLogger 

81from .web_middlewares import middleware, normalize_path_middleware 

82from .web_protocol import PayloadAccessError, RequestHandler, RequestPayloadError 

83from .web_request import BaseRequest, FileField, Request 

84from .web_response import ( 

85 ContentCoding, 

86 Response, 

87 StreamResponse, 

88 json_bytes_response, 

89 json_response, 

90) 

91from .web_routedef import ( 

92 AbstractRouteDef, 

93 RouteDef, 

94 RouteTableDef, 

95 StaticDef, 

96 delete, 

97 get, 

98 head, 

99 options, 

100 patch, 

101 post, 

102 put, 

103 route, 

104 static, 

105 view, 

106) 

107from .web_runner import ( 

108 AppRunner, 

109 BaseRunner, 

110 BaseSite, 

111 GracefulExit, 

112 NamedPipeSite, 

113 ServerRunner, 

114 SockSite, 

115 TCPSite, 

116 UnixSite, 

117) 

118from .web_server import Server 

119from .web_urldispatcher import ( 

120 AbstractResource, 

121 AbstractRoute, 

122 DynamicResource, 

123 PlainResource, 

124 PrefixedSubAppResource, 

125 Resource, 

126 ResourceRoute, 

127 StaticResource, 

128 UrlDispatcher, 

129 UrlMappingMatchInfo, 

130 View, 

131) 

132from .web_ws import WebSocketReady, WebSocketResponse, WSMsgType 

133 

134__all__ = ( 

135 # web_app 

136 "AppKey", 

137 "Application", 

138 "CleanupError", 

139 # web_exceptions 

140 "NotAppKeyWarning", 

141 "HTTPAccepted", 

142 "HTTPBadGateway", 

143 "HTTPBadRequest", 

144 "HTTPClientError", 

145 "HTTPConflict", 

146 "HTTPCreated", 

147 "HTTPError", 

148 "HTTPException", 

149 "HTTPExpectationFailed", 

150 "HTTPFailedDependency", 

151 "HTTPForbidden", 

152 "HTTPFound", 

153 "HTTPGatewayTimeout", 

154 "HTTPGone", 

155 "HTTPInsufficientStorage", 

156 "HTTPInternalServerError", 

157 "HTTPLengthRequired", 

158 "HTTPMethodNotAllowed", 

159 "HTTPMisdirectedRequest", 

160 "HTTPMove", 

161 "HTTPMovedPermanently", 

162 "HTTPMultipleChoices", 

163 "HTTPNetworkAuthenticationRequired", 

164 "HTTPNoContent", 

165 "HTTPNonAuthoritativeInformation", 

166 "HTTPNotAcceptable", 

167 "HTTPNotExtended", 

168 "HTTPNotFound", 

169 "HTTPNotImplemented", 

170 "HTTPNotModified", 

171 "HTTPOk", 

172 "HTTPPartialContent", 

173 "HTTPPaymentRequired", 

174 "HTTPPermanentRedirect", 

175 "HTTPPreconditionFailed", 

176 "HTTPPreconditionRequired", 

177 "HTTPProxyAuthenticationRequired", 

178 "HTTPRedirection", 

179 "HTTPRequestEntityTooLarge", 

180 "HTTPRequestHeaderFieldsTooLarge", 

181 "HTTPRequestRangeNotSatisfiable", 

182 "HTTPRequestTimeout", 

183 "HTTPRequestURITooLong", 

184 "HTTPResetContent", 

185 "HTTPSeeOther", 

186 "HTTPServerError", 

187 "HTTPServiceUnavailable", 

188 "HTTPSuccessful", 

189 "HTTPTemporaryRedirect", 

190 "HTTPTooManyRequests", 

191 "HTTPUnauthorized", 

192 "HTTPUnavailableForLegalReasons", 

193 "HTTPUnprocessableEntity", 

194 "HTTPUnsupportedMediaType", 

195 "HTTPUpgradeRequired", 

196 "HTTPUseProxy", 

197 "HTTPVariantAlsoNegotiates", 

198 "HTTPVersionNotSupported", 

199 # web_fileresponse 

200 "FileResponse", 

201 # web_middlewares 

202 "middleware", 

203 "normalize_path_middleware", 

204 # web_protocol 

205 "PayloadAccessError", 

206 "RequestHandler", 

207 "RequestPayloadError", 

208 # web_request 

209 "BaseRequest", 

210 "FileField", 

211 "Request", 

212 "RequestKey", 

213 # web_response 

214 "ContentCoding", 

215 "Response", 

216 "StreamResponse", 

217 "json_bytes_response", 

218 "json_response", 

219 "ResponseKey", 

220 # web_routedef 

221 "AbstractRouteDef", 

222 "RouteDef", 

223 "RouteTableDef", 

224 "StaticDef", 

225 "delete", 

226 "get", 

227 "head", 

228 "options", 

229 "patch", 

230 "post", 

231 "put", 

232 "route", 

233 "static", 

234 "view", 

235 # web_runner 

236 "AppRunner", 

237 "BaseRunner", 

238 "BaseSite", 

239 "GracefulExit", 

240 "ServerRunner", 

241 "SockSite", 

242 "TCPSite", 

243 "UnixSite", 

244 "NamedPipeSite", 

245 # web_server 

246 "Server", 

247 # web_urldispatcher 

248 "AbstractResource", 

249 "AbstractRoute", 

250 "DynamicResource", 

251 "PlainResource", 

252 "PrefixedSubAppResource", 

253 "Resource", 

254 "ResourceRoute", 

255 "StaticResource", 

256 "UrlDispatcher", 

257 "UrlMappingMatchInfo", 

258 "View", 

259 # web_ws 

260 "WebSocketReady", 

261 "WebSocketResponse", 

262 "WSMsgType", 

263 # web 

264 "run_app", 

265) 

266 

267 

268try: 

269 from ssl import SSLContext 

270except ImportError: # pragma: no cover 

271 SSLContext = object # type: ignore[misc,assignment] 

272 

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

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

275 

276HostSequence = TypingIterable[str] 

277 

278 

279async def _run_app( 

280 app: Application | Awaitable[Application], 

281 *, 

282 host: str | HostSequence | None = None, 

283 port: int | None = None, 

284 path: PathLike | TypingIterable[PathLike] | None = None, 

285 sock: socket.socket | TypingIterable[socket.socket] | None = None, 

286 ssl_context: SSLContext | None = None, 

287 print: Callable[..., None] | None = print, 

288 backlog: int = 128, 

289 reuse_address: bool | None = None, 

290 reuse_port: bool | None = None, 

291 **kwargs: Any, # TODO(PY311): Use Unpack 

292) -> None: 

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

294 if asyncio.iscoroutine(app): 

295 app = await app 

296 

297 app = cast(Application, app) 

298 

299 runner = AppRunner(app, **kwargs) 

300 

301 await runner.setup() 

302 

303 sites: list[BaseSite] = [] 

304 

305 try: 

306 if host is not None: 

307 if isinstance(host, str): 

308 sites.append( 

309 TCPSite( 

310 runner, 

311 host, 

312 port, 

313 ssl_context=ssl_context, 

314 backlog=backlog, 

315 reuse_address=reuse_address, 

316 reuse_port=reuse_port, 

317 ) 

318 ) 

319 else: 

320 for h in host: 

321 sites.append( 

322 TCPSite( 

323 runner, 

324 h, 

325 port, 

326 ssl_context=ssl_context, 

327 backlog=backlog, 

328 reuse_address=reuse_address, 

329 reuse_port=reuse_port, 

330 ) 

331 ) 

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

333 sites.append( 

334 TCPSite( 

335 runner, 

336 port=port, 

337 ssl_context=ssl_context, 

338 backlog=backlog, 

339 reuse_address=reuse_address, 

340 reuse_port=reuse_port, 

341 ) 

342 ) 

343 

344 if path is not None: 

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

346 sites.append( 

347 UnixSite( 

348 runner, 

349 path, 

350 ssl_context=ssl_context, 

351 backlog=backlog, 

352 ) 

353 ) 

354 else: 

355 for p in path: 

356 sites.append( 

357 UnixSite( 

358 runner, 

359 p, 

360 ssl_context=ssl_context, 

361 backlog=backlog, 

362 ) 

363 ) 

364 

365 if sock is not None: 

366 if not isinstance(sock, Iterable): 

367 sites.append( 

368 SockSite( 

369 runner, 

370 sock, 

371 ssl_context=ssl_context, 

372 backlog=backlog, 

373 ) 

374 ) 

375 else: 

376 for s in sock: 

377 sites.append( 

378 SockSite( 

379 runner, 

380 s, 

381 ssl_context=ssl_context, 

382 backlog=backlog, 

383 ) 

384 ) 

385 for site in sites: 

386 await site.start() 

387 

388 if print: # pragma: no branch 

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

390 print( 

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

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

393 ) 

394 

395 # sleep forever by 1 hour intervals, 

396 while True: 

397 await asyncio.sleep(3600) 

398 finally: 

399 await runner.cleanup() 

400 

401 

402def _cancel_tasks( 

403 to_cancel: set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop 

404) -> None: 

405 if not to_cancel: 

406 return 

407 

408 for task in to_cancel: 

409 task.cancel() 

410 

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

412 

413 for task in to_cancel: 

414 if task.cancelled(): 

415 continue 

416 if task.exception() is not None: 

417 loop.call_exception_handler( 

418 { 

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

420 "exception": task.exception(), 

421 "task": task, 

422 } 

423 ) 

424 

425 

426def run_app( 

427 app: Application | Awaitable[Application], 

428 *, 

429 debug: bool = False, 

430 host: str | HostSequence | None = None, 

431 port: int | None = None, 

432 path: PathLike | TypingIterable[PathLike] | None = None, 

433 sock: socket.socket | TypingIterable[socket.socket] | None = None, 

434 shutdown_timeout: float = 60.0, 

435 keepalive_timeout: float = 75.0, 

436 ssl_context: SSLContext | None = None, 

437 print: Callable[..., None] | None = print, 

438 backlog: int = 128, 

439 access_log_class: type[AbstractAccessLogger] = AccessLogger, 

440 access_log_format: str = AccessLogger.LOG_FORMAT, 

441 access_log: logging.Logger | None = access_logger, 

442 handle_signals: bool = True, 

443 reuse_address: bool | None = None, 

444 reuse_port: bool | None = None, 

445 handler_cancellation: bool = False, 

446 loop: asyncio.AbstractEventLoop | None = None, 

447 **kwargs: Any, 

448) -> None: 

449 """Run an app locally""" 

450 if loop is None: 

451 loop = asyncio.new_event_loop() 

452 loop.set_debug(debug) 

453 

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

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

456 if access_log.level == logging.NOTSET: 

457 access_log.setLevel(logging.DEBUG) 

458 if not access_log.hasHandlers(): 

459 access_log.addHandler(logging.StreamHandler()) 

460 

461 main_task = loop.create_task( 

462 _run_app( 

463 app, 

464 host=host, 

465 port=port, 

466 path=path, 

467 sock=sock, 

468 shutdown_timeout=shutdown_timeout, 

469 keepalive_timeout=keepalive_timeout, 

470 ssl_context=ssl_context, 

471 print=print, 

472 backlog=backlog, 

473 access_log_class=access_log_class, 

474 access_log_format=access_log_format, 

475 access_log=access_log, 

476 handle_signals=handle_signals, 

477 reuse_address=reuse_address, 

478 reuse_port=reuse_port, 

479 handler_cancellation=handler_cancellation, 

480 **kwargs, 

481 ) 

482 ) 

483 

484 try: 

485 asyncio.set_event_loop(loop) 

486 loop.run_until_complete(main_task) 

487 except (GracefulExit, KeyboardInterrupt): 

488 pass 

489 finally: 

490 try: 

491 main_task.cancel() 

492 with suppress(asyncio.CancelledError): 

493 loop.run_until_complete(main_task) 

494 finally: 

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

496 loop.run_until_complete(loop.shutdown_asyncgens()) 

497 loop.close() 

498 asyncio.set_event_loop(None) 

499 

500 

501def main(argv: list[str]) -> None: 

502 arg_parser = ArgumentParser( 

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

504 ) 

505 arg_parser.add_argument( 

506 "entry_func", 

507 help=( 

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

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

510 ), 

511 metavar="entry-func", 

512 ) 

513 arg_parser.add_argument( 

514 "-H", 

515 "--hostname", 

516 help="TCP/IP hostname to serve on (default: localhost)", 

517 default=None, 

518 ) 

519 arg_parser.add_argument( 

520 "-P", 

521 "--port", 

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

523 type=int, 

524 default=8080, 

525 ) 

526 arg_parser.add_argument( 

527 "-U", 

528 "--path", 

529 help="Unix file system path to serve on. Can be combined with hostname " 

530 "to serve on both Unix and TCP.", 

531 ) 

532 args, extra_argv = arg_parser.parse_known_args(argv) 

533 

534 # Import logic 

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

536 if not func_str or not mod_str: 

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

538 if mod_str.startswith("."): 

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

540 try: 

541 module = import_module(mod_str) 

542 except ImportError as ex: 

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

544 try: 

545 func = getattr(module, func_str) 

546 except AttributeError: 

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

548 

549 # Compatibility logic 

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

551 arg_parser.error( 

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

553 ) 

554 

555 logging.basicConfig(level=logging.DEBUG) 

556 

557 if args.path and args.hostname is None: 

558 host = port = None 

559 else: 

560 host = args.hostname or "localhost" 

561 port = args.port 

562 

563 app = func(extra_argv) 

564 run_app(app, host=host, port=port, path=args.path) 

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

566 

567 

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

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