An AsyncIO HTTP server
The full implementation of the server is as follows, and it is also available in the GitHub repository (https://github.com/PacktPublishing/Hands-On-Reactive-Programming-with-Python) for this book in the http_echo.py script:
import asyncio
from aiohttp import web
async def echo_handler(request):
response = web.Response(text=request.match_info['what'])
await response.prepare(request)
return response
async def start_server(runner):
await runner.setup()
site = web.TCPSite(runner, 'localhost', 8080)
await site.start()
app = web.Application()
app.router.add_route('GET', '/echo/{what}', echo_handler)
runner = web.AppRunner(app)
loop = asyncio.get_event_loop()
loop.create_task(start_server(runner))
loop.run_forever()
loop.close()
To test it, it must be executed from a terminal, and a client can be executed from another terminal. The curl tool is an easy way to test this server:
$ curl http://localhost:8080/echo/hello
hello
$ curl http://localhost:8080/echo/foo
foo
The whole code fits into less than 25 lines of code, despite not using the high-level APIs of aiohttp! The header of the script contains two imports: one for asyncio, and one for the web module of aiohttp. The web module contains all the APIs needed for this server.
Let's overlook the two coroutines for now and jump directly to the initialization of the server. The first part of the initialization consists of creating a web application. This is an object that is used to configure the HTTP routes of the application. Just after that, a route is created and added to the application. This route listens to HTTP GET requests on the path /echo. The remaining part of the path ({what}) is called a variable resource in aiohttp. This allows you to do pattern matching on the URI path. In this case, the last part of the URI path will be put in a variable named what. This is the value that will be returned by the server. The last line of this block creates a runner for this application. A runner is an object used to run an application.
The last block of the script retrieves the default event loop, creates a new task to start the server, and then runs forever. This program will never end unless it is interrupted manually (for example, by typing Ctrl + C on the terminal). The create_task method schedules the execution of a coroutine. It wraps the coroutine in future and returns a task object. This task object can be used to interrupt the action if needed, or to ensure that it is completed. In this implementation, the result of create_task is not used.
Let's now look at the implementation of the two coroutines:
- The first coroutine, echo_handler, is the implementation of the request handler. Remember that a coroutine is recognizable from its definition, which starts with the async keyword. It is called each time a new HTTP request is received. This coroutine first creates a response, and then fills the content of the response with some text. The content of the text is retrieved from the value of the what key of the match_info dictionary of the request. This value is the result of a parsing of the request path that was declared in the route. All variable resources declared in a route are accessible in the match_info dictionary of the requests. Then the response is prepared. This is a required step before returning it as the result of the coroutine. The response returned by the coroutine is used to construct the HTTP response of the associated request.
- The second coroutine, start_server, is the cone that starts the server. It first initializes the runner, passed as a parameter of the coroutine. Then an instance of the HTTP server is created, listening on localhost port 8080. This is a TCPSite object in aiohttp. Finally, the server is started.
This whole implementation is fully non-blocking. Note that all functions of the aiohttp web module are in fact coroutines. They are all called with the await prefix. All these await instances are locations in which the coroutines can be interrupted and resumed later.