[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/zend/Zend/Http/Client/Adapter/ -> Socket.php (source)

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1