Post

HTTP Communication

Let's learn `requests`, `httpx`, and `aiohttp`

HTTP Communication

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

Featurerequestshttpxaiohttp
Async SupportNot supported (synchronous only)Supported via AsyncClientSupported via ClientSession
Ease of UseVery easyEasy (similar to requests syntax)Slightly complex (requires async familiarity)
Performance (Parallel)Slow (thread-based)Fast (supports async)Very fast (designed for async)
StreamingInefficient (waits for full response)Efficient (supports streaming responses)Efficient (excellent streaming capabilities)
WebSocketNot supportedNot supportedBuilt-in WebSocket client
Best Use CaseSimple REST API callsMixed sync/async API callsWebSocket, 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

Featurerequests.Response (Sync)aiohttp.ClientResponse (Async)
Statusresponse.status_code, response.reasonresponse.status, response.reason
URLresponse.url (str)response.url (yarl.URL)
Methodresponse.request.methodresponse.method
Content-Typeresponse.headers["Content-Type"]response.content_type
CharsetParsed from response.encodingresponse.charset
Cookiesresponse.cookiesresponse.cookies (SimpleCookie, cast to dict)
Headersresponse.headers (dict)response.headers (CIMultiDictProxy, castable to dict)
Success checkresponse.okNot available (manually check 200 <= status < 400)
JSON parsingresponse.json()await response.json()
Text contentresponse.textawait response.text()
Binary contentresponse.contentawait response.read()
Raise exceptionresponse.raise_for_status()response.raise_for_status()
Streaming responseresponse.iter_content(), response.rawresponse.content.iter_chunked() or similar
File downloadwith open(...) + response.contentawait response.read() + with open(...)
This post is licensed under CC BY 4.0 by the author.