Same name and namespace in other branches
- 4.6.x includes/common.inc \drupal_http_request()
- 4.7.x includes/common.inc \drupal_http_request()
- 5.x includes/common.inc \drupal_http_request()
- 6.x includes/common.inc \drupal_http_request()
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.
Parameters
$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: An array containing the values for the request body or a string containing the request body, formatted as 'param=value¶m=value&...'; to generate this, use drupal_http_build_query(). 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.
See also
Related topics
15 calls to drupal_http_request()
- aggregator_aggregator_fetch in modules/
aggregator/ aggregator.fetcher.inc - Implements hook_aggregator_fetch().
- 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.
- DrupalHTTPRequestTestCase::testDrupalHTTPRequestRedirect in modules/
simpletest/ tests/ common.test
File
- includes/
common.inc, line 797 - Common functions that many Drupal modules will need to reference.
Code
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;
}
timer_start(__FUNCTION__);
// 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'] = (double) $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.
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'];
$options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
}
break;
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.
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
}
break;
case 'https':
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$socket = 'ssl://' . $uri['host'] . ':' . $port;
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
}
break;
default:
$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'];
}
// Convert array $options['data'] to query string.
if (is_array($options['data'])) {
$options['data'] = drupal_http_build_query($options['data']);
}
// 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((string) $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;
break;
}
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;
}
fclose($fp);
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((string) 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 201:
// Created
case 202:
// Accepted
case 203:
// Non-Authoritative Information
case 204:
// No Content
case 205:
// Reset Content
case 206:
// Partial Content
case 304:
// Not modified
break;
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']--;
// Check if we need to remove any potentially sensitive headers before
// following the redirect.
// @see https://www.rfc-editor.org/rfc/rfc9110.html#name-redirection-3xx
if (_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location)) {
unset($options['headers']['Cookie']);
unset($options['headers']['Authorization']);
}
// We need to unset the 'Host' header
// as we are redirecting to a new location.
unset($options['headers']['Host']);
$result = drupal_http_request($location, $options);
$result->redirect_code = $code;
}
if (!isset($result->redirect_url)) {
$result->redirect_url = $location;
}
break;
default:
$result->error = $result->status_message;
}
return $result;
}
Comments
Use with HTTP Basic Authentication
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.)
A word of warning
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:
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.
Example of a POST
Thank you!! :D
Thank you!! :D
Use http_build_query()
Use http_build_query() to create the $data string.
Note that if you want to
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:
+1
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
I was getting a strange error when trying to authenticate with the Youtube API:
AKA (adding html free version to help with searches):
The problem was that I was setting the method as 'post' rather than 'POST'. Setting it to uppercase solved the problem.
Alternative
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.
Method to send XML data.
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.
Hope this helps somene.
Thank you!
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!
cURL is a good alternative
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.
Headers
When hostname could not be resolved to DNS need use IP, but option 'Host' in 'Headers' always overridden.
I agree, you can NOT set the
I agree, you can NOT set the host header with this function.
Already in issue tracker
See https://www.drupal.org/node/2555649 for a patch which permits setting the Host header.
How can I get cookies?
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
Correct, cookie's are just another header item
Yes, and that cookies array
Yes, and that cookies array is stil part of the headers is part of the options array, so at simplest it would look like:
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...
POSTing a file?
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.
Connection timed out
i have 2 servers local and dev. i am try to get avatar from facebook
in my local server all ok - i get photo. but on dev - Connection timed out. Looks like server configuration problem.... What it can be?
Apache mod_security module
I was having Access Denied errors when using the Echo Module. It appeared that the Apache
mod_security.cmodule was in the way.I could then solve it by adding the following lines to the
.htaccess:It's not recommended to disable entirely mod_security
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
SecRuleRemoveByIdorSecRuleRemoveByTag), instead of disabling the entire module, exposing your website to possible attacks...I personally find echo's a
I personally find echo's a little out of place, and normally use drupal_set_message
drupal_set_message($i)
or for an array
drupal_set_message(''. print_r($_SESSION, true) .'');
It doesn't break the page layout, and is easy to read, and loads before default php errors.
Redirects to login
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?
Ty.
Drupal 8
$response = \Drupal::service('http_default_client')
->get($url)
->send()
->getBody(TRUE);
http-responses how fix it
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;
OOOOH finally I found the
OOOOH finally I found the solution.. it's by using SSL v3
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
Does drupal_http_request use curl to send http request?
Hi
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?
drupal_http_request uses a socket connection
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.
Error codes
While debugging various unexpected error codes, I found out that
$result->codecan actually contain far more things than merely the HTTP response codes. The documentation does not make this very clear:Here's a list of error codes you may encounter, with their meaning (
$result->error), grouped by source: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 actualconnect()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:Typo, can't edit
This lists the errors of the actual
connect()system call.Yes, thank you
Very helpful comment.
Timeout is DOUBLE what it should be
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.
RANDOM Use
ok so say you are trying to use drupal_http_request with in your own site
to a protected menu path ...but getting access denied ... but you are lodged ?!
well have a look at this ...
Cookie userSessionName = userSessionId
Only Status Message of list of sites
I need to know only the status message of a list of URLs. Whether it is accessible or Not Found. So that the data can be retrieved much faster. How can I do that.
Please note, this call does
Please note, this call does NOT yet support HTTP/1.1! Specifically 1.1 requires handling of chunked encoding
See https://www.drupal.org/node/2242123 and https://www.drupal.org/node/106506.
This is probably fixed in Drupal 8. (See https://www.drupal.org/node/1447736.)
Limit For Post Request?
We are sending data to a salesforce API and they are seeing long messages get cut. Is there a size limit for posts? When I run it through a debugger everything seems ok coming in to this function so I'm not sure where the data is being truncated.
GET request with body
I am calling a translation API that requires the payload of the GET request to be in the body.
I've tried the sample code from above, changing POST to GET, but no luck.
(I am able to do this through cURL, but I'd like to see if it is possible through http_drupal_request)
Hs anyone else had any luck doing this?
Snufkin's response worked
I'm in the same boat where the following worked:
drupal_http_request($url . '?key=value');But it didn't work to pass in the query params as the data property. Take a look at Snufkin's response above - four years ago and still relevant :)
Timeout may not return HTTP_REQUEST_TIMEOUT
Using Drupal 7.36, this function returns an error code of -60, which appears to be PHP's SOCKET_ETIMEDOUT value of 60 sign-inverted by this function. If you're writing code and depending on HTTP_REQUEST_TIMEOUT to let you know that you're request timed out, you better check for this value as well.
This has been removed in Drupal 8 (D8)
See change record: https://www.drupal.org/node/1862446
In Drupal 8 this function is
In Drupal 8 this function is replaced with Guzzle HTTP client
https://www.drupal.org/node/1862446
Does not support HTTPS over proxy
It should be mentioned in the description that this does not support HTTPS when also using a proxy. This is a very significant limitation.
Have you tried: https://www
Have you tried: https://www.drupal.org/project/chr
Skip SSL verification
To skip SSL verification (when testing), set the 'context' option like this:
Still working solution for
Still working solution for local testing, thanks!
https POST not working with PROXY
I confirm that this is NOT working and we had to use curl instead even with drupal 7.61
Error codes
If you have an error code that isn't an HTTP status code and isn't listed in Drupal, have a look here: http://www-numi.fnal.gov/offline_software/srt_public_context/WebDocs/Err... - it could be an error at the system level
This function decodes slashes
This function decodes slashes. Therefore a post request which includes a slash in the string, as we have with FriendlyCaptcha, is mangled.