Make WordPress Core

Ticket #4779: http.php

File http.php, 15.7 KB (added by darkdragon, 19 years ago)

Prototype of HTTP Request API

Line 
1<?php
2/**
3 * Simple HTTP request fallback system
4 *
5 * @package WordPress
6 * @subpackage HTTP
7 * @since None
8 * @author Jacob Santos <wordpress@santosj.name>
9 */
10
11/**
12 * Abstract class for all of the fallback implementation
13 * classes. The implementation classes will extend this class
14 * to keep common API methods universal between different
15 * functionality.
16 *
17 * @package WordPress
18 * @subpackage HTTP
19 * @since None
20 * @abstract
21 */
22class WP_HTTP_Base
23{
24 /**
25 * The timeout variable that will be used to set for the
26 * fallback HTTP retrieval classes. Default is 30 seconds.
27 *
28 * @var integer
29 * @access protected
30 */
31 var $timeout = 30;
32
33 /**
34 * Stores the Headers as an array for easy retrieval by header name.
35 *
36 * @var array
37 * @access protected
38 */
39 var $headers = null;
40
41 /**
42 * Stores the entire body string.
43 *
44 * @var string
45 * @access protected
46 */
47 var $body = null;
48
49 /**
50 * The error, if any, that occurred while trying to retrieve the URL.
51 *
52 * Will be the array, with the key as the HTTP response code and string of response.
53 *
54 * @var array
55 * @access protected
56 */
57 var $error = null;
58
59 /**
60 * Uses the POST HTTP method.
61 *
62 * Used for sending data that is expected to be in the body.
63 *
64 * @access public
65 * @param string $url The location of the site and page to retrieve.
66 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
67 * @param string $body Optional. The body that should be sent. Expected to be already processed.
68 * @return boolean
69 */
70 function post($url, $headers=null, $body=null)
71 {
72 return $this->request($url, 'POST', $headers, $body);
73 }
74
75 /**
76 * Uses the GET HTTP method.
77 *
78 * Used for sending data that is expected to be in the body.
79 *
80 * @access public
81 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
82 * @param string $body Optional. The body that should be sent. Expected to be already processed.
83 * @return boolean
84 */
85 function get($url, $headers=null, $body=null)
86 {
87 return $this->request($url, 'GET', $headers, $body);
88 }
89
90 /**
91 * Set the headers.
92 *
93 * Should only be used before sending the request for the URL retrieval. Setting it after will
94 * overwrite any headers that might have been processed.
95 *
96 * @access public
97 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
98 */
99 function setHeaders($headers)
100 {
101 if( is_numeric($headers) || is_object($headers) ) {
102 throw trigger_error('$headers variable must be of type string or array', E_USER_WARNING);
103 return;
104 }
105
106 $this->headers = $headers;
107 }
108
109 /**
110 * Get the headers from the retrieved site.
111 *
112 * Should be used after the request for the location. Default is to return the full list as an array.
113 *
114 * If $header is used, then the value from the header matching $header will be returned. if
115 * no header exists with that matches, an empty string will be returned instead.
116 *
117 * @access public
118 * @param string $header Optional. The header name to return.
119 * @return string|array If $header is used, will return value of header, else will return the whole headers list.
120 */
121 function getHeaders($header=null)
122 {
123 if( !is_array( $this->headers ) ) {
124 $arrTempHeaderList = explode("\n", str_replace("\r", '', $this->headers) );
125
126 $this->headers = array();
127 foreach($arrTempHeaderList as $header) {
128 list($key, $value) = explode(":", $header, 2);
129 $this->headers[$key] = trim($value);
130 }
131 }
132
133 if( !is_null($header) && isset($this->headers[$header]) )
134 return $this->headers[$header];
135
136 return $this->headers;
137 }
138
139 /**
140 * Sets the body for sending to the retrieved site.
141 *
142 * Should only be used before retrieving the site. Setting the body after doing so
143 * will overwrite whatever was retrieved from the page.
144 *
145 * The body should be already processed for submitting to the site.
146 *
147 * @access public
148 * @param string $body The processed string to be submitted after the headers to the site.
149 */
150 function setBody($body)
151 {
152 if( is_string($body) )
153 $this->body = $body;
154 }
155
156 /**
157 * Gets the body from the retrieved site.
158 *
159 * @access public
160 * @return null|string If location is retrieved, will always return string. If not, then null will be returned.
161 */
162 function getBody()
163 {
164 return $this->body;
165 }
166
167 /**
168 * Sets the timeout
169 *
170 * Has a filter applied so that plugins may set the timeout. The filter tag is
171 * 'http_request_timeout' and has no default hooks.
172 *
173 * @access public
174 * @param int $timeout The amount of seconds to try the url before failing
175 */
176 function setTimeout($timeout)
177 {
178 $this->timeout = apply_filters('http_request_timeout', intval($timeout) );
179 }
180
181 /**
182 * Whether an error occurred during processing the URL request.
183 *
184 * @access public
185 * @return boolean
186 */
187 function hasError()
188 {
189 return is_null($this->error);
190 }
191
192 /**
193 * Retrieve the error, if any, that might have occurred during processing
194 * the URL request.
195 *
196 * @access public
197 * @return null|WP_Error If error occurred, will be WP_Error, else will be null if no error.
198 */
199 function getError()
200 {
201 reset($this->error);
202
203 if( !$this->hasError() )
204 return WP_Error( key($this->error), current($this->error) );
205
206 return null;
207 }
208
209 /**
210 * Retrieve the location and set the class properties after the site has been retrieved.
211 *
212 * @abstract
213 * @access public
214 * @param string $url
215 * @param string $type Optional. Should be of HTTP Request type: GET, POST, HEAD.
216 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
217 * @param string $body Optional. The body that should be sent. Expected to be already processed.
218 * @return boolean
219 */
220 function request($url, $type='GET', $headers=null, $body=null) {
221 trigger_error('Class does not implement request method!', E_USER_ERROR);
222 }
223
224 /**
225 * Will test to make sure that the HTTP retrieval method is available for use.
226 *
227 * @abstract
228 * @access public
229 * @return boolean Whether the method can be used.
230 */
231 function test() {
232 trigger_error('Class does not implement test method!', E_USER_ERROR);
233 }
234}
235
236/**
237 * HTTP request method uses fsockopen function to retrieve the url.
238 *
239 * Preferred method since it works with all WordPress supported PHP versions.
240 *
241 * @package WordPress
242 * @subpackage HTTP
243 * @since None
244 */
245class WP_HTTP_Fsockopen extends WP_HTTP_Base
246{
247 /**
248 * Enter description here...
249 *
250 * @access public
251 * @param string $url
252 * @param string $type Optional. Should be of HTTP Request type: GET, POST, HEAD.
253 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
254 * @param string $body Optional. The body that should be sent. Expected to be already processed.
255 * @return boolean
256 */
257 function request($url, $type='GET', $headers=null, $body=null)
258 {
259
260 }
261
262 /**
263 * Whether this class can be used for retrieving an URL.
264 *
265 * @return boolean False means this class can not be used, true means it can.
266 */
267 function test()
268 {
269
270 }
271}
272
273/**
274 * HTTP request method uses fopen function to retrieve the url.
275 *
276 * Requires PHP version greater than 4.3.0 for stream support. Does not allow
277 * for $context support, but should still be okay, to write the headers, before
278 * getting the response. Also requires that 'allow_url_fopen' to be enabled.
279 *
280 * Second preferred method for handling the retrieving the url for PHP 4.3+.
281 *
282 * @package WordPress
283 * @subpackage HTTP
284 * @since None
285 */
286class WP_HTTP_Fopen extends WP_HTTP_Base
287{
288 /**
289 * Whether this class can be used for retrieving an URL.
290 *
291 * @return boolean False means this class can not be used, true means it can.
292 */
293 function test()
294 {
295 if( false === ini_get('allow_url_fopen') )
296 return false;
297
298 // Make the assumption that Streams could fail, or that the preference could change
299 // in the future for which goes first.
300 if( version_compare(PHP_VERSION, '4.3', '<') )
301 return false;
302
303 return true;
304 }
305}
306
307/**
308 * HTTP request method uses cURL PHP extension to retrieve the url.
309 *
310 * Requires that cURL extension be installed. If extension is found, then
311 * it should be assumed that it can be used to get retrieve the url.
312 *
313 * Third preferred method for getting the URL, after Streams and fopen.
314 *
315 * @package WordPress
316 * @subpackage HTTP
317 * @since None
318 */
319class WP_HTTP_Curl extends WP_HTTP_Base
320{
321 /**
322 * Whether this class can be used for retrieving an URL.
323 *
324 * @return boolean False means this class can not be used, true means it can.
325 */
326 function test()
327 {
328 if( function_exists('curl_init') )
329 return true;
330
331 return false;
332 }
333}
334
335/**
336 * HTTP request method uses Streams to retrieve the url.
337 *
338 * Requires PHP 5.0+ and uses fopen with stream context. Requires that
339 * 'allow_url_fopen' PHP setting to be enabled.
340 *
341 * Second preferred method for getting the URL, for PHP 5.
342 *
343 * @package WordPress
344 * @subpackage HTTP
345 * @since None
346 */
347class WP_HTTP_Streams extends WP_HTTP_Base
348{
349 /**
350 * Whether this class can be used for retrieving an URL.
351 *
352 * @return boolean False means this class can not be used, true means it can.
353 */
354 function test()
355 {
356 if( false === ini_get('allow_url_fopen') )
357 return false;
358
359 if( version_compare(PHP_VERSION, '5.0', '<') )
360 return false;
361
362 return true;
363 }
364}
365
366/**
367 * HTTP request method uses HTTP extension to retrieve the url.
368 *
369 * Requires the HTTP extension to be installed.
370 *
371 * Last ditch effort to retrieve the URL before complete failure.
372 *
373 * @package WordPress
374 * @subpackage HTTP
375 * @since None
376 */
377class WP_HTTP_ExtHTTP extends WP_HTTP_Base
378{
379 /**
380 * Whether this class can be used for retrieving an URL.
381 *
382 * @return boolean False means this class can not be used, true means it can.
383 */
384 function test()
385 {
386 if( function_exists('http_request') )
387 return true;
388
389 return false;
390 }
391}
392
393/**
394 * wp_remote_get_object() - Uses the working HTTP request object to retrieve URL.
395 *
396 * Sets up all of the HTTP Request objects until a working fallback has been
397 * found and will use it to retrieve the URL.
398 *
399 * The global $wp_fetch_http_obj is used to allow for a plugin to define its own
400 * object for retrieving URLs. It will also be used as an cache for reusing the same
401 * object known to work, for all requests.
402 *
403 * @package WordPress
404 * @subpackage HTTP
405 * @since None
406 *
407 * @global object $wp_fetch_http_obj WP_HTTP_Base extended object for retrieving remote files.
408 *
409 * @param string $url Site URL to retrieve.
410 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
411 * @param string $body Optional. The body that should be sent. Expected to be already processed.
412 * @param int $timeout Default is 30. The amount of time in seconds to continue trying to fetch the url before failing.
413 * @return WP_HTTP_Base Extended class that has the response of the request.
414 */
415function wp_remote_get_object($url, $headers=null, $body=null, $timeout=30) {
416 $wp_fetch_http_obj = _wp_http_get_object();
417
418 // This filter should probably create and apply an array instead of applying
419 // both strings and arrays. One would be best and it should be an array.
420 $headers = apply_filters('http_request_headers', $headers);
421
422 if( !is_null($headers) )
423 $wp_fetch_http_obj->setHeaders($headers);
424
425 if( !is_null($body) )
426 $wp_fetch_http_obj->setBody($body);
427
428 $wp_fetch_http_obj->setTimeout($timeout);
429
430 $wp_fetch_http_obj->get($url);
431
432 return $wp_fetch_http_obj;
433}
434
435/**
436 * wp_remote_get_body() - Retrieve only the body from the URL.
437 *
438 * @param string $url Site URL to retrieve.
439 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
440 * @param string $body Optional. The body that should be sent. Expected to be already processed.
441 * @param int $timeout Default is 30. The amount of time in seconds to continue trying to fetch the url before failing.
442 * @return string The body of the response
443 */
444function wp_remote_get_body($url, $headers=null, $body=null, $timeout=30) {
445 $objFetchSite = wp_remote_get_object($url, $headers, $body, $timeout);
446
447 if($objFetchSite->hasError() === true)
448 return $objFetchSite->getError();
449
450 return $objFetchSite->getBody();
451}
452
453/**
454 * wp_remote_get_headers() - Retrieve only the headers from the URL.
455 *
456 * @param string $url Site URL to retrieve.
457 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
458 * @param string $body Optional. The body that should be sent. Expected to be already processed.
459 * @param int $timeout Default is 30. The amount of time in seconds to continue trying to fetch the url before failing.
460 * @return array The headers of the response
461 */
462function wp_remote_get_headers($url, $headers=null, $body=null, $timeout=30) {
463 $objFetchSite = wp_remote_get_object($url, $headers, $body, $timeout);
464
465 if($objFetchSite->hasError() === true)
466 return $objFetchSite->getError();
467
468 return $objFetchSite->getHeaders();
469}
470
471/**
472 * wp_remote_get() - Retrieve both the body and headers
473 *
474 * The body and headers will be split and returned in an array. The body will be first, with the headers
475 * second.
476 *
477 * @param string $url Site URL to retrieve.
478 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
479 * @param string $body Optional. The body that should be sent. Expected to be already processed.
480 * @param int $timeout Default is 30. The amount of time in seconds to continue trying to fetch the url before failing.
481 * @return array The body and the headers, in that order.
482 */
483function wp_remote_get($url, $headers=null, $body=null, $timeout=30) {
484 $objFetchSite = wp_remote_get_object($url, $headers, $body, $timeout);
485
486 if($objFetchSite->hasError() === true)
487 return $objFetchSite->getError();
488
489 return array($objFetchSite->getBody(), $objFetchSite->getHeaders());
490}
491
492/**
493 * Tests the WordPress HTTP objects for an object to use and returns it.
494 *
495 * Tests all of the objects and returns the object that passes. Also caches that
496 * object to be used later.
497 *
498 * @package WordPress
499 * @subpackage HTTP
500 * @since None
501 * @access private
502 *
503 * @return WP_HTTP_Base|false False is failure to find working fallback object.
504 */
505function _wp_http_get_object() {
506 static $objHTTPRequest;
507
508 // Object already found, return it.
509 if( is_object($objHTTPRequest) )
510 return $objHTTPRequest;
511
512 // Find the working HTTP request object
513
514 // Apply filter first in case plugin sets up another object
515 $objHTTPRequest = apply_filters('http_request_use_custom_object', null);
516
517 if( !is_null($objHTTPRequest) )
518 return $objHTTPRequest;
519
520 // Primary method to use, should pass most of the time
521 $objHTTPRequest = new WP_HTTP_Fsockopen();
522
523 if( true === $objHTTPRequest->test() )
524 return $objHTTPRequest;
525
526 // Second fallback
527 $objHTTPRequest = new WP_HTTP_Streams();
528
529 if( true === $objHTTPRequest->test() )
530 return $objHTTPRequest;
531
532 // Third fallback
533 $objHTTPRequest = new WP_HTTP_Fopen();
534
535 if( true === $objHTTPRequest->test() )
536 return $objHTTPRequest;
537
538 // Fourth fallback
539 $objHTTPRequest = new WP_HTTP_Curl();
540
541 if( true === $objHTTPRequest->test() )
542 return $objHTTPRequest;
543
544 // Final fallback
545 $objHTTPRequest = new WP_HTTP_ExtHTTP();
546
547 if( true === $objHTTPRequest->test() )
548 return $objHTTPRequest;
549
550 // No fallback was found.
551 $objHTTPRequest = null;
552 return false;
553}
554
555?>