codekingpro/portable-devtools
115k
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 importlib import import_module
11from typing import (
12 TYPE_CHECKING,
13 Any,
14 Awaitable,
15 Callable,
16 Iterable as TypingIterable,
17 List,
18 Optional,
19 Set,
20 Type,
21 Union,
22 cast,
23)
24
25from .abc import AbstractAccessLogger
26from .helpers import AppKey as AppKey
27from .log import access_logger
28from .typedefs import PathLike
29from .web_app import Application as Application, CleanupError as CleanupError
30from .web_exceptions import (
31 HTTPAccepted as HTTPAccepted,
32 HTTPBadGateway as HTTPBadGateway,
33 HTTPBadRequest as HTTPBadRequest,
34 HTTPClientError as HTTPClientError,
35 HTTPConflict as HTTPConflict,
36 HTTPCreated as HTTPCreated,
37 HTTPError as HTTPError,
38 HTTPException as HTTPException,
39 HTTPExpectationFailed as HTTPExpectationFailed,
40 HTTPFailedDependency as HTTPFailedDependency,
41 HTTPForbidden as HTTPForbidden,
42 HTTPFound as HTTPFound,
43 HTTPGatewayTimeout as HTTPGatewayTimeout,
44 HTTPGone as HTTPGone,
45 HTTPInsufficientStorage as HTTPInsufficientStorage,
46 HTTPInternalServerError as HTTPInternalServerError,
47 HTTPLengthRequired as HTTPLengthRequired,
48 HTTPMethodNotAllowed as HTTPMethodNotAllowed,
49 HTTPMisdirectedRequest as HTTPMisdirectedRequest,
50 HTTPMove as HTTPMove,
51 HTTPMovedPermanently as HTTPMovedPermanently,
52 HTTPMultipleChoices as HTTPMultipleChoices,
53 HTTPNetworkAuthenticationRequired as HTTPNetworkAuthenticationRequired,
54 HTTPNoContent as HTTPNoContent,
55 HTTPNonAuthoritativeInformation as HTTPNonAuthoritativeInformation,
56 HTTPNotAcceptable as HTTPNotAcceptable,
57 HTTPNotExtended as HTTPNotExtended,
58 HTTPNotFound as HTTPNotFound,
59 HTTPNotImplemented as HTTPNotImplemented,
60 HTTPNotModified as HTTPNotModified,
61 HTTPOk as HTTPOk,
62 HTTPPartialContent as HTTPPartialContent,
63 HTTPPaymentRequired as HTTPPaymentRequired,
64 HTTPPermanentRedirect as HTTPPermanentRedirect,
65 HTTPPreconditionFailed as HTTPPreconditionFailed,
66 HTTPPreconditionRequired as HTTPPreconditionRequired,
67 HTTPProxyAuthenticationRequired as HTTPProxyAuthenticationRequired,
68 HTTPRedirection as HTTPRedirection,
69 HTTPRequestEntityTooLarge as HTTPRequestEntityTooLarge,
70 HTTPRequestHeaderFieldsTooLarge as HTTPRequestHeaderFieldsTooLarge,
71 HTTPRequestRangeNotSatisfiable as HTTPRequestRangeNotSatisfiable,
72 HTTPRequestTimeout as HTTPRequestTimeout,
73 HTTPRequestURITooLong as HTTPRequestURITooLong,
74 HTTPResetContent as HTTPResetContent,
75 HTTPSeeOther as HTTPSeeOther,
76 HTTPServerError as HTTPServerError,
77 HTTPServiceUnavailable as HTTPServiceUnavailable,
78 HTTPSuccessful as HTTPSuccessful,
79 HTTPTemporaryRedirect as HTTPTemporaryRedirect,
80 HTTPTooManyRequests as HTTPTooManyRequests,
81 HTTPUnauthorized as HTTPUnauthorized,
82 HTTPUnavailableForLegalReasons as HTTPUnavailableForLegalReasons,
83 HTTPUnprocessableEntity as HTTPUnprocessableEntity,
84 HTTPUnsupportedMediaType as HTTPUnsupportedMediaType,
85 HTTPUpgradeRequired as HTTPUpgradeRequired,
86 HTTPUseProxy as HTTPUseProxy,
87 HTTPVariantAlsoNegotiates as HTTPVariantAlsoNegotiates,
88 HTTPVersionNotSupported as HTTPVersionNotSupported,
89 NotAppKeyWarning as NotAppKeyWarning,
90)
91from .web_fileresponse import FileResponse as FileResponse
92from .web_log import AccessLogger
93from .web_middlewares import (
94 middleware as middleware,
95 normalize_path_middleware as normalize_path_middleware,
96)
97from .web_protocol import (
98 PayloadAccessError as PayloadAccessError,
99 RequestHandler as RequestHandler,
100 RequestPayloadError as RequestPayloadError,
101)
102from .web_request import (
103 BaseRequest as BaseRequest,
104 FileField as FileField,
105 Request as Request,
106)
107from .web_response import (
108 ContentCoding as ContentCoding,
109 Response as Response,
110 StreamResponse as StreamResponse,
111 json_response as json_response,
112)
113from .web_routedef import (
114 AbstractRouteDef as AbstractRouteDef,
115 RouteDef as RouteDef,
116 RouteTableDef as RouteTableDef,
117 StaticDef as StaticDef,
118 delete as delete,
119 get as get,
120 head as head,
121 options as options,
122 patch as patch,
123 post as post,
124 put as put,
125 route as route,
126 static as static,
127 view as view,
128)
129from .web_runner import (
130 AppRunner as AppRunner,
131 BaseRunner as BaseRunner,
132 BaseSite as BaseSite,
133 GracefulExit as GracefulExit,
134 NamedPipeSite as NamedPipeSite,
135 ServerRunner as ServerRunner,
136 SockSite as SockSite,
137 TCPSite as TCPSite,
138 UnixSite as UnixSite,
139)
140from .web_server import Server as Server
141from .web_urldispatcher import (
142 AbstractResource as AbstractResource,
143 AbstractRoute as AbstractRoute,
144 DynamicResource as DynamicResource,
145 PlainResource as PlainResource,
146 PrefixedSubAppResource as PrefixedSubAppResource,
147 Resource as Resource,
148 ResourceRoute as ResourceRoute,
149 StaticResource as StaticResource,
150 UrlDispatcher as UrlDispatcher,
151 UrlMappingMatchInfo as UrlMappingMatchInfo,
152 View as View,
153)
154from .web_ws import (
155 WebSocketReady as WebSocketReady,
156 WebSocketResponse as WebSocketResponse,
157 WSMsgType as WSMsgType,
158)
159
160__all__ = (
161 # web_app
162 "AppKey",
163 "Application",
164 "CleanupError",
165 # web_exceptions
166 "NotAppKeyWarning",
167 "HTTPAccepted",
168 "HTTPBadGateway",
169 "HTTPBadRequest",
170 "HTTPClientError",
171 "HTTPConflict",
172 "HTTPCreated",
173 "HTTPError",
174 "HTTPException",
175 "HTTPExpectationFailed",
176 "HTTPFailedDependency",
177 "HTTPForbidden",
178 "HTTPFound",
179 "HTTPGatewayTimeout",
180 "HTTPGone",
181 "HTTPInsufficientStorage",
182 "HTTPInternalServerError",
183 "HTTPLengthRequired",
184 "HTTPMethodNotAllowed",
185 "HTTPMisdirectedRequest",
186 "HTTPMove",
187 "HTTPMovedPermanently",
188 "HTTPMultipleChoices",
189 "HTTPNetworkAuthenticationRequired",
190 "HTTPNoContent",
191 "HTTPNonAuthoritativeInformation",
192 "HTTPNotAcceptable",
193 "HTTPNotExtended",
194 "HTTPNotFound",
195 "HTTPNotImplemented",
196 "HTTPNotModified",
197 "HTTPOk",
198 "HTTPPartialContent",
199 "HTTPPaymentRequired",
200 "HTTPPermanentRedirect",
201 "HTTPPreconditionFailed",
202 "HTTPPreconditionRequired",
203 "HTTPProxyAuthenticationRequired",
204 "HTTPRedirection",
205 "HTTPRequestEntityTooLarge",
206 "HTTPRequestHeaderFieldsTooLarge",
207 "HTTPRequestRangeNotSatisfiable",
208 "HTTPRequestTimeout",
209 "HTTPRequestURITooLong",
210 "HTTPResetContent",
211 "HTTPSeeOther",
212 "HTTPServerError",
213 "HTTPServiceUnavailable",
214 "HTTPSuccessful",
215 "HTTPTemporaryRedirect",
216 "HTTPTooManyRequests",
217 "HTTPUnauthorized",
218 "HTTPUnavailableForLegalReasons",
219 "HTTPUnprocessableEntity",
220 "HTTPUnsupportedMediaType",
221 "HTTPUpgradeRequired",
222 "HTTPUseProxy",
223 "HTTPVariantAlsoNegotiates",
224 "HTTPVersionNotSupported",
225 # web_fileresponse
226 "FileResponse",
227 # web_middlewares
228 "middleware",
229 "normalize_path_middleware",
230 # web_protocol
231 "PayloadAccessError",
232 "RequestHandler",
233 "RequestPayloadError",
234 # web_request
235 "BaseRequest",
236 "FileField",
237 "Request",
238 # web_response
239 "ContentCoding",
240 "Response",
241 "StreamResponse",
242 "json_response",
243 # web_routedef
244 "AbstractRouteDef",
245 "RouteDef",
246 "RouteTableDef",
247 "StaticDef",
248 "delete",
249 "get",
250 "head",
251 "options",
252 "patch",
253 "post",
254 "put",
255 "route",
256 "static",
257 "view",
258 # web_runner
259 "AppRunner",
260 "BaseRunner",
261 "BaseSite",
262 "GracefulExit",
263 "ServerRunner",
264 "SockSite",
265 "TCPSite",
266 "UnixSite",
267 "NamedPipeSite",
268 # web_server
269 "Server",
270 # web_urldispatcher
271 "AbstractResource",
272 "AbstractRoute",
273 "DynamicResource",
274 "PlainResource",
275 "PrefixedSubAppResource",
276 "Resource",
277 "ResourceRoute",
278 "StaticResource",
279 "UrlDispatcher",
280 "UrlMappingMatchInfo",
281 "View",
282 # web_ws
283 "WebSocketReady",
284 "WebSocketResponse",
285 "WSMsgType",
286 # web
287 "run_app",
288)
289
290
291if TYPE_CHECKING:
292 from ssl import SSLContext
293else:
294 try:
295 from ssl import SSLContext
296 except ImportError: # pragma: no cover
297 SSLContext = object # type: ignore[misc,assignment]
298
299# Only display warning when using -Wdefault, -We, -X dev or similar.
300warnings.filterwarnings("ignore", category=NotAppKeyWarning, append=True)
301
302HostSequence = TypingIterable[str]
303
304
305async def _run_app(
306 app: Union[Application, Awaitable[Application]],
307 *,
308 host: Optional[Union[str, HostSequence]] = None,
309 port: Optional[int] = None,
310 path: Union[PathLike, TypingIterable[PathLike], None] = None,
311 sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None,
312 ssl_context: Optional[SSLContext] = None,
313 print: Optional[Callable[..., None]] = print,
314 backlog: int = 128,
315 reuse_address: Optional[bool] = None,
316 reuse_port: Optional[bool] = None,
317 **kwargs: Any, # TODO(PY311): Use Unpack
318) -> None:
319 # An internal function to actually do all dirty job for application running
320 if asyncio.iscoroutine(app):
321 app = await app
322
323 app = cast(Application, app)
324
325 runner = AppRunner(app, **kwargs)
326
327 await runner.setup()
328
329 sites: List[BaseSite] = []
330
331 try:
332 if host is not None:
333 if isinstance(host, str):
334 sites.append(
335 TCPSite(
336 runner,
337 host,
338 port,
339 ssl_context=ssl_context,
340 backlog=backlog,
341 reuse_address=reuse_address,
342 reuse_port=reuse_port,
343 )
344 )
345 else:
346 for h in host:
347 sites.append(
348 TCPSite(
349 runner,
350 h,
351 port,
352 ssl_context=ssl_context,
353 backlog=backlog,
354 reuse_address=reuse_address,
355 reuse_port=reuse_port,
356 )
357 )
358 elif path is None and sock is None or port is not None:
359 sites.append(
360 TCPSite(
361 runner,
362 port=port,
363 ssl_context=ssl_context,
364 backlog=backlog,
365 reuse_address=reuse_address,
366 reuse_port=reuse_port,
367 )
368 )
369
370 if path is not None:
371 if isinstance(path, (str, os.PathLike)):
372 sites.append(
373 UnixSite(
374 runner,
375 path,
376 ssl_context=ssl_context,
377 backlog=backlog,
378 )
379 )
380 else:
381 for p in path:
382 sites.append(
383 UnixSite(
384 runner,
385 p,
386 ssl_context=ssl_context,
387 backlog=backlog,
388 )
389 )
390
391 if sock is not None:
392 if not isinstance(sock, Iterable):
393 sites.append(
394 SockSite(
395 runner,
396 sock,
397 ssl_context=ssl_context,
398 backlog=backlog,
399 )
400 )
401 else:
402 for s in sock:
403 sites.append(
404 SockSite(
405 runner,
406 s,
407 ssl_context=ssl_context,
408 backlog=backlog,
409 )
410 )
411 for site in sites:
412 await site.start()
413
414 if print: # pragma: no branch
415 names = sorted(str(s.name) for s in runner.sites)
416 print(
417 "======== Running on {} ========\n"
418 "(Press CTRL+C to quit)".format(", ".join(names))
419 )
420
421 # sleep forever by 1 hour intervals,
422 while True:
423 await asyncio.sleep(3600)
424 finally:
425 await runner.cleanup()
426
427
428def _cancel_tasks(
429 to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop
430) -> None:
431 if not to_cancel:
432 return
433
434 for task in to_cancel:
435 task.cancel()
436
437 loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
438
439 for task in to_cancel:
440 if task.cancelled():
441 continue
442 if task.exception() is not None:
443 loop.call_exception_handler(
444 {
445 "message": "unhandled exception during asyncio.run() shutdown",
446 "exception": task.exception(),
447 "task": task,
448 }
449 )
450
451
452def run_app(
453 app: Union[Application, Awaitable[Application]],
454 *,
455 host: Optional[Union[str, HostSequence]] = None,
456 port: Optional[int] = None,
457 path: Union[PathLike, TypingIterable[PathLike], None] = None,
458 sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None,
459 shutdown_timeout: float = 60.0,
460 keepalive_timeout: float = 75.0,
461 ssl_context: Optional[SSLContext] = None,
462 print: Optional[Callable[..., None]] = print,
463 backlog: int = 128,
464 access_log_class: Type[AbstractAccessLogger] = AccessLogger,
465 access_log_format: str = AccessLogger.LOG_FORMAT,
466 access_log: Optional[logging.Logger] = access_logger,
467 handle_signals: bool = True,
468 reuse_address: Optional[bool] = None,
469 reuse_port: Optional[bool] = None,
470 handler_cancellation: bool = False,
471 loop: Optional[asyncio.AbstractEventLoop] = None,
472 **kwargs: Any,
473) -> None:
474 """Run an app locally"""
475 if loop is None:
476 loop = asyncio.new_event_loop()
477
478 # Configure if and only if in debugging mode and using the default logger
479 if loop.get_debug() and access_log and access_log.name == "aiohttp.access":
480 if access_log.level == logging.NOTSET:
481 access_log.setLevel(logging.DEBUG)
482 if not access_log.hasHandlers():
483 access_log.addHandler(logging.StreamHandler())
484
485 main_task = loop.create_task(
486 _run_app(
487 app,
488 host=host,
489 port=port,
490 path=path,
491 sock=sock,
492 shutdown_timeout=shutdown_timeout,
493 keepalive_timeout=keepalive_timeout,
494 ssl_context=ssl_context,
495 print=print,
496 backlog=backlog,
497 access_log_class=access_log_class,
498 access_log_format=access_log_format,
499 access_log=access_log,
500 handle_signals=handle_signals,
501 reuse_address=reuse_address,
502 reuse_port=reuse_port,
503 handler_cancellation=handler_cancellation,
504 **kwargs,
505 )
506 )
507
508 try:
509 asyncio.set_event_loop(loop)
510 loop.run_until_complete(main_task)
511 except (GracefulExit, KeyboardInterrupt): # pragma: no cover
512 pass
513 finally:
514 try:
515 main_task.cancel()
516 with suppress(asyncio.CancelledError):
517 loop.run_until_complete(main_task)
518 finally:
519 _cancel_tasks(asyncio.all_tasks(loop), loop)
520 loop.run_until_complete(loop.shutdown_asyncgens())
521 loop.close()
522
523
524def main(argv: List[str]) -> None:
525 arg_parser = ArgumentParser(
526 description="aiohttp.web Application server", prog="aiohttp.web"
527 )
528 arg_parser.add_argument(
529 "entry_func",
530 help=(
531 "Callable returning the `aiohttp.web.Application` instance to "
532 "run. Should be specified in the 'module:function' syntax."
533 ),
534 metavar="entry-func",
535 )
536 arg_parser.add_argument(
537 "-H",
538 "--hostname",
539 help="TCP/IP hostname to serve on (default: localhost)",
540 default=None,
541 )
542 arg_parser.add_argument(
543 "-P",
544 "--port",
545 help="TCP/IP port to serve on (default: %(default)r)",
546 type=int,
547 default=8080,
548 )
549 arg_parser.add_argument(
550 "-U",
551 "--path",
552 help="Unix file system path to serve on. Can be combined with hostname "
553 "to serve on both Unix and TCP.",
554 )
555 args, extra_argv = arg_parser.parse_known_args(argv)
556
557 # Import logic
558 mod_str, _, func_str = args.entry_func.partition(":")
559 if not func_str or not mod_str:
560 arg_parser.error("'entry-func' not in 'module:function' syntax")
561 if mod_str.startswith("."):
562 arg_parser.error("relative module names not supported")
563 try:
564 module = import_module(mod_str)
565 except ImportError as ex:
566 arg_parser.error(f"unable to import {mod_str}: {ex}")
567 try:
568 func = getattr(module, func_str)
569 except AttributeError:
570 arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}")
571
572 # Compatibility logic
573 if args.path is not None and not hasattr(socket, "AF_UNIX"):
574 arg_parser.error(
575 "file system paths not supported by your operating environment"
576 )
577
578 logging.basicConfig(level=logging.DEBUG)
579
580 if args.path and args.hostname is None:
581 host = port = None
582 else:
583 host = args.hostname or "localhost"
584 port = args.port
585
586 app = func(extra_argv)
587 run_app(app, host=host, port=port, path=args.path)
588 arg_parser.exit(message="Stopped\n")
589
590
591if __name__ == "__main__": # pragma: no branch
592 main(sys.argv[1:]) # pragma: no cover
593 