15.3. Zend_Http_Client - 接続アダプタ

15.3.1. 概要

Zend_Http_Client は、接続アダプタとして設計されています。 接続アダプタは実際にサーバへの接続を行うオブジェクトで、 リクエストやレスポンスの処理も行います。 この接続アダプタは置き換えることができます。 つまり、デフォルトの接続アダプタを継承して自分の好みにあうように変更することができます。 HTTP クライアントクラス全体を書き換える必要はありません。 同じインターフェイスを実装しているだけでいいのです。

現在、Zend_Http_Client クラスは三つの組み込み接続アダプタを提供しています。

  • Zend_Http_Client_Adapter_Socket (デフォルト)

  • Zend_Http_Client_Adapter_Proxy

  • Zend_Http_Client_Adapter_Test

Zend_Http_Client オブジェクトの接続アダプタを指定するには、 設定オプション 'adapter' を使用します。 クライアントオブジェクトのインスタンスを作成する際に、オプション 'adapter' にアダプタの名前 (たとえば 'Zend_Http_Client_Adapter_Socket' など) を指定することができます。あるいは、アダプタオブジェクトの変数 (たとえば new Zend_Http_Client_Adapter_test など) を指定することもできます。 Zend_Http_Client->setConfig() メソッドを使用し、 アダプタを後で設定することも可能です。

15.3.2. ソケットアダプタ

デフォルトの接続アダプタは Zend_Http_Client_Adapter_Socket です。明示的に接続アダプタを指定しない場合は、これが使用されます。 Socket アダプタは PHP の組み込み関数 fsockopen() を使用しており、特別な拡張モジュールやコンパイルオプションは必要ありません。

ソケットアダプタでは、独自の設定オプション 'ssltransport' が指定できます (Zend_Http_Client->setConfig() に渡すか、あるいはコンストラクタで設定します)。 このパラメータは文字列で、デフォルトは 'sslv2' となります。 これを変更すると、HTTPS 接続に使用するストリームトランスポート層が変更されます。 よく使用する値は 'ssl'、'sslv2'、'sslv3' および 'tls' です。これ以外にも、 PHP がサポートしているストリームトランスポート層ならどれでも使用できます。 しかし、結果が予期せぬものになるかもしれません。これ以外の値を使用する場合には、 それが何を意味するのかをしっかり把握した上で使用するようにしましょう。 [2].

例 15.15. HTTPS トランスポート層の変更

<?php
    // 設定パラメータを指定します
    $config = array(
        'adapter'      => 'Zend_Http_Client_Adapter_Socket',
        'ssltransport' => 'tls'
    );
    
    // クライアントオブジェクトのインスタンスを作成します
    $client = Zend_Http_Client('https://www.example.com', $config);
    
    // これ以降のリクエストは、TLS セキュア接続上で行われます
    $response = $client->request();
?>

上の例の結果は、次の PHP コマンドで TCP 接続をオープンした場合と同じになります。

fsockopen('tls://www.example.com', 443)

15.3.3. プロキシアダプタ

Zend_Http_Client_Adapter_Proxy アダプタはデフォルトのソケットアダプタとほぼ同じです。 ただし、対象となるサーバに直接接続するのではなく HTTP プロキシサーバを経由して接続するという点が異なります。 これにより、Zend_Http_Client をプロキシサーバの中から使用できるようになります。 セキュリティやパフォーマンス上の理由により、これが必要となる場合もあるでしょう。

プロキシアダプタを使用するには、 デフォルトの 'adapter' オプション以外に いくつか追加のパラメータを設定する必要があります。

表 15.2. Zend_Http_Client の設定パラメータ

パラメータ 説明 想定している型 値の例
proxy_host プロキシサーバのアドレス string 'proxy.myhost.com' あるいは '10.1.2.3'
proxy_port プロキシサーバの TCP ポート integer 8080 (デフォルト) あるいは 81
proxy_user 必要に応じて、プロキシのユーザ名 string 'shahar' あるいは指定しない場合は '' (デフォルト)
proxy_pass 必要に応じて、プロキシのパスワード string 'secret' あるいは指定しない場合は '' (デフォルト)
proxy_auth プロキシの HTTP 認証形式 string Zend_Http_Client::AUTH_BASIC (デフォルト)

proxy_host は常に設定しなければなりません。指定しなかった場合は、 自動的に Zend_Http_Client_Adapter_Socket による直接接続に切り替わります。 proxy_port のデフォルトは '8080' です。もし別のポートをプロキシで使用している場合は、 適切に設定する必要があります。

proxy_user および proxy_pass は、 プロキシサーバが認証を必要とする場合にのみ設定します。 これらを指定すると、'Proxy-Authentication' ヘッダがリクエストに追加されます。プロキシで認証を必要としない場合は、 このふたつのオプションはそのままにしておきます。

proxy_auth は、プロキシが認証を必要としている場合に、 その認証形式を指定します。設定できる値は Zend_Http_Client::setAuth() メソッドと同じです。現在はベーシック認証 (Zend_Http_Client::AUTH_BASIC) のみをサポートしています。

例 15.16. プロキシサーバを使用した Zend_Http_Client の使用法

<?php
    // 接続パラメータを設定します
    $config = array(
        'adapter'    => 'Zend_Http_Client_Adapter_Proxy',
        'proxy_host' => 'proxy.int.zend.com',
        'proxy_port' => 8000,
        'proxy_user' => 'shahar.e',
        'proxy_pass' => 'bananashaped'
    );
    
    // クライアントオブジェクトのインスタンスを作成します
    $client = Zend_Http_Client('http://www.example.com', $config);
    
    // 作業を続けます...
?>

説明したとおり、もし proxy_host を省略したり空文字列を設定したりすると、 自動的に直接接続に切り替わります。これにより、設定パラメータによって オプションでプロキシを使用できるようなアプリケーションを書くことが可能となります。

15.3.4. テストアダプタ

HTTP 接続に依存するテストコードを書くのは非常に難しいものです。 たとえば、リモートサーバから RSS を取得するアプリケーションをテストするには、 ネットワークにつながっている必要があります。常にネットワークが使用できるとは限りません。

このようなときのためにあるのが Zend_Http_Client_Adapter_Test アダプタです。 Zend_Http_Client を使用するアプリケーションを作成し、それをテストしたい場合には、 デフォルトのアダプタを Test アダプタ (モックオブジェクト) に変更します。 これで、サーバに接続せずにテストを行えるようになります。

Zend_Http_Client_Adapter_Test には setResponse() というメソッドがあります。 このメソッドのパラメータには、HTTP レスポンスをテキストか Zend_Http_Response オブジェクトで指定することができます。 レスポンスを設定すると、Test アダプタは常にこのレスポンスを返すようになります。 実際の HTTP リクエストは行いません。

例 15.17. HTTP レスポンススタブを使用したテスト

<?php
    // 新しいアダプタとクライアントのインスタンスを作成します
    $adapter = new Zend_Http_Client_Adapter_Test();
    $client = Zend_Http_Client('http://www.example.com', array(
        'adapter' => $adapter
    ));
    
    // 想定するレスポンスを設定します
    $adapter->setResponse(
        "HTTP/1.1 200 OK"        . "\r\n" .
        "Content-type: text/xml" . "\r\n" .
                                   "\r\n" . 
        '<?xml version="1.0" encoding="UTF-8"?>' . 
        '<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"' . 
        '     xmlns:wfw="http://wellformedweb.org/CommentAPI/"' . 
        '     xmlns:dc="http://purl.org/dc/elements/1.1/">' . 
        '  <channel>' . 
        '    <title>Premature Optimization</title>' . 
        // などなど...
        '</rss>');
    
    $response = $client->request('GET');
    // .. $response の処理を続けます...
?>

上の例のようにすると、HTTP クライアントにお望みのレスポンスを返させることができます。 その際にネットワーク接続は使用しません。また、実際のサーバからのレスポンスも使用しません。 この場合、このテストでテストするのは、 レスポンス本文の XML をアプリケーションが正しくパースできるかどうかということです。

時には、オブジェクトに対するひとつのメソッド呼び出しの中で複数の HTTP トランザクションを行うこともあるでしょう。そのような場合は setResponse() を単独で使うことはできません。なぜなら、 結果が呼び出し元に返ってくるまで次のレスポンスを設定できないからです。

例 15.18. 複数の HTTP レスポンススタブを使用したテスト

<?php
    // 新しいアダプタおよびクライアントのインスタンスを作成します
    $adapter = new Zend_Http_Client_Adapter_Test();
    $client = Zend_Http_Client('http://www.example.com', array(
        'adapter' => $adapter
    ));
    
    // 最初の応答として期待する値を設定します
    $adapter->setResponse(
        "HTTP/1.1 302 Found"      . "\r\n" .
        "Location: /"             . "\r\n" .
        "Content-Type: text/html" . "\r\n" .
                                    "\r\n" . 
        '<html>' . 
        '  <head><title>Moved</title></head>' .
        '  <body><p>This page has moved.</p></body>' . 
        '</html>');

    // それに続くレスポンスを設定します
    $adapter->addResponse(
        "HTTP/1.1 200 OK"         . "\r\n" .
        "Content-Type: text/html" . "\r\n" .
                                    "\r\n" . 
        '<html>' . 
        '  <head><title>My Pet Store Home Page</title></head>' . 
        '  <body><p>...</p></body>' . 
        '</html>');

    // HTTP クライアントオブジェクト ($client) をテスト対象の
    // オブジェクトに注入し、オブジェクトの動きを以下でテストします
?>

setResponse() メソッドは、 Zend_Http_Client_Adapter_Test のバッファにあるレスポンスをすべて削除し、 最初に返されるレスポンスを設定します。addResponse() メソッドは、それに続くレスポンスを追加します。

レスポンスは、それを追加した順に再生されます。 登録したよりも多くのリクエストが発生した場合は、 返されるレスポンスは最初のものに戻り、そこからまた順に返されるようになります。

上の例で、このアダプタがテストするように設定されているのは、 302 リダイレクトが発生した場合のオブジェクトの挙動です。 アプリケーションの内容によって、リダイレクトさせるべきなのかそうでないのかは異なるでしょう。 この例ではリダイレクトさせることを想定しているので、 テストアダプタもそれにあわせて設定しています。 最初の 302 レスポンスを setResponse() メソッドで設定し、 次に返される 200 レスポンスを addResponse() メソッドで設定します。 テストアダプタを設定し終えたら、そのアダプタを含む HTTP クライアントをテスト対象オブジェクトに注入し、その挙動をテストします。

15.3.5. 独自の接続アダプタの作成

独自の接続アダプタを作成し、それを使用することもできます。 たとえば持続的なソケットを使用するアダプタを作成したり、 キャッシュ機能を追加したアダプタを作成したりなど、 作成するアプリケーションの要件にあわせたものを作成することが可能です。

そのためには、Zend_Http_Client_Adapter_Interface を実装したクラスを作成する必要があります。 以下の例は、ユーザ定義のアダプタクラスの雛形となります。 この例で定義されているすべてのパブリック関数を、 アダプタで定義する必要があります。

例 15.19. 独自の接続アダプタの作成

<?php
class MyApp_Http_Client_Adapter_BananaProtocol implements Zend_Http_Client_Adapter_Interface
{    
    /**
     * アダプタの設定配列を設定する
     *
     * @param array $config
     */
    public function setConfig($config = array())
    {
        // ここはほとんど変更することはありません -
        // 通常は Zend_Http_Client_Adapter_Socket の実装をコピーします
    }
    
    /**
     * リモートサーバに接続する
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // リモートサーバとの接続を確立します
    }
    
    /**
     * リクエストをリモートサーバに送信する
     *
     * @param string        $method
     * @param Zend_Uri_Http $url
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as text
     */
    public function write($method, $url, $http_ver = '1.1', $headers = array(), $body = '')
    {
        // リクエストをリモートサーバに送信します。
        // この関数は、リクエスト全体 (ヘッダおよび本文) を文字列で返します。
    }
    
    /**
     * サーバからのレスポンスを読み込む
     *
     * @return string
     */
    public function read()
    {
        // リモートサーバからのレスポンスを読み込み、それを文字列で返します。
    }
    
    /**
     * サーバとの接続を閉じる
     *
     */
    public function close()
    {
        // リモートサーバとの接続を閉じます。最後にコールされます。
    }
}    

// そして、このアダプタを使用します
$client = new Zend_Http_Client(array(
    'adapter' => 'MyApp_Http_Client_Adapter_BananaProtocol'
));


[2] PHP マニュアルの付録 O に、サポートされるソケットトランスポートの一覧があります。 http://www.php.net/manual/en/transports.php