| import asyncio |
| import logging |
| import os |
| import socket |
| import sys |
| import warnings |
| from argparse import ArgumentParser |
| from collections.abc import Iterable |
| from contextlib import suppress |
| from importlib import import_module |
| from typing import ( |
| TYPE_CHECKING, |
| Any, |
| Awaitable, |
| Callable, |
| Iterable as TypingIterable, |
| List, |
| Optional, |
| Set, |
| Type, |
| Union, |
| cast, |
| ) |
|
|
| from .abc import AbstractAccessLogger |
| from .helpers import AppKey as AppKey |
| from .log import access_logger |
| from .typedefs import PathLike |
| from .web_app import Application as Application, CleanupError as CleanupError |
| from .web_exceptions import ( |
| HTTPAccepted as HTTPAccepted, |
| HTTPBadGateway as HTTPBadGateway, |
| HTTPBadRequest as HTTPBadRequest, |
| HTTPClientError as HTTPClientError, |
| HTTPConflict as HTTPConflict, |
| HTTPCreated as HTTPCreated, |
| HTTPError as HTTPError, |
| HTTPException as HTTPException, |
| HTTPExpectationFailed as HTTPExpectationFailed, |
| HTTPFailedDependency as HTTPFailedDependency, |
| HTTPForbidden as HTTPForbidden, |
| HTTPFound as HTTPFound, |
| HTTPGatewayTimeout as HTTPGatewayTimeout, |
| HTTPGone as HTTPGone, |
| HTTPInsufficientStorage as HTTPInsufficientStorage, |
| HTTPInternalServerError as HTTPInternalServerError, |
| HTTPLengthRequired as HTTPLengthRequired, |
| HTTPMethodNotAllowed as HTTPMethodNotAllowed, |
| HTTPMisdirectedRequest as HTTPMisdirectedRequest, |
| HTTPMove as HTTPMove, |
| HTTPMovedPermanently as HTTPMovedPermanently, |
| HTTPMultipleChoices as HTTPMultipleChoices, |
| HTTPNetworkAuthenticationRequired as HTTPNetworkAuthenticationRequired, |
| HTTPNoContent as HTTPNoContent, |
| HTTPNonAuthoritativeInformation as HTTPNonAuthoritativeInformation, |
| HTTPNotAcceptable as HTTPNotAcceptable, |
| HTTPNotExtended as HTTPNotExtended, |
| HTTPNotFound as HTTPNotFound, |
| HTTPNotImplemented as HTTPNotImplemented, |
| HTTPNotModified as HTTPNotModified, |
| HTTPOk as HTTPOk, |
| HTTPPartialContent as HTTPPartialContent, |
| HTTPPaymentRequired as HTTPPaymentRequired, |
| HTTPPermanentRedirect as HTTPPermanentRedirect, |
| HTTPPreconditionFailed as HTTPPreconditionFailed, |
| HTTPPreconditionRequired as HTTPPreconditionRequired, |
| HTTPProxyAuthenticationRequired as HTTPProxyAuthenticationRequired, |
| HTTPRedirection as HTTPRedirection, |
| HTTPRequestEntityTooLarge as HTTPRequestEntityTooLarge, |
| HTTPRequestHeaderFieldsTooLarge as HTTPRequestHeaderFieldsTooLarge, |
| HTTPRequestRangeNotSatisfiable as HTTPRequestRangeNotSatisfiable, |
| HTTPRequestTimeout as HTTPRequestTimeout, |
| HTTPRequestURITooLong as HTTPRequestURITooLong, |
| HTTPResetContent as HTTPResetContent, |
| HTTPSeeOther as HTTPSeeOther, |
| HTTPServerError as HTTPServerError, |
| HTTPServiceUnavailable as HTTPServiceUnavailable, |
| HTTPSuccessful as HTTPSuccessful, |
| HTTPTemporaryRedirect as HTTPTemporaryRedirect, |
| HTTPTooManyRequests as HTTPTooManyRequests, |
| HTTPUnauthorized as HTTPUnauthorized, |
| HTTPUnavailableForLegalReasons as HTTPUnavailableForLegalReasons, |
| HTTPUnprocessableEntity as HTTPUnprocessableEntity, |
| HTTPUnsupportedMediaType as HTTPUnsupportedMediaType, |
| HTTPUpgradeRequired as HTTPUpgradeRequired, |
| HTTPUseProxy as HTTPUseProxy, |
| HTTPVariantAlsoNegotiates as HTTPVariantAlsoNegotiates, |
| HTTPVersionNotSupported as HTTPVersionNotSupported, |
| NotAppKeyWarning as NotAppKeyWarning, |
| ) |
| from .web_fileresponse import FileResponse as FileResponse |
| from .web_log import AccessLogger |
| from .web_middlewares import ( |
| middleware as middleware, |
| normalize_path_middleware as normalize_path_middleware, |
| ) |
| from .web_protocol import ( |
| PayloadAccessError as PayloadAccessError, |
| RequestHandler as RequestHandler, |
| RequestPayloadError as RequestPayloadError, |
| ) |
| from .web_request import ( |
| BaseRequest as BaseRequest, |
| FileField as FileField, |
| Request as Request, |
| ) |
| from .web_response import ( |
| ContentCoding as ContentCoding, |
| Response as Response, |
| StreamResponse as StreamResponse, |
| json_response as json_response, |
| ) |
| from .web_routedef import ( |
| AbstractRouteDef as AbstractRouteDef, |
| RouteDef as RouteDef, |
| RouteTableDef as RouteTableDef, |
| StaticDef as StaticDef, |
| delete as delete, |
| get as get, |
| head as head, |
| options as options, |
| patch as patch, |
| post as post, |
| put as put, |
| route as route, |
| static as static, |
| view as view, |
| ) |
| from .web_runner import ( |
| AppRunner as AppRunner, |
| BaseRunner as BaseRunner, |
| BaseSite as BaseSite, |
| GracefulExit as GracefulExit, |
| NamedPipeSite as NamedPipeSite, |
| ServerRunner as ServerRunner, |
| SockSite as SockSite, |
| TCPSite as TCPSite, |
| UnixSite as UnixSite, |
| ) |
| from .web_server import Server as Server |
| from .web_urldispatcher import ( |
| AbstractResource as AbstractResource, |
| AbstractRoute as AbstractRoute, |
| DynamicResource as DynamicResource, |
| PlainResource as PlainResource, |
| PrefixedSubAppResource as PrefixedSubAppResource, |
| Resource as Resource, |
| ResourceRoute as ResourceRoute, |
| StaticResource as StaticResource, |
| UrlDispatcher as UrlDispatcher, |
| UrlMappingMatchInfo as UrlMappingMatchInfo, |
| View as View, |
| ) |
| from .web_ws import ( |
| WebSocketReady as WebSocketReady, |
| WebSocketResponse as WebSocketResponse, |
| WSMsgType as WSMsgType, |
| ) |
|
|
| __all__ = ( |
| |
| "AppKey", |
| "Application", |
| "CleanupError", |
| |
| "NotAppKeyWarning", |
| "HTTPAccepted", |
| "HTTPBadGateway", |
| "HTTPBadRequest", |
| "HTTPClientError", |
| "HTTPConflict", |
| "HTTPCreated", |
| "HTTPError", |
| "HTTPException", |
| "HTTPExpectationFailed", |
| "HTTPFailedDependency", |
| "HTTPForbidden", |
| "HTTPFound", |
| "HTTPGatewayTimeout", |
| "HTTPGone", |
| "HTTPInsufficientStorage", |
| "HTTPInternalServerError", |
| "HTTPLengthRequired", |
| "HTTPMethodNotAllowed", |
| "HTTPMisdirectedRequest", |
| "HTTPMove", |
| "HTTPMovedPermanently", |
| "HTTPMultipleChoices", |
| "HTTPNetworkAuthenticationRequired", |
| "HTTPNoContent", |
| "HTTPNonAuthoritativeInformation", |
| "HTTPNotAcceptable", |
| "HTTPNotExtended", |
| "HTTPNotFound", |
| "HTTPNotImplemented", |
| "HTTPNotModified", |
| "HTTPOk", |
| "HTTPPartialContent", |
| "HTTPPaymentRequired", |
| "HTTPPermanentRedirect", |
| "HTTPPreconditionFailed", |
| "HTTPPreconditionRequired", |
| "HTTPProxyAuthenticationRequired", |
| "HTTPRedirection", |
| "HTTPRequestEntityTooLarge", |
| "HTTPRequestHeaderFieldsTooLarge", |
| "HTTPRequestRangeNotSatisfiable", |
| "HTTPRequestTimeout", |
| "HTTPRequestURITooLong", |
| "HTTPResetContent", |
| "HTTPSeeOther", |
| "HTTPServerError", |
| "HTTPServiceUnavailable", |
| "HTTPSuccessful", |
| "HTTPTemporaryRedirect", |
| "HTTPTooManyRequests", |
| "HTTPUnauthorized", |
| "HTTPUnavailableForLegalReasons", |
| "HTTPUnprocessableEntity", |
| "HTTPUnsupportedMediaType", |
| "HTTPUpgradeRequired", |
| "HTTPUseProxy", |
| "HTTPVariantAlsoNegotiates", |
| "HTTPVersionNotSupported", |
| |
| "FileResponse", |
| |
| "middleware", |
| "normalize_path_middleware", |
| |
| "PayloadAccessError", |
| "RequestHandler", |
| "RequestPayloadError", |
| |
| "BaseRequest", |
| "FileField", |
| "Request", |
| |
| "ContentCoding", |
| "Response", |
| "StreamResponse", |
| "json_response", |
| |
| "AbstractRouteDef", |
| "RouteDef", |
| "RouteTableDef", |
| "StaticDef", |
| "delete", |
| "get", |
| "head", |
| "options", |
| "patch", |
| "post", |
| "put", |
| "route", |
| "static", |
| "view", |
| |
| "AppRunner", |
| "BaseRunner", |
| "BaseSite", |
| "GracefulExit", |
| "ServerRunner", |
| "SockSite", |
| "TCPSite", |
| "UnixSite", |
| "NamedPipeSite", |
| |
| "Server", |
| |
| "AbstractResource", |
| "AbstractRoute", |
| "DynamicResource", |
| "PlainResource", |
| "PrefixedSubAppResource", |
| "Resource", |
| "ResourceRoute", |
| "StaticResource", |
| "UrlDispatcher", |
| "UrlMappingMatchInfo", |
| "View", |
| |
| "WebSocketReady", |
| "WebSocketResponse", |
| "WSMsgType", |
| |
| "run_app", |
| ) |
|
|
|
|
| if TYPE_CHECKING: |
| from ssl import SSLContext |
| else: |
| try: |
| from ssl import SSLContext |
| except ImportError: |
| SSLContext = object |
|
|
| |
| warnings.filterwarnings("ignore", category=NotAppKeyWarning, append=True) |
|
|
| HostSequence = TypingIterable[str] |
|
|
|
|
| async def _run_app( |
| app: Union[Application, Awaitable[Application]], |
| *, |
| host: Optional[Union[str, HostSequence]] = None, |
| port: Optional[int] = None, |
| path: Union[PathLike, TypingIterable[PathLike], None] = None, |
| sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, |
| shutdown_timeout: float = 60.0, |
| keepalive_timeout: float = 75.0, |
| ssl_context: Optional[SSLContext] = None, |
| print: Optional[Callable[..., None]] = print, |
| backlog: int = 128, |
| access_log_class: Type[AbstractAccessLogger] = AccessLogger, |
| access_log_format: str = AccessLogger.LOG_FORMAT, |
| access_log: Optional[logging.Logger] = access_logger, |
| handle_signals: bool = True, |
| reuse_address: Optional[bool] = None, |
| reuse_port: Optional[bool] = None, |
| handler_cancellation: bool = False, |
| ) -> None: |
| |
| if asyncio.iscoroutine(app): |
| app = await app |
|
|
| app = cast(Application, app) |
|
|
| runner = AppRunner( |
| app, |
| handle_signals=handle_signals, |
| access_log_class=access_log_class, |
| access_log_format=access_log_format, |
| access_log=access_log, |
| keepalive_timeout=keepalive_timeout, |
| shutdown_timeout=shutdown_timeout, |
| handler_cancellation=handler_cancellation, |
| ) |
|
|
| await runner.setup() |
|
|
| sites: List[BaseSite] = [] |
|
|
| try: |
| if host is not None: |
| if isinstance(host, str): |
| sites.append( |
| TCPSite( |
| runner, |
| host, |
| port, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| reuse_address=reuse_address, |
| reuse_port=reuse_port, |
| ) |
| ) |
| else: |
| for h in host: |
| sites.append( |
| TCPSite( |
| runner, |
| h, |
| port, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| reuse_address=reuse_address, |
| reuse_port=reuse_port, |
| ) |
| ) |
| elif path is None and sock is None or port is not None: |
| sites.append( |
| TCPSite( |
| runner, |
| port=port, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| reuse_address=reuse_address, |
| reuse_port=reuse_port, |
| ) |
| ) |
|
|
| if path is not None: |
| if isinstance(path, (str, os.PathLike)): |
| sites.append( |
| UnixSite( |
| runner, |
| path, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| ) |
| ) |
| else: |
| for p in path: |
| sites.append( |
| UnixSite( |
| runner, |
| p, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| ) |
| ) |
|
|
| if sock is not None: |
| if not isinstance(sock, Iterable): |
| sites.append( |
| SockSite( |
| runner, |
| sock, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| ) |
| ) |
| else: |
| for s in sock: |
| sites.append( |
| SockSite( |
| runner, |
| s, |
| ssl_context=ssl_context, |
| backlog=backlog, |
| ) |
| ) |
| for site in sites: |
| await site.start() |
|
|
| if print: |
| names = sorted(str(s.name) for s in runner.sites) |
| print( |
| "======== Running on {} ========\n" |
| "(Press CTRL+C to quit)".format(", ".join(names)) |
| ) |
|
|
| |
| while True: |
| await asyncio.sleep(3600) |
| finally: |
| await runner.cleanup() |
|
|
|
|
| def _cancel_tasks( |
| to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop |
| ) -> None: |
| if not to_cancel: |
| return |
|
|
| for task in to_cancel: |
| task.cancel() |
|
|
| loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) |
|
|
| for task in to_cancel: |
| if task.cancelled(): |
| continue |
| if task.exception() is not None: |
| loop.call_exception_handler( |
| { |
| "message": "unhandled exception during asyncio.run() shutdown", |
| "exception": task.exception(), |
| "task": task, |
| } |
| ) |
|
|
|
|
| def run_app( |
| app: Union[Application, Awaitable[Application]], |
| *, |
| host: Optional[Union[str, HostSequence]] = None, |
| port: Optional[int] = None, |
| path: Union[PathLike, TypingIterable[PathLike], None] = None, |
| sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, |
| shutdown_timeout: float = 60.0, |
| keepalive_timeout: float = 75.0, |
| ssl_context: Optional[SSLContext] = None, |
| print: Optional[Callable[..., None]] = print, |
| backlog: int = 128, |
| access_log_class: Type[AbstractAccessLogger] = AccessLogger, |
| access_log_format: str = AccessLogger.LOG_FORMAT, |
| access_log: Optional[logging.Logger] = access_logger, |
| handle_signals: bool = True, |
| reuse_address: Optional[bool] = None, |
| reuse_port: Optional[bool] = None, |
| handler_cancellation: bool = False, |
| loop: Optional[asyncio.AbstractEventLoop] = None, |
| ) -> None: |
| """Run an app locally""" |
| if loop is None: |
| loop = asyncio.new_event_loop() |
|
|
| |
| if loop.get_debug() and access_log and access_log.name == "aiohttp.access": |
| if access_log.level == logging.NOTSET: |
| access_log.setLevel(logging.DEBUG) |
| if not access_log.hasHandlers(): |
| access_log.addHandler(logging.StreamHandler()) |
|
|
| main_task = loop.create_task( |
| _run_app( |
| app, |
| host=host, |
| port=port, |
| path=path, |
| sock=sock, |
| shutdown_timeout=shutdown_timeout, |
| keepalive_timeout=keepalive_timeout, |
| ssl_context=ssl_context, |
| print=print, |
| backlog=backlog, |
| access_log_class=access_log_class, |
| access_log_format=access_log_format, |
| access_log=access_log, |
| handle_signals=handle_signals, |
| reuse_address=reuse_address, |
| reuse_port=reuse_port, |
| handler_cancellation=handler_cancellation, |
| ) |
| ) |
|
|
| try: |
| asyncio.set_event_loop(loop) |
| loop.run_until_complete(main_task) |
| except (GracefulExit, KeyboardInterrupt): |
| pass |
| finally: |
| try: |
| main_task.cancel() |
| with suppress(asyncio.CancelledError): |
| loop.run_until_complete(main_task) |
| finally: |
| _cancel_tasks(asyncio.all_tasks(loop), loop) |
| loop.run_until_complete(loop.shutdown_asyncgens()) |
| loop.close() |
|
|
|
|
| def main(argv: List[str]) -> None: |
| arg_parser = ArgumentParser( |
| description="aiohttp.web Application server", prog="aiohttp.web" |
| ) |
| arg_parser.add_argument( |
| "entry_func", |
| help=( |
| "Callable returning the `aiohttp.web.Application` instance to " |
| "run. Should be specified in the 'module:function' syntax." |
| ), |
| metavar="entry-func", |
| ) |
| arg_parser.add_argument( |
| "-H", |
| "--hostname", |
| help="TCP/IP hostname to serve on (default: localhost)", |
| default=None, |
| ) |
| arg_parser.add_argument( |
| "-P", |
| "--port", |
| help="TCP/IP port to serve on (default: %(default)r)", |
| type=int, |
| default=8080, |
| ) |
| arg_parser.add_argument( |
| "-U", |
| "--path", |
| help="Unix file system path to serve on. Can be combined with hostname " |
| "to serve on both Unix and TCP.", |
| ) |
| args, extra_argv = arg_parser.parse_known_args(argv) |
|
|
| |
| mod_str, _, func_str = args.entry_func.partition(":") |
| if not func_str or not mod_str: |
| arg_parser.error("'entry-func' not in 'module:function' syntax") |
| if mod_str.startswith("."): |
| arg_parser.error("relative module names not supported") |
| try: |
| module = import_module(mod_str) |
| except ImportError as ex: |
| arg_parser.error(f"unable to import {mod_str}: {ex}") |
| try: |
| func = getattr(module, func_str) |
| except AttributeError: |
| arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") |
|
|
| |
| if args.path is not None and not hasattr(socket, "AF_UNIX"): |
| arg_parser.error( |
| "file system paths not supported by your operating environment" |
| ) |
|
|
| logging.basicConfig(level=logging.DEBUG) |
|
|
| if args.path and args.hostname is None: |
| host = port = None |
| else: |
| host = args.hostname or "localhost" |
| port = args.port |
|
|
| app = func(extra_argv) |
| run_app(app, host=host, port=port, path=args.path) |
| arg_parser.exit(message="Stopped\n") |
|
|
|
|
| if __name__ == "__main__": |
| main(sys.argv[1:]) |
|
|