HTTP Server Usage

Changed in version 0.12: The module was deeply refactored which makes it backward incompatible.

Run a simple web server

In order to implement a web server, first create a request handler.

Handler is a coroutine or a regular function that accepts only request parameters of type Request and returns Response instance:

import asyncio
from aiohttp import web

def hello(request):
    return web.Response(body=b"Hello, world")

Next, you have to create a Application instance and register handler in the application’s router pointing HTTP method, path and handler:

app = web.Application()
app.router.add_route('GET', '/', hello)

After that, create a server and run the asyncio loop as usual:

loop = asyncio.get_event_loop()
f = loop.create_server(app.make_handler(), '', 8080)
srv = loop.run_until_complete(f)
print('serving on', srv.sockets[0].getsockname())
except KeyboardInterrupt:

That’s it.


Handler is an any callable that accepts a single Request argument and returns a StreamResponse derived (e.g. Response) instance.

Handler may be a coroutine, aiohttp.web will unyield returned result by applying yield from to the handler.

Handlers are connected to the Application via routes:

handler = Handler()
app.router.add_route('GET', '/', handler)

Variable routes

You can also use variable routes. If route contains strings like '/a/{name}/c' that means the route matches to the path like '/a/b/c' or '/a/1/c'.

Parsed path part will be available in the request handler as request.match_info['name']:

def variable_handler(request):
    return web.Response(
        text="Hello, {}".format(request.match_info['name']))

app.router.add_route('GET', '/{name}', variable_handler)

You can also specify regex for variable route in the form {name:regex}:

app.router.add_route('GET', r'/{name:\d+}', variable_handler)

By default regex is [^{}/]+.

New in version 0.13: Support for custom regexs in variable routes.

Named routes and url reverse constructing

Routes may have a name:

app.router.add_route('GET', '/root', handler, name='root')

In web-handler you may build URL for that route:


More interesting example is building URL for variable router:

app.router.add_route('GET', r'/{user}/info',
                     variable_handler, name='handler')

In this case you can pass route parameters also:

...     params={'user': 'john_doe'},
...     query="?a=b")

Using plain coroutines and classes for web-handlers

Handlers may be first-class functions, e.g.:

def hello(request):
    return web.Response(body=b"Hello, world")

app.router.add_route('GET', '/', hello)

But sometimes you would like to group logically coupled handlers into a python class.

aiohttp.web doesn’t dictate any implementation details, so application developer can use classes if he wants:

class Handler:

    def __init__(self):

    def handle_intro(self, request):
        return web.Response(body=b"Hello, world")

    def handle_greeting(self, request):
        name = request.match_info.get('name', "Anonymous")
        txt = "Hello, {}".format(name)
        return web.Response(text=txt)

handler = Handler()
app.router.add_route('GET', '/intro', handler.handle_intro)
app.router.add_route('GET', '/greet/{name}', handler.handle_greeting)

New in version 0.15.2: UrlDispatcher.add_route() supports wildcard as HTTP method:

app.router.add_route('*', '/path', handler)

That means the handler for '/path' is applied for every HTTP method.

Custom conditions for routes lookup

Sometimes you need to distinguish web-handlers on more complex criteria than HTTP method and path.

While UrlDispatcher doesn’t accept extra criterias there is an easy way to do the task by implementing the second routing layer by hand.

The next example shows custom processing based on HTTP Accept header:

class AcceptChooser:

    def __init__(self):
        self._accepts = {}

    def do_route(self, request):
        for accept in request.headers.getall('ACCEPT', []):
             acceptor = self._accepts.get(accept):
             if acceptor is not None:
                 return (yield from acceptor(request))
        raise HTTPNotAcceptable()

    def reg_acceptor(self, accept, handler):
        self._accepts[accept] = handler

def handle_json(request):
    # do json handling

def handle_xml(request):
    # do xml handling

chooser = AcceptChooser()
app.router.add_route('GET', '/', chooser.do_route)

chooser.reg_acceptor('application/json', handle_json)
chooser.reg_acceptor('application/xml', handle_xml)

Template rendering

aiohttp.web has no support for template rendering out-of-the-box.

But there is third-party library aiohttp_jinja2 which is supported by aiohttp authors.

The usage is simple: create dictionary with data and pass it into template renderer.

Before template rendering you have to setup jinja2 environment first (aiohttp_jinja2.setup() call):

app = web.Application(loop=self.loop)

After that you may use template engine in your web-handlers. The most convinient way is to use aiohttp_jinja2.template() decorator:

def handler(request):
    return {'name': 'Andrew', 'surname': 'Svetlov'}

If you prefer Mako template engine please take a look on aiohttp_mako library.

User sessions

Often you need a container for storing per-user data. The concept is usually called session.

aiohttp.web has no sessions but there is third-party aiohttp_session library for that:

import asyncio
import time
from aiohttp import web
from aiohttp_session import get_session, session_middleware
from aiohttp_session.cookie_storage import EncryptedCookieStorage

def handler(request):
    session = yield from get_session(request)
    session['last_visit'] = time.time()
    return web.Response(body=b'OK')

def init(loop):
    app = web.Application(middlewares=[session_middleware(
        EncryptedCookieStorage(b'Sixteen byte key'))])
    app.router.add_route('GET', '/', handler)
    srv = yield from loop.create_server(
        app.make_handler(), '', 8080)
    return srv

loop = asyncio.get_event_loop()
except KeyboardInterrupt:

Expect header support

New in version 0.15.

aiohttp.web supports Expect header. By default it responds with an HTTP/1.1 100 Continue status code. It is possible to specify custom Expect header handler on per route basis. This handler gets called after receiving all headers and before processing application middlewares Middlewares and route handler. Handler can return None, in that case the request processing continues as usual. If handler returns an instance of class StreamResponse, request handler uses it as response. Custom handler must write HTTP/1.1 100 Continue status if all checks pass.

This example shows custom handler for Except header:

def check_auth(request):
    if request.version != aiohttp.HttpVersion11:

    if request.headers.get('AUTHORIZATION') is None:
        return web.HTTPForbidden()

    request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")

def hello(request):
    return web.Response(body=b"Hello, world")

app = web.Application()
app.router.add_route('GET', '/', hello, except_handler=check_auth)

File Uploads

There are two steps necessary for handling file uploads. The first is to make sure that you have a form that has been setup correctly to accept files. This means adding the enctype attribute to your form element with the value of multipart/form-data. A very simple example would be a form that accepts a mp3 file. Notice, we have set up the form as previously explained and also added the input element of the file type:

<form action="/store_mp3" method="post" accept-charset="utf-8"

    <label for="mp3">Mp3</label>
    <input id="mp3" name="mp3" type="file" value="" />

    <input type="submit" value="submit" />

The second step is handling the file upload in your request handler (here assumed to answer on /store_mp3). The uploaded file is added to the request object as a FileField object accessible through the coroutine. The two properties we are interested in are file and filename and we will use those to read a file’s name and a content:

def store_mp3_view(request):

    data = yield from

    # filename contains the name of the file in string format.
    filename = data['mp3'].filename

    # input_file contains the actual file data which needs to be
    # stored somewhere.

    input_file = data['mp3'].file

    content =

    return web.Response(body=content,
                            {'CONTENT-DISPOSITION': input_file})


New in version 0.14.

aiohttp.web works with websockets out-of-the-box.

You have to create WebSocketResponse in web-handler and communicate with peer using response’s methods:

def websocket_handler(request):

    ws = web.WebSocketResponse()

    while True:
        msg = yield from ws.receive()

        if == aiohttp.MsgType.text:
            if == 'close':
                yield from ws.close()
                ws.send_str( + '/answer')
        elif == aiohttp.MsgType.close:
            print('websocket connection closed')
        elif == aiohttp.MsgType.error:
            print('ws connection closed with exception %s',

    return ws

You must use the only websocket task for both reading (e.g yield from ws.receive()) and writing but may have multiple writer tasks which can only send data asynchronously (by yield from ws.send_str('data') for example).


aiohttp.web defines exceptions for list of HTTP status codes.

Each class relates to a single HTTP status code. Each class is a subclass of the HTTPException.

Those exceptions are derived from Response too, so you can either return exception object from Handler or raise it.

The following snippets are the same:

def handler(request):
    return aiohttp.web.HTTPFound('/redirect')


def handler(request):
    raise aiohttp.web.HTTPFound('/redirect')

Each exception class has a status code according to RFC 2068: codes with 100-300 are not really errors; 400s are client errors, and 500s are server errors.

Http Exception hierarchy chart:

      * 200 - HTTPOk
      * 201 - HTTPCreated
      * 202 - HTTPAccepted
      * 203 - HTTPNonAuthoritativeInformation
      * 204 - HTTPNoContent
      * 205 - HTTPResetContent
      * 206 - HTTPPartialContent
      * 300 - HTTPMultipleChoices
      * 301 - HTTPMovedPermanently
      * 302 - HTTPFound
      * 303 - HTTPSeeOther
      * 304 - HTTPNotModified
      * 305 - HTTPUseProxy
      * 307 - HTTPTemporaryRedirect
        * 400 - HTTPBadRequest
        * 401 - HTTPUnauthorized
        * 402 - HTTPPaymentRequired
        * 403 - HTTPForbidden
        * 404 - HTTPNotFound
        * 405 - HTTPMethodNotAllowed
        * 406 - HTTPNotAcceptable
        * 407 - HTTPProxyAuthenticationRequired
        * 408 - HTTPRequestTimeout
        * 409 - HTTPConflict
        * 410 - HTTPGone
        * 411 - HTTPLengthRequired
        * 412 - HTTPPreconditionFailed
        * 413 - HTTPRequestEntityTooLarge
        * 414 - HTTPRequestURITooLong
        * 415 - HTTPUnsupportedMediaType
        * 416 - HTTPRequestRangeNotSatisfiable
        * 417 - HTTPExpectationFailed
        * 500 - HTTPInternalServerError
        * 501 - HTTPNotImplemented
        * 502 - HTTPBadGateway
        * 503 - HTTPServiceUnavailable
        * 504 - HTTPGatewayTimeout
        * 505 - HTTPVersionNotSupported

All http exceptions have the same constructor:

HTTPNotFound(*, headers=None, reason=None,
             body=None, text=None, content_type=None)

if other not directly specified. headers will be added to default response headers.

Classes HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy, HTTPTemporaryRedirect has constructor signature like:

HTTPFound(location, *, headers=None, reason=None,
          body=None, text=None, content_type=None)

where location is value for Location HTTP header.

HTTPMethodNotAllowed constructed with pointing trial method and list of allowed methods:

HTTPMethodNotAllowed(method, allowed_methods, *,
                     headers=None, reason=None,
                     body=None, text=None, content_type=None)


New in version 0.13.

Application accepts optional middlewares keyword-only parameter, which should be a sequence of middleware factories, e.g:

app = web.Application(middlewares=[middleware_factory_1,

The most trivial middleware factory example:

def middleware_factory(app, handler):
    def middleware(request):
        return (yield from handler(request))
    return middleware

Every factory is a coroutine that accepts two parameters: app (Application instance) and handler (next handler in middleware chain.

The last handler is web-handler selected by routing itself (resolve() call).

Middleware should return a new coroutine by wrapping handler parameter. Signature of returned handler should be the same as for web-handler: accept single request parameter, return response or raise exception.

The factory is a coroutine, thus it can do extra yield from calls on making new handler, e.g. call database etc.

After constructing outermost handler by applying middleware chain to web-handler in reversed order RequestHandler executes the outermost handler as regular web-handler.

Middleware usually calls an inner handler, but may do something other, like displaying 403 Forbidden page or raising HTTPForbidden exception if user has no permissions to access underlying resource. Also middleware may render errors raised by handler, do some pre- and post- processing like handling CORS and so on.

Changed in version 0.14: Middleware accepts route exceptions (HTTPNotFound and HTTPMethodNotAllowed).

Debug toolbar

aiohttp_debugtoolbar is very useful library that provides debug toolbar while you’re developing aiohttp.web application.

Install it via pip tool:

$ pip install aiohttp_debugtoolbar

After that attach middleware to your aiohttp.web.Application and call aiohttp_debugtoolbar.setup:

import aiohttp_debugtoolbar
from aiohttp_debugtoolbar import toolbar_middleware_factory

app = web.Application(loop=loop,

Debug toolbar is ready to use. Enjoy!!!