.. currentmodule:: aiohttp.web
In order to implement a web server, first create a :ref:`request handler <aiohttp-web-handler>`.
A request handler must be a :ref:`coroutine <coroutine>` that accepts a :class:`Request` instance as its only parameter and returns a :class:`Response` instance:
from aiohttp import web async def hello(request): return web.Response(text="Hello, world")
Next, create an :class:`Application` instance and register the request handler on a particular HTTP method and path:
app = web.Application() app.add_routes([web.get('/', hello)])
After that, run the application by :func:`run_app` call:
web.run_app(app)
That's it. Now, head over to http:https://localhost:8080/
to see the results.
Alternatively if you prefer route decorators create a route table and register a :term:`web-handler`:
routes = web.RouteTableDef() @routes.get('/') async def hello(request): return web.Response(text="Hello, world") app = web.Application() app.add_routes(routes) web.run_app(app)
Both ways essentially do the same work, the difference is only in your
taste: do you prefer Django style with famous urls.py
or Flask
with shiny route decorators.
aiohttp server documentation uses both ways in code snippets to emphasize their equality, switching from one style to another is very trivial.
.. seealso:: :ref:`aiohttp-web-graceful-shutdown` section explains what :func:`run_app` does and how to implement complex server initialization/finalization from scratch. :ref:`aiohttp-web-app-runners` for more handling more complex cases like *asynchronous* web application serving and multiple hosts support.
:mod:`aiohttp.web` implements a basic CLI for quickly serving an :class:`Application` in development over TCP/IP:
$ python -m aiohttp.web -H localhost -P 8080 package.module:init_func
package.module:init_func
should be an importable :term:`callable` that
accepts a list of any non-parsed command-line arguments and returns an
:class:`Application` instance after setting it up:
def init_func(argv): app = web.Application() app.router.add_get("/", index_handler) return app
A request handler must be a :ref:`coroutine<coroutine>` that accepts a :class:`Request` instance as its only argument and returns a :class:`StreamResponse` derived (e.g. :class:`Response`) instance:
async def handler(request): return web.Response()
Handlers are setup to handle requests by registering them with the :meth:`Application.add_routes` on a particular route (HTTP method and path pair) using helpers like :func:`get` and :func:`post`:
app.add_routes([web.get('/', handler), web.post('/post', post_handler), web.put('/put', put_handler)])
Or use route decorators:
routes = web.RouteTableDef() @routes.get('/') async def get_handler(request): ... @routes.post('/post') async def post_handler(request): ... @routes.put('/put') async def put_handler(request): ... app.add_routes(routes)
Wildcard HTTP method is also supported by :func:`route` or :meth:`RouteTableDef.route`, allowing a handler to serve incoming requests on a path having any HTTP method:
app.add_routes([web.route('*', '/path', all_handler)])
The HTTP method can be queried later in the request handler using the :attr:`Request.method` property.
By default endpoints added with GET
method will accept
HEAD
requests and return the same response headers as they would
for a GET
request. You can also deny HEAD
requests on a route:
web.get('/', handler, allow_head=False)
Here handler
won't be called on HEAD
request and the server
will respond with 405: Method Not Allowed
.
Internally routes are served by :attr:`Application.router` (:class:`UrlDispatcher` instance).
The router is a list of resources.
Resource is an entry in route table which corresponds to requested URL.
Resource in turn has at least one route.
Route corresponds to handling HTTP method by calling web handler.
Thus when you add a route the resouce object is created under the hood.
The library implementation merges all subsequent route additions for the same path adding the only resource for all HTTP methods.
Consider two examples:
app.add_routes([web.get('/path1', get_1), web.post('/path1', post_1), web.get('/path2', get_2), web.post('/path2', post_2)]
and:
app.add_routes([web.get('/path1', get_1), web.get('/path2', get_2), web.post('/path2', post_2), web.post('/path1', post_1)]
First one is optimized. You have got the idea.
Resource may have variable path also. For instance, a resource with
the path '/a/{name}/c'
would match all incoming requests with
paths such as '/a/b/c'
, '/a/1/c'
, and '/a/etc/c'
.
A variable part is specified in the form {identifier}
, where the
identifier
can be used later in a
:ref:`request handler <aiohttp-web-handler>` to access the matched value for
that part. This is done by looking up the identifier
in the
:attr:`Request.match_info` mapping:
@routes.get('/{name}') async def variable_handler(request): return web.Response( text="Hello, {}".format(request.match_info['name']))
By default, each part matches the regular expression [^{}/]+
.
You can also specify a custom regex in the form {identifier:regex}
:
web.get(r'/{name:\d+}', handler)
Routes can also be given a name:
@routes.get('/root', name='root') async def handler(request): ...
Which can then be used to access and build a URL for that resource later (e.g. in a :ref:`request handler <aiohttp-web-handler>`):
url = request.app.router['root'].url_for().with_query({"a": "b", "c": "d"}) assert url == URL('/root?a=b&c=d')
A more interesting example is building URLs for :ref:`variable resources <aiohttp-web-variable-handler>`:
app.router.add_resource(r'/{user}/info', name='user-info')
In this case you can also pass in the parts of the route:
url = request.app.router['user-info'].url_for(user='john_doe') url_with_qs = url.with_query("a=b") assert url_with_qs == '/john_doe/info?a=b'
As discussed above, :ref:`handlers <aiohttp-web-handler>` can be first-class coroutines:
async def hello(request): return web.Response(text="Hello, world") app.router.add_get('/', hello)
But sometimes it's convenient to group logically similar handlers into a Python class.
Since :mod:`aiohttp.web` does not dictate any implementation details, application developers can organize handlers in classes if they so wish:
class Handler: def __init__(self): pass async def handle_intro(self, request): return web.Response(text="Hello, world") async def handle_greeting(self, request): name = request.match_info.get('name', "Anonymous") txt = "Hello, {}".format(name) return web.Response(text=txt) handler = Handler() app.add_routes([web.get('/intro', handler.handle_intro), web.get('/greet/{name}', handler.handle_greeting)])
:mod:`aiohttp.web` has support for class based views.
You can derive from :class:`View` and define methods for handling http requests:
class MyView(web.View): async def get(self): return await get_resp(self.request) async def post(self): return await post_resp(self.request)
Handlers should be coroutines accepting self only and returning response object as regular :term:`web-handler`. Request object can be retrieved by :attr:`View.request` property.
After implementing the view (MyView
from example above) should be
registered in application's router:
web.view('/path/to', MyView)
or:
@routes.view('/path/to') class MyView(web.View): ...
Example will process GET and POST requests for /path/to but raise 405 Method not allowed exception for unimplemented HTTP methods.
All registered resources in a router can be viewed using the :meth:`UrlDispatcher.resources` method:
for resource in app.router.resources(): print(resource)
A subset of the resources that were registered with a name can be viewed using the :meth:`UrlDispatcher.named_resources` method:
for name, resource in app.router.named_resources().items(): print(name, resource)
Code examples shown above use imperative style for adding new
routes: they call app.router.add_get(...)
etc.
There are two alternatives: route tables and route decorators.
Route tables look like Django way:
async def handle_get(request): ... async def handle_post(request): ... app.router.add_routes([web.get('/get', handle_get), web.post('/post', handle_post),
The snippet calls :meth:`~aiohttp.web.UrlDispather.add_routes` to register a list of route definitions (:class:`aiohttp.web.RouteDef` instances) created by :func:`aiohttp.web.get` or :func:`aiohttp.web.post` functions.
.. seealso:: :ref:`aiohttp-web-route-def` reference.
Route decorators are closer to Flask approach:
routes = web.RouteTableDef() @routes.get('/get') async def handle_get(request): ... @routes.post('/post') async def handle_post(request): ... app.router.add_routes(routes)
It is also possible to use decorators with class-based views:
routes = web.RouteTableDef() @routes.view("/view") class MyView(web.View): async def get(self): ... async def post(self): ... app.router.add_routes(routes)
The example creates a :class:`aiohttp.web.RouteTableDef` container first.
The container is a list-like object with additional decorators :meth:`aiohttp.web.RouteTableDef.get`, :meth:`aiohttp.web.RouteTableDef.post` etc. for registering new routes.
After filling the container :meth:`~aiohttp.web.UrlDispather.add_routes` is used for adding registered route definitions into application's router.
.. seealso:: :ref:`aiohttp-web-route-table-def` reference.
All tree ways (imperative calls, route tables and decorators) are equivalent, you could use what do you prefer or even mix them on your own.
.. versionadded:: 2.3
It is a common case to return JSON data in response, :mod:`aiohttp.web` provides a shortcut for returning JSON -- :func:`aiohttp.web.json_response`:
async def handler(request): data = {'some': 'data'} return web.json_response(data)
The shortcut method returns :class:`aiohttp.web.Response` instance so you can for example set cookies before returning it from handler.
Often you need a container for storing user data across requests. The concept is usually called a session.
:mod:`aiohttp.web` has no built-in concept of a session, however, there is a third-party library, :mod:`aiohttp_session`, that adds session support:
import asyncio import time import base64 from cryptography import fernet from aiohttp import web from aiohttp_session import setup, get_session, session_middleware from aiohttp_session.cookie_storage import EncryptedCookieStorage async def handler(request): session = await get_session(request) last_visit = session['last_visit'] if 'last_visit' in session else None text = 'Last visited: {}'.format(last_visit) return web.Response(text=text) async def make_app(): app = web.Application() # secret_key must be 32 url-safe base64-encoded bytes fernet_key = fernet.Fernet.generate_key() secret_key = base64.urlsafe_b64decode(fernet_key) setup(app, EncryptedCookieStorage(secret_key)) app.add_routes([web.get('/', handler)]) return app web.run_app(make_app())
HTTP Forms are supported out of the box.
If form's method is "GET"
(<form method="get">
) use
:attr:`Request.query` for getting form data.
To access form data with "POST"
method use
:meth:`Request.post` or :meth:`Request.multipart`.
:meth:`Request.post` accepts both
'application/x-www-form-urlencoded'
and 'multipart/form-data'
form's data encoding (e.g. <form enctype="multipart/form-data">
).
It stores files data in temporary directory. If client_max_size is
specified post raises ValueError exception.
For efficiency use :meth:`Request.multipart`, It is especially effective
for uploading large files (:ref:`aiohttp-web-file-upload`).
Values submitted by the following form:
<form action="/login" method="post" accept-charset="utf-8"
enctype="application/x-www-form-urlencoded">
<label for="login">Login</label>
<input id="login" name="login" type="text" value="" autofocus/>
<label for="password">Password</label>
<input id="password" name="password" type="password" value=""/>
<input type="submit" value="login"/>
</form>
could be accessed as:
async def do_login(request): data = await request.post() login = data['login'] password = data['password']
:mod:`aiohttp.web` has built-in support for handling files uploaded from the browser.
First, make sure that the HTML <form>
element has its enctype attribute
set to enctype="multipart/form-data"
. As an example, here is a form that
accepts an MP3 file:
<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>
Then, in the :ref:`request handler <aiohttp-web-handler>` you can access the file input field as a :class:`FileField` instance. :class:`FileField` is simply a container for the file as well as some of its metadata:
async def store_mp3_handler(request): # WARNING: don't do that if you plan to receive large files! data = await request.post() mp3 = data['mp3'] # .filename contains the name of the file in string format. filename = mp3.filename # .file contains the actual file data that needs to be stored somewhere. mp3_file = data['mp3'].file content = mp3_file.read() return web.Response(body=content, headers=MultiDict( {'CONTENT-DISPOSITION': mp3_file}))
You might have noticed a big warning in the example above. The general issue is that :meth:`Request.post` reads the whole payload in memory, resulting in possible :abbr:`OOM (Out Of Memory)` errors. To avoid this, for multipart uploads, you should use :meth:`Request.multipart` which returns a :ref:`multipart reader <aiohttp-multipart>`:
async def store_mp3_handler(request): reader = await request.multipart() # /!\ Don't forget to validate your inputs /!\ # reader.next() will `yield` the fields of your form field = await reader.next() assert field.name == 'name' name = await field.read(decode=True) field = await reader.next() assert field.name == 'mp3' filename = field.filename # You cannot rely on Content-Length if transfer is chunked. size = 0 with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f: while True: chunk = await field.read_chunk() # 8192 bytes by default. if not chunk: break size += len(chunk) f.write(chunk) return web.Response(text='{} sized of {} successfully stored' ''.format(filename, size))
:mod:`aiohttp.web` supports WebSockets out-of-the-box.
To setup a WebSocket, create a :class:`WebSocketResponse` in a :ref:`request handler <aiohttp-web-handler>` and then use it to communicate with the peer:
async def websocket_handler(request): ws = web.WebSocketResponse() await ws.prepare(request) async for msg in ws: if msg.type == aiohttp.WSMsgType.TEXT: if msg.data == 'close': await ws.close() else: await ws.send_str(msg.data + '/answer') elif msg.type == aiohttp.WSMsgType.ERROR: print('ws connection closed with exception %s' % ws.exception()) print('websocket connection closed') return ws
The handler should be registered as HTTP GET processor:
app.add_routes([web.get('/ws', websocket_handler)])
To redirect user to another endpoint - raise :class:`HTTPFound` with an absolute URL, relative URL or view name (the argument from router):
raise web.HTTPFound('/redirect')
The following example shows redirect to view named 'login' in routes:
async def handler(request): location = request.app.router['login'].url_for() raise web.HTTPFound(location=location) router.add_get('/handler', handler) router.add_get('/login', login_handler, name='login')
Example with login validation:
@aiohttp_jinja2.template('login.html') async def login(request): if request.method == 'POST': form = await request.post() error = validate_login(form) if error: return {'error': error} else: # login form is valid location = request.app.router['index'].url_for() raise web.HTTPFound(location=location) return {} app.router.add_get('/', index, name='index') app.router.add_get('/login', login, name='login') app.router.add_post('/login', login, name='login')
:mod:`aiohttp.web` defines a set of exceptions for every HTTP status code.
Each exception is a subclass of :class:`~HTTPException` and relates to a single HTTP status code:
async def handler(request): raise aiohttp.web.HTTPFound('/redirect')
Warning
Returning :class:`~HTTPException` or its subclasses is deprecated and will be removed in subsequent aiohttp versions.
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 * 308 - HTTPPermanentRedirect 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 * 421 - HTTPMisdirectedRequest * 422 - HTTPUnprocessableEntity * 424 - HTTPFailedDependency * 426 - HTTPUpgradeRequired * 428 - HTTPPreconditionRequired * 429 - HTTPTooManyRequests * 431 - HTTPRequestHeaderFieldsTooLarge * 451 - HTTPUnavailableForLegalReasons HTTPServerError * 500 - HTTPInternalServerError * 501 - HTTPNotImplemented * 502 - HTTPBadGateway * 503 - HTTPServiceUnavailable * 504 - HTTPGatewayTimeout * 505 - HTTPVersionNotSupported * 506 - HTTPVariantAlsoNegotiates * 507 - HTTPInsufficientStorage * 510 - HTTPNotExtended * 511 - HTTPNetworkAuthenticationRequired
All HTTP exceptions have the same constructor signature:
HTTPNotFound(*, headers=None, reason=None, body=None, text=None, content_type=None)
If not directly specified, headers will be added to the default response headers.
Classes :class:`HTTPMultipleChoices`, :class:`HTTPMovedPermanently`, :class:`HTTPFound`, :class:`HTTPSeeOther`, :class:`HTTPUseProxy`, :class:`HTTPTemporaryRedirect` have the following constructor signature:
HTTPFound(location, *, headers=None, reason=None, body=None, text=None, content_type=None)
where location is value for Location HTTP header.
:class:`HTTPMethodNotAllowed` is constructed by providing the incoming unsupported method and list of allowed methods:
HTTPMethodNotAllowed(method, allowed_methods, *, headers=None, reason=None, body=None, text=None, content_type=None)