Learning Platform
Глоссарий Troubleshooting
Урок 07.03 · 25 мин
Начальный
httpxhttp2timeoutsproxysync-client

httpx 0.28: requests с человеческим лицом и HTTP/2

requests — стандарт с 2011 года, и он отличный. Но он устарел в нескольких местах: только HTTP/1.1, не поддерживает asyncio, имеет странный дизайн дефолтного timeout (его нет — запрос может висеть вечно). httpx — современная альтернатива от Encode (создатели Starlette и FastAPI), запущена в 2019. Слоган: “next-generation HTTP client”. Версия 0.28 (актуальная на 2026) — production-ready.

В этом уроке разберём sync API httpx (async будет в следующем уроке), посмотрим что он делает иначе чем requests, и решим вопрос “переходить ли с requests на httpx или нет”. Spoiler: переходить не обязательно, но знать httpx — обязательно, потому что половина новых проектов уже на нём.


Установка

$ pip install httpx==0.28.0

# С поддержкой HTTP/2 (опционально, тянет дополнительные C-зависимости через h2)
$ pip install "httpx[http2]==0.28.0"
>>> import httpx
>>> httpx.__version__
'0.28.0'

Базовый API — почти как requests

import httpx

# Top-level функции (как requests)
r = httpx.get("https://api.example.com/users/1")
r = httpx.post("https://api.example.com/users", json={"name": "Alice"})
r = httpx.put("https://api.example.com/users/1", json={"name": "Alice"})
r = httpx.delete("https://api.example.com/users/1")

# Response API -- почти идентичен requests
r.status_code
r.text
r.content
r.json()
r.headers
r.url

Если ты пришёл с requests — 90 процентов кода скопируется без изменений. Под капотом другая реализация, но API близкий.


httpx.Client() — как Session, только лучше

httpx.Client() — аналог requests.Session(). Connection pool, persistent connections, общие настройки.

import httpx

with httpx.Client(
    headers={"User-Agent": "MyETL/1.0", "Authorization": "Bearer ..."},
    base_url="https://api.example.com",
    timeout=30.0,
) as client:
    r = client.get("/users/1")           # request to https://api.example.com/users/1
    r = client.get("/users", params={"role": "admin"})

Что лучше чем у requests:

1. base_url

requests требует полные URL в каждом запросе. httpx позволяет задать base_url= на клиенте и шлёт относительные пути. Это избавляет от string concat-ов:

# requests
session = requests.Session()
BASE = "https://api.example.com"
r = session.get(f"{BASE}/users/1")
r = session.get(f"{BASE}/orders")
# ...

# httpx
with httpx.Client(base_url="https://api.example.com") as c:
    r = c.get("/users/1")
    r = c.get("/orders")

2. Timeouts — везде по умолчанию

В requests timeout не задаётся по умолчанию — запрос может висеть бесконечно (это часть, за которую разработчики ругают requests). В httpx есть дефолт — 5 секунд:

# httpx -- дефолтный timeout 5 секунд
r = httpx.get("https://slow.example.com")  # бросит TimeoutException через 5с

# requests -- без явного timeout
r = requests.get("https://slow.example.com")  # будет висеть пока сервер не закроет

И httpx позволяет настраивать каждую фазу отдельно:

import httpx

# Tuple-based -- четыре фазы
timeout = httpx.Timeout(
    connect=5.0,    # время на TCP+TLS handshake
    read=30.0,      # время на чтение ответа
    write=10.0,     # время на отправку body
    pool=5.0,       # время на получение соединения из pool
)
with httpx.Client(timeout=timeout) as c:
    r = c.get("https://api.example.com/users")

# Простой: один timeout на все фазы
with httpx.Client(timeout=30.0) as c:
    r = c.get("...")

# Отключить timeout совсем (опасно в production)
with httpx.Client(timeout=None) as c:
    r = c.get("...")
TIP

Для DE pipeline-ов разделяй timeout-ы: connect короткий (5с — сервер либо ответит, либо нет), read длинный (30-300с — большой JSON может качаться долго). Это даёт быструю reaction на сетевые проблемы и одновременно терпение к медленным запросам.


HTTP/3 и QUIC: следующий шаг после HTTP/2

HTTP/2 — реальный плюс над requests

requests поддерживает только HTTP/1.1. httpx с http2=True использует HTTP/2 если сервер согласен (ALPN negotiation в TLS):

import httpx

with httpx.Client(http2=True) as c:
    r = c.get("https://www.google.com/")
    print(r.http_version)  # 'HTTP/2'

Что даёт HTTP/2:

  1. Multiplexing: много запросов параллельно через одно TCP-соединение. У HTTP/1.1 — последовательно или несколько TCP параллельно (max 6 на хост в браузерах).
  2. Header compression (HPACK): повторяющиеся headers (User-Agent, Cookie, Authorization) сжимаются. Экономия трафика 5-15 процентов.
  3. Binary framing: более эффективный парсинг.

Реальная разница на 100 параллельных запросах к одному API — не x10 как иногда обещают маркетологи, но 1.3-1.5x в типичных случаях. Если API не поддерживает HTTP/2 (многие internal API на nginx без настройки — не поддерживают), httpx fallback на HTTP/1.1 прозрачно.

import httpx

with httpx.Client(http2=True) as c:
    r = c.get("https://api.example.com/...")
    if r.http_version == "HTTP/2":
        print("Используем HTTP/2")
    else:
        print(f"Fallback на {r.http_version}")
HTTP/1.1 vs HTTP/2: multiplexing запросов
HTTP/1.1 keep-alive: запросы последовательно. Каждый ждёт ответа предыдущего. Pipelining редко поддерживается из-за HOL blocking
req 1TCP connection #1
req 2Ждёт ответ на req 1
req 3Ждёт ответ на req 2
HTTP/2: все запросы параллельно через одно TCP. Streams независимы, multiplexing в обе стороны
req 1Stream 1
req 2Stream 2 параллельно
req 3Stream 3 параллельно
Одно TCP/TLS соединение, multiplex

Proxy: стандартизированный конфиг

requests конфигурирует прокси через dict {"http": "...", "https": "..."} — рудимент urllib. httpx сделал нормальный API:

import httpx

# Один прокси на всё
with httpx.Client(proxy="http://user:[email protected]:8080") as c:
    r = c.get("https://api.example.com/...")

# Разные прокси по схеме
proxies = {
    "http://": "http://proxy1.corp.com:8080",
    "https://": "http://proxy2.corp.com:8080",
}
with httpx.Client(mounts={k: httpx.HTTPTransport(proxy=v) for k, v in proxies.items()}) as c:
    r = c.get("https://api.example.com/...")

В корпоративных средах с обязательным прокси (PaCO, Squid) — это спасение.


raise_for_status — chainable

В requests r.raise_for_status() возвращает None. В httpx — возвращает self, можно делать chain:

# requests
r = requests.get("...")
r.raise_for_status()
data = r.json()

# httpx -- короче
data = httpx.get("...").raise_for_status().json()

# Или внутри chain
print(httpx.get("https://api.example.com/users/1").raise_for_status().json()["name"])

Микро-удобство, но приятно.


Сравнение API: requests vs httpx

Чтоrequestshttpx
Top-level get/postrequests.get(url)httpx.get(url)
Session/Clientrequests.Session()httpx.Client()
Base URLнет (manual concat)base_url=
Default timeoutNone (висит вечно)5 секунд
Tuple timeout(connect, read)httpx.Timeout(connect, read, write, pool)
HTTP/2нетда (http2=True)
Async supportнетда (httpx.AsyncClient)
Type hintspartial (stubs)native PEP 484
raise_for_statusreturns Nonereturns self (chainable)
Proxy configproxies={} dictproxy= или mounts=
Cookies APIRequestsCookieJarhttpx.Cookies
Hooksчерез session.hooksчерез event_hooks={...}
Streamingiter_contentiter_bytes, iter_text, iter_lines
Default User-Agentpython-requests/2.34python-httpx/0.28

Hooks в httpx

Похоже на requests, но через event_hooks параметр:

import httpx

def log_request(request):
    print(f"-> {request.method} {request.url}")

def log_response(response):
    print(f"<- {response.status_code} {response.url}")

with httpx.Client(
    event_hooks={
        "request": [log_request],
        "response": [log_response],
    }
) as c:
    r = c.get("https://api.example.com/...")

Заметь — hooks принимают список callable. И есть отдельные хуки на request (до отправки) и response (после получения).


Когда httpx vs когда requests

Выбирай httpx если:

  • Нужен async/await (FastAPI, asyncio-pipeline).
  • Хочешь HTTP/2.
  • Нужен base_url, нативные type hints, лучшие timeouts.
  • Начинаешь новый проект — там нет смысла тащить requests.

Оставайся на requests если:

  • Существующий код, который работает — миграция не оправдана.
  • Команда привыкла к requests, нет необходимости перестраиваться.
  • Зависимость от specific requests-плагина (есть auth plugins для AWS, OAuth, которые не портированы на httpx).

Оба знай:

  • В indústrии 60 процентов кода ещё на requests, 40 процентов уже на httpx.
  • httpx читается легче (modern Python), но requests — стандарт legacy.
NOTE

httpx не пытается быть “ещё одной библиотекой”. Авторы прямо пишут в README: “httpx это эволюция requests для современной экосистемы Python (asyncio, HTTP/2, type hints)”. Если завтра requests добавит async и HTTP/2 — конкуренция исчезнет. Но requests этого почти не делает (последние major-релизы — багфиксы).


Попробуй сам

import httpx
import time

# 1. Basic GET с timeout-ом
with httpx.Client(timeout=10.0) as c:
    r = c.get("https://jsonplaceholder.typicode.com/users/1")
    r.raise_for_status()
    print(r.json())

# 2. Base URL -- без копипасты
with httpx.Client(
    base_url="https://jsonplaceholder.typicode.com",
    timeout=10.0,
) as c:
    users = c.get("/users").raise_for_status().json()
    posts = c.get("/posts").raise_for_status().json()
    print(f"Users: {len(users)}, Posts: {len(posts)}")

# 3. Сравнение: HTTP/1.1 vs HTTP/2 latency на одной странице
# Не каждый сервер поддерживает HTTP/2, но Google -- да
for use_h2 in [False, True]:
    start = time.time()
    with httpx.Client(http2=use_h2) as c:
        for _ in range(20):
            r = c.get("https://www.google.com/")
    print(f"http2={use_h2}: {time.time() - start:.2f}s, version={r.http_version}")

# 4. Структурированные timeouts
timeout = httpx.Timeout(connect=2.0, read=30.0, write=5.0, pool=5.0)
with httpx.Client(timeout=timeout) as c:
    try:
        r = c.get("https://httpbin.org/delay/35")  # ждёт 35 секунд
    except httpx.ReadTimeout:
        print("Read timeout (но не connect -- сервер отвечает быстро)")

Перепиши свой ETL-скрипт с requests на httpx. Заметь разницу в количестве кода — особенно если у тебя был большой BASE_URL = "..." и f"{BASE_URL}/..." везде.


Killer takeaway

httpx 0.28 — современная альтернатива requests от создателей FastAPI. API близок к requests (большинство кода скопируется), но: дефолтный timeout 5с (requests = бесконечный), structured timeouts (connect/read/write/pool отдельно), base_url у Client, HTTP/2 через http2=True, native type hints, async API в одном пакете. httpx.Client() = requests.Session() с лучшим API. HTTP/2 даёт реальный 1.3-1.5x ускорение на параллельных запросах если сервер поддерживает. Если начинаешь новый проект — выбирай httpx. Если есть работающий код на requests — миграция не оправдана. Знай оба: 60/40 в индустрии.

Проверка знанийKnowledge check
ОтветAnswer

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. В чём ключевые отличия httpx 0.28 от requests 2.34?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5