TigerZF
🌐Español

38.3. Zend_Http_Client - Adaptadores de conexión

38.3.1. Resumen

Zend_Http_Client se basa en un diseño de adaptador de conexión. El adaptador de conexión es el objeto encargado de realizar la conexión real con el servidor, así como de escribir las peticiones y leer las respuestas. Este adaptador de conexión puede reemplazarse, y puede crear y extender los adaptadores de conexión por defecto para adaptarse a sus necesidades especiales, sin necesidad de extender o reemplazar toda la clase de cliente HTTP, y con la misma interfaz.

Actualmente, la clase Zend_Http_Client proporciona cuatro adaptadores de conexión incorporados:

  • Zend_Http_Client_Adapter_Socket (por defecto)

  • Zend_Http_Client_Adapter_Proxy

  • Zend_Http_Client_Adapter_Curl

  • Zend_Http_Client_Adapter_Test

El adaptador de conexión del objeto Zend_Http_Client se establece usando la opción de configuración 'adapter'. Al instanciar el objeto cliente, puede establecer la opción de configuración 'adapter' a una cadena que contenga el nombre del adaptador (ej. 'Zend_Http_Client_Adapter_Socket') o a una variable que contenga un objeto adaptador (ej. new Zend_Http_Client_Adapter_Test). También puede establecer el adaptador más tarde, usando el método Zend_Http_Client->setConfig().

38.3.2. El adaptador Socket

El adaptador de conexión por defecto es el adaptador Zend_Http_Client_Adapter_Socket - este adaptador se usará a menos que establezca explícitamente el adaptador de conexión. El adaptador Socket se basa en la función incorporada de PHP fsockopen(), y no requiere ninguna extensión especial o flags de compilación.

El adaptador Socket permite varias opciones de configuración adicionales que pueden establecerse usando Zend_Http_Client->setConfig() o pasarse al constructor del cliente.

Tabla 38.2. Parámetros de configuración de Zend_Http_Client_Adapter_Socket

Parámetro Descripción Tipo esperado Valor por defecto
persistent Si se deben usar conexiones TCP persistentes boolean FALSE
ssltransport Capa de transporte SSL (ej. 'sslv2', 'tls') string ssl
sslcert Ruta a un certificado SSL codificado en PEM string NULL
sslpassphrase Frase de paso para el archivo de certificado SSL string NULL
sslusecontext Permite que las conexiones proxy usen SSL incluso si la propia conexión proxy no lo hace. boolean FALSE


[Note] Conexiones TCP persistentes

Usar conexiones TCP persistentes puede acelerar potencialmente las peticiones HTTP - pero en la mayoría de los casos de uso tendrá poco efecto positivo y podría sobrecargar el servidor HTTP al que se está conectando.

Se recomienda usar conexiones TCP persistentes solo si se conecta al mismo servidor con mucha frecuencia, y está seguro de que el servidor es capaz de manejar un gran número de conexiones concurrentes. En cualquier caso se le anima a evaluar el efecto de las conexiones persistentes tanto en la velocidad del cliente como en la carga del servidor antes de usar esta opción.

Además, al usar conexiones persistentes se recomienda habilitar las peticiones HTTP Keep-Alive como se describe en la sección de configuración - de lo contrario las conexiones persistentes podrían tener poco o ningún efecto.

[Note] Parámetros de flujo SSL de HTTPS

ssltransport, sslcert y sslpassphrase solo son relevantes al conectar usando HTTPS.

Aunque los ajustes SSL por defecto deberían funcionar para la mayoría de las aplicaciones, es posible que necesite cambiarlos si el servidor al que se conecta requiere una configuración especial del cliente. En ese caso, debería leer las secciones sobre capas de transporte SSL y opciones aquí.

Ejemplo 38.17. Cambiando la capa de transporte HTTPS

// Set the configuration parameters
$config = array(
    'adapter'      => 'Zend_Http_Client_Adapter_Socket',
    'ssltransport' => 'tls'
);

// Instantiate a client object
$client = new Zend_Http_Client('https://www.example.com', $config);

// The following request will be sent over a TLS secure connection.
$response = $client->request();

El resultado del ejemplo anterior será similar a abrir una conexión TCP usando el siguiente comando de PHP:

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

38.3.2.1. Personalizar y acceder al contexto de flujo del adaptador Socket

A partir de Zend Framework 1.9, Zend_Http_Client_Adapter_Socket proporciona acceso directo al contexto de flujo subyacente usado para conectar con el servidor remoto. Esto permite al usuario pasar opciones y parámetros específicos al flujo TCP, y al wrapper SSL en el caso de conexiones HTTPS.

Puede acceder al contexto de flujo usando los siguientes métodos de Zend_Http_Client_Adapter_Socket:

  • setStreamContext($context) Establece el contexto de flujo que usará el adaptador. Puede aceptar bien un recurso de contexto de flujo creado usando la función PHP stream_context_create(), o un array de opciones de contexto de flujo, en el mismo formato que se proporciona a esta función. Proporcionar un array creará un nuevo contexto de flujo con estas opciones, y lo establecerá.

  • getStreamContext() Obtiene el contexto de flujo del adaptador. Si no se ha establecido ningún contexto de flujo, creará un contexto de flujo por defecto y lo devolverá. Luego puede establecer u obtener el valor de diferentes opciones de contexto usando las funciones normales de contexto de flujo de PHP.

Ejemplo 38.18. Establecer opciones de contexto de flujo para el adaptador Socket

// Array of options
$options = array(
    'socket' => array(
        // Bind local socket side to a specific interface
        'bindto' => '10.1.2.3:50505'
    ),
    'ssl' => array(
        // Verify server side certificate,
        // do not accept invalid or self-signed SSL certificates
        'verify_peer' => true,
        'allow_self_signed' => false,

        // Capture the peer's certificate
        'capture_peer_cert' => true
    )
);

// Create an adapter object and attach it to the HTTP client
$adapter = new Zend_Http_Client_Adapter_Socket();
$client = new Zend_Http_Client();
$client->setAdapter($adapter);

// Method 1: pass the options array to setStreamContext()
$adapter->setStreamContext($options);

// Method 2: create a stream context and pass it to setStreamContext()
$context = stream_context_create($options);
$adapter->setStreamContext($context);

// Method 3: get the default stream context and set the options on it
$context = $adapter->getStreamContext();
stream_context_set_option($context, $options);

// Now, preform the request
$response = $client->request();

// If everything went well, you can now access the context again
$opts = stream_context_get_options($adapter->getStreamContext());
echo $opts['ssl']['peer_certificate'];

[Note] Nota

Tenga en cuenta que debe establecer cualquier opción de contexto de flujo antes de usar el adaptador para realizar peticiones reales. Si no se establece ningún contexto antes de realizar peticiones HTTP con el adaptador Socket, se creará un contexto de flujo por defecto. Se puede acceder a este recurso de contexto después de realizar cualquier petición usando el método getStreamContext().

38.3.3. El adaptador Proxy

El adaptador Zend_Http_Client_Adapter_Proxy es similar al adaptador Socket por defecto - solo que la conexión se realiza a través de un servidor proxy HTTP en lugar de una conexión directa al servidor de destino. Esto permite el uso de Zend_Http_Client detrás de servidores proxy - lo cual a veces es necesario por razones de seguridad o rendimiento.

Usar el adaptador Proxy requiere que se establezcan varios parámetros de configuración adicionales, además de la opción 'adapter' por defecto:

Tabla 38.3. Parámetros de configuración de Zend_Http_Client

Parámetro Descripción Tipo esperado Valor de ejemplo
proxy_host Dirección del servidor proxy string 'proxy.myhost.com' o '10.1.2.3'
proxy_port Puerto TCP del servidor proxy integer 8080 (por defecto) u 81
proxy_user Nombre de usuario del proxy, si se requiere string 'shahar' o '' para ninguno (por defecto)
proxy_pass Contraseña del proxy, si se requiere string 'secret' o '' para ninguno (por defecto)
proxy_auth Tipo de autenticación HTTP del proxy string Zend_Http_Client::AUTH_BASIC (por defecto)


proxy_host siempre debe establecerse - si no se establece, el cliente recurrirá a una conexión directa usando Zend_Http_Client_Adapter_Socket. proxy_port por defecto es '8080' - si su proxy escucha en un puerto diferente debe establecer este también.

proxy_user y proxy_pass solo son necesarios si su servidor proxy requiere que se autentique. Proporcionar estos añadirá una cabecera 'Proxy-Authentication' a la petición. Si su proxy no requiere autenticación, puede omitir estas dos opciones.

proxy_auth establece el tipo de autenticación del proxy, si su servidor proxy requiere autenticación. Los valores posibles son similares a los aceptados por el método Zend_Http_Client::setAuth(). Actualmente, solo se admite la autenticación básica (Zend_Http_Client::AUTH_BASIC).

Ejemplo 38.19. Usar Zend_Http_Client detrás de un servidor proxy

// Set the configuration parameters
$config = array(
    'adapter'    => 'Zend_Http_Client_Adapter_Proxy',
    'proxy_host' => 'proxy.int.zend.com',
    'proxy_port' => 8000,
    'proxy_user' => 'shahar.e',
    'proxy_pass' => 'bananashaped'
);

// Instantiate a client object
$client = new Zend_Http_Client('http://www.example.com', $config);

// Continue working...

Como se mencionó, si proxy_host no está establecido o está establecido a una cadena en blanco, la conexión recurrirá a una conexión directa normal. Esto le permite escribir fácilmente su aplicación de una manera que permita usar un proxy opcionalmente, según un parámetro de configuración.

[Note] Nota

Dado que el adaptador proxy hereda de Zend_Http_Client_Adapter_Socket, puede usar el método de acceso al contexto de flujo (ver esta sección) para establecer opciones de contexto de flujo en conexiones Proxy como se demostró anteriormente.

38.3.4. El adaptador cURL

cURL es una biblioteca de cliente HTTP estándar que se distribuye con muchos sistemas operativos y puede usarse en PHP a través de la extensión cURL. Ofrece funcionalidad para muchos casos especiales que pueden darse en un cliente HTTP y la convierten en una elección perfecta para un adaptador HTTP. Admite conexiones seguras, proxy, todo tipo de mecanismos de autenticación y destaca en aplicaciones que mueven archivos grandes entre servidores.

Ejemplo 38.20. Establecer opciones de cURL

$config = array(
    'adapter'   => 'Zend_Http_Client_Adapter_Curl',
    'curloptions' => array(CURLOPT_FOLLOWLOCATION => true),
);
$client = new Zend_Http_Client($uri, $config);

Por defecto el adaptador cURL está configurado para comportarse exactamente como el adaptador Socket y también acepta los mismos parámetros de configuración que los adaptadores Socket y Proxy. También puede cambiar las opciones de cURL especificando la clave 'curloptions' en el constructor del adaptador o llamando a setCurlOption($name, $value). La clave $name corresponde a las constantes CURL_* de la extensión cURL. Puede obtener acceso al handle de Curl llamando a $adapter->getHandle();

Ejemplo 38.21. Transferir archivos por handle

Puede usar cURL para transferir archivos muy grandes por HTTP mediante un manejador de archivo.

$putFileSize   = filesize("filepath");
$putFileHandle = fopen("filepath", "r");

$adapter = new Zend_Http_Client_Adapter_Curl();
$client = new Zend_Http_Client();
$client->setAdapter($adapter);
$adapter->setConfig(array(
    'curloptions' => array(
        CURLOPT_INFILE => $putFileHandle,
        CURLOPT_INFILESIZE => $putFileSize
    )
));
$client->request("PUT");

38.3.5. El adaptador Test

A veces, es muy difícil probar código que depende de conexiones HTTP. Por ejemplo, probar una aplicación que obtiene un feed RSS de un servidor remoto requerirá una conexión de red, la cual no siempre está disponible.

Por esta razón, se proporciona el adaptador Zend_Http_Client_Adapter_Test. Puede escribir su aplicación para usar Zend_Http_Client, y solo para fines de prueba, por ejemplo en su conjunto de pruebas unitarias, puede reemplazar el adaptador por defecto con un adaptador Test (un objeto simulado, o "mock"), lo que le permite ejecutar pruebas sin realizar realmente conexiones al servidor.

El adaptador Zend_Http_Client_Adapter_Test proporciona un método adicional, el método setResponse(). Este método toma un parámetro, que representa una respuesta HTTP ya sea como texto o como un objeto Zend_Http_Response. Una vez establecido, su adaptador Test siempre devolverá esta respuesta, sin siquiera realizar una petición HTTP real.

Ejemplo 38.22. Probar contra un único stub de respuesta HTTP

// Instantiate a new adapter and client
$adapter = new Zend_Http_Client_Adapter_Test();
$client = new Zend_Http_Client('http://www.example.com', array(
    'adapter' => $adapter
));

// Set the expected response
$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>' .
    // and so on...
    '</rss>');

$response = $client->request('GET');
// .. continue parsing $response..

El ejemplo anterior muestra cómo puede preconfigurar su cliente HTTP para que devuelva la respuesta que necesita. Luego, puede continuar probando su propio código, sin depender de una conexión de red, la respuesta del servidor, etc. En este caso, la prueba continuaría comprobando cómo la aplicación analiza el XML en el cuerpo de la respuesta.

A veces, una única llamada a un método de un objeto puede resultar en que ese objeto realice múltiples transacciones HTTP. En este caso, no es posible usar setResponse() por sí solo porque no hay oportunidad de establecer la(s) siguiente(s) respuesta(s) que su programa pueda necesitar antes de devolver el control al llamador.

Ejemplo 38.23. Probar contra múltiples stubs de respuesta HTTP

// Instantiate a new adapter and client
$adapter = new Zend_Http_Client_Adapter_Test();
$client = new Zend_Http_Client('http://www.example.com', array(
    'adapter' => $adapter
));

// Set the first expected response
$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>');

// Set the next successive response
$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>');

// inject the http client object ($client) into your object
// being tested and then test your object's behavior below

El método setResponse() borra cualquier respuesta en el búfer de Zend_Http_Client_Adapter_Test y establece la primera respuesta que se devolverá. El método addResponse() añadirá respuestas sucesivas.

Las respuestas se reproducirán en el orden en que se añadieron. Si se realizan más peticiones que el número de respuestas almacenadas, las respuestas volverán a repetirse en orden.

En el ejemplo anterior, el adaptador está configurado para probar el comportamiento de su objeto cuando encuentra una redirección 302. Dependiendo de su aplicación, seguir una redirección puede o no ser el comportamiento deseado. En nuestro ejemplo, esperamos que se siga la redirección y configuramos el adaptador de prueba para ayudarnos a probar esto. La respuesta inicial 302 se configura con el método setResponse() y la respuesta 200 que se devolverá a continuación se añade con el método addResponse(). Después de configurar el adaptador de prueba, inyecte el cliente HTTP que contiene el adaptador en su objeto bajo prueba y pruebe su comportamiento.

Si necesita que el adaptador falle bajo demanda puede usar setNextRequestWillFail($flag). El método hará que la siguiente llamada a connect() lance una excepción Zend_Http_Client_Adapter_Exception. Esto puede ser útil cuando su aplicación almacena en caché contenido de un sitio externo (en caso de que el sitio caiga) y desea probar esta funcionalidad.

Ejemplo 38.24. Forzar el fallo del adaptador

// Instantiate a new adapter and client
$adapter = new Zend_Http_Client_Adapter_Test();
$client = new Zend_Http_Client('http://www.example.com', array(
    'adapter' => $adapter
));

// Force the next request to fail with an exception
$adapter->setNextRequestWillFail(true);

try {
    // This call will result in a Zend_Http_Client_Adapter_Exception
    $client->request();
} catch (Zend_Http_Client_Adapter_Exception $e) {
    // ...
}

// Further requests will work as expected until
// you call setNextRequestWillFail(true) again

38.3.6. Crear sus propios adaptadores de conexión

Puede crear sus propios adaptadores de conexión y usarlos. Podría, por ejemplo, crear un adaptador de conexión que use sockets persistentes, o un adaptador de conexión con capacidades de caché, y usarlos según sea necesario en su aplicación.

Para hacerlo, debe crear su propia clase adaptadora que implemente la interfaz Zend_Http_Client_Adapter_Interface. El siguiente ejemplo muestra el esqueleto de una clase adaptadora implementada por el usuario. Todas las funciones públicas definidas en este ejemplo deben definirse también en su adaptador:

Ejemplo 38.25. Crear su propio adaptador de conexión

class MyApp_Http_Client_Adapter_BananaProtocol
    implements Zend_Http_Client_Adapter_Interface
{
    /**
     * Set the configuration array for the adapter
     *
     * @param array $config
     */
    public function setConfig($config = array())
    {
        // This rarely changes - you should usually copy the
        // implementation in Zend_Http_Client_Adapter_Socket.
    }

    /**
     * Connect to the remote server
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // Set up the connection to the remote server
    }

    /**
     * Send request to the remote server
     *
     * @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 = '')
    {
        // Send request to the remote server.
        // This function is expected to return the full request
        // (headers and body) as a string
    }

    /**
     * Read response from server
     *
     * @return string
     */
    public function read()
    {
        // Read response from remote server and return it as a string
    }

    /**
     * Close the connection to the server
     *
     */
    public function close()
    {
        // Close the connection to the remote server - called last.
    }
}

// Then, you could use this adapter:
$client = new Zend_Http_Client(array(
    'adapter' => 'MyApp_Http_Client_Adapter_BananaProtocol'
));