| | 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 (
|
| | 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",
|
| | )
|
| |
|
| |
|
| | try:
|
| | from ssl import SSLContext
|
| | except ImportError:
|
| | SSLContext = Any
|
| |
|
| |
|
| | 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, bytes, bytearray, memoryview)):
|
| | 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: %(default)r)",
|
| | default="localhost",
|
| | )
|
| | 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. Specifying a path will cause "
|
| | "hostname and port arguments to be ignored.",
|
| | )
|
| | 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)
|
| |
|
| | app = func(extra_argv)
|
| | run_app(app, host=args.hostname, port=args.port, path=args.path)
|
| | arg_parser.exit(message="Stopped\n")
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| | main(sys.argv[1:])
|
| |
|