非同期、HTTP/2対応のモダンなライブラリを使いこなそう!
httpx とは?
httpx
は、Python 3 用のモダンで高機能なHTTPクライアントライブラリです。有名な requests
ライブラリにインスパイアされた使いやすいAPIを持ちながら、現代的なWeb開発の要求に応えるための強力な機能を備えています。
主な特徴として、非同期 (async/await) リクエストのサポートと、HTTP/1.1 および HTTP/2 の両プロトコルへの対応が挙げられます。これにより、大量のリクエストを効率的に処理したり、最新のWebサーバーとの通信を最適化したりすることが可能です。
requests
を使ったことがある開発者であれば、比較的スムーズに httpx
へ移行できます。それでいて、より高度な機能やパフォーマンスを求める場合に強力な選択肢となります。
httpx の主な利点
- 非同期サポート: `asyncio`, `trio` といった非同期フレームワークと連携し、高効率なI/O処理を実現。
- HTTP/2 サポート: より高速で効率的な通信プロトコルに対応(オプション)。
- requests互換API: 既存の `requests` ユーザーにとって学習コストが低い。
- 厳格なタイムアウト: 接続、読み取り、書き込み、プールなど、各所でタイムアウトを設定可能。
- 型ヒント完全対応: 静的解析ツールとの相性が良く、開発効率とコード品質を向上。
- WSGI/ASGI アプリケーションへの直接リクエスト: テストなどで便利。
- コマンドラインクライアント: 簡単なリクエストならターミナルから直接実行可能(オプション)。
インストール
httpx
のインストールは pip を使って簡単に行えます。
pip install httpx
HTTP/2 サポートを利用したい場合は、追加の依存関係を含めてインストールします。Brotli圧縮のサポートも同様に追加できます。
# HTTP/2 サポートを追加
pip install httpx[http2]
# HTTP/3 サポートを追加 (httpcore の機能)
# httpx は内部で httpcore を使用しており、httpcore が HTTP/3 をサポートします
pip install httpx[http3]
# Brotli 圧縮サポートを追加
pip install httpx[brotli]
# 複数まとめてインストール
pip install httpx[http2,http3,brotli]
また、コマンドラインから httpx
を使いたい場合は、cli
オプションを追加します。
pip install httpx[cli]
注意: httpx
は Python 3.8 以降が必要です。
基本的な使い方 (同期クライアント)
requests
ライブラリと同様の感覚で、同期的なHTTPリクエストを簡単に送信できます。
GET リクエスト
最も基本的な GET リクエストの例です。
import httpx
# シンプルな GET リクエスト
response = httpx.get('https://httpbin.org/get')
# ステータスコードの確認
print(f"ステータスコード: {response.status_code}")
# ヘッダーの表示 (辞書ライクなオブジェクト)
print(f"Content-Type: {response.headers.get('content-type')}")
# レスポンスボディ (テキスト)
# print(f"レスポンスボディ:\n{response.text}")
# レスポンスボディ (JSON)
try: json_data = response.json() print("レスポンス JSON:") import json print(json.dumps(json_data, indent=2, ensure_ascii=False))
except httpx.ResponseNotRead: print("レスポンスボディはまだ読み込まれていません。")
except json.JSONDecodeError: print("レスポンスは有効な JSON ではありません。")
except Exception as e: print(f"エラーが発生しました: {e}")
# レスポンスボディ (バイナリ)
# print(f"レスポンスボディ (bytes): {response.content}")
POST リクエスト (JSONデータ)
JSON データを送信する場合は json
引数を使用します。
import httpx
import json
payload = {'name': 'Taro Yamada', 'age': 30}
# POST リクエスト (JSON)
response = httpx.post('https://httpbin.org/post', json=payload)
print(f"ステータスコード: {response.status_code}")
try: response_data = response.json() print("サーバーからのレスポンス JSON:") print(json.dumps(response_data, indent=2, ensure_ascii=False))
except Exception as e: print(f"JSON デコードエラー: {e}")
クエリパラメータとカスタムヘッダー
params
引数でクエリパラメータを、headers
引数でカスタムヘッダーを付与できます。
import httpx
import json
params = {'search': 'python httpx', 'page': 1}
headers = {'User-Agent': 'my-cool-app/1.0', 'Accept': 'application/json'}
response = httpx.get('https://httpbin.org/get', params=params, headers=headers)
print(f"ステータスコード: {response.status_code}")
try: print("サーバーからのレスポンス JSON:") print(json.dumps(response.json(), indent=2, ensure_ascii=False))
except Exception as e: print(f"JSON デコードエラー: {e}")
Client インスタンスの使用
複数のリクエストで設定(ヘッダー、Cookie、タイムアウトなど)を共有したい場合や、コネクションプーリングを活用したい場合は httpx.Client
を使用します。with
文を使うことで、リソースが確実に解放されます。
import httpx
import json
# Client インスタンスを作成 (with 文で自動的にクローズ)
with httpx.Client(base_url='https://httpbin.org', headers={'X-Custom-Header': 'value'}) as client: # GET リクエスト (/get) response_get = client.get('/get', params={'param1': 'test'}) print(f"GET ステータス: {response_get.status_code}") try: print("GET レスポンス:") print(json.dumps(response_get.json(), indent=2, ensure_ascii=False)) except Exception as e: print(f"JSON デコードエラー: {e}") print("-" * 20) # POST リクエスト (/post) response_post = client.post('/post', json={'message': 'Hello from Client!'}) print(f"POST ステータス: {response_post.status_code}") try: print("POST レスポンス:") print(json.dumps(response_post.json(), indent=2, ensure_ascii=False)) except Exception as e: print(f"JSON デコードエラー: {e}")
httpx.Client()
は requests.Session()
と同様の目的で使用され、コネクションの再利用や設定の共有に役立ちます。
非同期クライアントの使い方 (Async)
httpx
の真価が発揮されるのが非同期処理です。Python の asyncio
などの非同期フレームワークと組み合わせることで、特に多数の I/O 待ちが発生するようなタスク(例: 大量の API リクエスト)を非常に効率的に実行できます。
非同期処理には async
と await
キーワード、そして httpx.AsyncClient
を使用します。
基本的な非同期 GET リクエスト
import httpx
import asyncio
import json
async def fetch_data(url: str): # 非同期クライアントを生成 async with httpx.AsyncClient() as client: print(f"リクエスト開始: {url}") # await を使って非同期にリクエストを実行 response = await client.get(url) print(f"リクエスト完了: {url}, ステータス: {response.status_code}") try: return response.json() except Exception as e: print(f"JSON デコードエラー ({url}): {e}") return None
async def main(): url1 = 'https://httpbin.org/get?source=async1' url2 = 'https://httpbin.org/delay/1' # 1秒待つAPI # asyncio.gather を使って複数の非同期タスクを並行実行 results = await asyncio.gather( fetch_data(url1), fetch_data(url2) ) print("\\n--- 取得結果 ---") for i, result in enumerate(results): if result: print(f"結果 {i+1}:") print(json.dumps(result, indent=2, ensure_ascii=False)) else: print(f"結果 {i+1}: 取得失敗")
# 非同期関数を実行
if __name__ == "__main__": asyncio.run(main())
上記の例では、fetch_data
という非同期関数を定義し、asyncio.gather
を使って2つのURLへのリクエストを同時に(並行して)開始します。/delay/1
エンドポイントは1秒待ってから応答しますが、その間にもう一方の /get
リクエストは処理を進めることができます。これにより、逐次的にリクエストを送るよりも全体の処理時間を短縮できます。
非同期処理のポイント
async def
で非同期関数を定義します。- 時間のかかる I/O 処理(
httpx
のリクエストなど)の前にはawait
を付けます。これにより、処理が完了するまで他のタスクに制御を移すことができます。 httpx.AsyncClient
を非同期クライアントとして使用します。with
文 (async with
) と組み合わせるのが一般的です。- 複数の非同期タスクを並行実行するには
asyncio.gather
などを使用します。 - 非同期処理は、ネットワークI/OやファイルI/Oなど、CPUではなく待ち時間が多い処理で特に効果を発揮します。
httpx vs requests 比較
httpx
は多くの点で requests
と似ていますが、重要な違いもいくつかあります。どちらを選択するかは、プロジェクトの要件によって異なります。
機能 | httpx | requests | 備考 |
---|---|---|---|
API デザイン | ほぼ requests 互換 | 広く使われているデファクトスタンダード | httpx は requests からの移行が容易 |
非同期サポート | ネイティブ対応 (async/await) 〇 | 非対応 ✕ | requests で非同期を実現するには別ライブラリや工夫が必要 |
HTTP/2 サポート | 対応 (オプション) 〇 | 非対応 ✕ | httpx は pip install httpx[http2] で有効化 |
HTTP/3 サポート | 対応 (オプション, httpcore経由) 〇 | 非対応 ✕ | pip install httpx[http3] で有効化 (QUIC / UDPベース) |
タイムアウト | 詳細設定可能 (Connect, Read, Write, Pool) | 基本的なタイムアウト設定のみ | httpx はより厳格な制御が可能 |
WSGI/ASGI 直接リクエスト | 対応 〇 | 非対応 ✕ | テストフレームワークとの連携に便利 |
コマンドラインクライアント | あり (オプション) 〇 | なし ✕ | pip install httpx[cli] で利用可能 |
開発状況 | 活発 | 安定・メンテナンスモード | httpx は新しい機能が積極的に追加されている |
依存ライブラリ | httpcore, h11, sniffio, certifi, idna 等 | urllib3, chardet, certifi, idna 等 | 基盤となるライブラリが異なる |
どちらを選ぶべきか?
- 単純なスクリプトや、同期処理のみで十分な場合:
requests
が依然としてシンプルで良い選択肢です。ドキュメントやコミュニティサポートも豊富です。 - 非同期処理が必要な場合(Webフレームワーク、大量のAPIアクセスなど):
httpx
が最適です。 - HTTP/2 や HTTP/3 の機能を利用したい場合:
httpx
を選択します。 - モダンなPython機能(型ヒントなど)を最大限活用したい場合:
httpx
の方がより現代的な設計になっています。 - 新しいライブラリを試してみたい、より高機能・高性能なクライアントが必要な場合:
httpx
を検討する価値があります。
2025年現在、多くの新しいプロジェクト、特に非同期処理を前提とする FastAPI や Starlette などのフレームワークと組み合わせる際には、httpx
が第一候補となるケースが増えています。
高度な機能
httpx
は基本的なリクエスト以外にも、多くの高度な機能を提供します。
HTTP/2 と HTTP/3 サポート
前述の通り、httpx
は HTTP/2 と HTTP/3 をサポートします。これにより、特に多重化(一つの接続で複数のリクエスト/レスポンスをやり取りする)によるパフォーマンス向上が期待できます。
利用するには、まず対応する依存ライブラリをインストールします。
# HTTP/2 の場合
pip install httpx[http2]
# HTTP/3 の場合
pip install httpx[http3]
そして、Client または AsyncClient を初期化する際に http2=True
を指定します。HTTP/3 は、サーバーが対応していれば自動的にネゴシエーションされる場合があります(httpcore の実装に依存)。
import httpx
import asyncio
async def check_http_version(url: str): # http2=True を指定して HTTP/2 を優先的に試みる # サーバーが HTTP/3 に対応していれば、httpcore がそれを選択する可能性もある async with httpx.AsyncClient(http2=True, verify=False) as client: # verify=False は自己署名証明書などの場合に注意して使用 try: response = await client.get(url) print(f"{url} - 使用されたプロトコル: {response.http_version}") # response.raise_for_status() # 必要に応じてステータスチェック except httpx.RequestError as exc: print(f"{url} へのリクエスト中にエラーが発生しました: {exc.__class__.__name__}") except Exception as e: print(f"{url} で予期せぬエラー: {e}")
async def main(): # HTTP/2 や HTTP/3 をサポートしている可能性のあるサイト # 注意: サーバー側の設定やネットワーク環境により結果は異なります urls = [ "https://www.google.com", "https://httpbin.org/headers", # HTTP/1.1 のみの場合が多い "https://cloudflare.com", # "https://localhost:8443" # ローカルでテストサーバーを立てる場合など ] tasks = [check_http_version(url) for url in urls] await asyncio.gather(*tasks)
if __name__ == "__main__": # 注意: 自己署名証明書などを使用しているサーバーの場合、SSL検証エラーが発生することがあります。 # その場合は `verify=False` を指定するか、適切な証明書設定が必要です。 # 本番環境での `verify=False` の使用はセキュリティリスクを伴います。 asyncio.run(main())
HTTP/2 や HTTP/3 の利用はサーバー側の対応が必要です。また、HTTP/3 は UDP 上の QUIC プロトコルを使用するため、ネットワーク環境(ファイアウォールなど)によっては通信できない場合があります。HTTP/3 はデフォルトで TLSv1.3 による暗号化が必須です。
タイムアウト設定
httpx
では、きめ細やかなタイムアウト設定が可能です。httpx.Timeout
オブジェクトを使って指定します。
connect
: サーバーへの接続確立までのタイムアウト(秒)。read
: サーバーからデータのチャンクを読み取る間のタイムアウト(秒)。write
: サーバーにデータのチャンクを書き込む間のタイムアウト(秒)。pool
: コネクションプールから接続を取得するまでのタイムアウト(秒)。
デフォルトではすべて5秒に設定されています。
import httpx
# 接続タイムアウトを 2秒、読み取りタイムアウトを 10秒に設定
timeout_config = httpx.Timeout(2.0, read=10.0)
try: with httpx.Client(timeout=timeout_config) as client: # このリクエストは最大10秒待機する response = client.get('https://httpbin.org/delay/5') # 5秒遅延するエンドポイント print("タイムアウトせずに成功しました。") # response = client.get('https://httpbin.org/delay/15') # これは read タイムアウトになるはず # print("タイムアウトせずに成功しました。")
except httpx.ReadTimeout: print("読み取りタイムアウトが発生しました。")
except httpx.ConnectTimeout: print("接続タイムアウトが発生しました。")
except httpx.TimeoutException as e: print(f"その他のタイムアウト関連エラー: {e}")
except httpx.RequestError as e: print(f"リクエストエラー: {e}")
リダイレクト制御
デフォルトでは、httpx
はリダイレクトに追従します (最大30回)。follow_redirects=False
を設定することで、リダイレクトを無効にできます。
import httpx
url = 'https://httpbin.org/redirect-to?url=https://www.example.com'
# リダイレクトに追従する (デフォルト)
response_follow = httpx.get(url)
print(f"追従あり - 最終URL: {response_follow.url}")
print(f"追従あり - ステータス: {response_follow.status_code}")
print(f"追従あり - リダイレクト履歴: {response_follow.history}")
print("\\n" + "-"*20 + "\\n")
# リダイレクトに追従しない
response_nofollow = httpx.get(url, follow_redirects=False)
print(f"追従なし - URL: {response_nofollow.url}")
print(f"追従なし - ステータス: {response_nofollow.status_code}") # 3xx 系になるはず
print(f"追従なし - Locationヘッダ: {response_nofollow.headers.get('location')}")
print(f"追従なし - リダイレクト履歴: {response_nofollow.history}") # 空になるはず
認証 (Authentication)
Basic 認証や Digest 認証などをサポートしています。auth
引数にタプルまたは認証クラスのインスタンスを渡します。
import httpx
# Basic 認証
basic_auth = ('your_username', 'your_password')
url_basic = 'https://httpbin.org/basic-auth/your_username/your_password'
try: response_basic = httpx.get(url_basic, auth=basic_auth) response_basic.raise_for_status() # エラーがあれば例外を発生させる print("Basic認証 成功") # print(response_basic.json())
except httpx.HTTPStatusError as e: print(f"Basic認証 失敗: ステータス {e.response.status_code}")
except httpx.RequestError as e: print(f"Basic認証 リクエストエラー: {e}")
# 独自認証クラスの例 (例: トークン認証)
class TokenAuth(httpx.Auth): def __init__(self, token): self._token = token def auth_flow(self, request): request.headers['Authorization'] = f'Bearer {self._token}' yield request
# custom_auth = TokenAuth('my-secret-token')
# response_custom = httpx.get('https://api.example.com/protected', auth=custom_auth)
# print(f"カスタム認証 ステータス: {response_custom.status_code}")
クライアントのカスタマイズ
httpx.Client
や httpx.AsyncClient
をインスタンス化する際に、様々なオプションを指定して動作をカスタマイズできます。
base_url
: ベースURL。相対パスでリクエストを送る際に便利。headers
: すべてのリクエストに適用されるデフォルトヘッダー。cookies
: すべてのリクエストに適用されるデフォルトクッキー。params
: すべてのリクエストに適用されるデフォルトクエリパラメータ。timeout
: デフォルトのタイムアウト設定 (httpx.Timeout
オブジェクト)。limits
: 接続数の制限 (httpx.Limits
オブジェクト)。同時接続数やキープアライブ接続数を制御。proxies
: プロキシ設定。verify
: SSL/TLS 証明書の検証設定 (True
,False
, または証明書ファイルのパス)。cert
: クライアント証明書の設定。http1
,http2
: 使用するHTTPバージョンを制御。transport
: カスタムトランスポートクラスのインスタンス。event_hooks
: イベントフック関数。follow_redirects
: デフォルトのリダイレクト追従設定。
import httpx
# カスタマイズ例
limits = httpx.Limits(max_connections=100, max_keepalive_connections=20)
timeout = httpx.Timeout(10.0, connect=5.0)
headers = {"X-Client-Name": "MyCustomClient"}
# proxy_url = "http://user:password@myproxy.com:8080" # プロキシの例
client = httpx.Client( base_url="https://httpbin.org/", headers=headers, timeout=timeout, limits=limits, # proxies=proxy_url, # プロキシを使う場合 follow_redirects=True, http2=False # HTTP/1.1 を強制する場合
)
try: with client: # with 文で使う response = client.get("/headers") # base_url からの相対パス response.raise_for_status() print("カスタムクライアントでのリクエスト成功:") # import json # print(json.dumps(response.json(), indent=2, ensure_ascii=False)) # ヘッダーに 'X-Client-Name': 'MyCustomClient' が含まれているか確認 received_headers = response.json().get('headers', {}) print(f"X-Client-Name ヘッダー: {received_headers.get('X-Client-Name')}")
except httpx.RequestError as e: print(f"リクエストエラー: {e}")
イベントフック (Event Hooks)
リクエストの送信前やレスポンスの受信後などに特定の関数(フック)を呼び出すことができます。ロギング、モニタリング、リクエスト/レスポンスの改変などに利用できます。
event_hooks
引数に、イベント名 ('request'
または 'response'
) と呼び出す関数のリストをマッピングした辞書を渡します。
import httpx
import time
def log_request(request): print(f"[Request Hook] > {request.method} {request.url}") # リクエストヘッダーにタイムスタンプを追加する例 request.headers['X-Request-Timestamp'] = str(time.time())
def log_response(response): request = response.request # 対応するリクエストオブジェクトを取得 elapsed = response.elapsed.total_seconds() print(f"[Response Hook] < {request.method} {request.url} - Status {response.status_code} ({elapsed:.2f}s)") # レスポンスヘッダーを確認する例 # print(f" Server Header: {response.headers.get('Server')}")
# イベントフックを設定してクライアントを作成
event_hooks = {'request': [log_request], 'response': [log_response]}
with httpx.Client(event_hooks=event_hooks) as client: print("イベントフック付きでリクエストを送信します...") client.get('https://httpbin.org/get') client.post('https://httpbin.org/post', data={'key': 'value'}) print("リクエスト完了。")
Transport API と Mounting
httpx
は低レベルの Transport API を提供しており、リクエストの送信方法を完全にカスタマイズできます。例えば、特定のドメインに対して異なる設定(プロキシ、SSL検証など)を持つトランスポートを「マウント」することが可能です。
これは非常に高度な機能であり、特定のユースケース(例: Microservices 環境で特定の内部サービスへの通信方法を変える)で役立ちます。
import httpx
# 例: example.com へのリクエストのみ特定のプロキシを使う Transport
custom_transport = httpx.HTTPTransport(proxy="http://specific-proxy.com:8080")
# 例: ローカルの WSGI/ASGI アプリケーション用 Transport
# app = MyWSGIApp()
# wsgi_transport = httpx.WSGITransport(app=app)
# Client に Transport をマウントする
# デフォルトの Transport は通常のインターネットアクセス用
# 特定のプレフィックスにマッチする場合、マウントした Transport が使われる
mounts = { "all://example.com": custom_transport, # "http://internal-service": wsgi_transport # WSGIアプリをマウントする例
}
with httpx.Client(mounts=mounts) as client: # このリクエストは custom_transport (プロキシ経由) で送信される response_example = client.get("https://example.com") print(f"example.com status: {response_example.status_code}") # このリクエストはデフォルトの Transport で送信される response_httpbin = client.get("https://httpbin.org/get") print(f"httpbin.org status: {response_httpbin.status_code}") # このリクエストは wsgi_transport で処理される (もしマウントされていれば) # response_internal = client.get("http://internal-service/status") # print(f"internal-service status: {response_internal.status_code}")
まとめ
httpx
は、Python における HTTP 通信のための強力でモダンなライブラリです。requests
の使いやすさを引き継ぎつつ、非同期処理と HTTP/2・HTTP/3 という現代的な Web 技術に対応している点が大きな魅力です。
特に、非同期 I/O が求められるアプリケーションや、最新のプロトコルを活用してパフォーマンスを向上させたい場合に、httpx
は非常に有効な選択肢となります。タイムアウト、認証、プロキシ、イベントフック、トランスポート API など、豊富なカスタマイズオプションも提供されており、複雑な要件にも対応可能です。
もしあなたが Python で Web API を利用したり、Web スクレイピングを行ったり、あるいは非同期処理に関心があるなら、ぜひ httpx
を試してみてください。きっとそのパワフルさと柔軟性に満足するはずです!
公式ドキュメント (https://www.python-httpx.org/) には、さらに詳細な情報や使用例が豊富に掲載されていますので、ぜひ参照してください。