function drupal_http_request

You are here

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

Performs an HTTP request.

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


$url: A string containing a fully qualified URI.

array $options: (optional) An array that can have one or more of the following elements:

  • headers: An array containing request headers to send as name/value pairs.
  • method: A string containing the request method. Defaults to 'GET'.
  • data: A string containing the request body, formatted as 'param=value&param=value&...'. Defaults to NULL.
  • max_redirects: An integer representing how many times a redirect may be followed. Defaults to 3.
  • 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.
  • context: A context resource created with stream_context_create().

Return value

object An object that can have one or more of the following components:

  • request: A string containing the request body that was sent.
  • code: An integer containing the response status code, or the error code if an error occurred.
  • protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
  • status_message: The status message from the response, if a response was received.
  • redirect_code: If redirected, an integer containing the initial response status code.
  • redirect_url: If redirected, a string containing the URL of the redirect target.
  • error: If an error occurred, the error message. Otherwise not set.
  • headers: An array containing the response headers as name/value pairs. HTTP header names are case-insensitive (RFC 2616, section 4.2), so for easy access the array keys are returned in lower case.
  • data: A string containing the response body that was received.

Related topics

16 calls to drupal_http_request()
aggregator_aggregator_fetch in modules/aggregator/
Implements hook_aggregator_fetch().
aggregator_form_opml_submit in modules/aggregator/
Form submission handler for aggregator_form_opml().
DrupalHTTPRequestTestCase::testDrupalHTTPRequest in modules/simpletest/tests/common.test
DrupalHTTPRequestTestCase::testDrupalHTTPRequestBasicAuth in modules/simpletest/tests/common.test
DrupalHTTPRequestTestCase::testDrupalHTTPRequestHeaders in modules/simpletest/tests/common.test
Tests Content-language headers generated by Drupal.

... See full list


includes/, line 787
Common functions that many Drupal modules will need to reference.


function drupal_http_request($url, array $options = array()) {
  // Allow an alternate HTTP client library to replace Drupal's default
  // implementation.
  $override_function = variable_get('drupal_http_request_function', FALSE);
  if (!empty($override_function) && function_exists($override_function)) {
    return $override_function($url, $options);

  $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;


  // Merge the default options.
  $options += array(
    'headers' => array(),
    'method' => 'GET',
    'data' => NULL,
    'max_redirects' => 3,
    'timeout' => 30.0,
    'context' => NULL,

  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+',

  // stream_socket_client() requires timeout to be a float.
  $options['timeout'] = (float) $options['timeout'];

  // Use a proxy if one is defined and the host is not on the excluded list.
  $proxy_server = variable_get('proxy_server', '');
  if ($proxy_server && _drupal_http_use_proxy($uri['host'])) {
    // Set the scheme so we open a socket to the proxy server.
    $uri['scheme'] = 'proxy';
    // Set the path to be the full URL.
    $uri['path'] = $url;
    // Since the URL is passed as the path, we won't use the parsed query.

    // Add in username and password to Proxy-Authorization header if needed.
    if ($proxy_username = variable_get('proxy_username', '')) {
      $proxy_password = variable_get('proxy_password', '');
      $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
    // Some proxies reject requests with any User-Agent headers, while others
    // require a specific one.
    $proxy_user_agent = variable_get('proxy_user_agent', '');
    // The default value matches neither condition.
    if ($proxy_user_agent === NULL) {
    elseif ($proxy_user_agent) {
      $options['headers']['User-Agent'] = $proxy_user_agent;

  switch ($uri['scheme']) {
    case 'proxy':
      // Make the socket connection to a proxy server.
      $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
      // The Host header still needs to match the real request.
      $options['headers']['Host'] = $uri['host'];
      $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';

    case 'http':
    case 'feed':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $socket = 'tcp://' . $uri['host'] . ':' . $port;
      // RFC 2616: "non-standard ports MUST, default ports MAY be included".
      // We don't add the standard port to prevent from breaking rewrite rules
      // checking the host that do not take into account the port number.
      $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');

    case 'https':
      // Note: Only works when PHP is compiled with OpenSSL support.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $socket = 'ssl://' . $uri['host'] . ':' . $port;
      $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');

      $result->error = 'invalid schema ' . $uri['scheme'];
      $result->code = -1003;
      return $result;

  if (empty($options['context'])) {
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
  else {
    // Create a stream with context. Allows verification of a SSL certificate.
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);

  // 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) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));

    // 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'];

  // 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($options['data']);
  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
    $options['headers']['Content-Length'] = $content_length;

  // If the server URL has a user then attempt to use basic authentication.
  if (isset($uri['user'])) {
    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($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.
  $test_info = &$GLOBALS['drupal_test_info'];
  if (!empty($test_info['test_run_id'])) {
    $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);

  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
  foreach ($options['headers'] as $name => $value) {
    $request .= $name . ': ' . trim($value) . "\r\n";
  $request .= "\r\n" . $options['data'];
  $result->request = $request;
  // Calculate how much time is left of the original timeout value.
  $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  if ($timeout > 0) {
    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
    fwrite($fp, $request);

  // Fetch response. Due to PHP bugs like
  // and we can't rely on feof(), but
  // instead must invoke stream_get_meta_data() each iteration.
  $info = stream_get_meta_data($fp);
  $alive = !$info['eof'] && !$info['timed_out'];
  $response = '';

  while ($alive) {
    // Calculate how much time is left of the original timeout value.
    $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
    if ($timeout <= 0) {
      $info['timed_out'] = TRUE;
    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
    $chunk = fread($fp, 1024);
    $response .= $chunk;
    $info = stream_get_meta_data($fp);
    $alive = !$info['eof'] && !$info['timed_out'] && $chunk;

  if ($info['timed_out']) {
    $result->code = HTTP_REQUEST_TIMEOUT;
    $result->error = 'request timed out';
    return $result;
  // 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.
  list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  $response = preg_split("/\r\n|\n|\r/", $response);

  // Parse the response status line.
  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
  $result->protocol = $protocol;
  $result->status_message = $status_message;

  $result->headers = array();

  // Parse the response headers.
  while ($line = trim(array_shift($response))) {
    list($name, $value) = explode(':', $line, 2);
    $name = strtolower($name);
    if (isset($result->headers[$name]) && $name == '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[$name] .= ',' . trim($value);
    else {
      $result->headers[$name] = 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;
  $result->code = $code;

  switch ($code) {
    case 200: // OK
    case 304: // Not modified
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['location'];
      $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
      if ($options['timeout'] <= 0) {
        $result->code = HTTP_REQUEST_TIMEOUT;
        $result->error = 'request timed out';
      elseif ($options['max_redirects']) {
        // Redirect to the new location.
        $result = drupal_http_request($location, $options);
        $result->redirect_code = $code;
      if (!isset($result->redirect_url)) {
        $result->redirect_url = $location;
      $result->error = $status_message;

  return $result;


See my comment on the D6 version of this function for information on how to use with HTTP Basic Authentication. (From the code above, it appears that part is unchanged between versions.)

D7 is a significant rewrite from D6, and things can go wrong.

Drupal relies on this function for a number of things, including available update info from The Drupal system module does a check that the hosting system can fetch outside info by checking for the php function ftp_connect() using the php function function_exists(). Don't ya just love this stuff?

D6 uses the functions fsockopen() fread() and fwrite()

D7 uses the functions stream_socket_client(), fread(), fwrite(), stream_set_timeout(), and stream_get_meta_data(). It also does some kind of funky chicken dance for handling timeouts.

In both cases, the functions to open the connection are prefixed with '@' characters, which suppresses error messages and inhibits fatal errors.

All worked fine on the dev machine, but when I uploaded to the host server, I started to get peppered with errors like these:

Notice: Undefined variable: errno in drupal_http_request() (line 829 of /
Notice: Undefined variable: errstr in drupal_http_request() (line 830 of /

In my case (as it turns out), my hosting provider had disabled a whole basket of php functions (41 in total) for security reasons. One of the disabled functions was stream_socket_client().

My host provider was kind enough to enable this function (after I told them it was now part of Drupal 7 core) but they offered the opinion that this was a security compromise.

I will leave that point for the experts to debate. In the meantime, checking for ftp_connect() and then using stream_socket_client() as the function is probably not the best practice. This error was a bugger to figure out.

I am also get the same error.How to resolve that.

Thanks in advance.

$data = 'name=value&name1=value1';

$options = array(
                'method' => 'POST',
                'data' => $data,
                'timeout' => 15,
                'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),

$result = drupal_http_request('', $options);

Thank you!! :D

Note that if you want to perform a GET requests then $options['data'] does not make any sense, since GET requests do not have a body.

For example:

= '';
$data = array(
'key1' => $value1,
'key2' => $value2,

$full_url = url($url, array('query' => $data));

I was getting a strange error when trying to authenticate with the Youtube API:

The request method <code>post</code> is inappropriate for the URL <code>/accounts/ClientLogin</code>. <ins>That’s all we know.</ins>

AKA (adding html free version to help with searches):

The request method post is inappropriate for the URL /accounts/ClientLogin. That’s all we know.

The problem was that I was setting the method as 'post' rather than 'POST'. Setting it to uppercase solved the problem.

If you're looking for an alternative (that has very similar syntax) to what's in core, checkout the HTTP Parallel Request Library. It fixes a couple of bugs that I've encountered with core and is generally faster. The best part is it can issue multiple http requests at the same time and you can issue non-blocking requests.

If you want to send XML data by drupal_http_request, the following function might help. Apache Solr Module works this way and used the same concept in my work.

= '';
$first_name = "Test";
$age = "25";
$data = '<FirstName>' . $first_name . '</FirstName>';
$data .= '<Age>' . $age '</Age>';
//You can concatenate more tags to the data.
$options = array(
'method' => 'POST',
'data' => $data,
'headers' => array('Content-Type' => 'text/xml; charset=UTF-8'),
$result = drupal_http_request($url, $options);

Hope this helps somene.

My first experience trying to use this function instead of using something that takes a few more lines is not promising. There might be a hard to pin down bug in the implementation somewhere.

I tried to use this function to get a JSON response from an Arduino unit and consistently would lose a chunk of the JSON at the end. The problem completely ceased once I went back to using cURL.

When hostname could not be resolved to DNS need use IP, but option 'Host' in 'Headers' always overridden.

$options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
$options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');

I was looking at which shows how to get cookies with an initial drupal_http_request in Drupal 6, but I'm having no luck yet adapting it to the Drupal 7 version. I'm guessing I need to put something in $options['headers'], but what?

Correct, cookie's are just another header item

Yes, and that cookies array is stil part of the headers is part of the options array, so at simplest it would look like:

drupal_http_request($url, array(
  'headers' => array(
    'Cookies' => ...

Side note: To pass client browser cookies to persist local session with remote sites, check out

It it possible to send a multipart/form-data (enctype HTTP header) POST that includes a file using drupal_http_request? I don't seem to see anything in the code about it, but wanted to make sure.

Like an HTML form on a web page, I want to perform a POST with a file as one of the pieces of data to post into a server.

i have 2 servers local and dev. i am try to get avatar from facebook

$photo = drupal_http_request('');

in my local server all ok - i get photo. but on dev - Connection timed out. Looks like server configuration problem.... What it can be?

I was having Access Denied errors when using the Echo Module. It appeared that the Apache mod_security.c module was in the way.

I could then solve it by adding the following lines to the .htaccess:

<IfModule mod_security.c>
  SecFilterEngine Off
  SecFilterScanPOST Off

Hi guys, i have a problem, when i trie to use this drupal_http_request function its return /login page with redirected response 302. Can i send some data that shows that user is logged, or to escape this redirect and get the page i need?

$response = \Drupal::service('http_default_client')

Notice: Undefined offset: 1 в функции drupal_http_request() (строка 989 в файле /home//public_html//includes/

Notice: Undefined offset: 2 в функции drupal_http_request() (строка 993 в файле /home//public_html//includes/

Notice: Undefined offset: 1 в функции drupal_http_request() (строка 993 в файле /home//public_html//includes/

list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
$response = preg_split("/\r\n|\n|\r/", $response);

// Parse the response status line.
list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
$result->protocol = $protocol;
$result->status_message = $status_message;


When I try to read this URL: ""
using drupal_http_request('') I got this error: "Error opening socket ssl://"

any idea please?

I've been getting the same error, and as mentioned below, it is related to SSL V3. There is an issue that patches core that seems to fix it

OOOOH finally I found the solution.. it's by using SSL v3

curl_setopt($ch, CURLOPT_SSLVERSION, 3);