【PHP】PSR-18 HTTP Client(HTTPクライアント)

公開
更新
注意最終更新から 6 年以上経過しています。最新の情報もあわせてご確認ください。

PSR(PHP標準勧告)


PSR-18ドキュメントでは、HTTP リクエストを送信し、HTTP レエスポンスを受信するための一般的なインターフェイスについて説明しています。

PSR-18の目指すところ

この PSR の目標は、開発者が HTTP クライアントの実装から切り離されたライブラリを作成できるようにすることです。 これにより依存関係の数が減り、バージョンの競合の可能性が低くなるため、ライブラリがより再利用可能になります。

2つ目の目標は、HTTP クライアントをリスコフの置換原則に従って置き換えることができることです。 これは、リクエストを送信するときにすべてのクライアントが同じように動作する必要があることを意味します。

定義

クライアント
クライアントは、PSR-7互換の HTTP リクエストメッセージを送信し、呼び出し側ライブラリに PSR-7 互換の HTTP レスポンスメッセージを返すためにこの仕様を実装するライブラリです。
呼び出しライブラリ
呼び出しライブラリはクライアントを利用するコードです。PSR-18 ではインターフェースを実装しませんが、それらを実装するオブジェクト(クライアント)を使用します。

クライアント

クライアントは、ClientInterface を実装するオブジェクトです。

  • 変更された HTTP リクエストを、提供されたものから送信することを選択します。例えば、発信メッセージ body を圧縮できます。
  • 呼び出しライブラリに返す前に、受信した HTTP レスポンスを変更することを選択します。例えば、着信メッセージ body を解凍できます。

クライアントが HTTP リクエストまたは HTTP レスポンスのいずれかを変更することを選択した場合、オブジェクトが内部的に一貫性を保つようにしなければなりません。例えば、クライアントがメッセージ本文の圧縮解除を選択した場合、Content-Encoding ヘッダーも削除して、Content-Length ヘッダーを調整する必要があります。

結果として、PSR-7オブジェクトは不変であるため、呼び出し側ライブラリは、ClientInterface::sendRequest() に渡されるオブジェクトが実際に送信される PHP オブジェクトであると仮定してはなりません。例えば例外によって返される Request オブジェクトは、sendRequest() に渡されたオブジェクトとは異なる可能性があるため、参照による比較(===)は不可能です。

呼び出し元ライブラリに返されるのがステータスコード 200 以上の有効な HTTP レスポンスになるように、マルチステップ HTTP 1xx レスポンス自体を再構築します。

エラーハンドリング

クライアントは、整形式の HTTP リクエストまたは HTTP レスポンスをエラー状態として扱わないでください。例えば 400 および 500 の範囲のレスポンスステータスコードは例外を引き起こしてはならず、通常どおり呼び出しライブラリに返される必要があります。

クライアントは HTTP リクエストをまったく送信できない場合、または HTTP レスポンスをPSR-7レスポンスオブジェクトで解析できなかった場合にのみ、Psr\Http\Client\ClientExceptionInterface のインスタンスをスローする必要があります。

リクエストメッセージが整形式の HTTP リクエストではないか、重要な情報(ホストやメソッドなど)がないためにリクエストを送信できない場合、クライアントは Psr\Http\Client\RequestExceptionInterface のインスタンスをスローする必要があります。

タイムアウトなど、あらゆる種類のネットワーク障害が原因でリクエストを送信できない場合、クライアントは Psr\Http\Client\NetworkExceptionInterface のインスタンスをスローする必要があります。

クライアントは上記で定義された適切なインターフェイスを実装している場合、ここで定義された例外(たとえば、TimeOutException または HostNotFoundException)よりも具体的な例外をスローできます。

インターフェース

ClientInterface
namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ClientInterface
{
    /**
     * PSR-7リクエストを送信し、PSR-7レスポンスを返す。
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws \Psr\Http\Client\ClientExceptionInterface リクエストの処理中にエラーが発生した場合
     */
    public function sendRequest(RequestInterface $request): ResponseInterface;
}
ClientExceptionInterface
namespace Psr\Http\Client;

/**
 * 全ての HTTP クライアント関連の例外は、このインターフェイスを実装する必要があります。
 */
interface ClientExceptionInterface extends \Throwable
{
}
RequestExceptionInterface
namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;

/**
 * リクエストが失敗したときの例外
 *
 *  リクエストが無効(例:メソッドが無い)
 *  ランタイムリクエストエラー(例:ボディストリームがシークできない)
 *  etc.
 */
interface RequestExceptionInterface extends ClientExceptionInterface
{
    /**
     * リクエストを返す
     *
     * リクエストオブジェクトは、ClientInterface::sendRequest()に渡されるオブジェクトとは異なるオブジェクトである場合があります
     *
     * @return RequestInterface
     */
    public function getRequest(): RequestInterface;
}
NetworkExceptionInterface
namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;

/**
 * ネットワークの問題のためにリクエストを完了できない場合にスローされる
 *
 * レスポンスが受信されなかったときにこの例外がスローされるため、レスポンスオブジェクトは無し
 * (ターゲットホスト名を解決できないか、接続に失敗した etc.)
 */
interface NetworkExceptionInterface extends ClientExceptionInterface
{
    /**
     * リクエストを返す
     *
     * リクエストオブジェクトは、ClientInterface::sendRequest()に渡されるオブジェクトとは異なるオブジェクトである場合がある
     *
     * @return RequestInterface
     */
    public function getRequest(): RequestInterface;
}

概要

HTTPリクエストとレスポンスは Web プログラミングの 2 つの基本的なオブジェクトです。外部APIと通信するすべてのクライアントは、何らかの形式の HTTP クライアントを使用します。

多くのライブラリは 1 つの特定のクライアントに結合されているか、クライアントやアダプター層を実装しています。これは、ライブラリの設計不良、バージョンの競合、またはライブラリドメインに関係のないコードにつながります。

理由

PSR-7 のおかげで、HTTP リクエストと HTTP レスポンスが理想的にどのように見えるかを知っていますが、リクエストの送信方法とレスポンスの受信方法を定義するものは何もありません。 HTTP クライアントの共通インターフェースにより、ライブラリを特定の実装から切り離すことができます。

範囲

以下、PSR-18 が提供するところ、しないところ。

範囲とするところ

PSR-7 メッセージを送信し、PSR-7 レスポンスを返すための共通インターフェース。

範囲としないところ

  • 非同期 HTTP リクエストのサポートは、別の将来の PSR に残されています。
  • この PSR では HTTP クライアントの構成方法を定義しません。デフォルトの動作のみを指定します。
  • この PSR はミドルウェアの使用に関して中立です。

非同期 HTTP クライアント

非同期リクエストがこの PSR でカバーされない理由は、Promise の共通標準の欠如です。Promise は独自の仕様に値するほど十分に複雑であり、この仕様にラップされるべきではありません。

Promise PSR が受け入れられると、非同期リクエストの個別のインターフェイスを個別の PSR で定義できます。非同期呼び出しの戻り値の型は Promise になるため、非同期リクエストのメソッドシグネチャは同期リクエストのメソッドシグネチャと異なる必要があります。したがってこの PSR は上位互換性があり、クライアントは公開したい機能に基づいて 1 つまたは両方のインターフェイスを実装できます。

アプローチ

以下、策定に関するアプローチについて

デフォルトの動作

この PSR の意図は、明確に定義された動作を持つ HTTP クライアントをライブラリ開発者に提供することです。ライブラリはクライアント実装の詳細を処理するための特別なコードなしで準拠クライアントを使用できる必要があります(リスコフの置換原則)。

PSR は、HTTPクライアントの構成方法を制限または定義しようとしません。

別のアプローチは、構成をクライアントに渡すことです。このアプローチにはいくつかの欠点があります。

  • 構成は PSR で定義する必要があります。
  • すべてのクライアントは、定義された構成をサポートする必要があります。
  • クライアントに構成が渡されない場合、動作は予測できません。

命名の根拠

メインインターフェイスの動作は、sendRequest(RequestInterface $request):ResponseInterface メソッドによって定義されます。

より短いメソッド名 send() が提案されていますが、これは Guzzle などの既存の非常に一般的な HTTP クライアントですでに使用されています。

そのためこの標準を採用する場合、仕様を実装するために後方互換性を破る必要があります。 代わりに sendRequest() を定義することにより、BCが即座に中断することなく採用できるようにします。

例外モデル

ドメイン例外 NetworkExceptionInterface および RequestExceptionInterface は、互いに非常に類似したコントラクトを定義します。

選択されたアプローチは、継承がドメインモデルで意味をなさないため互いに拡張しないようにすることです。RequestExceptionInterface は、単に NetworkExceptionInterface ではありません。

RequestAwareException および(または) ResponseAwareException インターフェースを拡張するための例外の許可については説明しましたが、これは取るべきではない便利なショートカットです。むしろ特定の例外をキャッチして、それに応じて処理する必要があります。

例外を定義するときはより細かくすることができます。 例えば TimeOutException および HostNotFoundException は NetworkExceptionInterface のサブタイプである可能性があります。選択されたアプローチはそのようなサブタイプを定義することではありません。消費ライブラリでの例外処理はほとんどの場合、それらの例外間で変わらないからです。

4xxおよび5xxレスポンスの例外スロー

最初のアイデアは、HTTPステータス4xxおよび5xxのレスポンスに対して例外をスローするようにクライアントを構成できるようにすることでした。ライブラリを使用すると、4xxおよび5xxのレスポンスを 2 回チェックする必要があるため、このアプローチは望ましくありません。

仕様をより予測可能にするために、HTTPクライアントは4xxおよび5xxレスポンスに対して例外をスローしないことが決定されました。

ミドルウェアとクライアントのラッピング

仕様では、HTTPクライアントをラップ/装飾するミドルウェアまたはクラスに制限を設けていません。装飾クラスも ClientInterface を実装する場合は、仕様にも従う必要があります。

HTTPクライアントに設定を許可したりミドルウェアを追加したりして、リダイレクトを追跡したり例外をスローしたりすることができます。それがアプリケーション開発者の決定である場合、仕様を破りたいと明確に述べています。これは、アプリケーション開発者が処理すべき問題(または機能)です。

サードパーティのライブラリは、HTTPクライアントが仕様に違反していると想定してはなりません。

背景

HTTP クライアント PSR は、php-http チームによってインスピレーションを得て作成されました。 2015年に、彼らは HTTPlug を HTTP クライアントの共通インターフェースとして作成しました。

彼らは、特定の HTTP クライアント実装に依存しないように、サードパーティのライブラリが使用できる抽象化を望んでいました。 2016 年 1 月に安定バージョンがタグ付けされ、それ以来プロジェクトは広く採用されています。

最初の安定バージョンから 2 年間で300万回以上のダウンロードがあったため、この「事実上の」標準を正式な仕様とすることとしました。

Ritolabo