简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

ネットワーククライアント

ErisPulse は、HTTP リクエスト、WebSocket 接続、および接続プール管理を統合した統一されたネットワーククライアントを提供しています。モジュールやアダプタは、このクライアントを優先して使用する必要があります。aiohttp / httpx / requests などのサードパーティライブラリを直接インポートしてはいけません。

概要

ネットワーククライアントの主な機能:

快速開始

HTTP リクエスト

from ErisPulse.Core import client

# GET リクエスト
resp = await client.get("https://httpbin.org/get")
data = await resp.json()
print(resp.status)  # 200

# POST リクエスト
resp = await client.post(
    "https://httpbin.org/post",
    json={"key": "value"},
)
data = await resp.json()

WebSocket 接続

from ErisPulse.Core import client

ws = await client.ws_connect("wss://example.com/ws")

async for text in ws.iter_text():
    await ws.send_text(f"Echo: {text}")

HttpResponse

すべてのリクエストメソッドは HttpResponse オブジェクトを返します:

from ErisPulse.Core import client

resp = await client.get("https://httpbin.org/get")

resp.status       # int - HTTP ステータスコード (例: 200, 404)
resp.reason       # str | None - ステータスの説明 (例: "OK")
resp.headers      # レスポンスヘッダー (大文字小文字を区別しません)
resp.content_type # str | None - Content-Type
resp.url          # 最終URL (リダイレクトにより変化する可能性があります)
resp.raw          # ベースの生のレスポンスオブジェクト (現在は aiohttp.ClientResponse)

# レスポンスボディの読み取り
body = await resp.read()       # bytes
text = await resp.text()       # str
data = await resp.json()       # JSONを解析
text = await resp.text("gbk")  # 指定されたエンコーディング

リクエストメソッド

GET

from ErisPulse.Core import client

resp = await client.get(
    "https://api.example.com/users",
    params={"page": "1", "limit": "10"},
    headers={"Authorization": "Bearer token"},
)

POST

from ErisPulse.Core import client

# JSONリクエストボディ
resp = await client.post(
    "https://api.example.com/users",
    json={"name": "Alice", "age": 30},
)

# フォームリクエストボディ
resp = await client.post(
    "https://api.example.com/login",
    data={"username": "admin", "password": "123"},
)

# ロウデータ
resp = await client.post(
    "https://api.example.com/upload",
    data=b"raw bytes",
    headers={"Content-Type": "application/octet-stream"},
)

# ファイルアップロード (filesパラメータを使用、aiohttpのインポート不要)
# 形式: {フィールド名: ファイルオブジェクト/bytes/(filename, file)/(filename, file, content_type)}
resp = await client.post(
    "https://api.example.com/upload",
    data={"description": "プロフィール画像"},            # 任意: 普通のフォームフィールドを同時に送信
    files={
        "file": ("photo.png", open("photo.png", "rb"), "image/png"),
    },
)

# 簡易記法: ファイルオブジェクトを直接渡す
resp = await client.post(
    "https://api.example.com/upload",
    files={"file": open("photo.png", "rb")},
)

# メモリ内のデータを直接アップロード (ファイルを保存する必要なし)
import io

resp = await client.post(
    "https://api.example.com/upload",
    files={"file": ("data.txt", io.BytesIO(b"file content"), "text/plain")},
)

PUT / DELETE / PATCH

from ErisPulse.Core import client

resp = await client.put("https://api.example.com/users/1", json={"name": "Bob"})
resp = await client.delete("https://api.example.com/users/1")
resp = await client.patch("https://api.example.com/users/1", json={"age": 31})

一般的な request

from ErisPulse.Core import client

resp = await client.request(
    "OPTIONS",
    "https://api.example.com/resource",
    headers={"Origin": "https://example.com"},
)

パラメータの説明

HTTPリクエストパラメータ

パラメータ 型 説明
url str リクエストURL
params dict[str, str] クエリパラメータ (オプション)
headers dict[str, str] 追加のリクエストヘッダー (オプション)
data Any リクエストボディ (フォームまたは生データ) (オプション)
json Any JSONリクエストボディ (オプション)
files dict[str, Any] ファイルアップロードフィールド (オプション、multipart/form-dataを自動的に構築)
timeout float 本次リクエストのタイムアウト (秒) (オプション、デフォルト値を上書き)
max_retries int 本次の最大リトライ回数 (オプション、デフォルト値を上書き)

ws_connect パラメータ

パラメータ 型 説明
url str WebSocketサーバーのURL
headers dict[str, str] 追加のリクエストヘッダー (オプション)
heartbeat float ハートビート間隔 (秒) (オプション)

タイムアウトとリトライ

from ErisPulse.Core import Client

# カスタムタイムアウトを設定したクライアントを作成
client = Client(
    timeout=60,           # 要求全体のタイムアウト 60秒
    connect_timeout=5,    # 接続のタイムアウト 5秒
    max_retries=3,        # 失敗時に自動でリトライ 3回
    retry_delay=2,        # リトライ間隔 2秒
)

# 単一の要求でタイムアウトをオーバーライド
resp = await client.get("https://slow-api.example.com/data", timeout=120)

Note

クライアントクラスは 2.8.0 以降、Client に名前が変更されました(sdk.client の属性名は変更されません)。古い名前 HttpClient は互換性のエイリアスとして保持されており、古いコードは変更する必要はありません。

デフォルトのヘッダーをカスタマイズ

client = Client(
    headers={
        "Authorization": "Bearer token",
        "X-App-Id": "my-app",
    },
    user_agent="MyBot/1.0",
)

リクエスト統計

from ErisPulse.Core import client

# 統計を表示
stats = client.stats
# {"total_requests": 42, "total_errors": 1, "total_bytes_sent": 0, "total_bytes_received": 0}

# 統計をリセット
client.reset_stats()

ライフサイクルイベント

HTTP リクエストイベント

リクエストが完了するたびに client.request イベントがトリガーされ、監視に使用できます。

from ErisPulse.Core import lifecycle

@lifecycle.on("client.request")
async def on_request(event_data):
    print(f"{event_data['method']} {event_data['url']} -> {event_data['status']} ({event_data['elapsed']}s)")

WebSocket 接続イベント

WebSocket 接続が確立されたたびに client.ws.connect イベントがトリガーされます。

from ErisPulse.Core import lifecycle

@lifecycle.on("client.ws.connect")
async def on_ws_connect(event_data):
    print(f"WS 接続: {event_data['url']}")

コンテキスト管理

# コンテキストマネージャーとして使用し、セッションを自動的に閉じます
async with Client(timeout=30) as client:
    resp = await client.get("https://httpbin.org/get")
    data = await resp.json()

WebSocket クライアント

client.ws_connect() を使用して WebSocket クライアント接続を確立し、ClientWebSocket オブジェクトを返します。クライアントとサーバーの WebSocket は共通の WebSocketConnectionBase 基底クラスを共有し、send/receive/iter インターフェースは完全に一致します。

基本的な使用法

from ErisPulse.Core import client

ws = await client.ws_connect("wss://example.com/ws", heartbeat=30)

await ws.send_text("Hello")
await ws.send_bytes(b"\x00\x01\x02")
await ws.send_json({"type": "ping"})

メッセージの受信

高レベル方法(推奨)

メッセージの型を自動的にフィルタリングし、切断時に WebSocketDisconnect を送出します:

from ErisPulse.Core import client
from ErisPulse.Core.Bases.errors import WebSocketDisconnect

ws = await client.ws_connect("wss://example.com/ws")

# 単一のメッセージ受信
text = await ws.receive_text()    # str
data = await ws.receive_bytes()   # bytes
obj = await ws.receive_json()     # dict / list

# 反復処理による受信(切断時に自動的に停止)
async for text in ws.iter_text():
    print(text)

async for data in ws.iter_bytes():
    print(data)

async for obj in ws.iter_json():
    print(obj)

低レベル方法

receive() と iter_messages() を使用して、原始的なメッセージ型を処理し、TEXT / BINARY / CLOSE / ERROR を区別できます:

from ErisPulse.Core import client
from ErisPulse.Core.Bases.websocket import WSMessage

ws = await client.ws_connect("wss://example.com/ws")

# 単一のメッセージ受信
msg = await ws.receive()
# msg.type  -> WSMessage.TEXT / WSMessage.BINARY / WSMessage.CLOSE / WSMessage.ERROR
# msg.data  -> str | bytes | None

# 反復処理によるメッセージ受信(CLOSE/ERROR で自動的に停止)
async for msg in ws.iter_messages():
    if msg.type == WSMessage.TEXT:
        print(f"テキスト: {msg.data}")
    elif msg.type == WSMessage.BINARY:
        print(f"バイナリ: {len(msg.data)} bytes")

WSMessage

WSMessage は、下層のライブラリに依存しない統一された WebSocket メッセージ型です:

属性 型 説明
type str メッセージ型: WSMessage.TEXT / WSMessage.BINARY / WSMessage.CLOSE / WSMessage.ERROR
data Any メッセージデータ

ClientWebSocket 属性

属性 型 説明
url URL 接続 URL
headers Headers 応答ヘッダー
closed bool 接続が閉じられているか
raw object 下層の生のオブジェクト (aiohttp.ClientWebSocketResponse)

ライフサイクルフック

サービス側 WebSocketConnection と同様に、on_disconnect と on_error コールバックをサポートします:

from ErisPulse.Core import client

ws = await client.ws_connect("wss://example.com/ws")

@ws.on_disconnect
async def handle_disconnect(ws, reason="unknown"):
    print(f"接続が切断されました: {reason}")

@ws.on_error
async def handle_error(ws, error=""):
    print(f"接続エラー: {error}")

接続の切断

await ws.close(code=1000, reason="Normal closure")

異常体系

ErisPulse は、統一された異常階層を定義しており、sdk.client を介してリクエストを発行すると、自動的に下層の aiohttp 異常が ErisPulse 異常に変換されます。

後方互換性:aiohttp.ClientSession を直接使用する旧モジュール/アダプターは完全に影響を受けません。異常変換は sdk.client を介してリクエストを発行した場合にのみ有効であり、aiohttp を直接使用するコードは、aiohttp.ClientError などの元の異常をキャッチし続けます。両方の方法は共存可能です。

異常階層

ErisPulseError
├── ClientError                  # すべての HTTP/WS クライアントリクエスト異常の基底クラス
│   ├── ClientConnectionError    # 接続失敗 (DNS 解析失敗、接続拒否、ネットワーク不可達)
│   ├── ClientTimeoutError       # 接続タイムアウトまたはリクエストタイムアウト
│   └── HTTPStatusError          # HTTP 4xx/5xx 状態コードエラー
└── WebSocketError               # WebSocket 異常の基底クラス
    └── WebSocketDisconnect      # WebSocket 接続切断 (クライアントおよびサーバー共通)

異常のキャッチ

from ErisPulse.Core import client
from ErisPulse.Core.Bases.errors import (
    ClientError,
    ClientConnectionError,
    ClientTimeoutError,
    HTTPStatusError,
    WebSocketDisconnect,
    WebSocketError,
)

# HTTP リクエストの異常処理
try:
    resp = await client.get("https://api.example.com/data")
    data = await resp.json()
except ClientConnectionError:
    print("サーバーに接続できません")
except ClientTimeoutError:
    print("リクエストがタイムアウトしました")
except ClientError as e:
    print(f"リクエストが失敗しました: {e}")

# WebSocket の異常処理
try:
    ws = await client.ws_connect("wss://example.com/ws")
    async for text in ws.iter_text():
        await ws.send_text(f"Echo: {text}")
except WebSocketDisconnect as e:
    print(f"接続が切断されました: code={e.code}, reason={e.reason}")
except WebSocketError as e:
    print(f"WebSocket エラー: {e}")

統一されたキャッチ

ClientError を使用して、すべての HTTP/WS クライアントリクエスト異常を統一的にキャッチします:

from ErisPulse.Core.Bases.errors import ClientError

try:
    resp = await client.get("https://api.example.com/data")
except ClientError as e:
    print(f"クライアントエラー: {e}")

HTTPStatusError

リクエスト後にステータスコードをチェックし、エラーを投げる必要がある場合、手動で使用できます:

from ErisPulse.Core.Bases.errors import HTTPStatusError

resp = await client.get("https://api.example.com/data")
if resp.status >= 400:
    raise HTTPStatusError(resp.status, await resp.text())

アダプターでの使用

アダプターは、グローバルクライアントまたは独自にクライアントインスタンスを作成して、プラットフォームAPIリクエストを送信できます:

from ErisPulse.Core import client
from ErisPulse.Core.Bases import BaseAdapter
from ErisPulse.Core.Bases.errors import ClientError

class MyAdapter(BaseAdapter):
    async def call_api(self, endpoint, **params):
        try:
            resp = await client.post(
                f"https://api.platform.com/{endpoint}",
                json=params,
                headers={"Authorization": f"Bearer {self.token}"},
            )
            return await resp.json()
        except ClientError as e:
            self.logger.error(f"API 調用失敗: {e}")
            raise

from ErisPulse import sdk を使用して sdk.client を使うこともでき、効果は同じです。

最佳実践

  1. グローバルクライアントの優先使用:from ErisPulse.Core import client を使用してグローバルシングルトンを取得し、フレームワークによる統一的な管理と監視を容易にする。
  2. aiohttp の直接インポートを避ける:client を aiohttp.ClientSession の代わりに使用し、将来の下層実装の変更時にコードの修正が不要になる。従来の aiohttp を直接使用するコードは正常に動作し続け、両方の方法を同時に使用できる。
  3. ErisPulse の例外体系の使用:sdk.client でリクエストを行う際は aiohttp.ClientError ではなく ClientError をキャッチし、コードが特定の HTTP ライブラリに依存しないようにする。aiohttp を直接使用する従来のコードには影響しない。
  4. タイムアウトの適切な設定:API の応答速度に応じて適切なタイムアウト時間を設定し、長時間のブロッキングを避ける。
  5. リトライメカニズムの使用:不安定な API に対してリトライを有効化し、信頼性を高める。
  6. リクエスト統計の監視:sdk.client.stats または client.request のライフサイクルイベントを使用してリクエスト状況を監視する。
  7. WebSocket での高機能メソッドの使用:iter_text / iter_json などの高機能メソッドを優先し、メッセージの種類を区別する必要がある場合にのみ iter_messages を使用する。

関連ドキュメント