* Inspired by Requests for Python.
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
* Inspired by Requests for Python.
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
const OPTIONS = 'OPTIONS';
* @link https://tools.ietf.org/html/rfc5789
* Default size of buffer size to read streams
const BUFFER_SIZE = 1160;
* Current version of Requests
const VERSION = '1.7-3470169';
* Registered transport classes
protected static $transports = array();
* Selected transport name
* Use {@see get_transport()} instead
public static $transport = array();
* Default certificate path.
* @see Requests::get_certificate_path()
* @see Requests::set_certificate_path()
protected static $certificate_path;
* This is a static class, do not instantiate it
private function __construct() {}
* Autoloader for Requests
* Register this with {@see register_autoloader()} if you'd like to avoid
* having to create your own.
* (You can also use `spl_autoload_register` directly if you'd prefer.)
* @param string $class Class name to load
public static function autoloader($class) {
// Check that the class starts with "Requests"
if (strpos($class, 'Requests') !== 0) {
$file = str_replace('_', '/', $class);
if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) {
require_once(dirname(__FILE__) . '/' . $file . '.php');
* Register the built-in autoloader
public static function register_autoloader() {
spl_autoload_register(array('Requests', 'autoloader'));
* @param string $transport Transport class to add, must support the Requests_Transport interface
public static function add_transport($transport) {
if (empty(self::$transports)) {
self::$transports = array(
'Requests_Transport_cURL',
'Requests_Transport_fsockopen',
self::$transports = array_merge(self::$transports, array($transport));
* Get a working transport
* @throws Requests_Exception If no valid transport is found (`notransport`)
* @return Requests_Transport
protected static function get_transport($capabilities = array()) {
// Caching code, don't bother testing coverage
// @codeCoverageIgnoreStart
// array of capabilities as a string to be used as an array key
$cap_string = serialize($capabilities);
// Don't search for a transport if it's already been done for these $capabilities
if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {
$class = self::$transport[$cap_string];
// @codeCoverageIgnoreEnd
if (empty(self::$transports)) {
self::$transports = array(
'Requests_Transport_cURL',
'Requests_Transport_fsockopen',
// Find us a working transport
foreach (self::$transports as $class) {
if (!class_exists($class)) {
$result = call_user_func(array($class, 'test'), $capabilities);
self::$transport[$cap_string] = $class;
if (self::$transport[$cap_string] === null) {
throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
$class = self::$transport[$cap_string];
* @return Requests_Response
public static function get($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::GET, $options);
public static function head($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::HEAD, $options);
public static function delete($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::DELETE, $options);
public static function trace($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::TRACE, $options);
* @return Requests_Response
public static function post($url, $headers = array(), $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::POST, $options);
public static function put($url, $headers = array(), $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::PUT, $options);
* Send an OPTIONS request
public static function options($url, $headers = array(), $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::OPTIONS, $options);
* Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
* specification recommends that should send an ETag
* @link https://tools.ietf.org/html/rfc5789
public static function patch($url, $headers, $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::PATCH, $options);
* Main interface for HTTP requests
* This method initiates a request and sends it via a transport before
* The `$options` parameter takes an associative array with the following
* - `timeout`: How long should we wait for a response?
* Note: for cURL, a minimum of 1 second applies, as DNS resolution
* operates at second-resolution only.
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
* - `connect_timeout`: How long should we wait while trying to connect?
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
* - `useragent`: Useragent to send to the server
* (string, default: php-requests/$version)
* - `follow_redirects`: Should we follow 3xx redirects?
* (boolean, default: true)
* - `redirects`: How many times should we redirect before erroring?
* - `blocking`: Should we block processing on this request?
* (boolean, default: true)
* - `filename`: File to stream the body to instead.
* (string|boolean, default: false)
* - `auth`: Authentication handler or array of user/password details to use
* for Basic authentication
* (Requests_Auth|array|boolean, default: false)
* - `proxy`: Proxy details to use for proxy by-passing and authentication
* (Requests_Proxy|array|string|boolean, default: false)
* - `max_bytes`: Limit for the response body size.
* (integer|boolean, default: false)
* - `idn`: Enable IDN parsing
* (boolean, default: true)
* - `transport`: Custom transport. Either a class name, or a
* transport object. Defaults to the first working transport from
* (string|Requests_Transport, default: {@see getTransport()})
* - `hooks`: Hooks handler.
* (Requests_Hooker, default: new Requests_Hooks())
* - `verify`: Should we verify SSL certificates? Allows passing in a custom
* certificate file as a string. (Using true uses the system-wide root
* certificate store instead, but this may have different behaviour
* (string|boolean, default: library/Requests/Transport/cacert.pem)
* - `verifyname`: Should we verify the common name in the SSL certificate?
* (boolean: default, true)
* - `data_format`: How should we send the `$data` parameter?
* (string, one of 'query' or 'body', default: 'query' for
* HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
* @throws Requests_Exception On invalid URLs (`nonhttp`)
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type (use Requests constants)
* @param array $options Options for the request (see description for more information)
* @return Requests_Response
public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
if (empty($options['type'])) {
$options['type'] = $type;
$options = array_merge(self::get_default_options(), $options);
self::set_defaults($url, $headers, $data, $type, $options);
$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
if (!empty($options['transport'])) {
$transport = $options['transport'];
if (is_string($options['transport'])) {
$transport = new $transport();
$need_ssl = (0 === stripos($url, 'https://'));
$capabilities = array('ssl' => $need_ssl);
$transport = self::get_transport($capabilities);
$response = $transport->request($url, $headers, $data, $options);
$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
return self::parse_response($response, $url, $headers, $data, $options);
* Send multiple HTTP requests simultaneously
* The `$requests` parameter takes an associative or indexed array of
* request fields. The key of each request can be used to match up the
* request with the returned data, or with the request passed into your
* `multiple.request.complete` callback.
* The request fields value is an associative array with the following keys:
* - `url`: Request URL Same as the `$url` parameter to
* {@see Requests::request}
* - `headers`: Associative array of header fields. Same as the `$headers`
* parameter to {@see Requests::request}
* (array, default: `array()`)
* - `data`: Associative array of data fields or a string. Same as the
* `$data` parameter to {@see Requests::request}
* (array|string, default: `array()`)
* - `type`: HTTP request type (use Requests constants). Same as the `$type`
* parameter to {@see Requests::request}
* (string, default: `Requests::GET`)
* - `cookies`: Associative array of cookie name to value, or cookie jar.
* (array|Requests_Cookie_Jar)
* If the `$options` parameter is specified, individual requests will
* inherit options from it. This can be used to use a single hooking system,
* or set all the types to `Requests::POST`, for example.
* In addition, the `$options` parameter takes the following global options:
* - `complete`: A callback for when a request is complete. Takes two
* parameters, a Requests_Response/Requests_Exception reference, and the
* ID from the request array (Note: this can also be overridden on a
* per-request basis, although that's a little silly)
* @param array $requests Requests data (see description for more information)
* @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either Requests_Response or a Requests_Exception object)
public static function request_multiple($requests, $options = array()) {
$options = array_merge(self::get_default_options(true), $options);
if (!empty($options['hooks'])) {
$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($options['complete'])) {
$options['hooks']->register('multiple.request.complete', $options['complete']);
foreach ($requests as $id => &$request) {
if (!isset($request['headers'])) {
$request['headers'] = array();
if (!isset($request['data'])) {
$request['data'] = array();
if (!isset($request['type'])) {
$request['type'] = self::GET;
if (!isset($request['options'])) {
$request['options'] = $options;
$request['options']['type'] = $request['type'];
if (empty($request['options']['type'])) {
$request['options']['type'] = $request['type'];
$request['options'] = array_merge($options, $request['options']);
self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
// Ensure we only hook in once
if ($request['options']['hooks'] !== $options['hooks']) {
$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($request['options']['complete'])) {
$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
if (!empty($options['transport'])) {
$transport = $options['transport'];
if (is_string($options['transport'])) {
$transport = new $transport();
$transport = self::get_transport();
$responses = $transport->request_multiple($requests, $options);
foreach ($responses as $id => &$response) {
// If our hook got messed with somehow, ensure we end up with the
if (is_string($response)) {
$request = $requests[$id];
self::parse_multiple($response, $request);
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
* Get the default options
* @see Requests::request() for values returned by this method