WordPress.org

Make WordPress Core

Ticket #4779: http.php

File http.php, 15.7 KB (added by darkdragon, 6 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?>