HTTP Communication
Let's learn `requests`, `httpx`, and `aiohttp`
When working with HTTP communication in Python, three popular libraries often come up:
requests
, httpx
, and aiohttp
.
In this post, we’ll dive into the characteristics, usage examples, and differences between these libraries to help you choose the right one for your project.
The main application was written as follows.
1
2
3
4
5
6
7
8
9
10
11
12
# /routers/basic.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/response/case1")
def response_case1():
result = {
"user_name": "DS2Man",
"message": "Hello, World!"
}
return result
Key Differences Comparison
Feature | requests | httpx | aiohttp |
---|---|---|---|
Async Support | Not supported (synchronous only) | Supported via AsyncClient | Supported via ClientSession |
Ease of Use | Very easy | Easy (similar to requests syntax) | Slightly complex (requires async familiarity) |
Performance (Parallel) | Slow (thread-based) | Fast (supports async) | Very fast (designed for async) |
Streaming | Inefficient (waits for full response) | Efficient (supports streaming responses) | Efficient (excellent streaming capabilities) |
WebSocket | Not supported | Not supported | Built-in WebSocket client |
Best Use Case | Simple REST API calls | Mixed sync/async API calls | WebSocket, high-concurrency or async-heavy apps |
As shown in the table above, requests
does not support asynchronous operations, whereas both httpx
and aiohttp
do. Therefore, I will primarily use aiohttp
moving forward. When synchronous code is required, I will explicitly indicate the use of requests
. Please keep this in mind.
requests
requests is considered the standard for making HTTP requests in Python.
It is synchronous and known for its simplicity and readability.
Key Features
- Extremely easy to use
- Synchronous execution (waits for each response)
- Supports sessions, cookies, and various authentication mechanisms
- Extensive documentation and a huge community
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests
from pprint import pprint
def log_response_info(response: requests.Response):
print("\n[Response Info]")
print(f"Status : {response.status_code} {response.reason}")
print(f"URL : {response.url}")
print(f"Method : {response.request.method}")
print(f"Content-Type : {response.headers.get('Content-Type', 'N/A')}")
print(f"Encoding : {response.encoding}")
print(f"Cookies : {response.cookies.get_dict()}")
print(f"Headers:")
pprint(dict(response.headers))
print(f"OK? : {response.ok}")
try:
data = response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
except ValueError:
print(f"\nText Body:\n{response.text}")
try:
url = "http://127.0.0.1:7249/ds2man/basic/response/case1"
response = requests.get(url)
response.raise_for_status()
log_response_info(response)
except requests.HTTPError as e:
print(f"[requests.HTTPError] {e}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Response Info]
Status : 200 OK
URL : http://127.0.0.1:7249/ds2man/basic/response/case1
Method : GET
Content-Type : application/json
Encoding : utf-8
Cookies : {}
Headers:
{'content-length': '48',
'content-type': 'application/json',
'date': 'Fri, 02 May 2025 18:23:50 GMT',
'server': 'uvicorn'}
OK? : True
JSON Body:
type(data): <class 'dict'>
{'message': 'Hello, World!', 'user_name': 'DS2Man'}
aiohttp
aiohttp is a fully asynchronous HTTP client and server framework based on Python’s asyncio
.
It is designed for high-performance scenarios where scalability is critical.
Key Features
- 100% asynchronous
- Provides both HTTP client and server capabilities
- Supports WebSockets
- Fine-grained connection management
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import aiohttp
from pprint import pprint
async def log_response_info(response: aiohttp.ClientResponse):
print("\n[Response Info]")
print(f"Status : {response.status} {response.reason}")
print(f"URL : {response.url}")
print(f"Method : {response.method}")
print(f"Content-Type : {response.content_type}")
print(f"Charset : {response.charset}")
print(f"Cookies : {dict(response.cookies)}")
print(f"Headers:")
pprint(dict(response.headers))
print(f"OK? : {response.ok}")
try:
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
except aiohttp.ContentTypeError:
text = await response.text()
print(f"\nText Body:\n{text}")
try:
url = "http://127.0.0.1:7249/ds2man/basic/response/case1"
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url) as response:
response.raise_for_status()
await log_response_info(response)
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Response Info]
Status : 200 OK
URL : http://127.0.0.1:7249/ds2man/basic/response/case1
Method : GET
Content-Type : application/json
Charset : None
Cookies : {}
Headers:
{'Content-Length': '48',
'Content-Type': 'application/json',
'Date': 'Fri, 02 May 2025 18:27:06 GMT',
'Server': 'uvicorn'}
OK? : True
JSON Body:
type(data): <class 'dict'>
{'message': 'Hello, World!', 'user_name': 'DS2Man'}
What is a response
object?
The response object is returned as a result of request functions such as get()
or post()
using either requests
or aiohttp
, and it contains the content of the server’s response.
requests.Response
vs aiohttp.ClientResponse
Feature | requests.Response (Sync) | aiohttp.ClientResponse (Async) |
---|---|---|
Status | response.status_code , response.reason | response.status , response.reason |
URL | response.url (str) | response.url (yarl.URL) |
Method | response.request.method | response.method |
Content-Type | response.headers["Content-Type"] | response.content_type |
Charset | Parsed from response.encoding | response.charset |
Cookies | response.cookies | response.cookies (SimpleCookie, cast to dict) |
Headers | response.headers (dict) | response.headers (CIMultiDictProxy, castable to dict) |
Success check | response.ok | Not available (manually check 200 <= status < 400 ) |
JSON parsing | response.json() | await response.json() |
Text content | response.text | await response.text() |
Binary content | response.content | await response.read() |
Raise exception | response.raise_for_status() | response.raise_for_status() |
Streaming response | response.iter_content() , response.raw | response.content.iter_chunked() or similar |
File download | with open(...) + response.content | await response.read() + with open(...) |