Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
socket.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage HTTP
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * HTTP transport class for using sockets directly.
14  *
15  * @package Joomla.Platform
16  * @subpackage HTTP
17  * @since 11.3
18  */
20 {
21  /**
22  * @var array Reusable socket connections.
23  * @since 11.3
24  */
25  protected $connections;
26 
27  /**
28  * @var JRegistry The client options.
29  * @since 11.3
30  */
31  protected $options;
32 
33  /**
34  * Constructor.
35  *
36  * @param JRegistry $options Client options object.
37  *
38  * @since 11.3
39  * @throws RuntimeException
40  */
41  public function __construct(JRegistry $options)
42  {
43  if (!self::isSupported())
44  {
45  throw new RuntimeException('Cannot use a socket transport when fsockopen() is not available.');
46  }
47 
48  $this->options = $options;
49  }
50 
51  /**
52  * Send a request to the server and return a JHttpResponse object with the response.
53  *
54  * @param string $method The HTTP method for sending the request.
55  * @param JUri $uri The URI to the resource to request.
56  * @param mixed $data Either an associative array or a string to be sent with the request.
57  * @param array $headers An array of request headers to send with the request.
58  * @param integer $timeout Read timeout in seconds.
59  * @param string $userAgent The optional user agent string to send with the request.
60  *
61  * @return JHttpResponse
62  *
63  * @since 11.3
64  * @throws RuntimeException
65  */
66  public function request($method, JUri $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
67  {
68  $connection = $this->connect($uri, $timeout);
69 
70  // Make sure the connection is alive and valid.
71  if (is_resource($connection))
72  {
73  // Make sure the connection has not timed out.
74  $meta = stream_get_meta_data($connection);
75 
76  if ($meta['timed_out'])
77  {
78  throw new RuntimeException('Server connection timed out.');
79  }
80  }
81  else
82  {
83  throw new RuntimeException('Not connected to server.');
84  }
85 
86  // Get the request path from the URI object.
87  $path = $uri->toString(array('path', 'query'));
88 
89  // If we have data to send make sure our request is setup for it.
90  if (!empty($data))
91  {
92  // If the data is not a scalar value encode it to be sent with the request.
93  if (!is_scalar($data))
94  {
95  $data = http_build_query($data);
96  }
97 
98  if (!isset($headers['Content-Type']))
99  {
100  $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
101  }
102 
103  // Add the relevant headers.
104  $headers['Content-Length'] = strlen($data);
105  }
106 
107  // Build the request payload.
108  $request = array();
109  $request[] = strtoupper($method) . ' ' . ((empty($path)) ? '/' : $path) . ' HTTP/1.0';
110  $request[] = 'Host: ' . $uri->getHost();
111 
112  // If an explicit user agent is given use it.
113  if (isset($userAgent))
114  {
115  $headers['User-Agent'] = $userAgent;
116  }
117 
118  // If there are custom headers to send add them to the request payload.
119  if (is_array($headers))
120  {
121  foreach ($headers as $k => $v)
122  {
123  $request[] = $k . ': ' . $v;
124  }
125  }
126 
127  // If we have data to send add it to the request payload.
128  if (!empty($data))
129  {
130  $request[] = null;
131  $request[] = $data;
132  }
133 
134  // Send the request to the server.
135  fwrite($connection, implode("\r\n", $request) . "\r\n\r\n");
136 
137  // Get the response data from the server.
138  $content = '';
139 
140  while (!feof($connection))
141  {
142  $content .= fgets($connection, 4096);
143  }
144 
145  return $this->getResponse($content);
146  }
147 
148  /**
149  * Method to get a response object from a server response.
150  *
151  * @param string $content The complete server response, including headers.
152  *
153  * @return JHttpResponse
154  *
155  * @since 11.3
156  * @throws UnexpectedValueException
157  */
158  protected function getResponse($content)
159  {
160  // Create the response object.
161  $return = new JHttpResponse;
162 
163  if (empty($content))
164  {
165  throw new UnexpectedValueException('No content in response.');
166  }
167 
168  // Split the response into headers and body.
169  $response = explode("\r\n\r\n", $content, 2);
170 
171  // Get the response headers as an array.
172  $headers = explode("\r\n", $response[0]);
173 
174  // Set the body for the response.
175  $return->body = empty($response[1]) ? '' : $response[1];
176 
177  // Get the response code from the first offset of the response headers.
178  preg_match('/[0-9]{3}/', array_shift($headers), $matches);
179  $code = $matches[0];
180 
181  if (is_numeric($code))
182  {
183  $return->code = (int) $code;
184  }
185 
186  // No valid response code was detected.
187  else
188  {
189  throw new UnexpectedValueException('No HTTP response code found.');
190  }
191 
192  // Add the response headers to the response object.
193  foreach ($headers as $header)
194  {
195  $pos = strpos($header, ':');
196  $return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
197  }
198 
199  return $return;
200  }
201 
202  /**
203  * Method to connect to a server and get the resource.
204  *
205  * @param JUri $uri The URI to connect with.
206  * @param integer $timeout Read timeout in seconds.
207  *
208  * @return resource Socket connection resource.
209  *
210  * @since 11.3
211  * @throws RuntimeException
212  */
213  protected function connect(JUri $uri, $timeout = null)
214  {
215  $errno = null;
216  $err = null;
217 
218  // Get the host from the uri.
219  $host = ($uri->isSSL()) ? 'ssl://' . $uri->getHost() : $uri->getHost();
220 
221  // If the port is not explicitly set in the URI detect it.
222  if (!$uri->getPort())
223  {
224  $port = ($uri->getScheme() == 'https') ? 443 : 80;
225  }
226 
227  // Use the set port.
228  else
229  {
230  $port = $uri->getPort();
231  }
232 
233  // Build the connection key for resource memory caching.
234  $key = md5($host . $port);
235 
236  // If the connection already exists, use it.
237  if (!empty($this->connections[$key]) && is_resource($this->connections[$key]))
238  {
239  // Connection reached EOF, cannot be used anymore
240  $meta = stream_get_meta_data($this->connections[$key]);
241 
242  if ($meta['eof'])
243  {
244  if (!fclose($this->connections[$key]))
245  {
246  throw new RuntimeException('Cannot close connection');
247  }
248  }
249 
250  // Make sure the connection has not timed out.
251  elseif (!$meta['timed_out'])
252  {
253  return $this->connections[$key];
254  }
255  }
256 
257  if (!is_numeric($timeout))
258  {
259  $timeout = ini_get('default_socket_timeout');
260  }
261 
262  // Capture PHP errors
263  $php_errormsg = '';
264  $track_errors = ini_get('track_errors');
265  ini_set('track_errors', true);
266 
267  // PHP sends a warning if the uri does not exists; we silence it and throw an exception instead.
268  // Attempt to connect to the server
269  $connection = @fsockopen($host, $port, $errno, $err, $timeout);
270 
271  if (!$connection)
272  {
273  if (!$php_errormsg)
274  {
275  // Error but nothing from php? Create our own
276  $php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
277  }
278 
279  // Restore error tracking to give control to the exception handler
280  ini_set('track_errors', $track_errors);
281 
282  throw new RuntimeException($php_errormsg);
283  }
284 
285  // Restore error tracking to what it was before.
286  ini_set('track_errors', $track_errors);
287 
288  // Since the connection was successful let's store it in case we need to use it later.
289  $this->connections[$key] = $connection;
290 
291  // If an explicit timeout is set, set it.
292  if (isset($timeout))
293  {
294  stream_set_timeout($this->connections[$key], (int) $timeout);
295  }
296 
297  return $this->connections[$key];
298  }
299 
300  /**
301  * Method to check if http transport socket available for use
302  *
303  * @return boolean True if available else false
304  *
305  * @since 12.1
306  */
307  public static function isSupported()
308  {
309  return function_exists('fsockopen') && is_callable('fsockopen');
310  }
311 
312 }