Framework
aio-libs/aiohttp avatar
aio-libs/aiohttp

aiohttp: an async HTTP client and server for asyncio, reviewed for adoption

Asynchronous HTTP client/server framework for asyncio and Python

16,553 stars2,419 forksPythonApache-2.0

At a glance

What is it?
aiohttp is the long-running asyncio HTTP framework that ships both a client and a server, WebSocket support on both sides, and a middleware-based router. It installs from PyPI, needs Python 3.10 or newer, and its last push to master was on 2026-09-19.
Who is it for?
Adopt aiohttp when you are already inside an asyncio program and want the client and the server to share one event loop, one session model and one WebSocket API, and when a dependency tree that pulls in multidict, yarl and frozenlist is acceptable. Do not adopt it for a blocking script that makes one request, and do not adopt it expecting a sync-style API: the README's own client example is five indented lines and the project links a page explaining why.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap aiohttp fills between requests and a hand-rolled asyncio loop

The problem is not "make an HTTP request in Python". requests already does that, and the README anticipates the reaction directly: it asks whether you are coming from requests and links to a page explaining why aiohttp needs so many lines. The problem aiohttp addresses is what happens when the calling code is already asynchronous and the work is concurrent. A blocking client occupies the thread it runs on, so a program that has to fan out to many endpoints either spawns threads or accepts serial latency. aiohttp is built on asyncio and exposes the client and the server side of HTTP from the same package, so a process that both serves and calls out does it on one event loop.

Who it is for: engineers writing asyncio services, crawlers, proxies, WebSocket endpoints, or internal APIs where the request handler already has to await something else. The project's classifiers list Development Status 5 (Production/Stable) and Framework :: AsyncIO, and the README states it supports both the client and the server side of the HTTP protocol plus client and server WebSockets, with a web server that provides middleware and pluggable routing. That combination, one library covering both roles, is the reason to look at it rather than assembling a client and a server from separate projects.

How the client session, the server application and the WebSocket layer fit together

Two objects carry most of the design. On the client side it is ClientSession. The README's example opens a session with an async context manager and then opens a request inside it. That nesting is not decoration: the session owns the connection pooling and the lifecycle, and the request is a context manager because the response body is streamed and the connection is only released when the block exits. This is why aiohttp code looks longer than requests code, and it is the mechanism the project points readers at when they ask about the line count.

On the server side the object is web.Application. Routes are registered with app.add_routes, and the README's server example shows three at once: web.get('/', handle), web.get('/echo', wshandle) and web.get('/{name}', handle). The /{name} pattern is matched through request.match_info, which the handler reads with request.match_info.get('name', "Anonymous"), so path parameters are extracted by the router rather than parsed by hand. Handlers return web.Response; the README's example returns web.Response(text=text).

WebSockets reuse the same handler shape. wshandle prepares a web.WebSocketResponse, awaits ws.prepare(request), then iterates with async for msg in ws. The message type is dispatched on web.WSMsgType: text messages go back out via ws.send_str, binary via ws.send_bytes, and a close message breaks the loop. The README's claim that this "avoids Callback Hell" is really a statement about this loop: you read messages in the same coroutine that owns the socket instead of registering callbacks per event. In the repository layout, examples/client_ws.py and examples/web_srv_route_deco.py sit alongside examples/retry_middleware.py and examples/logging_middleware.py, which is where the middleware story is documented by example rather than in the README.

Installing aiohttp and running a first request and server

The package is on PyPI. The README does not spell out an install command, but the project metadata names the distribution aiohttp and the badge links to https://pypi.org/project/aiohttp, so the standard install is a pip install. Python 3.10 or newer is required; setup.py exits with a runtime error below that version, and pyproject.toml sets requires-python = ">= 3.10".

bash
python -m pip install aiohttp

After that, the README's client example is the smallest thing worth running. It opens a ClientSession, issues a GET against https://python.org, and prints the status, the content-type header and the first characters of the body.

python
import aiohttp
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://python.org') as response:
            print("Status:", response.status)
            print("Content-type:", response.headers['content-type'])
            html = await response.text()
            print("Body:", html[:15], "...")

asyncio.run(main())

The README states this prints Status: 200, Content-type: text/html; charset=utf-8 and Body: <!doctype html> ... . If you see an exception about the event loop instead, you are calling the coroutine without asyncio.run or from inside an already-running loop.

The server side is the same shape. The README ships this file as examples/server_simple.py, and it defines a plain handler, a WebSocket echo handler, and the routes in one application object.

python
from aiohttp import web

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.add_routes([web.get('/', handle), web.get('/{name}', handle)])

if __name__ == '__main__':
    web.run_app(app)

web.run_app is the entry point the README uses; it starts the server on the event loop. Visiting / returns "Hello, Anonymous" and visiting /alice returns "Hello, alice", because the second route captures the path segment into match_info.

Where aiohttp is the wrong tool: sync code, one-off scripts, and the pure-Python path

The clearest limitation is the one the README admits by linking away from it: aiohttp is not a drop-in replacement for requests. If your code is synchronous, wrapping every call in asyncio.run buys you nothing but indentation, and mixing a ClientSession into a thread that has no running loop fails. For a script that fetches one URL and exits, a blocking client is simpler and the concurrency aiohttp offers is unused.

The second constraint is the build. aiohttp ships C extensions, and setup.py shows what that costs. Building from a git clone requires the vendored llhttp submodule: if vendor/llhttp/README.md is missing, setup.py prints "Install submodules when building from git clone", suggests git submodule update --init, and exits with status 2. There is an escape hatch, AIOHTTP_NO_EXTENSIONS, and it is forced on for any implementation that is not CPython, but the pure-Python path is a different performance profile from the compiled one. If you are on a platform without wheels, or vendoring aiohttp into an application that forbids a compiler at install time, plan for that.

The third is the dependency tree. pyproject.toml lists aiohappyeyeballs >= 2.5.0, aiosignal >= 1.4.0, frozenlist >= 1.1.1, multidict >=4.5, < 7.0, propcache >= 0.2.0, yarl >= 1.25.1, < 2.0, plus typing_extensions and async-timeout on older interpreters. That is a real footprint for a library you may only want for a handful of outbound calls. The optional speedups extra adds aiodns and aiofastnet, and the README separately calls aiodns "highly recommended for sake of speed", which means the default install is not the fastest configuration the project knows how to build.

aiohttp against httpx, requests and FastAPI: different answers to different questions

The comparison people search for most is aiohttp against httpx. The structural difference is that httpx offers one client API that works synchronously and asynchronously, while aiohttp is asynchronous only and ships a server as well. If you need the same call sites to run in a sync script and in an async service, that single-API property matters more than anything else in the comparison, and aiohttp cannot give it to you. If you need the server half in the same dependency, httpx does not provide it. The README does not make this comparison, and the project publishes no benchmark numbers of its own; it points to a community-maintained benchmark list on the asyncio wiki instead.

Against requests the difference is the programming model, not the feature list. requests blocks; aiohttp awaits. Porting is a rewrite of the call sites, which is exactly why the README links a page titled "why we need so many lines" for people arriving from requests.

Against FastAPI the difference is scope. FastAPI is a framework layered on top of an ASGI server for building APIs, with validation and dependency injection as its selling points. aiohttp's server is lower level: an Application, routes registered with add_routes, handlers returning web.Response, and middleware you write yourself. The repository's examples directory reflects that, with separate files for background tasks, basic auth middleware, combined middleware, logging middleware, retry middleware, token refresh middleware and header rewriting. Nothing there validates a request body for you. If you want schema validation and generated docs, aiohttp's web module is the wrong layer; if you want a small server inside a process that is already asynchronous, it is the right one.

Maintenance, licensing and what upgrading actually involves

The repository is not archived, and the last push to master was on 2026-09-19, two days before this review, so the codebase is being touched. Release cadence is visible in the tags: v3.14.1 on 2026-06-07, v3.14.2 on 2026-07-20, and v3.14.3 on 2026-07-22. The 3.14 line moved twice in three days in July, which is worth knowing if you pin loosely: patch releases arrive close together.

The upgrade cost is dominated by the Python floor, not by the HTTP API. pyproject.toml sets requires-python = ">= 3.10" and setup.py raises "aiohttp 4.x requires Python 3.10+" below it, so a runtime older than 3.10 is a hard stop rather than a warning. The dependency pins are the second lever: multidict is capped below 7.0 and yarl below 2.0, so a major bump in either is a coordinated upgrade, not something pip resolves quietly on a Friday. The version field is dynamic, generated at build time, so the number you see in pip show comes from the build rather than a literal in pyproject.toml.

On licensing: pyproject.toml declares license = {text = "Apache-2.0 AND MIT"}, and the repository carries LICENSE.txt. The Apache-2.0 identifier in the project metadata is not the whole story, and the two-licence expression is what a compliance review will want to see. This is a description of what the files say, not legal advice; the repository also carries SECURITY_EXTRA.md and THREAT_MODEL.md, and the README says nothing about either.

Editorial conclusion

Adopt aiohttp when you are already inside an asyncio program and want the client and the server to share one event loop, one session model and one WebSocket API, and when a dependency tree that pulls in multidict, yarl and frozenlist is acceptable. Do not adopt it for a blocking script that makes one request, and do not adopt it expecting a sync-style API: the README's own client example is five indented lines and the project links a page explaining why. Before you commit, verify the Python floor against your runtime (requires-python is >= 3.10), check whether you get the C extensions or the pure-Python path on your platform, and read THREAT_MODEL.md and SECURITY_EXTRA.md in the repository root, since the README itself says nothing about the security posture of the server side.

Frequently asked questions

What is aiohttp used for?

The README describes it as an async HTTP client and server framework for asyncio, supporting both sides of the HTTP protocol and both client and server WebSockets, with a web server that provides middleware and pluggable routing. In practice that covers outbound concurrent requests, WebSocket endpoints, and small internal HTTP services running in the same process.

Is aiohttp built into Python?

No. It is a separate distribution published on PyPI, and the project metadata lists its own dependencies (multidict, yarl, frozenlist, aiohappyeyeballs, aiosignal and propcache) rather than relying on the standard library alone. Only asyncio, which aiohttp is built on, ships with Python.

How do I install aiohttp?

Install it from PyPI with pip. Python 3.10 or newer is required, since pyproject.toml sets requires-python = ">= 3.10" and setup.py exits with an error below that version.

What is aiohttp ClientSession?

ClientSession is the object the README's client example opens with an async context manager before issuing a request inside it. It is the client-side entry point that owns the connection lifecycle, which is why the request is nested inside the session block rather than called standalone.

Is aiohttp faster than httpx?

The repository does not publish a comparison or any benchmark numbers of its own. The README points readers to a community-maintained benchmark list on the asyncio wiki instead, and the optional speedups extra (aiodns, aiofastnet) is described as an add-on rather than the default install.

Is aiohttp better than requests?

They solve the same request problem under different models. requests blocks the calling thread; aiohttp is built on asyncio and requires the calling code to be asynchronous, which is why the README links a page explaining the extra lines for people arriving from requests. Choose based on whether your program already runs an event loop.

Official sources

  1. aio-libs/aiohttp on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aio-libs-aiohttp.svg)](https://hysenlabs.com/projects/aio-libs-aiohttp)