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)

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/aggregator.fetcher.inc
Implements hook_aggregator_fetch().
aggregator_form_opml_submit in modules/aggregator/aggregator.admin.inc
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/common.inc, line 782
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 (+http://drupal.org/)',

  // 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.
    unset($uri ['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) {
      unset($options ['headers']['User-Agent']);
    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 http://bugs.php.net/bug.php?id=43782
  // and http://bugs.php.net/bug.php?id=46049 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.
  $response_status_array = _drupal_parse_response_status(trim(array_shift($response)));
  $result->protocol = $response_status_array ['http_version'];
  $result->status_message = $response_status_array ['reason_phrase'];
  $code = $response_status_array ['response_code'];

  $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.
        $options ['max_redirects'];
        --$result = drupal_http_request($location, $options);
        $result->redirect_code = $code;
      if (!isset($result->redirect_url)) {
        $result->redirect_url = $location;
      $result->error = $result->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 drupal.org. 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 /foobar.com/includes/common.inc).
Notice: Undefined variable: errstr in drupal_http_request() (line 830 of /foobar.com/includes/common.inc).

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('http://somewhere.com', $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:

= 'http://example.com';
$data = array(
'key1' => $value1,
'key2' => $value2,

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

Yes this helped me, using the previous one gave me errors in the response, so paying attention to whether making a get or post call is important

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.

= 'http://example.com/';
$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.

This comment helped me a ton. I only wish I had stumbled on it a week ago, it would have saved me hours of research!

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 agree, you can NOT set the host header with this function.

I was looking at http://www.mugginsoft.com/content/getting-and-setting-cookies-drupalhttp... 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 http://www.bronius.com/pass-browser-cookie-values-drupal-http-request-us...

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('https://graph.facebook.com/100001258517356/picture');

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, the module mod_security is there for a reason, I suggest you to investigate on the exact cause of the Access Denied errors and disable just the single rule that triggers the errors (with SecRuleRemoveById or SecRuleRemoveByTag), instead of disabling the entire module, exposing your website to possible attacks...

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/common.inc).

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

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

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: "https://www.exporttrader.com"
using drupal_http_request('https://www.exporttrader.com') I got this error: "Error opening socket ssl://www.exporttrader.com:443"

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 https://drupal.org/node/1879970

This cropped up with PHP 5.6. Openssl was enabled, and the problem wasn't happening on 5.3. I tried the patch and forced it to use sslv3 and tls, but it did not do the job for me. I switched to the chr module, but as of now, SSL posts using drupal_http_request under PHP 5.6 is not working for me.

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

curl_setopt($ch, CURLOPT_SSLVERSION, 3);

I want to know that does drupal_http_request use curl to send http request? If we disabled curl, will drupal_http_request work or not?

It does not use cURL.

Looking at the function code, it uses a socket connection with the method stream_socket_client(). The working of this function will depend on how the sockets related functions are configured on your server. Also look up the comment by Diogenes.

While debugging various unexpected error codes, I found out that $result->code can actually contain far more things than merely the HTTP response codes. The documentation does not make this very clear:

code: An integer containing the response status code, or the error code if an error occurred.

Here's a list of error codes you may encounter, with their meaning ($result->error), grouped by source:

  • Error codes stemming from the function itself:
    • -1 (request timed out)
    • -1001 (unable to parse URL)
    • -1002 (missing schema)
    • -1003 (invalid schema [schema])
  • HTTP response codes (if an error, it will be the name of the HTTP status message as returned by the web server):
    • 200
    • 404 (Not Found)
    • 500 (Internal Server Error)
    • etc...
  • Error codes returned by stream_socket_client(), negated to prevent clashing with HTTP status codes. God knows if they may collide with the negative error codes the function itself can return, though. As per the docs, most of the time errors from this will be the error from the actual <a href="http://www.tutorialspoint.com/unix_system_calls/connect.htm">connect()</a> system call. The actual numerical values of the errors are listed here. I've also seen error code 0 and I have no idea what that could mean:
    • -110 (Connection timed out)
    • -111 (Connection refused)
    • etc...
    • 0 (????)

This lists the errors of the actual connect() system call.

Thank you for your helpful comment on code list.

I was getting a lot of timeout problems with a client's site to an offsite REST API so to help it out I added a timeout to drupal_http_request thinking that the timeout value would be exactly what I set it to. HAH fat chance. As Diogenes note's above, D7 does a chicken dance for timeouts, I think it should be renamed to "The fail dance" hehe. Anyway, setting a time out of 10, you'd think it would timeout after 10 seconds. But it doesnt. It times out after 20 seconds which is exactly double what I put. So to test this theory I used a brand new D7 and made it connect to the SAME server hitting a test.php in it that had nothing other than a sleep(30); echo "hi"; in it. Then I set the timeout to 20. Sure enough, it displayed "hi" when it SHOULD have timed out. So, I lowered it to 15. It then started showing "hi" intermittantly. Sometimes it would show, others it wouldnt. Lowered it to 10 and it timed out after 20 seconds each time. I used chrome's network console to see the times before it timed out.

So word to the wise, if you use timeout's, remember that they are DOUBLED for whatever reason.