Python講座 / HTTP APIとJSON応答・通信エラーを扱う

HTTP APIはリクエストを受けてステータスとデータを返す仕組みです。Pythonのクライアントでは、通信の成功、待ち時間の上限、応答の大きさ、JSONの構造と値をそれぞれ確認します。

通信方式・場所・形式を区別する

HTTPはリクエストと応答の通信規約、URLはリソースを特定するアドレス、JSONは本文のデータ形式の一つです。成功ステータスでも、アプリケーションが期待するデータとは限りません。外部サービスでは公式のHTTPS、認証、利用制限にも従います。

この実習は応答を管理できるローカルサーバーを使います。クライアントの処理を学ぶためのもので、本番サーバーの配備手順ではありません。

JSONの型検証と保存は、JSONデータを変換して保存するで扱う規則とつながります。

応答ファイルとローカルサーバーを準備する

新しいフォルダーへexpenses.jsonを保存します。

[{"id":"e1","name":"coffee","amount":3500},{"id":"e2","name":"lunch","amount":8000}]

一つ目のターミナルでそのフォルダーへ移動し、実行します。

python -m http.server 8000 --bind 127.0.0.1

127.0.0.1へのバインドは同じPCからの接続に限ります。8000番が使用中なら別のポートを選び、クライアントのURLも合わせます。

クライアント全体を保存して実行する

fetch_expenses.pyへ保存します。追加パッケージは不要です。

import json
import sys
from urllib.request import urlopen
from urllib.error import HTTPError, URLError

url = sys.argv[1]
try:
    with urlopen(url, timeout=5) as response:
        if response.status != 200:
            raise ValueError("unexpected status")
        raw = response.read(1_000_001)
        if len(raw) > 1_000_000:
            raise ValueError("response too large")
        data = json.loads(raw.decode("utf-8"))
except (HTTPError, URLError, TimeoutError, UnicodeError, ValueError) as error:
    raise SystemExit(f"request failed: {type(error).__name__}")
if not isinstance(data, list):
    raise SystemExit("expected a list")
total = 0
for row in data:
    if not isinstance(row, dict) or type(row.get("amount")) is not int or row["amount"] < 0:
        raise SystemExit("invalid record")
    total += row["amount"]
print("records:", len(data))
print("total:", total)

仮想環境を選んだ二つ目のターミナルで実行します。

python fetch_expenses.py http://127.0.0.1:8000/expenses.json

records: 2、total: 11500が予想結果です。終了時は一つ目のターミナルでCtrl+Cを押し、サーバーを止めます。

通信失敗と不正なデータを区別する

存在しないパスではHTTPError、接続失敗ではURLErrorなどが起こります。timeoutは個々の待ちを制限し、処理全体が必ずその秒数で終わる保証ではありません。大きすぎる応答、UTF-8やJSONの不正、型や金額の不正も別に拒否します。

ファイル名を誤記する、金額を文字列にする、サーバーを止める、という条件を一つずつ試して元へ戻します。再試行には回数・間隔の制限と副作用の検討が必要です。支払いなどを無条件で再送しないでください。認証情報をコード・URL・ログへ残さず管理します。API収集の実習では検証後に取引単位で保存します。

HTTPErrorはエラーのHTTP応答、URLErrorは名前解決や接続などの通信失敗を表します。応答を受けてもサイズ・JSON・フィールド検証で失敗する場合があります。成功らしいデータに置き換えず、原因を区別して記録します。再試行は冪等性、回数、待ち時間の方針を決めてから追加します。

関連する公式リファレンス