You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

484 lines
15 KiB

4 years ago
  1. import asyncio
  2. import logging
  3. import socket
  4. import sys
  5. from argparse import ArgumentParser
  6. from collections.abc import Iterable
  7. from importlib import import_module
  8. from typing import Any, Awaitable, Callable, List, Optional, Type, Union, cast
  9. from .abc import AbstractAccessLogger
  10. from .helpers import all_tasks
  11. from .log import access_logger
  12. from .web_app import Application, CleanupError
  13. from .web_exceptions import (
  14. HTTPAccepted,
  15. HTTPBadGateway,
  16. HTTPBadRequest,
  17. HTTPClientError,
  18. HTTPConflict,
  19. HTTPCreated,
  20. HTTPError,
  21. HTTPException,
  22. HTTPExpectationFailed,
  23. HTTPFailedDependency,
  24. HTTPForbidden,
  25. HTTPFound,
  26. HTTPGatewayTimeout,
  27. HTTPGone,
  28. HTTPInsufficientStorage,
  29. HTTPInternalServerError,
  30. HTTPLengthRequired,
  31. HTTPMethodNotAllowed,
  32. HTTPMisdirectedRequest,
  33. HTTPMovedPermanently,
  34. HTTPMultipleChoices,
  35. HTTPNetworkAuthenticationRequired,
  36. HTTPNoContent,
  37. HTTPNonAuthoritativeInformation,
  38. HTTPNotAcceptable,
  39. HTTPNotExtended,
  40. HTTPNotFound,
  41. HTTPNotImplemented,
  42. HTTPNotModified,
  43. HTTPOk,
  44. HTTPPartialContent,
  45. HTTPPaymentRequired,
  46. HTTPPermanentRedirect,
  47. HTTPPreconditionFailed,
  48. HTTPPreconditionRequired,
  49. HTTPProxyAuthenticationRequired,
  50. HTTPRedirection,
  51. HTTPRequestEntityTooLarge,
  52. HTTPRequestHeaderFieldsTooLarge,
  53. HTTPRequestRangeNotSatisfiable,
  54. HTTPRequestTimeout,
  55. HTTPRequestURITooLong,
  56. HTTPResetContent,
  57. HTTPSeeOther,
  58. HTTPServerError,
  59. HTTPServiceUnavailable,
  60. HTTPSuccessful,
  61. HTTPTemporaryRedirect,
  62. HTTPTooManyRequests,
  63. HTTPUnauthorized,
  64. HTTPUnavailableForLegalReasons,
  65. HTTPUnprocessableEntity,
  66. HTTPUnsupportedMediaType,
  67. HTTPUpgradeRequired,
  68. HTTPUseProxy,
  69. HTTPVariantAlsoNegotiates,
  70. HTTPVersionNotSupported,
  71. )
  72. from .web_fileresponse import FileResponse
  73. from .web_log import AccessLogger
  74. from .web_middlewares import middleware, normalize_path_middleware
  75. from .web_protocol import (
  76. PayloadAccessError,
  77. RequestHandler,
  78. RequestPayloadError,
  79. )
  80. from .web_request import BaseRequest, FileField, Request
  81. from .web_response import (
  82. ContentCoding,
  83. Response,
  84. StreamResponse,
  85. json_response,
  86. )
  87. from .web_routedef import (
  88. AbstractRouteDef,
  89. RouteDef,
  90. RouteTableDef,
  91. StaticDef,
  92. delete,
  93. get,
  94. head,
  95. options,
  96. patch,
  97. post,
  98. put,
  99. route,
  100. static,
  101. view,
  102. )
  103. from .web_runner import (
  104. AppRunner,
  105. BaseRunner,
  106. BaseSite,
  107. GracefulExit,
  108. ServerRunner,
  109. SockSite,
  110. TCPSite,
  111. UnixSite,
  112. )
  113. from .web_server import Server
  114. from .web_urldispatcher import (
  115. AbstractResource,
  116. AbstractRoute,
  117. DynamicResource,
  118. PlainResource,
  119. Resource,
  120. ResourceRoute,
  121. StaticResource,
  122. UrlDispatcher,
  123. UrlMappingMatchInfo,
  124. View,
  125. )
  126. from .web_ws import WebSocketReady, WebSocketResponse, WSMsgType
  127. __all__ = (
  128. # web_app
  129. 'Application',
  130. 'CleanupError',
  131. # web_exceptions
  132. 'HTTPAccepted',
  133. 'HTTPBadGateway',
  134. 'HTTPBadRequest',
  135. 'HTTPClientError',
  136. 'HTTPConflict',
  137. 'HTTPCreated',
  138. 'HTTPError',
  139. 'HTTPException',
  140. 'HTTPExpectationFailed',
  141. 'HTTPFailedDependency',
  142. 'HTTPForbidden',
  143. 'HTTPFound',
  144. 'HTTPGatewayTimeout',
  145. 'HTTPGone',
  146. 'HTTPInsufficientStorage',
  147. 'HTTPInternalServerError',
  148. 'HTTPLengthRequired',
  149. 'HTTPMethodNotAllowed',
  150. 'HTTPMisdirectedRequest',
  151. 'HTTPMovedPermanently',
  152. 'HTTPMultipleChoices',
  153. 'HTTPNetworkAuthenticationRequired',
  154. 'HTTPNoContent',
  155. 'HTTPNonAuthoritativeInformation',
  156. 'HTTPNotAcceptable',
  157. 'HTTPNotExtended',
  158. 'HTTPNotFound',
  159. 'HTTPNotImplemented',
  160. 'HTTPNotModified',
  161. 'HTTPOk',
  162. 'HTTPPartialContent',
  163. 'HTTPPaymentRequired',
  164. 'HTTPPermanentRedirect',
  165. 'HTTPPreconditionFailed',
  166. 'HTTPPreconditionRequired',
  167. 'HTTPProxyAuthenticationRequired',
  168. 'HTTPRedirection',
  169. 'HTTPRequestEntityTooLarge',
  170. 'HTTPRequestHeaderFieldsTooLarge',
  171. 'HTTPRequestRangeNotSatisfiable',
  172. 'HTTPRequestTimeout',
  173. 'HTTPRequestURITooLong',
  174. 'HTTPResetContent',
  175. 'HTTPSeeOther',
  176. 'HTTPServerError',
  177. 'HTTPServiceUnavailable',
  178. 'HTTPSuccessful',
  179. 'HTTPTemporaryRedirect',
  180. 'HTTPTooManyRequests',
  181. 'HTTPUnauthorized',
  182. 'HTTPUnavailableForLegalReasons',
  183. 'HTTPUnprocessableEntity',
  184. 'HTTPUnsupportedMediaType',
  185. 'HTTPUpgradeRequired',
  186. 'HTTPUseProxy',
  187. 'HTTPVariantAlsoNegotiates',
  188. 'HTTPVersionNotSupported',
  189. # web_fileresponse
  190. 'FileResponse',
  191. # web_middlewares
  192. 'middleware',
  193. 'normalize_path_middleware',
  194. # web_protocol
  195. 'PayloadAccessError',
  196. 'RequestHandler',
  197. 'RequestPayloadError',
  198. # web_request
  199. 'BaseRequest',
  200. 'FileField',
  201. 'Request',
  202. # web_response
  203. 'ContentCoding',
  204. 'Response',
  205. 'StreamResponse',
  206. 'json_response',
  207. # web_routedef
  208. 'AbstractRouteDef',
  209. 'RouteDef',
  210. 'RouteTableDef',
  211. 'StaticDef',
  212. 'delete',
  213. 'get',
  214. 'head',
  215. 'options',
  216. 'patch',
  217. 'post',
  218. 'put',
  219. 'route',
  220. 'static',
  221. 'view',
  222. # web_runner
  223. 'AppRunner',
  224. 'BaseRunner',
  225. 'BaseSite',
  226. 'GracefulExit',
  227. 'ServerRunner',
  228. 'SockSite',
  229. 'TCPSite',
  230. 'UnixSite',
  231. # web_server
  232. 'Server',
  233. # web_urldispatcher
  234. 'AbstractResource',
  235. 'AbstractRoute',
  236. 'DynamicResource',
  237. 'PlainResource',
  238. 'Resource',
  239. 'ResourceRoute',
  240. 'StaticResource',
  241. 'UrlDispatcher',
  242. 'UrlMappingMatchInfo',
  243. 'View',
  244. # web_ws
  245. 'WebSocketReady',
  246. 'WebSocketResponse',
  247. 'WSMsgType',
  248. # web
  249. 'run_app',
  250. )
  251. try:
  252. from ssl import SSLContext
  253. except ImportError: # pragma: no cover
  254. SSLContext = Any # type: ignore
  255. async def _run_app(app: Union[Application, Awaitable[Application]], *,
  256. host: Optional[str]=None,
  257. port: Optional[int]=None,
  258. path: Optional[str]=None,
  259. sock: Optional[socket.socket]=None,
  260. shutdown_timeout: float=60.0,
  261. ssl_context: Optional[SSLContext]=None,
  262. print: Callable[..., None]=print,
  263. backlog: int=128,
  264. access_log_class: Type[AbstractAccessLogger]=AccessLogger,
  265. access_log_format: str=AccessLogger.LOG_FORMAT,
  266. access_log: Optional[logging.Logger]=access_logger,
  267. handle_signals: bool=True,
  268. reuse_address: Optional[bool]=None,
  269. reuse_port: Optional[bool]=None) -> None:
  270. # A internal functio to actually do all dirty job for application running
  271. if asyncio.iscoroutine(app):
  272. app = await app # type: ignore
  273. app = cast(Application, app)
  274. runner = AppRunner(app, handle_signals=handle_signals,
  275. access_log_class=access_log_class,
  276. access_log_format=access_log_format,
  277. access_log=access_log)
  278. await runner.setup()
  279. sites = [] # type: List[BaseSite]
  280. try:
  281. if host is not None:
  282. if isinstance(host, (str, bytes, bytearray, memoryview)):
  283. sites.append(TCPSite(runner, host, port,
  284. shutdown_timeout=shutdown_timeout,
  285. ssl_context=ssl_context,
  286. backlog=backlog,
  287. reuse_address=reuse_address,
  288. reuse_port=reuse_port))
  289. else:
  290. for h in host:
  291. sites.append(TCPSite(runner, h, port,
  292. shutdown_timeout=shutdown_timeout,
  293. ssl_context=ssl_context,
  294. backlog=backlog,
  295. reuse_address=reuse_address,
  296. reuse_port=reuse_port))
  297. elif path is None and sock is None or port is not None:
  298. sites.append(TCPSite(runner, port=port,
  299. shutdown_timeout=shutdown_timeout,
  300. ssl_context=ssl_context, backlog=backlog,
  301. reuse_address=reuse_address,
  302. reuse_port=reuse_port))
  303. if path is not None:
  304. if isinstance(path, (str, bytes, bytearray, memoryview)):
  305. sites.append(UnixSite(runner, path,
  306. shutdown_timeout=shutdown_timeout,
  307. ssl_context=ssl_context,
  308. backlog=backlog))
  309. else:
  310. for p in path:
  311. sites.append(UnixSite(runner, p,
  312. shutdown_timeout=shutdown_timeout,
  313. ssl_context=ssl_context,
  314. backlog=backlog))
  315. if sock is not None:
  316. if not isinstance(sock, Iterable):
  317. sites.append(SockSite(runner, sock,
  318. shutdown_timeout=shutdown_timeout,
  319. ssl_context=ssl_context,
  320. backlog=backlog))
  321. else:
  322. for s in sock:
  323. sites.append(SockSite(runner, s,
  324. shutdown_timeout=shutdown_timeout,
  325. ssl_context=ssl_context,
  326. backlog=backlog))
  327. for site in sites:
  328. await site.start()
  329. if print: # pragma: no branch
  330. names = sorted(str(s.name) for s in runner.sites)
  331. print("======== Running on {} ========\n"
  332. "(Press CTRL+C to quit)".format(', '.join(names)))
  333. while True:
  334. await asyncio.sleep(3600) # sleep forever by 1 hour intervals
  335. finally:
  336. await runner.cleanup()
  337. def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None:
  338. to_cancel = all_tasks(loop)
  339. if not to_cancel:
  340. return
  341. for task in to_cancel:
  342. task.cancel()
  343. loop.run_until_complete(
  344. asyncio.gather(*to_cancel, loop=loop, return_exceptions=True))
  345. for task in to_cancel:
  346. if task.cancelled():
  347. continue
  348. if task.exception() is not None:
  349. loop.call_exception_handler({
  350. 'message': 'unhandled exception during asyncio.run() shutdown',
  351. 'exception': task.exception(),
  352. 'task': task,
  353. })
  354. def run_app(app: Union[Application, Awaitable[Application]], *,
  355. host: Optional[str]=None,
  356. port: Optional[int]=None,
  357. path: Optional[str]=None,
  358. sock: Optional[socket.socket]=None,
  359. shutdown_timeout: float=60.0,
  360. ssl_context: Optional[SSLContext]=None,
  361. print: Callable[..., None]=print,
  362. backlog: int=128,
  363. access_log_class: Type[AbstractAccessLogger]=AccessLogger,
  364. access_log_format: str=AccessLogger.LOG_FORMAT,
  365. access_log: Optional[logging.Logger]=access_logger,
  366. handle_signals: bool=True,
  367. reuse_address: Optional[bool]=None,
  368. reuse_port: Optional[bool]=None) -> None:
  369. """Run an app locally"""
  370. loop = asyncio.get_event_loop()
  371. # Configure if and only if in debugging mode and using the default logger
  372. if loop.get_debug() and access_log and access_log.name == 'aiohttp.access':
  373. if access_log.level == logging.NOTSET:
  374. access_log.setLevel(logging.DEBUG)
  375. if not access_log.hasHandlers():
  376. access_log.addHandler(logging.StreamHandler())
  377. try:
  378. loop.run_until_complete(_run_app(app,
  379. host=host,
  380. port=port,
  381. path=path,
  382. sock=sock,
  383. shutdown_timeout=shutdown_timeout,
  384. ssl_context=ssl_context,
  385. print=print,
  386. backlog=backlog,
  387. access_log_class=access_log_class,
  388. access_log_format=access_log_format,
  389. access_log=access_log,
  390. handle_signals=handle_signals,
  391. reuse_address=reuse_address,
  392. reuse_port=reuse_port))
  393. except (GracefulExit, KeyboardInterrupt): # pragma: no cover
  394. pass
  395. finally:
  396. _cancel_all_tasks(loop)
  397. if sys.version_info >= (3, 6): # don't use PY_36 to pass mypy
  398. loop.run_until_complete(loop.shutdown_asyncgens())
  399. loop.close()
  400. def main(argv: List[str]) -> None:
  401. arg_parser = ArgumentParser(
  402. description="aiohttp.web Application server",
  403. prog="aiohttp.web"
  404. )
  405. arg_parser.add_argument(
  406. "entry_func",
  407. help=("Callable returning the `aiohttp.web.Application` instance to "
  408. "run. Should be specified in the 'module:function' syntax."),
  409. metavar="entry-func"
  410. )
  411. arg_parser.add_argument(
  412. "-H", "--hostname",
  413. help="TCP/IP hostname to serve on (default: %(default)r)",
  414. default="localhost"
  415. )
  416. arg_parser.add_argument(
  417. "-P", "--port",
  418. help="TCP/IP port to serve on (default: %(default)r)",
  419. type=int,
  420. default="8080"
  421. )
  422. arg_parser.add_argument(
  423. "-U", "--path",
  424. help="Unix file system path to serve on. Specifying a path will cause "
  425. "hostname and port arguments to be ignored.",
  426. )
  427. args, extra_argv = arg_parser.parse_known_args(argv)
  428. # Import logic
  429. mod_str, _, func_str = args.entry_func.partition(":")
  430. if not func_str or not mod_str:
  431. arg_parser.error(
  432. "'entry-func' not in 'module:function' syntax"
  433. )
  434. if mod_str.startswith("."):
  435. arg_parser.error("relative module names not supported")
  436. try:
  437. module = import_module(mod_str)
  438. except ImportError as ex:
  439. arg_parser.error("unable to import %s: %s" % (mod_str, ex))
  440. try:
  441. func = getattr(module, func_str)
  442. except AttributeError:
  443. arg_parser.error("module %r has no attribute %r" % (mod_str, func_str))
  444. # Compatibility logic
  445. if args.path is not None and not hasattr(socket, 'AF_UNIX'):
  446. arg_parser.error("file system paths not supported by your operating"
  447. " environment")
  448. logging.basicConfig(level=logging.DEBUG)
  449. app = func(extra_argv)
  450. run_app(app, host=args.hostname, port=args.port, path=args.path)
  451. arg_parser.exit(message="Stopped\n")
  452. if __name__ == "__main__": # pragma: no branch
  453. main(sys.argv[1:]) # pragma: no cover