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_Interface, Microsoft_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(null, null);
  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 == 101) return $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_to) break;
  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->socket, 8192);
  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(null, null);
  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 Sat, 03 Dec 2011 13:59:39 +0100 by phpDocumentor 1.4.3