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.
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
24 require_once 'external/Zend/Uri/Http.php';
25 require_once 'external/Zend/Http/Client/Adapter/Interface.php';
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.
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
37 class Zend_Http_Client_Adapter_Socket
implements Zend_Http_Client_Adapter_Interface
{
39 * The socket for server connection
43 protected $socket = null;
46 * What host/port are we connected to?
50 protected $connected_to = array(null, null);
57 protected $config = array('ssltransport' => 'ssl', 'sslcert' => null, 'sslpassphrase' => null);
60 * Request method - will be set by write() and might be used by read()
64 protected $method = null;
67 * Adapter constructor, currently empty. Config is set using setConfig()
70 public function __construct() {}
73 * Set the configuration array for the adapter
75 * @param array $config
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.');
83 foreach ($config as $k => $v) {
84 $this->config
[strtolower($k)] = $v;
89 * Connect to the remote server
93 * @param boolean $secure
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;
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();
105 // Now, if we are not connected, connect
106 if (! is_resource($this->socket
) ||
! $this->config
['keepalive']) {
107 $context = stream_context_create();
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');
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');
123 $this->socket
= @stream_socket_client
($host . ':' . $port, $errno, $errstr, (int)$this->config
['timeout'], STREAM_CLIENT_CONNECT
, $context);
124 if (! $this->socket
) {
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);
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');
136 // Update connected_to
137 $this->connected_to
= array($host, $port);
142 * Send request to the remote server
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
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');
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');
165 // Save request method for later
166 $this->method
= $method;
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";
177 // Add the request body
178 $request .= "\r\n" . $body;
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');
190 * Read response from server
194 public function read() {
195 // First, read headers only
198 while ($line = @fgets
($this->socket
)) {
199 $gotStatus = $gotStatus ||
(strpos($line, 'HTTP') !== false);
202 if (! chop($line)) break;
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();
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;
212 // Check headers to see what kind of connection / transfer encoding we have
213 $headers = Zend_Http_Response
::extractHeaders($response);
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)) {
223 // Else, if we got a transfer-encoding header (chunked body)
224 } elseif (isset($headers['transfer-encoding'])) {
225 if ($headers['transfer-encoding'] == 'chunked') {
228 $line = @fgets
($this->socket
);
231 $hexchunksize = ltrim(chop($line), '0');
232 $hexchunksize = strlen($hexchunksize) ?
strtolower($hexchunksize) : 0;
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');
241 $left_to_read = $chunksize;
242 while ($left_to_read > 0) {
243 $line = @fread
($this->socket
, $left_to_read);
245 $left_to_read -= strlen($line);
248 $chunk .= @fgets
($this->socket
);
250 } while ($chunksize > 0);
252 throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . $headers['transfer-encoding'] . '" transfer encoding');
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'];
259 while ($left_to_read > 0) {
260 $chunk = @fread
($this->socket
, $left_to_read);
261 $left_to_read -= strlen($chunk);
265 // Fallback: just read the response (should not happen)
267 while ($buff = @fread
($this->socket
, 8192)) {
278 * Close the connection to the server
281 public function close() {
282 if (is_resource($this->socket
)) @fclose
($this->socket
);
283 $this->socket
= null;
284 $this->connected_to
= array(null, null);
288 * Destructor: make sure the socket is disconnected
291 public function __destruct() {
292 if ($this->socket
) $this->close();