Microsoft_Http
[ class tree: Microsoft_Http ] [ index: Microsoft_Http ] [ all elements ]

Source for file Socket.php

Documentation is available at Socket.php

  1. <?php
  2.  
  3. /**
  4.  * Zend Framework
  5.  *
  6.  * LICENSE
  7.  *
  8.  * This source file is subject to the new BSD license that is bundled
  9.  * with this package in the file LICENSE.txt.
  10.  * It is also available through the world-wide-web at this URL:
  11.  * http://framework.zend.com/license/new-bsd
  12.  * If you did not receive a copy of the license and are unable to
  13.  * obtain it through the world-wide-web, please send an email
  14.  * to license@zend.com so we can send you a copy immediately.
  15.  *
  16.  * @category   Microsoft
  17.  * @package    Microsoft_Http
  18.  * @subpackage Client_Adapter
  19.  * @version    $Id: Socket.php 19219 2009-11-24 22:25:36Z stas $
  20.  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  21.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  22.  */
  23.  
  24. /**
  25.  * @see Microsoft_AutoLoader
  26.  */
  27. require_once dirname(__FILE__'/../../../AutoLoader.php';
  28.  
  29. /**
  30.  * A sockets based (stream_socket_client) adapter class for Microsoft_Http_Client. Can be used
  31.  * on almost every PHP environment, and does not require any special extensions.
  32.  *
  33.  * @category   Microsoft
  34.  * @package    Microsoft_Http
  35.  * @subpackage Client_Adapter
  36.  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  37.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  38.  */
  39. class Microsoft_Http_Client_Adapter_Socket implements Microsoft_Http_Client_Adapter_InterfaceMicrosoft_Http_Client_Adapter_Stream
  40. {
  41.     /**
  42.      * The socket for server connection
  43.      *
  44.      * @var resource|null
  45.      */
  46.     protected $socket = null;
  47.  
  48.     /**
  49.      * What host/port are we connected to?
  50.      *
  51.      * @var array 
  52.      */
  53.     protected $connected_to = array(nullnull);
  54.  
  55.     /**
  56.      * Stream for storing output
  57.      * 
  58.      * @var resource 
  59.      */
  60.     protected $out_stream = null;
  61.     
  62.     /**
  63.      * Parameters array
  64.      *
  65.      * @var array 
  66.      */
  67.     protected $config = array(
  68.         'persistent'    => false,
  69.         'ssltransport'  => 'ssl',
  70.         'sslcert'       => null,
  71.         'sslpassphrase' => null
  72.     );
  73.  
  74.     /**
  75.      * Request method - will be set by write() and might be used by read()
  76.      *
  77.      * @var string 
  78.      */
  79.     protected $method = null;
  80.  
  81.     /**
  82.      * Stream context
  83.      *
  84.      * @var resource 
  85.      */
  86.     protected $_context = null;
  87.  
  88.     /**
  89.      * Adapter constructor, currently empty. Config is set using setConfig()
  90.      *
  91.      */
  92.     public function __construct()
  93.     {
  94.     }
  95.  
  96.     /**
  97.      * Set the configuration array for the adapter
  98.      *
  99.      * @param array $config 
  100.      */
  101.     public function setConfig($config array())
  102.     {
  103.         if (is_array($config)) {
  104.             require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  105.             throw new Microsoft_Http_Client_Adapter_Exception(
  106.                 'Array expected, got ' gettype($config)
  107.             );
  108.         }
  109.  
  110.         foreach ($config as $k => $v{
  111.             $this->config[strtolower($k)$v;
  112.         }
  113.     }
  114.  
  115.     /**
  116.       * Retrieve the array of all configuration options
  117.       *
  118.       * @return array 
  119.       */
  120.      public function getConfig()
  121.      {
  122.          return $this->config;
  123.      }
  124.  
  125.      /**
  126.      * Set the stream context for the TCP connection to the server
  127.      *
  128.      * Can accept either a pre-existing stream context resource, or an array
  129.      * of stream options, similar to the options array passed to the
  130.      * stream_context_create() PHP function. In such case a new stream context
  131.      * will be created using the passed options.
  132.      *
  133.      * @since  Zend Framework 1.9
  134.      *
  135.      * @param  mixed $context Stream context or array of context options
  136.      * @return Microsoft_Http_Client_Adapter_Socket 
  137.      */
  138.     public function setStreamContext($context)
  139.     {
  140.         if (is_resource($context&& get_resource_type($context== 'stream-context'{
  141.             $this->_context = $context;
  142.  
  143.         elseif (is_array($context)) {
  144.             $this->_context = stream_context_create($context);
  145.  
  146.         else {
  147.             // Invalid parameter
  148.             require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  149.             throw new Microsoft_Http_Client_Adapter_Exception(
  150.                 "Expecting either a stream context resource or array, got " gettype($context)
  151.             );
  152.         }
  153.  
  154.         return $this;
  155.     }
  156.  
  157.     /**
  158.      * Get the stream context for the TCP connection to the server.
  159.      *
  160.      * If no stream context is set, will create a default one.
  161.      *
  162.      * @return resource 
  163.      */
  164.     public function getStreamContext()
  165.     {
  166.         if ($this->_context{
  167.             $this->_context = stream_context_create();
  168.         }
  169.  
  170.         return $this->_context;
  171.     }
  172.  
  173.     /**
  174.      * Connect to the remote server
  175.      *
  176.      * @param string  $host 
  177.      * @param int     $port 
  178.      * @param boolean $secure 
  179.      */
  180.     public function connect($host$port 80$secure false)
  181.     {
  182.         // If the URI should be accessed via SSL, prepend the Hostname with ssl://
  183.         $host ($secure $this->config['ssltransport''tcp''://' $host;
  184.  
  185.         // If we are connected to the wrong host, disconnect first
  186.         if (($this->connected_to[0!= $host || $this->connected_to[1!= $port)) {
  187.             if (is_resource($this->socket)) $this->close();
  188.         }
  189.  
  190.         // Now, if we are not connected, connect
  191.         if (is_resource($this->socket|| $this->config['keepalive']{
  192.             $context $this->getStreamContext();
  193.             if ($secure{
  194.                 if ($this->config['sslcert'!== null{
  195.                     if (stream_context_set_option($context'ssl''local_cert',
  196.                                                     $this->config['sslcert'])) {
  197.                         require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  198.                         throw new Microsoft_Http_Client_Adapter_Exception('Unable to set sslcert option');
  199.                     }
  200.                 }
  201.                 if ($this->config['sslpassphrase'!== null{
  202.                     if (stream_context_set_option($context'ssl''passphrase',
  203.                                                     $this->config['sslpassphrase'])) {
  204.                         require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  205.                         throw new Microsoft_Http_Client_Adapter_Exception('Unable to set sslpassphrase option');
  206.                     }
  207.                 }
  208.             }
  209.  
  210.             $flags STREAM_CLIENT_CONNECT;
  211.             if ($this->config['persistent']$flags |= STREAM_CLIENT_PERSISTENT;
  212.  
  213.             $this->socket = @stream_socket_client($host ':' $port,
  214.                                                   $errno,
  215.                                                   $errstr,
  216.                                                   (int) $this->config['timeout'],
  217.                                                   $flags,
  218.                                                   $context);
  219.  
  220.             if ($this->socket{
  221.                 $this->close();
  222.                 require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  223.                 throw new Microsoft_Http_Client_Adapter_Exception(
  224.                     'Unable to Connect to ' $host ':' $port '. Error #' $errno ': ' $errstr);
  225.             }
  226.  
  227.             // Set the stream timeout
  228.             if (stream_set_timeout($this->socket(int) $this->config['timeout'])) {
  229.                 require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  230.                 throw new Microsoft_Http_Client_Adapter_Exception('Unable to set the connection timeout');
  231.             }
  232.  
  233.             // Update connected_to
  234.             $this->connected_to = array($host$port);
  235.         }
  236.     }
  237.  
  238.     /**
  239.      * Send request to the remote server
  240.      *
  241.      * @param string        $method 
  242.      * @param Microsoft_Uri_Http $uri 
  243.      * @param string        $http_ver 
  244.      * @param array         $headers 
  245.      * @param string        $body 
  246.      * @return string Request as string
  247.      */
  248.     public function write($method$uri$http_ver '1.1'$headers array()$body '')
  249.     {
  250.         // Make sure we're properly connected
  251.         if ($this->socket{
  252.             require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  253.             throw new Microsoft_Http_Client_Adapter_Exception('Trying to write but we are not connected');
  254.         }
  255.  
  256.         $host $uri->getHost();
  257.         $host (strtolower($uri->getScheme()) == 'https' $this->config['ssltransport''tcp''://' $host;
  258.         if ($this->connected_to[0!= $host || $this->connected_to[1!= $uri->getPort()) {
  259.             require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  260.             throw new Microsoft_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host');
  261.         }
  262.  
  263.         // Save request method for later
  264.         $this->method = $method;
  265.  
  266.         // Build request headers
  267.         $path $uri->getPath();
  268.         if ($uri->getQuery()) $path .= '?' $uri->getQuery();
  269.         $request "{$method} {$path} HTTP/{$http_ver}\r\n";
  270.         foreach ($headers as $k => $v{
  271.             if (is_string($k)) $v ucfirst($k"$v";
  272.             $request .= "$v\r\n";
  273.         }
  274.  
  275.         if(is_resource($body)) {
  276.             $request .= "\r\n";
  277.         else {
  278.             // Add the request body
  279.             $request .= "\r\n" $body;
  280.         }
  281.         
  282.         // Send the request
  283.         if (@fwrite($this->socket$request)) {
  284.             require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  285.             throw new Microsoft_Http_Client_Adapter_Exception('Error writing request to server');
  286.         }
  287.         
  288.         if(is_resource($body)) {
  289.             if(stream_copy_to_stream($body$this->socket== 0{
  290.                 require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  291.                 throw new Microsoft_Http_Client_Adapter_Exception('Error writing request to server');
  292.             }
  293.         }
  294.  
  295.         return $request;
  296.     }
  297.  
  298.     /**
  299.      * Read response from server
  300.      *
  301.      * @return string 
  302.      */
  303.     public function read()
  304.     {
  305.         // First, read headers only
  306.         $response '';
  307.         $gotStatus false;
  308.         $stream !empty($this->config['stream']);
  309.  
  310.         while (($line @fgets($this->socket)) !== false{
  311.             $gotStatus $gotStatus || (strpos($line'HTTP'!== false);
  312.             if ($gotStatus{
  313.                 $response .= $line;
  314.                 if (rtrim($line=== ''break;
  315.             }
  316.         }
  317.         
  318.         $this->_checkSocketReadTimeout();
  319.  
  320.         $statusCode Microsoft_Http_Response::extractCode($response);
  321.  
  322.         // Handle 100 and 101 responses internally by restarting the read again
  323.         if ($statusCode == 100 || $statusCode == 101return $this->read();
  324.  
  325.         // Check headers to see what kind of connection / transfer encoding we have
  326.         $headers Microsoft_Http_Response::extractHeaders($response);
  327.  
  328.         /**
  329.          * Responses to HEAD requests and 204 or 304 responses are not expected
  330.          * to have a body - stop reading here
  331.          */
  332.         if ($statusCode == 304 || $statusCode == 204 ||
  333.             $this->method == Microsoft_Http_Client::HEAD{
  334.  
  335.             // Close the connection if requested to do so by the server
  336.             if (isset($headers['connection']&& $headers['connection'== 'close'{
  337.                 $this->close();
  338.             }
  339.             return $response;
  340.         }
  341.  
  342.         // If we got a 'transfer-encoding: chunked' header
  343.         if (isset($headers['transfer-encoding'])) {
  344.             
  345.             if (strtolower($headers['transfer-encoding']== 'chunked'{
  346.  
  347.                 do {
  348.                     $line  @fgets($this->socket);
  349.                     $this->_checkSocketReadTimeout();
  350.  
  351.                     $chunk $line;
  352.  
  353.                     // Figure out the next chunk size
  354.                     $chunksize trim($line);
  355.                     if (ctype_xdigit($chunksize)) {
  356.                         $this->close();
  357.                         require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  358.                         throw new Microsoft_Http_Client_Adapter_Exception('Invalid chunk size "' .
  359.                             $chunksize '" unable to read chunked body');
  360.                     }
  361.  
  362.                     // Convert the hexadecimal value to plain integer
  363.                     $chunksize hexdec($chunksize);
  364.  
  365.                     // Read next chunk
  366.                     $read_to ftell($this->socket$chunksize;
  367.  
  368.                     do {
  369.                         $current_pos ftell($this->socket);
  370.                         if ($current_pos >= $read_tobreak;
  371.  
  372.                         if($this->out_stream{
  373.                             if(stream_copy_to_stream($this->socket$this->out_stream$read_to $current_pos== 0{
  374.                               $this->_checkSocketReadTimeout();
  375.                               break;   
  376.                              }
  377.                         else {
  378.                             $line @fread($this->socket$read_to $current_pos);
  379.                             if ($line === false || strlen($line=== 0{
  380.                                 $this->_checkSocketReadTimeout();
  381.                                 break;
  382.                             }
  383.                                     $chunk .= $line;
  384.                         }
  385.                     while (feof($this->socket));
  386.  
  387.                     $chunk .= @fgets($this->socket);
  388.                     $this->_checkSocketReadTimeout();
  389.  
  390.                     if(!$this->out_stream{
  391.                         $response .= $chunk;
  392.                     }
  393.                 while ($chunksize 0);
  394.             else {
  395.                 $this->close();
  396.                 throw new Microsoft_Http_Client_Adapter_Exception('Cannot handle "' .
  397.                     $headers['transfer-encoding''" transfer encoding');
  398.             }
  399.             
  400.             // We automatically decode chunked-messages when writing to a stream
  401.             // this means we have to disallow the Microsoft_Http_Response to do it again
  402.             if ($this->out_stream{
  403.                 $response str_ireplace("Transfer-Encoding: chunked\r\n"''$response);
  404.             }
  405.         // Else, if we got the content-length header, read this number of bytes
  406.         elseif (isset($headers['content-length'])) {
  407.  
  408.             $current_pos ftell($this->socket);
  409.             $chunk '';
  410.  
  411.             for ($read_to $current_pos $headers['content-length'];
  412.                  $read_to $current_pos;
  413.                  $current_pos ftell($this->socket)) {
  414.  
  415.                  if($this->out_stream{
  416.                      if(@stream_copy_to_stream($this->socket$this->out_stream$read_to $current_pos== 0{
  417.                           $this->_checkSocketReadTimeout();
  418.                           break;   
  419.                      }
  420.                  else {
  421.                     $chunk @fread($this->socket$read_to $current_pos);
  422.                     if ($chunk === false || strlen($chunk=== 0{
  423.                         $this->_checkSocketReadTimeout();
  424.                         break;
  425.                     }
  426.  
  427.                     $response .= $chunk;
  428.                 }
  429.  
  430.                 // Break if the connection ended prematurely
  431.                 if (feof($this->socket)) break;
  432.             }
  433.  
  434.         // Fallback: just read the response until EOF
  435.         else {
  436.  
  437.             do {
  438.                 if($this->out_stream{
  439.                     if(@stream_copy_to_stream($this->socket$this->out_stream== 0{
  440.                           $this->_checkSocketReadTimeout();
  441.                           break;   
  442.                      }
  443.                 }  else {
  444.                     $buff @fread($this->socket8192);
  445.                     if ($buff === false || strlen($buff=== 0{
  446.                         $this->_checkSocketReadTimeout();
  447.                         break;
  448.                     else {
  449.                         $response .= $buff;
  450.                     }
  451.                 }
  452.  
  453.             while (feof($this->socket=== false);
  454.  
  455.             $this->close();
  456.         }
  457.  
  458.         // Close the connection if requested to do so by the server
  459.         if (isset($headers['connection']&& $headers['connection'== 'close'{
  460.             $this->close();
  461.         }
  462.  
  463.         return $response;
  464.     }
  465.  
  466.     /**
  467.      * Close the connection to the server
  468.      *
  469.      */
  470.     public function close()
  471.     {
  472.         if (is_resource($this->socket)) @fclose($this->socket);
  473.         $this->socket = null;
  474.         $this->connected_to = array(nullnull);
  475.     }
  476.  
  477.     /**
  478.      * Check if the socket has timed out - if so close connection and throw
  479.      * an exception
  480.      *
  481.      * @throws Microsoft_Http_Client_Adapter_Exception with READ_TIMEOUT code
  482.      */
  483.     protected function _checkSocketReadTimeout()
  484.     {
  485.         if ($this->socket{
  486.             $info stream_get_meta_data($this->socket);
  487.             $timedout $info['timed_out'];
  488.             if ($timedout{
  489.                 $this->close();
  490.                 require_once 'Microsoft/Http/Client/Adapter/Exception.php';
  491.                 throw new Microsoft_Http_Client_Adapter_Exception(
  492.                     "Read timed out after {$this->config['timeout']} seconds",
  493.                     Microsoft_Http_Client_Adapter_Exception::READ_TIMEOUT
  494.                 );
  495.             }
  496.         }
  497.     }
  498.     
  499.     /**
  500.      * Set output stream for the response
  501.      * 
  502.      * @param resource $stream 
  503.      * @return Microsoft_Http_Client_Adapter_Socket 
  504.      */
  505.     public function setOutputStream($stream
  506.     {
  507.         $this->out_stream = $stream;
  508.         return $this;
  509.     }
  510.     
  511.     /**
  512.      * Destructor: make sure the socket is disconnected
  513.      *
  514.      * If we are in persistent TCP mode, will not close the connection
  515.      *
  516.      */
  517.     public function __destruct()
  518.     {
  519.         if ($this->config['persistent']{
  520.             if ($this->socket$this->close();
  521.         }
  522.     }
  523. }

Documentation generated on Wed, 18 May 2011 12:06:52 +0200 by phpDocumentor 1.4.3