GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Python aiohttp - asynchronous HTTP in Python Python aiohttplast modified October 30, 2026 Python 3.15 In this article we show how to use the aiohttp module to create asynchronous HTTP clients and servers in Python. With asynchronous programming, we can execute many tasks concurrently in a single thread. See Python async/await and Python asyncio for an introduction. With the aiohttp module, we can create asynchronous HTTP clients and servers in Python. The module also supports WebSockets. It allows to create web servers with pluggable middleware and routing. It is installed with the following command: $ pip install aiohttp Python aiohttp simple clientThe first example is a simple asynchronous HTTP client. simple_client.py
import asyncio
import re
import aiohttp
url = 'https://example.com'
async def main():
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
print('Status:', response.status)
print('Content type:', response.content_type)
data = await response.text()
print('Length:', len(data))
title = re.search(r'<title>(.*?)</title>', data).group(1)
print('Title:', title)
asyncio.run(main())
In the program, we connect to a small web site and retrieve its status and content. async with aiohttp.ClientSession() as session: A client session is created. It holds a connection pool and should be shared by all requests of the program, rather than created for each request. async with session.get(url) as response:
We create a GET request with the print('Status:', response.status)
print('Content type:', response.content_type)
From the response object, we get the status code via the data = await response.text()
Reading the body is an IO operation, so the asyncio.run(main())
The $ python simple_client.py Status: 200 Content type: text/html Length: 577 Title: Example Domain Python aiohttp multiple async requestsIn the next example, we generate multiple asynchronous requests with aiohttp. multiple.py
import asyncio
import time
import aiohttp
urls = ['https://example.com', 'https://httpbin.org/get',
'https://www.python.org', 'https://github.com', 'https://pypi.org']
async def get_status(session, url):
async with session.get(url) as resp:
return resp.status
async def main():
async with aiohttp.ClientSession() as session:
statuses = await asyncio.gather(*(get_status(session, url)
for url in urls))
for url, status in zip(urls, statuses):
print(f'{url:25} {status}')
start = time.perf_counter()
asyncio.run(main())
print(f'elapsed: {time.perf_counter() - start:0.2f} s')
In the program, we create asynchronous HTTP GET requests to multiple URLs. async def get_status(session, url):
async with session.get(url) as resp:
return resp.status
The coroutine sends a request with the shared session and returns the status code. The response is released when the block ends. statuses = await asyncio.gather(*(get_status(session, url)
for url in urls))
We create a coroutine for each URL and unpack them into the arguments of
$ python multiple.py https://example.com 200 https://httpbin.org/get 200 https://www.python.org 200 https://github.com 200 https://pypi.org 200 elapsed: 0.58 s Query parameters and JSON responses
The query_params.py
import asyncio
import aiohttp
async def main():
params = {'name': 'John Doe', 'occupation': 'gardener'}
async with aiohttp.ClientSession() as session:
async with session.get('https://httpbin.org/get',
params=params) as resp:
data = await resp.json()
print(resp.url)
print(data['args'])
asyncio.run(main())
The parameters are encoded in the URL; the space in $ python query_params.py
https://httpbin.org/get?name=John+Doe&occupation=gardener
{'name': 'John Doe', 'occupation': 'gardener'}
Python aiohttp POST request
The post_json.py
import asyncio
import aiohttp
async def main():
user = {'name': 'John Doe', 'email': '[email protected]'}
async with aiohttp.ClientSession() as session:
async with session.post('https://httpbin.org/post',
json=user) as resp:
data = await resp.json()
print(resp.status)
print(data['headers']['Content-Type'])
print(data['json'])
asyncio.run(main())
We send a user object to $ python post_json.py
200
application/json
{'email': '[email protected]', 'name': 'John Doe'}
Errors and timeouts
By default, aiohttp does not raise an exception for error status codes. The
errors.py
import asyncio
import aiohttp
async def fetch(session, url):
try:
async with session.get(url) as resp:
resp.raise_for_status()
return f'{url}: {resp.status}'
except aiohttp.ClientResponseError as e:
return f'{url}: HTTP error {e.status} {e.message}'
except TimeoutError:
return f'{url}: timed out'
async def main():
timeout = aiohttp.ClientTimeout(total=2)
urls = ['https://httpbin.org/status/200', 'https://httpbin.org/status/404',
'https://httpbin.org/delay/5']
async with aiohttp.ClientSession(timeout=timeout) as session:
for res in await asyncio.gather(*(fetch(session, url) for url in urls)):
print(res)
asyncio.run(main())
We request a successful page, a missing page, and a page that responds after five seconds. timeout = aiohttp.ClientTimeout(total=2)
The $ python errors.py https://httpbin.org/status/200: 200 https://httpbin.org/status/404: HTTP error 404 Not Found https://httpbin.org/delay/5: timed out Python aiohttp simple web serverThe following example creates a simple web server. simple_web.py
from aiohttp import web
async def home(request):
return web.Response(text='home page')
app = web.Application()
app.add_routes([web.get('/', home)])
web.run_app(app, host='localhost', port=8080)
The web server has one route that returns a text message. async def home(request):
return web.Response(text='home page')
The app = web.Application()
app.add_routes([web.get('/', home)])
A web application is created. A new route is added with
web.run_app(app, host='localhost', port=8080)
The
We start the server in the background and send requests with
$ python simple_web.py & ======== Running on http://localhost:8080 ======== (Press CTRL+C to quit) $ curl localhost:8080 home page $ curl -i localhost:8080/about HTTP/1.1 404 Not Found Content-Type: text/plain; charset=utf-8 Content-Length: 14 Date: Fri, 30 Oct 2026 17:48:37 GMT Server: Python/3.15 aiohttp/3.14.5 404: Not Found Python aiohttp route decorators
We can use decorators to define routes. The routes are collected in a
decor.py
from aiohttp import web
routes = web.RouteTableDef()
@routes.get('/')
async def home(request):
return web.Response(text='home page')
@routes.get('/about')
async def about(request):
return web.Response(text='<h1>About</h1>', content_type='text/html')
app = web.Application()
app.add_routes(routes)
web.run_app(app, host='localhost', port=8080)
This program creates a web server with two routes. The routes are defined
with the return web.Response(text='<h1>About</h1>', content_type='text/html')
The default content type is $ python decor.py & ======== Running on http://localhost:8080 ======== (Press CTRL+C to quit) $ curl localhost:8080 home page $ curl -i localhost:8080/about HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 14 Date: Fri, 30 Oct 2026 17:48:38 GMT Server: Python/3.15 aiohttp/3.14.5 <h1>About</h1> Python aiohttp JSON response
JSON (JavaScript Object Notation) is a lightweight data-interchange
format. The official Internet media type for JSON is
The send_json.py
from aiohttp import web
routes = web.RouteTableDef()
users = [
{'id': 1, 'name': 'John Doe', 'email': '[email protected]'},
{'id': 2, 'name': 'Roger Roe', 'email': '[email protected]'},
]
@routes.get('/users')
async def get_users(request):
return web.json_response(users)
@routes.get('/users/{id}')
async def get_user(request):
uid = int(request.match_info['id'])
for user in users:
if user['id'] == uid:
return web.json_response(user)
raise web.HTTPNotFound(text=f'user {uid} not found')
@routes.get('/search')
async def search(request):
q = request.query.get('q', '').lower()
found = [user for user in users if q in user['name'].lower()]
return web.json_response(found)
app = web.Application()
app.add_routes(routes)
web.run_app(app, host='localhost', port=8080)
The @routes.get('/users/{id}')
async def get_user(request):
uid = int(request.match_info['id'])
The raise web.HTTPNotFound(text=f'user {uid} not found')
HTTP errors are raised as exceptions. The q = request.query.get('q', '').lower()
Query parameters are in the $ python send_json.py &
======== Running on http://localhost:8080 ========
(Press CTRL+C to quit)
$ curl localhost:8080/users
[{"id": 1, "name": "John Doe", "email": "[email protected]"}, {"id": 2, "name": "Roger Roe", "email": "[email protected]"}]
$ curl localhost:8080/users/2
{"id": 2, "name": "Roger Roe", "email": "[email protected]"}
$ curl -i localhost:8080/users/3
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 16
Date: Fri, 30 Oct 2026 17:48:39 GMT
Server: Python/3.15 aiohttp/3.14.5
user 3 not found
$ curl 'localhost:8080/search?q=roe'
[{"id": 2, "name": "Roger Roe", "email": "[email protected]"}]
Python aiohttp POST handler
The post_handler.py
from aiohttp import web
routes = web.RouteTableDef()
users = []
@routes.post('/users')
async def create_user(request):
try:
data = await request.json()
except ValueError:
raise web.HTTPBadRequest(text='invalid JSON')
if 'name' not in data:
raise web.HTTPBadRequest(text='name is required')
user = {'id': len(users) + 1, 'name': data['name']}
users.append(user)
return web.json_response(user, status=201)
@routes.get('/users')
async def get_users(request):
return web.json_response(users)
app = web.Application()
app.add_routes(routes)
web.run_app(app, host='localhost', port=8080)
The try:
data = await request.json()
except ValueError:
raise web.HTTPBadRequest(text='invalid JSON')
An invalid body raises a $ python post_handler.py &
======== Running on http://localhost:8080 ========
(Press CTRL+C to quit)
$ curl -i -X POST -H 'Content-Type: application/json' -d '{"name": "John Doe"}' localhost:8080/users
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Content-Length: 29
Date: Fri, 30 Oct 2026 17:48:39 GMT
Server: Python/3.15 aiohttp/3.14.5
{"id": 1, "name": "John Doe"}
$ curl -X POST -d '{"name": "Roger Roe"}' localhost:8080/users
{"id": 2, "name": "Roger Roe"}
$ curl -X POST -d '{"email": "[email protected]"}' localhost:8080/users
name is required
$ curl localhost:8080/users
[{"id": 1, "name": "John Doe"}, {"id": 2, "name": "Roger Roe"}]
Python aiohttp middlewareA middleware is a coroutine that wraps every request handler. It can modify the request and the response, or handle errors. middleware.py
import asyncio
import time
from aiohttp import web
def elapsed_ms(start):
return (time.perf_counter() - start) * 1000
@web.middleware
async def timing(request, handler):
start = time.perf_counter()
try:
response = await handler(request)
except web.HTTPException as e:
print(f'{request.method} {request.path} {e.status} {elapsed_ms(start):.1f} ms')
raise
response.headers['X-Process-Time'] = f'{elapsed_ms(start):.1f} ms'
print(f'{request.method} {request.path} {response.status} {elapsed_ms(start):.1f} ms')
return response
async def home(request):
return web.Response(text='home page')
async def slow(request):
await asyncio.sleep(0.2)
return web.Response(text='slow page')
app = web.Application(middlewares=[timing])
app.add_routes([web.get('/', home), web.get('/slow', slow)])
web.run_app(app, host='localhost', port=8080, print=None)
The try:
response = await handler(request)
except web.HTTPException as e:
print(f'{request.method} {request.path} {e.status} {elapsed_ms(start):.1f} ms')
raise
The middleware calls the next handler in the chain. HTTP errors, including
the 404 for a path without a route, are raised as
web.run_app(app, host='localhost', port=8080, print=None)
The $ python middleware.py & $ curl -i localhost:8080/slow HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 X-Process-Time: 200.3 ms Content-Length: 9 Date: Fri, 30 Oct 2026 17:48:40 GMT Server: Python/3.15 aiohttp/3.14.5 slow page GET /slow 200 200.4 ms $ curl localhost:8080/ home page GET / 200 0.1 ms $ curl localhost:8080/missing 404: Not Found GET /missing 404 0.0 ms Server and client in one program
The server_client.py
import asyncio
import aiohttp
from aiohttp import web
async def hello(request):
name = request.query.get('name', 'guest')
return web.json_response({'message': f'Hello {name}!'})
async def main():
app = web.Application()
app.add_routes([web.get('/hello', hello)])
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, 'localhost', 8080)
await site.start()
async with aiohttp.ClientSession('http://localhost:8080') as session:
for name in [None, 'Jane']:
params = {'name': name} if name else {}
async with session.get('/hello', params=params) as resp:
print(resp.status, await resp.json())
await runner.cleanup()
asyncio.run(main())
We start a server, send it two requests with a client session, and shut the server down. runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, 'localhost', 8080) await site.start()
The async with aiohttp.ClientSession('http://localhost:8080') as session:
A session created with a base URL accepts relative paths. await runner.cleanup()
The $ python server_client.py
200 {'message': 'Hello guest!'}
200 {'message': 'Hello Jane!'}
SourceIn this article we have worked with the Python aiohttp module. AuthorMy name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming. List all Python tutorials. |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |