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

@asyncio.coroutine
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(), '0.0.0.0', 8080)
srv = loop.run_until_complete(f)
print('serving on', srv.sockets[0].getsockname())
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

That’s it.

Handler

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)

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']:

@asyncio.coroutine
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.

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

@asyncio.coroutine
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):
        pass

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

    @asyncio.coroutine
    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 = {}

    @asyncio.coroutine
    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


@asyncio.coroutine
def handle_json(request):
    # do json handling

@asyncio.coroutine
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)
aiohttp_jinja2.setup(app,
    loader=jinja2.FileSystemLoader('/path/to/templates/folder'))

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

@aiohttp_jinja2.template('tmpl.jinja2')
def handler(request):
    return {'name': 'Andrew', 'surname': 'Svetlov'}

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 asycio
import time
from aiohttp import web
import aiohttp_session

@asyncio.coroutine
def handler(request):
    session = yield from aiohttp_session.get_session(request)
    session['last_visit'] = time.time()
    return web.Response('OK')

app = web.Application(middlewares=aiohttp_session.session_middleware([
    aiohttp_session.EncryptedCookieStorage(b'Sixteen byte key'))])

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

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:

@asyncio.coroutine
def check_auth(request):
    if request.version != aiohttp.HttpVersion11:
        return

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

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

@asyncio.coroutine
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"
      enctype="multipart/form-data">

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

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

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 Request.post() 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:

@asyncio.coroutine
def store_mp3_view(request):

    data = yield from request.post()

    # 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 = input_file.read()

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

WebSockets

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:

@asyncio.coroutine
def websocket_handler(request):

    ws = web.WebSocketResponse()
    ws.start(request)

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

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

    return ws

You can have only one websocket reader task (which can call yield from ws.receive()) and multiple writer tasks which can only send data asynchronously (by yield from ws.send_str('data') for example).

Exceptions

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:

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

and:

@asyncio.coroutine
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:

Exception
  HTTPException
    HTTPSuccessful
      * 200 - HTTPOk
      * 201 - HTTPCreated
      * 202 - HTTPAccepted
      * 203 - HTTPNonAuthoritativeInformation
      * 204 - HTTPNoContent
      * 205 - HTTPResetContent
      * 206 - HTTPPartialContent
    HTTPRedirection
      * 300 - HTTPMultipleChoices
      * 301 - HTTPMovedPermanently
      * 302 - HTTPFound
      * 303 - HTTPSeeOther
      * 304 - HTTPNotModified
      * 305 - HTTPUseProxy
      * 307 - HTTPTemporaryRedirect
    HTTPError
      HTTPClientError
        * 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
      HTTPServerError
        * 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)

Middlewares

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,
                                   middleware_factory_2])

The most trivial middleware factory example:

@asyncio.coroutine
def middleware_factory(app, handler):
    @asyncio.coroutine
    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).

Fork me on GitHub