PageRenderTime 40ms CodeModel.GetById 30ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 0ms

/php/external/Zend/Http/Client/Adapter/Socket.php

https://github.com/anirvan/shindig-profiles
PHP | 294 lines | 147 code | 39 blank | 108 comment | 50 complexity | 811a17d6fd46b205cda3ac216bd95cc5 MD5 | raw file
  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   Zend
 17 * @package    Zend_Http
 18 * @subpackage Client_Adapter
 19 * @version    $Id: Socket.php 8064 2008-02-16 10:58:39Z thomas $
 20 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 21 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 22 */
 23
 24require_once 'external/Zend/Uri/Http.php';
 25require_once 'external/Zend/Http/Client/Adapter/Interface.php';
 26
 27/**
 28 * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used
 29 * on almost every PHP environment, and does not require any special extensions.
 30 *
 31 * @category   Zend
 32 * @package    Zend_Http
 33 * @subpackage Client_Adapter
 34 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 35 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 36 */
 37class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface {
 38  /**
 39   * The socket for server connection
 40   *
 41   * @var resource|null
 42   */
 43  protected $socket = null;
 44  
 45  /**
 46   * What host/port are we connected to?
 47   *
 48   * @var array
 49   */
 50  protected $connected_to = array(null, null);
 51  
 52  /**
 53   * Parameters array
 54   *
 55   * @var array
 56   */
 57  protected $config = array('ssltransport' => 'ssl', 'sslcert' => null, 'sslpassphrase' => null);
 58  
 59  /**
 60   * Request method - will be set by write() and might be used by read()
 61   *
 62   * @var string
 63   */
 64  protected $method = null;
 65
 66  /**
 67   * Adapter constructor, currently empty. Config is set using setConfig()
 68   *
 69   */
 70  public function __construct() {}
 71
 72  /**
 73   * Set the configuration array for the adapter
 74   *
 75   * @param array $config
 76   */
 77  public function setConfig($config = array()) {
 78    if (! is_array($config)) {
 79      require_once 'external/Zend/Http/Client/Adapter/Exception.php';
 80      throw new Zend_Http_Client_Adapter_Exception('$config expects an array, ' . gettype($config) . ' recieved.');
 81    }
 82    
 83    foreach ($config as $k => $v) {
 84      $this->config[strtolower($k)] = $v;
 85    }
 86  }
 87
 88  /**
 89   * Connect to the remote server
 90   *
 91   * @param string  $host
 92   * @param int     $port
 93   * @param boolean $secure
 94   * @param int     $timeout
 95   */
 96  public function connect($host, $port = 80, $secure = false) {
 97    // If the URI should be accessed via SSL, prepend the Hostname with ssl://
 98    $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
 99    
100    // If we are connected to the wrong host, disconnect first
101    if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
102      if (is_resource($this->socket)) $this->close();
103    }
104    
105    // Now, if we are not connected, connect
106    if (! is_resource($this->socket) || ! $this->config['keepalive']) {
107      $context = stream_context_create();
108      if ($secure) {
109        if ($this->config['sslcert'] !== null) {
110          if (! stream_context_set_option($context, 'ssl', 'local_cert', $this->config['sslcert'])) {
111            require_once 'external/Zend/Http/Client/Adapter/Exception.php';
112            throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option');
113          }
114        }
115        if ($this->config['sslpassphrase'] !== null) {
116          if (! stream_context_set_option($context, 'ssl', 'passphrase', $this->config['sslpassphrase'])) {
117            require_once 'external/Zend/Http/Client/Adapter/Exception.php';
118            throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option');
119          }
120        }
121      }
122      
123      $this->socket = @stream_socket_client($host . ':' . $port, $errno, $errstr, (int)$this->config['timeout'], STREAM_CLIENT_CONNECT, $context);
124      if (! $this->socket) {
125        $this->close();
126        require_once 'external/Zend/Http/Client/Adapter/Exception.php';
127        throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr);
128      }
129      
130      // Set the stream timeout
131      if (! stream_set_timeout($this->socket, (int)$this->config['timeout'])) {
132        require_once 'external/Zend/Http/Client/Adapter/Exception.php';
133        throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout');
134      }
135      
136      // Update connected_to
137      $this->connected_to = array($host, $port);
138    }
139  }
140
141  /**
142   * Send request to the remote server
143   *
144   * @param string        $method
145   * @param Zend_Uri_Http $uri
146   * @param string        $http_ver
147   * @param array         $headers
148   * @param string        $body
149   * @return string Request as string
150   */
151  public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') {
152    // Make sure we're properly connected
153    if (! $this->socket) {
154      require_once 'external/Zend/Http/Client/Adapter/Exception.php';
155      throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected');
156    }
157    
158    $host = $uri->getHost();
159    $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
160    if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) {
161      require_once 'external/Zend/Http/Client/Adapter/Exception.php';
162      throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host');
163    }
164    
165    // Save request method for later
166    $this->method = $method;
167    
168    // Build request headers
169    $path = $uri->getPath();
170    if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
171    $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
172    foreach ($headers as $k => $v) {
173      if (is_string($k)) $v = ucfirst($k) . ": $v";
174      $request .= "$v\r\n";
175    }
176    
177    // Add the request body
178    $request .= "\r\n" . $body;
179    
180    // Send the request
181    if (! @fwrite($this->socket, $request)) {
182      require_once 'external/Zend/Http/Client/Adapter/Exception.php';
183      throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
184    }
185    
186    return $request;
187  }
188
189  /**
190   * Read response from server
191   *
192   * @return string
193   */
194  public function read() {
195    // First, read headers only
196    $response = '';
197    $gotStatus = false;
198    while ($line = @fgets($this->socket)) {
199      $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
200      if ($gotStatus) {
201        $response .= $line;
202        if (! chop($line)) break;
203      }
204    }
205    
206    // Handle 100 and 101 responses internally by restarting the read again
207    if (Zend_Http_Response::extractCode($response) == 100 || Zend_Http_Response::extractCode($response) == 101) return $this->read();
208    
209    // If this was a HEAD request, return after reading the header (no need to read body)
210    if ($this->method == Zend_Http_Client::HEAD) return $response;
211    
212    // Check headers to see what kind of connection / transfer encoding we have
213    $headers = Zend_Http_Response::extractHeaders($response);
214    
215    // if the connection is set to close, just read until socket closes
216    if (isset($headers['connection']) && $headers['connection'] == 'close') {
217      while ($buff = @fread($this->socket, 8192)) {
218        $response .= $buff;
219      }
220      
221      $this->close();
222      
223    // Else, if we got a transfer-encoding header (chunked body)
224    } elseif (isset($headers['transfer-encoding'])) {
225      if ($headers['transfer-encoding'] == 'chunked') {
226        do {
227          $chunk = '';
228          $line = @fgets($this->socket);
229          $chunk .= $line;
230          
231          $hexchunksize = ltrim(chop($line), '0');
232          $hexchunksize = strlen($hexchunksize) ? strtolower($hexchunksize) : 0;
233          
234          $chunksize = hexdec(chop($line));
235          if (dechex($chunksize) != $hexchunksize) {
236            @fclose($this->socket);
237            require_once 'external/Zend/Http/Client/Adapter/Exception.php';
238            throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' . $hexchunksize . '" unable to read chunked body');
239          }
240          
241          $left_to_read = $chunksize;
242          while ($left_to_read > 0) {
243            $line = @fread($this->socket, $left_to_read);
244            $chunk .= $line;
245            $left_to_read -= strlen($line);
246          }
247          
248          $chunk .= @fgets($this->socket);
249          $response .= $chunk;
250        } while ($chunksize > 0);
251      } else {
252        throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . $headers['transfer-encoding'] . '" transfer encoding');
253      }
254      
255    // Else, if we got the content-length header, read this number of bytes
256    } elseif (isset($headers['content-length'])) {
257      $left_to_read = $headers['content-length'];
258      $chunk = '';
259      while ($left_to_read > 0) {
260        $chunk = @fread($this->socket, $left_to_read);
261        $left_to_read -= strlen($chunk);
262        $response .= $chunk;
263      }
264      
265    // Fallback: just read the response (should not happen)
266    } else {
267      while ($buff = @fread($this->socket, 8192)) {
268        $response .= $buff;
269      }
270      
271      $this->close();
272    }
273    
274    return $response;
275  }
276
277  /**
278   * Close the connection to the server
279   *
280   */
281  public function close() {
282    if (is_resource($this->socket)) @fclose($this->socket);
283    $this->socket = null;
284    $this->connected_to = array(null, null);
285  }
286
287  /**
288   * Destructor: make sure the socket is disconnected
289   *
290   */
291  public function __destruct() {
292    if ($this->socket) $this->close();
293  }
294}