function drupal_http_request

You are here

7 common.inc drupal_http_request($url, array $options = array())
4.6 common.inc drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3)
4.7 common.inc drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3)
5 common.inc drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3)
6 common.inc drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0)

Perform an HTTP request.

This is a flexible and powerful HTTP client implementation. Correctly handles GET, POST, PUT or any other HTTP requests. Handles redirects.

Parameters

$url: A string containing a fully qualified URI.

$headers: An array containing an HTTP header => value pair.

$method: A string defining the HTTP request to use.

$data: A string containing data to include in the request.

$retry: An integer representing how many times to retry the request in case of a redirect.

$timeout: A float representing the maximum number of seconds the function call may take. The default is 30 seconds. If a timeout occurs, the error code is set to the HTTP_REQUEST_TIMEOUT constant.

Return value

An object containing the HTTP request headers, response code, protocol, status message, headers, data and redirect status.

Related topics

7 calls to drupal_http_request()
aggregator_refresh in modules/aggregator/aggregator.module
Checks a news feed for new items.
openid_association in modules/openid/openid.module
Attempt to create a shared secret with the OpenID Provider.
openid_discovery in modules/openid/openid.module
Perform discovery on a claimed ID to determine the OpenID provider endpoint.
openid_verify_assertion in modules/openid/openid.module
Attempt to verify the response received from the OpenID Provider.
system_check_http_request in modules/system/system.module
Checks whether the server is capable of issuing HTTP requests.

... See full list

File

includes/common.inc, line 456
Common functions that many Drupal modules will need to reference.

Code

function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0) {
  global $db_prefix;

  $result = new stdClass();

  // Parse the URL and make sure we can handle the schema.
  $uri = parse_url($url);

  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
    $result->code = -1001;
    return $result;
  }

  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
    $result->code = -1002;
    return $result;
  }

  timer_start(__FUNCTION__);

  switch ($uri['scheme']) {
    case 'http':
    case 'feed':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout);
      break;
    case 'https':
      // Note: Only works for PHP 4.3 compiled with OpenSSL.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $timeout);
      break;
    default:
      $result->error = 'invalid schema ' . $uri['scheme'];
      $result->code = -1003;
      return $result;
  }

  // Make sure the socket opened properly.
  if (!$fp) {
    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
    $result->code = -$errno;
    $result->error = trim($errstr);

    // Mark that this request failed. This will trigger a check of the web
    // server's ability to make outgoing HTTP requests the next time that
    // requirements checking is performed.
    // @see system_requirements()
    variable_set('drupal_http_request_fails', TRUE);

    return $result;
  }

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?' . $uri['query'];
  }

  // Create HTTP request.
  $defaults = array(
    
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
    // We don't add the port to prevent from breaking rewrite rules checking the
    // host that do not take into account the port number.
    'Host' => "Host: $host",
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
  );

  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
  $content_length = strlen($data);
  if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
    $defaults['Content-Length'] = 'Content-Length: ' . $content_length;
  }

  // If the server URL has a user then attempt to use basic authentication
  if (isset($uri['user'])) {
    $defaults['Authorization'] = 'Authorization: Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  }

  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
  if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
    $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
  }

  foreach ($headers as $header => $value) {
    $defaults[$header] = $header . ': ' . $value;
  }

  $request = $method . ' ' . $path . " HTTP/1.0\r\n";
  $request .= implode("\r\n", $defaults);
  $request .= "\r\n\r\n";
  $request .= $data;

  $result->request = $request;

  // Calculate how much time is left of the original timeout value.
  $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
  if ($time_left > 0) {
    stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
    fwrite($fp, $request);
  }

  // Fetch response.
  $response = '';
  while (!feof($fp)) {
    // Calculate how much time is left of the original timeout value.
    $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
    if ($time_left <= 0) {
      $result->code = HTTP_REQUEST_TIMEOUT;
      $result->error = 'request timed out';
      return $result;
    }
    stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
    $chunk = fread($fp, 1024);
    $response .= $chunk;
  }
  fclose($fp);

  // Parse response headers from the response body.
  // Be tolerant of malformed HTTP responses that separate header and body with
  // \n\n or \r\r instead of \r\n\r\n.  See http://drupal.org/node/183435
  list($split, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  $split = preg_split("/\r\n|\n|\r/", $split);

  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
  $result->protocol = $protocol;
  $result->status_message = $status_message;

  $result->headers = array();

  // Parse headers.
  while ($line = trim(array_shift($split))) {
    list($header, $value) = explode(':', $line, 2);
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
      // RFC 2109: the Set-Cookie response header comprises the token Set-
      // Cookie:, followed by a comma-separated list of one or more cookies.
      $result->headers[$header] .= ',' . trim($value);
    }
    else {
      $result->headers[$header] = trim($value);
    }
  }

  $responses = array(
    100 => 'Continue',
    101 => 'Switching Protocols',
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    300 => 'Multiple Choices',
    301 => 'Moved Permanently',
    302 => 'Found',
    303 => 'See Other',
    304 => 'Not Modified',
    305 => 'Use Proxy',
    307 => 'Temporary Redirect',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    402 => 'Payment Required',
    403 => 'Forbidden',
    404 => 'Not Found',
    405 => 'Method Not Allowed',
    406 => 'Not Acceptable',
    407 => 'Proxy Authentication Required',
    408 => 'Request Time-out',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'Length Required',
    412 => 'Precondition Failed',
    413 => 'Request Entity Too Large',
    414 => 'Request-URI Too Large',
    415 => 'Unsupported Media Type',
    416 => 'Requested range not satisfiable',
    417 => 'Expectation Failed',
    500 => 'Internal Server Error',
    501 => 'Not Implemented',
    502 => 'Bad Gateway',
    503 => 'Service Unavailable',
    504 => 'Gateway Time-out',
    505 => 'HTTP Version not supported',
  );
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  // base code in their class.
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }

  switch ($code) {
    case 200: // OK
    case 304: // Not modified
      break;
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['Location'];
      $timeout -= timer_read(__FUNCTION__) / 1000;
      if ($timeout <= 0) {
        $result->code = HTTP_REQUEST_TIMEOUT;
        $result->error = 'request timed out';
      }
      elseif ($retry) {
        $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry, $timeout);
        $result->redirect_code = $result->code;
      }
      $result->redirect_url = $location;

      break;
    default:
      $result->error = $status_message;
  }

  $result->code = $code;
  return $result;
}

Comments

I was having trouble using this with the POST method and found this post that worked for me: Solution for $_POST empty after drupal_http_request.

Note that feof() will return true if the socket times out.

The docs for fsockopen state() that you have to call stream_set_timeout() to set timeouts for reading and writing but the feof() timeout behaviour seems on by default.

The default feof() timeout is 60 seconds but it can be hard to detect on processes that become incrementally longer that feof() timing out is an issue.

The default socket time-out can be overridden like so:

<?php
ini_set
('default_socket_timeout','1200');
?>

Also note that the timeout used to open the socket is of no relevance here.

Drupal 6 users should note that http_request() uses feof() and is therefore susceptible to timing out.

If you are posting data, ensure you set your header and data like so:

// Set some parameters for sending request.
$request_url = 'http://your-url.com';
$request_headers = array('Content-Type' => 'application/x-www-form-urlencoded');
$request_method = 'POST';
$request_retry = 3;
$data = 'name=Amarjit&status=1';
 
// Send the request.
$result = drupal_http_request($request_url, $request_headers, $request_method, $data, $request_retry);

A note about the format of posted variables (i.e. $data).

The format of the $data string should be as follows:

name1=val1&name2=val2&name3=val3

A very quick way to build that string from an array is to use http_build_query().
Example:

<?php
$fruits
= array('apple' => 'red', 'banana' => 'yellow', 'pear' => 'green');
$data = http_build_query($fruits, '', '&');
?>

This will yield:
apple=red&banana=yellow&pear=green

When calling http_build_query(), make sure the third argument is "&". Otherwise, your system may default to "&amp;" which means something completely different to the server processing the request (i.e. apple=red&amp;banana=yellow&amp;pear=green).

Does anyone have any info on the speed of drupal_http_request vs. using a cURL call? I am thinking that since drupal_http_request requires a drupal bootstrap it should be a bit slower - but would appreciate any info on this.

The patch in [#82410] made it possible to use HTTP Basic Authentication with drupal_http_request(). This is especially useful when making calls to a REST API, or other web service.

The function leverages PHP's native parse_url() function in order to get the user & pass parameters needed for authentication. They are simply extracted from the URL, which should be in the format http://user:pass@example.com/end/point.php, or https://user:pass@example.com/end/point.php. If necessary, you can also append a query string to the URL.

Do not try doing the authentication adding user & pass as query string parameters because that will not work, and it will be unclear why not.

Example code for doing HTTP Basic Authentication (somewhat simplified from its original context):

<?php
function get_embed_code($media_id, $width = '', $height = '') {
   
$scheme 'https';
   
$user = 'api';
   
$pass = variable_get('api_key', '');
   
$host = 'api.example.com';
   
$path = '/v1/medias/' . $media_id . '/embed';
    if(isset(
$width)) { $query['width'] = 'width=' . $width; }
    if(isset(
$height)) { $query['height'] = 'height=' . $height; }
   
// Implodes the query string options with an '&'
   
$query = implode($query, '&');
   
// Builds the URL for drupal_http_request to use in REST API call
    /* Expected format: https://api:<api-key>@api.example.com/v1/medias/<media-id>/embed?width=<width>&height=<height> */
   
$embed_url = $scheme . '://' . $user . ':' . $pass . '@' . $host . $path . '?' . $query;
   
$result = drupal_http_request($embed_url);
    if(isset(
$result) && in_array($result->code, array('200', '302', '307')) && $result->data != '') {
     
$embed_code = $result->data;
    }
    if(isset(
$embed_code) && $embed_code != '') {
      return
$embed_code;
    }
}
?>

In case the example above was not clear, in order to do basic auth using drupal_http_request simply enter the username and password into the url in the format below. Drupal_http_request will extract the username and password, base64 encode them and pass them along in the header appropriately

$url = 'http://username:password@target_url.com';

For GET requests, the query has to be appended to the URL and not entered as the $data parameter i.e.

$target_url = 'http://your-url.com';
$request_method = 'GET';
$request_retry = 3;
$query = 'name=Amarjit&status=1';
$data ='';  // not required for GET
$request_headers='';  // not required for GET
 
$request_url = $target_url.'?'.$query;

// Send the request.
$result = drupal_http_request($request_url, $request_headers, $request_method, $data, $request_retry);