1.. _aiohttp-web-advanced: 2 3Web Server Advanced 4=================== 5 6.. currentmodule:: aiohttp.web 7 8 9Unicode support 10--------------- 11 12*aiohttp* does :term:`requoting` of incoming request path. 13 14Unicode (non-ASCII) symbols are processed transparently on both *route 15adding* and *resolving* (internally everything is converted to 16:term:`percent-encoding` form by :term:`yarl` library). 17 18But in case of custom regular expressions for 19:ref:`aiohttp-web-variable-handler` please take care that URL is 20*percent encoded*: if you pass Unicode patterns they don't match to 21*requoted* path. 22 23Peer disconnection 24------------------ 25 26When a client peer is gone a subsequent reading or writing raises :exc:`OSError` 27or more specific exception like :exc:`ConnectionResetError`. 28 29The reason for disconnection is vary; it can be a network issue or explicit 30socket closing on the peer side without reading the whole server response. 31 32*aiohttp* handles disconnection properly but you can handle it explicitly, e.g.:: 33 34 async def handler(request): 35 try: 36 text = await request.text() 37 except OSError: 38 # disconnected 39 40Passing a coroutine into run_app and Gunicorn 41--------------------------------------------- 42 43:func:`run_app` accepts either application instance or a coroutine for 44making an application. The coroutine based approach allows to perform 45async IO before making an app:: 46 47 async def app_factory(): 48 await pre_init() 49 app = web.Application() 50 app.router.add_get(...) 51 return app 52 53 web.run_app(app_factory()) 54 55Gunicorn worker supports a factory as well. For Gunicorn the factory 56should accept zero parameters:: 57 58 async def my_web_app(): 59 app = web.Application() 60 app.router.add_get(...) 61 return app 62 63Start gunicorn: 64 65.. code-block:: shell 66 67 $ gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker 68 69.. versionadded:: 3.1 70 71Custom Routing Criteria 72----------------------- 73 74Sometimes you need to register :ref:`handlers <aiohttp-web-handler>` on 75more complex criteria than simply a *HTTP method* and *path* pair. 76 77Although :class:`UrlDispatcher` does not support any extra criteria, routing 78based on custom conditions can be accomplished by implementing a second layer 79of routing in your application. 80 81The following example shows custom routing based on the *HTTP Accept* header:: 82 83 class AcceptChooser: 84 85 def __init__(self): 86 self._accepts = {} 87 88 async def do_route(self, request): 89 for accept in request.headers.getall('ACCEPT', []): 90 acceptor = self._accepts.get(accept) 91 if acceptor is not None: 92 return (await acceptor(request)) 93 raise HTTPNotAcceptable() 94 95 def reg_acceptor(self, accept, handler): 96 self._accepts[accept] = handler 97 98 99 async def handle_json(request): 100 # do json handling 101 102 async def handle_xml(request): 103 # do xml handling 104 105 chooser = AcceptChooser() 106 app.add_routes([web.get('/', chooser.do_route)]) 107 108 chooser.reg_acceptor('application/json', handle_json) 109 chooser.reg_acceptor('application/xml', handle_xml) 110 111.. _aiohttp-web-static-file-handling: 112 113Static file handling 114-------------------- 115 116The best way to handle static files (images, JavaScripts, CSS files 117etc.) is using `Reverse Proxy`_ like `nginx`_ or `CDN`_ services. 118 119.. _Reverse Proxy: https://en.wikipedia.org/wiki/Reverse_proxy 120.. _nginx: https://nginx.org/ 121.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network 122 123But for development it's very convenient to handle static files by 124aiohttp server itself. 125 126To do it just register a new static route by 127:meth:`RouteTableDef.static` or :func:`static` calls:: 128 129 app.add_routes([web.static('/prefix', path_to_static_folder)]) 130 131 routes.static('/prefix', path_to_static_folder) 132 133When a directory is accessed within a static route then the server responses 134to client with ``HTTP/403 Forbidden`` by default. Displaying folder index 135instead could be enabled with ``show_index`` parameter set to ``True``:: 136 137 web.static('/prefix', path_to_static_folder, show_index=True) 138 139When a symlink from the static directory is accessed, the server responses to 140client with ``HTTP/404 Not Found`` by default. To allow the server to follow 141symlinks, parameter ``follow_symlinks`` should be set to ``True``:: 142 143 web.static('/prefix', path_to_static_folder, follow_symlinks=True) 144 145When you want to enable cache busting, 146parameter ``append_version`` can be set to ``True`` 147 148Cache busting is the process of appending some form of file version hash 149to the filename of resources like JavaScript and CSS files. 150The performance advantage of doing this is that we can tell the browser 151to cache these files indefinitely without worrying about the client not getting 152the latest version when the file changes:: 153 154 web.static('/prefix', path_to_static_folder, append_version=True) 155 156Template Rendering 157------------------ 158 159:mod:`aiohttp.web` does not support template rendering out-of-the-box. 160 161However, there is a third-party library, :mod:`aiohttp_jinja2`, which is 162supported by the *aiohttp* authors. 163 164Using it is rather simple. First, setup a *jinja2 environment* with a call 165to :func:`aiohttp_jinja2.setup`:: 166 167 app = web.Application() 168 aiohttp_jinja2.setup(app, 169 loader=jinja2.FileSystemLoader('/path/to/templates/folder')) 170 171After that you may use the template engine in your 172:ref:`handlers <aiohttp-web-handler>`. The most convenient way is to simply 173wrap your handlers with the :func:`aiohttp_jinja2.template` decorator:: 174 175 @aiohttp_jinja2.template('tmpl.jinja2') 176 async def handler(request): 177 return {'name': 'Andrew', 'surname': 'Svetlov'} 178 179If you prefer the `Mako`_ template engine, please take a look at the 180`aiohttp_mako`_ library. 181 182.. warning:: 183 184 :func:`aiohttp_jinja2.template` should be applied **before** 185 :meth:`RouteTableDef.get` decorator and family, e.g. it must be 186 the *first* (most *down* decorator in the chain):: 187 188 189 @routes.get('/path') 190 @aiohttp_jinja2.template('tmpl.jinja2') 191 async def handler(request): 192 return {'name': 'Andrew', 'surname': 'Svetlov'} 193 194 195.. _Mako: http://www.makotemplates.org/ 196 197.. _aiohttp_mako: https://github.com/aio-libs/aiohttp_mako 198 199 200.. _aiohttp-web-websocket-read-same-task: 201 202Reading from the same task in WebSockets 203---------------------------------------- 204 205Reading from the *WebSocket* (``await ws.receive()``) **must only** be 206done inside the request handler *task*; however, writing 207(``ws.send_str(...)``) to the *WebSocket*, closing (``await 208ws.close()``) and canceling the handler task may be delegated to other 209tasks. See also :ref:`FAQ section 210<aiohttp_faq_terminating_websockets>`. 211 212:mod:`aiohttp.web` creates an implicit :class:`asyncio.Task` for 213handling every incoming request. 214 215.. note:: 216 217 While :mod:`aiohttp.web` itself only supports *WebSockets* without 218 downgrading to *LONG-POLLING*, etc., our team supports SockJS_, an 219 aiohttp-based library for implementing SockJS-compatible server 220 code. 221 222.. _SockJS: https://github.com/aio-libs/sockjs 223 224 225.. warning:: 226 227 Parallel reads from websocket are forbidden, there is no 228 possibility to call :meth:`WebSocketResponse.receive` 229 from two tasks. 230 231 See :ref:`FAQ section <aiohttp_faq_parallel_event_sources>` for 232 instructions how to solve the problem. 233 234 235.. _aiohttp-web-data-sharing: 236 237Data Sharing aka No Singletons Please 238------------------------------------- 239 240:mod:`aiohttp.web` discourages the use of *global variables*, aka *singletons*. 241Every variable should have its own context that is *not global*. 242 243So, :class:`Application` and :class:`Request` 244support a :class:`collections.abc.MutableMapping` interface (i.e. they are 245dict-like objects), allowing them to be used as data stores. 246 247 248.. _aiohttp-web-data-sharing-app-config: 249 250Application's config 251^^^^^^^^^^^^^^^^^^^^ 252 253For storing *global-like* variables, feel free to save them in an 254:class:`Application` instance:: 255 256 app['my_private_key'] = data 257 258and get it back in the :term:`web-handler`:: 259 260 async def handler(request): 261 data = request.app['my_private_key'] 262 263In case of :ref:`nested applications 264<aiohttp-web-nested-applications>` the desired lookup strategy could 265be the following: 266 2671. Search the key in the current nested application. 2682. If the key is not found continue searching in the parent application(s). 269 270For this please use :attr:`Request.config_dict` read-only property:: 271 272 async def handler(request): 273 data = request.config_dict['my_private_key'] 274 275 276Request's storage 277^^^^^^^^^^^^^^^^^ 278 279Variables that are only needed for the lifetime of a :class:`Request`, can be 280stored in a :class:`Request`:: 281 282 async def handler(request): 283 request['my_private_key'] = "data" 284 ... 285 286This is mostly useful for :ref:`aiohttp-web-middlewares` and 287:ref:`aiohttp-web-signals` handlers to store data for further processing by the 288next handlers in the chain. 289 290Response's storage 291^^^^^^^^^^^^^^^^^^ 292 293:class:`StreamResponse` and :class:`Response` objects 294also support :class:`collections.abc.MutableMapping` interface. This is useful 295when you want to share data with signals and middlewares once all the work in 296the handler is done:: 297 298 async def handler(request): 299 [ do all the work ] 300 response['my_metric'] = 123 301 return response 302 303 304Naming hint 305^^^^^^^^^^^ 306 307To avoid clashing with other *aiohttp* users and third-party libraries, please 308choose a unique key name for storing data. 309 310If your code is published on PyPI, then the project name is most likely unique 311and safe to use as the key. 312Otherwise, something based on your company name/url would be satisfactory (i.e. 313``org.company.app``). 314 315 316.. _aiohttp-web-contextvars: 317 318 319ContextVars support 320------------------- 321 322Starting from Python 3.7 asyncio has :mod:`Context Variables <contextvars>` as a 323context-local storage (a generalization of thread-local concept that works with asyncio 324tasks also). 325 326 327*aiohttp* server supports it in the following way: 328 329* A server inherits the current task's context used when creating it. 330 :func:`aiohttp.web.run_app()` runs a task for handling all underlying jobs running 331 the app, but alternatively :ref:`aiohttp-web-app-runners` can be used. 332 333* Application initialization / finalization events (:attr:`Application.cleanup_ctx`, 334 :attr:`Application.on_startup` and :attr:`Application.on_shutdown`, 335 :attr:`Application.on_cleanup`) are executed inside the same context. 336 337 E.g. all context modifications made on application startup are visible on teardown. 338 339* On every request handling *aiohttp* creates a context copy. :term:`web-handler` has 340 all variables installed on initialization stage. But the context modification made by 341 a handler or middleware is invisible to another HTTP request handling call. 342 343An example of context vars usage:: 344 345 from contextvars import ContextVar 346 347 from aiohttp import web 348 349 VAR = ContextVar('VAR', default='default') 350 351 352 async def coro(): 353 return VAR.get() 354 355 356 async def handler(request): 357 var = VAR.get() 358 VAR.set('handler') 359 ret = await coro() 360 return web.Response(text='\n'.join([var, 361 ret])) 362 363 364 async def on_startup(app): 365 print('on_startup', VAR.get()) 366 VAR.set('on_startup') 367 368 369 async def on_cleanup(app): 370 print('on_cleanup', VAR.get()) 371 VAR.set('on_cleanup') 372 373 374 async def init(): 375 print('init', VAR.get()) 376 VAR.set('init') 377 app = web.Application() 378 app.router.add_get('/', handler) 379 380 app.on_startup.append(on_startup) 381 app.on_cleanup.append(on_cleanup) 382 return app 383 384 385 web.run_app(init()) 386 print('done', VAR.get()) 387 388.. versionadded:: 3.5 389 390 391.. _aiohttp-web-middlewares: 392 393Middlewares 394----------- 395 396:mod:`aiohttp.web` provides a powerful mechanism for customizing 397:ref:`request handlers<aiohttp-web-handler>` via *middlewares*. 398 399A *middleware* is a coroutine that can modify either the request or 400response. For example, here's a simple *middleware* which appends 401``' wink'`` to the response:: 402 403 from aiohttp.web import middleware 404 405 @middleware 406 async def middleware(request, handler): 407 resp = await handler(request) 408 resp.text = resp.text + ' wink' 409 return resp 410 411.. note:: 412 413 The example won't work with streamed responses or websockets 414 415Every *middleware* should accept two parameters, a :class:`request 416<Request>` instance and a *handler*, and return the response or raise 417an exception. If the exception is not an instance of 418:exc:`HTTPException` it is converted to ``500`` 419:exc:`HTTPInternalServerError` after processing the 420middlewares chain. 421 422.. warning:: 423 424 Second argument should be named *handler* exactly. 425 426When creating an :class:`Application`, these *middlewares* are passed to 427the keyword-only ``middlewares`` parameter:: 428 429 app = web.Application(middlewares=[middleware_1, 430 middleware_2]) 431 432Internally, a single :ref:`request handler <aiohttp-web-handler>` is constructed 433by applying the middleware chain to the original handler in reverse order, 434and is called by the :class:`RequestHandler` as a regular *handler*. 435 436Since *middlewares* are themselves coroutines, they may perform extra 437``await`` calls when creating a new handler, e.g. call database etc. 438 439*Middlewares* usually call the handler, but they may choose to ignore it, 440e.g. displaying *403 Forbidden page* or raising :exc:`HTTPForbidden` exception 441if the user does not have permissions to access the underlying resource. 442They may also render errors raised by the handler, perform some pre- or 443post-processing like handling *CORS* and so on. 444 445The following code demonstrates middlewares execution order:: 446 447 from aiohttp import web 448 449 async def test(request): 450 print('Handler function called') 451 return web.Response(text="Hello") 452 453 @web.middleware 454 async def middleware1(request, handler): 455 print('Middleware 1 called') 456 response = await handler(request) 457 print('Middleware 1 finished') 458 return response 459 460 @web.middleware 461 async def middleware2(request, handler): 462 print('Middleware 2 called') 463 response = await handler(request) 464 print('Middleware 2 finished') 465 return response 466 467 468 app = web.Application(middlewares=[middleware1, middleware2]) 469 app.router.add_get('/', test) 470 web.run_app(app) 471 472Produced output:: 473 474 Middleware 1 called 475 Middleware 2 called 476 Handler function called 477 Middleware 2 finished 478 Middleware 1 finished 479 480Example 481^^^^^^^ 482 483A common use of middlewares is to implement custom error pages. The following 484example will render 404 errors using a JSON response, as might be appropriate 485a JSON REST service:: 486 487 from aiohttp import web 488 489 @web.middleware 490 async def error_middleware(request, handler): 491 try: 492 response = await handler(request) 493 if response.status != 404: 494 return response 495 message = response.message 496 except web.HTTPException as ex: 497 if ex.status != 404: 498 raise 499 message = ex.reason 500 return web.json_response({'error': message}) 501 502 app = web.Application(middlewares=[error_middleware]) 503 504 505Middleware Factory 506^^^^^^^^^^^^^^^^^^ 507 508A *middleware factory* is a function that creates a middleware with passed arguments. For example, here's a trivial *middleware factory*:: 509 510 def middleware_factory(text): 511 @middleware 512 async def sample_middleware(request, handler): 513 resp = await handler(request) 514 resp.text = resp.text + text 515 return resp 516 return sample_middleware 517 518Remember that contrary to regular middlewares you need the result of a middleware factory not the function itself. So when passing a middleware factory to an app you actually need to call it:: 519 520 app = web.Application(middlewares=[middleware_factory(' wink')]) 521 522.. _aiohttp-web-signals: 523 524Signals 525------- 526 527Although :ref:`middlewares <aiohttp-web-middlewares>` can customize 528:ref:`request handlers<aiohttp-web-handler>` before or after a :class:`Response` 529has been prepared, they can't customize a :class:`Response` **while** it's 530being prepared. For this :mod:`aiohttp.web` provides *signals*. 531 532For example, a middleware can only change HTTP headers for *unprepared* 533responses (see :meth:`StreamResponse.prepare`), but sometimes we 534need a hook for changing HTTP headers for streamed responses and WebSockets. 535This can be accomplished by subscribing to the 536:attr:`Application.on_response_prepare` signal, which is called after default 537headers have been computed and directly before headers are sent:: 538 539 async def on_prepare(request, response): 540 response.headers['My-Header'] = 'value' 541 542 app.on_response_prepare.append(on_prepare) 543 544 545Additionally, the :attr:`Application.on_startup` and 546:attr:`Application.on_cleanup` signals can be subscribed to for 547application component setup and tear down accordingly. 548 549The following example will properly initialize and dispose an aiopg connection 550engine:: 551 552 from aiopg.sa import create_engine 553 554 async def create_aiopg(app): 555 app['pg_engine'] = await create_engine( 556 user='postgre', 557 database='postgre', 558 host='localhost', 559 port=5432, 560 password='' 561 ) 562 563 async def dispose_aiopg(app): 564 app['pg_engine'].close() 565 await app['pg_engine'].wait_closed() 566 567 app.on_startup.append(create_aiopg) 568 app.on_cleanup.append(dispose_aiopg) 569 570 571Signal handlers should not return a value but may modify incoming mutable 572parameters. 573 574Signal handlers will be run sequentially, in order they were 575added. All handlers must be asynchronous since *aiohttp* 3.0. 576 577.. _aiohttp-web-cleanup-ctx: 578 579Cleanup Context 580--------------- 581 582Bare :attr:`Application.on_startup` / :attr:`Application.on_cleanup` 583pair still has a pitfall: signals handlers are independent on each other. 584 585E.g. we have ``[create_pg, create_redis]`` in *startup* signal and 586``[dispose_pg, dispose_redis]`` in *cleanup*. 587 588If, for example, ``create_pg(app)`` call fails ``create_redis(app)`` 589is not called. But on application cleanup both ``dispose_pg(app)`` and 590``dispose_redis(app)`` are still called: *cleanup signal* has no 591knowledge about startup/cleanup pairs and their execution state. 592 593 594The solution is :attr:`Application.cleanup_ctx` usage:: 595 596 async def pg_engine(app): 597 app['pg_engine'] = await create_engine( 598 user='postgre', 599 database='postgre', 600 host='localhost', 601 port=5432, 602 password='' 603 ) 604 yield 605 app['pg_engine'].close() 606 await app['pg_engine'].wait_closed() 607 608 app.cleanup_ctx.append(pg_engine) 609 610The attribute is a list of *asynchronous generators*, a code *before* 611``yield`` is an initialization stage (called on *startup*), a code 612*after* ``yield`` is executed on *cleanup*. The generator must have only 613one ``yield``. 614 615*aiohttp* guarantees that *cleanup code* is called if and only if 616*startup code* was successfully finished. 617 618Asynchronous generators are supported by Python 3.6+, on Python 3.5 619please use `async_generator <https://pypi.org/project/async_generator/>`_ 620library. 621 622.. versionadded:: 3.1 623 624.. _aiohttp-web-nested-applications: 625 626Nested applications 627------------------- 628 629Sub applications are designed for solving the problem of the big 630monolithic code base. 631Let's assume we have a project with own business logic and tools like 632administration panel and debug toolbar. 633 634Administration panel is a separate application by its own nature but all 635toolbar URLs are served by prefix like ``/admin``. 636 637Thus we'll create a totally separate application named ``admin`` and 638connect it to main app with prefix by 639:meth:`Application.add_subapp`:: 640 641 admin = web.Application() 642 # setup admin routes, signals and middlewares 643 644 app.add_subapp('/admin/', admin) 645 646Middlewares and signals from ``app`` and ``admin`` are chained. 647 648It means that if URL is ``'/admin/something'`` middlewares from 649``app`` are applied first and ``admin.middlewares`` are the next in 650the call chain. 651 652The same is going for 653:attr:`Application.on_response_prepare` signal -- the 654signal is delivered to both top level ``app`` and ``admin`` if 655processing URL is routed to ``admin`` sub-application. 656 657Common signals like :attr:`Application.on_startup`, 658:attr:`Application.on_shutdown` and 659:attr:`Application.on_cleanup` are delivered to all 660registered sub-applications. The passed parameter is sub-application 661instance, not top-level application. 662 663 664Third level sub-applications can be nested into second level ones -- 665there are no limitation for nesting level. 666 667Url reversing for sub-applications should generate urls with proper prefix. 668 669But for getting URL sub-application's router should be used:: 670 671 admin = web.Application() 672 admin.add_routes([web.get('/resource', handler, name='name')]) 673 674 app.add_subapp('/admin/', admin) 675 676 url = admin.router['name'].url_for() 677 678The generated ``url`` from example will have a value 679``URL('/admin/resource')``. 680 681If main application should do URL reversing for sub-application it could 682use the following explicit technique:: 683 684 admin = web.Application() 685 admin.add_routes([web.get('/resource', handler, name='name')]) 686 687 app.add_subapp('/admin/', admin) 688 app['admin'] = admin 689 690 async def handler(request): # main application's handler 691 admin = request.app['admin'] 692 url = admin.router['name'].url_for() 693 694.. _aiohttp-web-expect-header: 695 696*Expect* Header 697--------------- 698 699:mod:`aiohttp.web` supports *Expect* header. By default it sends 700``HTTP/1.1 100 Continue`` line to client, or raises 701:exc:`HTTPExpectationFailed` if header value is not equal to 702"100-continue". It is possible to specify custom *Expect* header 703handler on per route basis. This handler gets called if *Expect* 704header exist in request after receiving all headers and before 705processing application's :ref:`aiohttp-web-middlewares` and 706route handler. Handler can return *None*, in that case the request 707processing continues as usual. If handler returns an instance of class 708:class:`StreamResponse`, *request handler* uses it as response. Also 709handler can raise a subclass of :exc:`HTTPException`. In this case all 710further processing will not happen and client will receive appropriate 711http response. 712 713.. note:: 714 A server that does not understand or is unable to comply with any of the 715 expectation values in the Expect field of a request MUST respond with 716 appropriate error status. The server MUST respond with a 417 717 (Expectation Failed) status if any of the expectations cannot be met or, 718 if there are other problems with the request, some other 4xx status. 719 720 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20 721 722If all checks pass, the custom handler *must* write a *HTTP/1.1 100 Continue* 723status code before returning. 724 725The following example shows how to setup a custom handler for the *Expect* 726header:: 727 728 async def check_auth(request): 729 if request.version != aiohttp.HttpVersion11: 730 return 731 732 if request.headers.get('EXPECT') != '100-continue': 733 raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect) 734 735 if request.headers.get('AUTHORIZATION') is None: 736 raise HTTPForbidden() 737 738 request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n") 739 740 async def hello(request): 741 return web.Response(body=b"Hello, world") 742 743 app = web.Application() 744 app.add_routes([web.add_get('/', hello, expect_handler=check_auth)]) 745 746.. _aiohttp-web-custom-resource: 747 748Custom resource implementation 749------------------------------ 750 751To register custom resource use :meth:`UrlDispatcher.register_resource`. 752Resource instance must implement `AbstractResource` interface. 753 754.. _aiohttp-web-app-runners: 755 756Application runners 757------------------- 758 759:func:`run_app` provides a simple *blocking* API for running an 760:class:`Application`. 761 762For starting the application *asynchronously* or serving on multiple 763HOST/PORT :class:`AppRunner` exists. 764 765The simple startup code for serving HTTP site on ``'localhost'``, port 766``8080`` looks like:: 767 768 runner = web.AppRunner(app) 769 await runner.setup() 770 site = web.TCPSite(runner, 'localhost', 8080) 771 await site.start() 772 773 while True: 774 await asyncio.sleep(3600) # sleep forever 775 776To stop serving call :meth:`AppRunner.cleanup`:: 777 778 await runner.cleanup() 779 780.. versionadded:: 3.0 781 782.. _aiohttp-web-graceful-shutdown: 783 784Graceful shutdown 785------------------ 786 787Stopping *aiohttp web server* by just closing all connections is not 788always satisfactory. 789 790The problem is: if application supports :term:`websocket`\s or *data 791streaming* it most likely has open connections at server 792shutdown time. 793 794The *library* has no knowledge how to close them gracefully but 795developer can help by registering :attr:`Application.on_shutdown` 796signal handler and call the signal on *web server* closing. 797 798Developer should keep a list of opened connections 799(:class:`Application` is a good candidate). 800 801The following :term:`websocket` snippet shows an example for websocket 802handler:: 803 804 from aiohttp import web 805 import weakref 806 807 app = web.Application() 808 app['websockets'] = weakref.WeakSet() 809 810 async def websocket_handler(request): 811 ws = web.WebSocketResponse() 812 await ws.prepare(request) 813 814 request.app['websockets'].add(ws) 815 try: 816 async for msg in ws: 817 ... 818 finally: 819 request.app['websockets'].discard(ws) 820 821 return ws 822 823Signal handler may look like:: 824 825 from aiohttp import WSCloseCode 826 827 async def on_shutdown(app): 828 for ws in set(app['websockets']): 829 await ws.close(code=WSCloseCode.GOING_AWAY, 830 message='Server shutdown') 831 832 app.on_shutdown.append(on_shutdown) 833 834Both :func:`run_app` and :meth:`AppRunner.cleanup` call shutdown 835signal handlers. 836 837.. _aiohttp-web-background-tasks: 838 839Background tasks 840----------------- 841 842Sometimes there's a need to perform some asynchronous operations just 843after application start-up. 844 845Even more, in some sophisticated systems there could be a need to run some 846background tasks in the event loop along with the application's request 847handler. Such as listening to message queue or other network message/event 848sources (e.g. ZeroMQ, Redis Pub/Sub, AMQP, etc.) to react to received messages 849within the application. 850 851For example the background task could listen to ZeroMQ on 852:data:`zmq.SUB` socket, process and forward retrieved messages to 853clients connected via WebSocket that are stored somewhere in the 854application (e.g. in the :obj:`application['websockets']` list). 855 856To run such short and long running background tasks aiohttp provides an 857ability to register :attr:`Application.on_startup` signal handler(s) that 858will run along with the application's request handler. 859 860For example there's a need to run one quick task and two long running 861tasks that will live till the application is alive. The appropriate 862background tasks could be registered as an :attr:`Application.on_startup` 863signal handlers as shown in the example below:: 864 865 866 async def listen_to_redis(app): 867 try: 868 sub = await aioredis.create_redis(('localhost', 6379)) 869 ch, *_ = await sub.subscribe('news') 870 async for msg in ch.iter(encoding='utf-8'): 871 # Forward message to all connected websockets: 872 for ws in app['websockets']: 873 ws.send_str('{}: {}'.format(ch.name, msg)) 874 except asyncio.CancelledError: 875 pass 876 finally: 877 await sub.unsubscribe(ch.name) 878 await sub.quit() 879 880 881 async def start_background_tasks(app): 882 app['redis_listener'] = asyncio.create_task(listen_to_redis(app)) 883 884 885 async def cleanup_background_tasks(app): 886 app['redis_listener'].cancel() 887 await app['redis_listener'] 888 889 890 app = web.Application() 891 app.on_startup.append(start_background_tasks) 892 app.on_cleanup.append(cleanup_background_tasks) 893 web.run_app(app) 894 895 896The task :func:`listen_to_redis` will run forever. 897To shut it down correctly :attr:`Application.on_cleanup` signal handler 898may be used to send a cancellation to it. 899 900Handling error pages 901-------------------- 902 903Pages like *404 Not Found* and *500 Internal Error* could be handled 904by custom middleware, see :ref:`polls demo <aiohttp-demos-polls-middlewares>` 905for example. 906 907.. _aiohttp-web-forwarded-support: 908 909Deploying behind a Proxy 910------------------------ 911 912As discussed in :ref:`aiohttp-deployment` the preferable way is 913deploying *aiohttp* web server behind a *Reverse Proxy Server* like 914:term:`nginx` for production usage. 915 916In this way properties like :attr:`BaseRequest.scheme` 917:attr:`BaseRequest.host` and :attr:`BaseRequest.remote` are 918incorrect. 919 920Real values should be given from proxy server, usually either 921``Forwarded`` or old-fashion ``X-Forwarded-For``, 922``X-Forwarded-Host``, ``X-Forwarded-Proto`` HTTP headers are used. 923 924*aiohttp* does not take *forwarded* headers into account by default 925because it produces *security issue*: HTTP client might add these 926headers too, pushing non-trusted data values. 927 928That's why *aiohttp server* should setup *forwarded* headers in custom 929middleware in tight conjunction with *reverse proxy configuration*. 930 931For changing :attr:`BaseRequest.scheme` :attr:`BaseRequest.host` and 932:attr:`BaseRequest.remote` the middleware might use 933:meth:`BaseRequest.clone`. 934 935.. seealso:: 936 937 https://github.com/aio-libs/aiohttp-remotes provides secure helpers 938 for modifying *scheme*, *host* and *remote* attributes according 939 to ``Forwarded`` and ``X-Forwarded-*`` HTTP headers. 940 941Swagger support 942--------------- 943 944`aiohttp-swagger <https://github.com/cr0hn/aiohttp-swagger>`_ is a 945library that allow to add Swagger documentation and embed the 946Swagger-UI into your :mod:`aiohttp.web` project. 947 948CORS support 949------------ 950 951:mod:`aiohttp.web` itself does not support `Cross-Origin Resource 952Sharing <https://en.wikipedia.org/wiki/Cross-origin_resource_sharing>`_, but 953there is an aiohttp plugin for it: 954`aiohttp_cors <https://github.com/aio-libs/aiohttp_cors>`_. 955 956 957Debug Toolbar 958------------- 959 960`aiohttp-debugtoolbar`_ is a very useful library that provides a 961debugging toolbar while you're developing an :mod:`aiohttp.web` 962application. 963 964Install it with ``pip``: 965 966.. code-block:: shell 967 968 $ pip install aiohttp_debugtoolbar 969 970 971Just call :func:`aiohttp_debugtoolbar.setup`:: 972 973 import aiohttp_debugtoolbar 974 from aiohttp_debugtoolbar import toolbar_middleware_factory 975 976 app = web.Application() 977 aiohttp_debugtoolbar.setup(app) 978 979The toolbar is ready to use. Enjoy!!! 980 981.. _aiohttp-debugtoolbar: https://github.com/aio-libs/aiohttp_debugtoolbar 982 983 984Dev Tools 985--------- 986 987`aiohttp-devtools`_ provides a couple of tools to simplify development of 988:mod:`aiohttp.web` applications. 989 990 991Install with ``pip``: 992 993.. code-block:: shell 994 995 $ pip install aiohttp-devtools 996 997* ``runserver`` provides a development server with auto-reload, 998 live-reload, static file serving and `aiohttp-debugtoolbar`_ 999 integration. 1000* ``start`` is a `cookiecutter command which does the donkey work 1001 of creating new :mod:`aiohttp.web` Applications. 1002 1003Documentation and a complete tutorial of creating and running an app 1004locally are available at `aiohttp-devtools`_. 1005 1006.. _aiohttp-devtools: https://github.com/aio-libs/aiohttp-devtools 1007