Просмотр исходного кода

Implementing ZF-2946: Adding ability to set / control stream context options
- Adding the setStreamContext and getStreamContext methods
- Adding unit tests
- Adding documentation


git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@17009 44c647ce-9c0f-0410-b52a-842ac1e357ba

shahar 16 лет назад
Родитель
Сommit
05b3abb2cc

+ 153 - 60
documentation/manual/en/module_specs/Zend_Http_Client-Adapters.xml

@@ -31,12 +31,12 @@
                 </listitem>
                 <listitem>
                     <para>
-                        <classname>Zend_Http_Client_Adapter_Test</classname>
+                        <classname>Zend_Http_Client_Adapter_Curl</classname>
                     </para>
                 </listitem>
                 <listitem>
                     <para>
-                        <classname>Zend_Http_Client_Adapter_Curl</classname>
+                        <classname>Zend_Http_Client_Adapter_Test</classname>
                     </para>
                 </listitem>
             </itemizedlist>
@@ -48,7 +48,7 @@
             a string containing the adapter's name (eg. 'Zend_Http_Client_Adapter_Socket')
             or to a variable holding an adapter object (eg. <code>
             new Zend_Http_Client_Adapter_Test</code>). You can also set the
-            adapter later, using the Zend_Http_Client->setConfig() method.
+            adapter later, using the <classname>Zend_Http_Client->setConfig()</classname> method.
         </para>
     </sect2>
 
@@ -168,6 +168,93 @@ $response = $client->request();
         <para>
             <code>fsockopen('tls://www.example.com', 443)</code>
         </para>
+        
+        <sect3 id="zend.http.client.adapters.socket.streamcontext">
+            <title>Customizing and accessing the Socket adapter stream context</title>
+            <para>
+                Starting from Zend Framework 1.9, <classname>Zend_Http_Client_Adapter_Socket</classname> 
+                provides direct access to the underlying <ulink url="http://php.net/manual/en/stream.contexts.php">stream context</ulink>
+                used to connect to the remote server. This allows the user to pass 
+                specific options and parameters to the TCP stream, and to the SSL wrapper in
+                case of HTTPS connections.
+            </para>
+                        
+            <para>
+                You can access the stream context using the following methods of <classname>Zend_Http_Client_Adapter_Socket</classname>:
+                <itemizedlist>
+                    <listitem>
+                        <para>
+                            <firstterm><classname>setStreamContext($context)</classname></firstterm>
+	                        Sets the stream context to be used by the adapter. Can accept either
+	                        a stream context resource created using the 
+	                        <ulink url="http://php.net/manual/en/function.stream-context-create.php"><classname>stream_context_create()</classname></ulink>
+	                        PHP function, or an array of stream context options, in the same format provided to this function. 
+	                        Providing an array will create a new stream context using these options, and set it.
+	                    </para>
+	                </listitem>
+	                <listitem>
+	                    <para>
+	                        <firstterm><classname>getStreamContext()</classname></firstterm>
+	                        Get the stream context of the adapter. If no stream context was set, 
+	                        will create a default stream context and return it. You can then set
+	                        or get the value of different context options using regular PHP stream
+	                        context functions.
+	                    </para>
+	                </listitem>
+	            </itemizedlist>    
+            </para>
+            <example id="zend.http.client.adapters.socket.streamcontext.example-1">
+            <title>Setting stream context options for the Socket adapter</title>
+            <programlisting language="php"><![CDATA[
+// 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'];
+]]></programlisting>
+        </example>
+            <note>
+                Note that you must set any stream context options before using the adapter
+                to preform actual requests. If no context is set before preforming HTTP requests
+                with the Socket adapter, a default stream context will be created. This context
+                resource could be accessed after preforming any requests using the 
+                <classname>getStreamContext()</classname> method. 
+            </note>
+        </sect3>
     </sect2>
 
     <sect2 id="zend.http.client.adapters.proxy">
@@ -271,6 +358,69 @@ $client = new Zend_Http_Client('http://www.example.com', $config);
             allows you to easily write your application in a way that allows a
             proxy to be used optionally, according to a configuration parameter.
         </para>
+        
+        <note>
+            Since the proxy adapter inherits from <classname>Zend_Http_Client_Adapter_Socket</classname>,
+            you can use the stream context access method (see <xref linkend="zend.http.client.adapters.socket.streamcontext" />)
+            to set stream context options on Proxy connections as demonstrated above.
+        </note>
+    </sect2>
+
+    <sect2 id="zend.http.client.adapters.curl">
+        <title>The cURL Adapter</title>
+        <para>
+            cURL is a standard HTTP client library that is distributed with many
+            operating systems and can be used in PHP via the cURL extension. It
+            offers functionality for many special cases which can occur for a HTTP
+            client and make it a perfect choice for a HTTP adapter. It supports
+            secure connections, proxy, all sorts of authentication mechanisms
+            and shines in applications that move large files around between servers.
+        </para>
+
+        <example id="zend.http.client.adapters.curl.example-1">
+            <title>Setting cURL options</title>
+            <programlisting language="php"><![CDATA[
+$config = array(
+    'adapter'   => 'Zend_Http_Client_Adapter_Curl',
+    'curloptions' => array(CURLOPT_FOLLOWLOCATION => true),
+);
+$client = new Zend_Http_Client($uri, $config);
+]]></programlisting>
+        </example>
+
+        <para>
+            By default the cURL adapter is configured to behave exactly like
+            the Socket Adapter and it also accepts the same configuration parameters
+            as the Socket and Proxy adapters. You can also change the cURL options by either specifying
+            the 'curloptions' key in the constructor of the adapter or by calling
+            <code>setCurlOption($name, $value)</code>. The <code>$name</code> key
+            corresponds to the CURL_* constants of the cURL extension. You can
+            get access to the Curl handle by calling <code>$adapter->getHandle();</code>
+        </para>
+
+        <example id="zend.http.client.adapters.curl.example-2">
+            <title>Transfering Files by Handle</title>
+
+            <para>
+                You can use cURL to transfer very large files over HTTP by filehandle.
+            </para>
+
+            <programlisting language="php"><![CDATA[
+$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");
+]]></programlisting>
+        </example>
     </sect2>
 
     <sect2 id="zend.http.client.adapters.test">
@@ -398,63 +548,6 @@ $adapter->addResponse(
         </para>
     </sect2>
 
-<sect2 id="zend.http.client.adapters.curl">
-        <title>The cURL Adapter</title>
-        <para>
-            cURL is a standard HTTP client library that is distributed with many
-            operating systems and can be used in PHP via the cURL extension. It
-            offers functionality for many special cases which can occur for a HTTP
-            client and make it a perfect choice for a HTTP adapter. It supports
-            secure connections, proxy, all sorts of authentication mechanisms
-            and shines in applications that move large files around between servers.
-        </para>
-
-        <example id="zend.http.client.adapters.curl.example-1">
-            <title>Setting cURL options</title>
-            <programlisting language="php"><![CDATA[
-$config = array(
-    'adapter'   => 'Zend_Http_Client_Adapter_Curl',
-    'curloptions' => array(CURLOPT_FOLLOWLOCATION => true),
-);
-$client = new Zend_Http_Client($uri, $config);
-]]></programlisting>
-        </example>
-
-        <para>
-            By default the cURL adapter is configured to behave exactly like
-            the Socket Adapter and it also accepts the same configuration parameters
-            as the Socket and Proxy adapters. You can also change the cURL options by either specifying
-            the 'curloptions' key in the constructor of the adapter or by calling
-            <code>setCurlOption($name, $value)</code>. The <code>$name</code> key
-            corresponds to the CURL_* constants of the cURL extension. You can
-            get access to the Curl handle by calling <code>$adapter->getHandle();</code>
-        </para>
-
-        <example id="zend.http.client.adapters.curl.example-2">
-            <title>Transfering Files by Handle</title>
-
-            <para>
-                You can use cURL to transfer very large files over HTTP by filehandle.
-            </para>
-
-            <programlisting language="php"><![CDATA[
-$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");
-]]></programlisting>
-        </example>
-    </sect2>
-
     <sect2 id="zend.http.client.adapters.extending">
         <title>Creating your own connection adapters</title>
         <para>

+ 1 - 1
library/Zend/Http/Client/Adapter/Proxy.php

@@ -105,7 +105,7 @@ class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
                 $this->close();
                 require_once 'Zend/Http/Client/Adapter/Exception.php';
                 throw new Zend_Http_Client_Adapter_Exception(
-                    'Unable to Connect to proxy server ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr);
+                    "Unable to Connect to proxy server $host:$port. Error #$errno: $errstr");
             }
 
             // Set the stream timeout

+ 57 - 1
library/Zend/Http/Client/Adapter/Socket.php

@@ -76,6 +76,13 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
     protected $method = null;
 
     /**
+     * Stream context
+     * 
+     * @var resource
+     */
+    protected $_context = null;
+    
+    /**
      * Adapter constructor, currently empty. Config is set using setConfig()
      *
      */
@@ -100,6 +107,54 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
             $this->config[strtolower($k)] = $v;
         }
     }
+    
+    /**
+     * Set the stream context for the TCP connection to the server
+     * 
+     * Can accept either a pre-existing stream context resource, or an array
+     * of stream options, similar to the options array passed to the 
+     * stream_context_create() PHP function. In such case a new stream context
+     * will be created using the passed options.
+     * 
+     * @since  Zend Framework 1.9
+     * 
+     * @param  mixed $context Stream context or array of context options
+     * @return Zend_Http_Client_Adapter_Socket
+     */
+    public function setStreamContext($context)
+    {
+        if (is_resource($context) && get_resource_type($context) == 'stream-context') {
+            $this->_context = $context;
+            
+        } elseif (is_array($context)) {
+            $this->_context = stream_context_create($context);
+            
+        } else {
+            // Invalid parameter
+            require_once 'Zend/Http/Client/Adapter/Exception.php';
+            throw new Zend_Http_Client_Adapter_Exception(
+                "Expecting either a stream context resource or array, got " . gettype($context)
+            );
+        }
+        
+        return $this;
+    }
+    
+    /**
+     * Get the stream context for the TCP connection to the server. 
+     * 
+     * If no stream context is set, will create a default one. 
+     * 
+     * @return resource
+     */
+    public function getStreamContext()
+    {
+        if (! $this->_context) {
+            $this->_context = stream_context_create();
+        }
+        
+        return $this->_context;
+    }
 
     /**
      * Connect to the remote server
@@ -121,7 +176,7 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
 
         // Now, if we are not connected, connect
         if (! is_resource($this->socket) || ! $this->config['keepalive']) {
-            $context = stream_context_create();
+            $context = $this->getStreamContext();
             if ($secure) {
                 if ($this->config['sslcert'] !== null) {
                     if (! stream_context_set_option($context, 'ssl', 'local_cert',
@@ -148,6 +203,7 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                                                   (int) $this->config['timeout'],
                                                   $flags,
                                                   $context);
+                                                  
             if (! $this->socket) {
                 $this->close();
                 require_once 'Zend/Http/Client/Adapter/Exception.php';

+ 3 - 1
tests/Zend/Http/Client/ProxyAdapterTest.php

@@ -2,6 +2,8 @@
 
 require_once dirname(__FILE__) . '/SocketTest.php';
 
+require_once 'Zend/Http/Client/Adapter/Proxy.php';
+
 /**
  * Zend_Http_Client_Adapter_Proxy test suite.
  *
@@ -64,7 +66,7 @@ class Zend_Http_Client_ProxyAdapterTest extends Zend_Http_Client_SocketTest
 		    parent::setUp();
 
 		} else {
-                        $this->markTestSkipped("Zend_Http_Client proxy server tests are not enabled in TestConfiguration.php");
+            $this->markTestSkipped("Zend_Http_Client proxy server tests are not enabled in TestConfiguration.php");
 		}
 	}
 	

+ 96 - 1
tests/Zend/Http/Client/SocketTest.php

@@ -2,6 +2,8 @@
 
 require_once dirname(__FILE__) . '/CommonHttpTests.php';
 
+require_once 'Zend/Http/Client/Adapter/Socket.php';
+
 /**
  * This Testsuite includes all Zend_Http_Client that require a working web
  * server to perform. It was designed to be extendable, so that several
@@ -30,6 +32,99 @@ class Zend_Http_Client_SocketTest extends Zend_Http_Client_CommonHttpTests
      * @var array
      */
     protected $config = array(
-        'adapter'     => 'Zend_Http_Client_Adapter_Socket'
+        'adapter' => 'Zend_Http_Client_Adapter_Socket'
     );
+    
+    /**
+     * Stream context related tests
+     */
+    
+    public function testGetNewStreamContext()
+    {
+        $adapter = new $this->config['adapter'];
+        $context = $adapter->getStreamContext();
+        
+        $this->assertEquals('stream-context', get_resource_type($context));
+    }
+    
+    public function testSetNewStreamContextResource()
+    {
+        $adapter = new $this->config['adapter'];
+        $context = stream_context_create();
+        
+        $adapter->setStreamContext($context);
+        
+        $this->assertEquals($context, $adapter->getStreamContext());
+    }
+    
+    public function testSetNewStreamContextOptions()
+    {
+        $adapter = new $this->config['adapter'];
+        $options = array(
+            'socket' => array(
+                'bindto' => '1.2.3.4:0'
+            ),
+            'ssl' => array(
+                'verify_peer' => true,
+                'allow_self_signed' => false
+            )
+        );
+        
+        $adapter->setStreamContext($options);
+        
+        $this->assertEquals($options, stream_context_get_options($adapter->getStreamContext()));
+    }
+    
+    /**
+     * Test that setting invalid options / context causes an exception
+     * 
+     * @dataProvider      invalidContextProvider
+     * @expectedException Zend_Http_Client_Adapter_Exception
+     */
+    public function testSetInvalidContextOptions($invalid)
+    {
+        $adapter = new $this->config['adapter'];
+        $adapter->setStreamContext($invalid);
+    }
+    
+    public function testSetHttpsStreamContextParam()
+    {
+        if ($this->client->getUri()->getScheme() != 'https') {
+            $this->markTestSkipped();
+        }
+        
+        $adapter = new $this->config['adapter'];
+        $adapter->setStreamContext(array(
+            'ssl' => array(
+                'capture_peer_cert' => true,
+                'capture_peer_chain' => true
+            )
+        ));
+        
+        $this->client->setAdapter($adapter);
+        $this->client->setUri($this->baseuri . '/testSimpleRequests.php');
+        $this->client->request();
+        
+        $opts = stream_context_get_options($adapter->getStreamContext());
+        $this->assertTrue(isset($opts['ssl']['peer_certificate']));
+    } 
+        
+    /**
+     * Data Providers
+     */
+    
+    /**
+     * Provide invalid context resources / options 
+     *  
+     * @return array
+     */
+    static public function invalidContextProvider()
+    {
+        return array(
+            array(new stdClass()),
+            array(fopen('data://text/plain,', 'r')),
+            array(false),
+            array(null)
+        );
+    }
 }