Commit 36e3d552 authored by Dries's avatar Dries

- Patch #156582 by c960657, Damien Tournoud, townxelliot: added support for...

- Patch #156582 by c960657, Damien Tournoud, townxelliot: added support for timeouts to drupal_http_request().
parent e9f8dc82
......@@ -55,6 +55,12 @@
*/
define('JS_THEME', 100);
/**
* Error code indicating that the request made by drupal_http_request() exceeded
* the specified timeout.
*/
define('HTTP_REQUEST_TIMEOUT', 1);
/**
* Add content to a specified region.
*
......@@ -422,6 +428,10 @@ function drupal_access_denied() {
* - 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.
* @return
* An object which can have one or more of the following parameters:
* - request
......@@ -462,17 +472,28 @@ function drupal_http_request($url, array $options = array()) {
return $result;
}
timer_start(__FUNCTION__);
// Merge the default options.
$options += array(
'headers' => array(),
'method' => 'GET',
'data' => NULL,
'max_redirects' => 3,
'timeout' => 30,
);
switch ($uri['scheme']) {
case 'http':
$port = isset($uri['port']) ? $uri['port'] : 80;
$host = $uri['host'] . ($port != 80 ? ':' . $port : '');
$fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
$fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
break;
case 'https':
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$host = $uri['host'] . ($port != 443 ? ':' . $port : '');
$fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, 20);
$fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
break;
default:
$result->error = 'invalid schema ' . $uri['scheme'];
......@@ -501,14 +522,6 @@ function drupal_http_request($url, array $options = array()) {
$path .= '?' . $uri['query'];
}
// Merge the default options.
$options += array(
'headers' => array(),
'method' => 'GET',
'data' => NULL,
'max_redirects' => 3,
);
// Merge the default headers.
$options['headers'] += array(
'User-Agent' => 'Drupal (+http://drupal.org/)',
......@@ -553,8 +566,16 @@ function drupal_http_request($url, array $options = array()) {
// Fetch response.
$response = '';
while (!feof($fp) && $chunk = fread($fp, 1024)) {
$response .= $chunk;
while (!feof($fp)) {
// Calculate how much time is left of the original timeout value.
$timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
if ($timeout <= 0) {
$result->code = HTTP_REQUEST_TIMEOUT;
$result->error = 'request timed out';
return $result;
}
stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
$response .= fread($fp, 1024);
}
fclose($fp);
......@@ -639,7 +660,12 @@ function drupal_http_request($url, array $options = array()) {
case 302: // Moved temporarily
case 307: // Moved temporarily
$location = $result->headers['Location'];
if ($options['max_redirects']) {
$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);
......
......@@ -309,6 +309,19 @@ class DrupalHTTPRequestTestCase extends DrupalWebTestCase {
$this->assertTrue(!empty($result->protocol), t('Result protocol is returned.'));
$this->assertEqual($result->code, '404', t('Result code is 404'));
$this->assertEqual($result->status_message, 'Not Found', t('Result status message is "Not Found"'));
// Test that timeout is respected. The test machine is expected to be able
// to make the connection (i.e. complete the fsockopen()) in 2 seconds and
// return within a total of 5 seconds. If the test machine is extremely
// slow, the test will fail. fsockopen() has been seen to time out in
// slightly less than the specified timeout, so allow a little slack on the
// minimum expected time (i.e. 1.8 instead of 2).
timer_start(__METHOD__);
$result = drupal_http_request(url('system-test/sleep/10', array('absolute' => TRUE)), array('timeout' => 2));
$time = timer_read(__METHOD__) / 1000;
$this->assertTrue(1.8 < $time && $time < 5, t('Request timed out (%time seconds).', array('%time' => $time)));
$this->assertTrue($result->error, t('An error message was returned.'));
$this->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, t('Proper error code was returned.'));
}
function testDrupalHTTPRequestBasicAuth() {
......
......@@ -5,6 +5,12 @@
* Implement hook_menu().
*/
function system_test_menu() {
$items['system-test/sleep/%'] = array(
'page callback' => 'system_test_sleep',
'page arguments' => array(2),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['system-test/auth'] = array(
'page callback' => 'system_test_basic_auth_page',
'access callback' => TRUE,
......@@ -56,6 +62,10 @@ function system_test_menu() {
return $items;
}
function system_test_sleep($seconds) {
sleep($seconds);
}
function system_test_basic_auth_page() {
$output = t('$_SERVER[\'PHP_AUTH_USER\'] is @username.', array('@username' => $_SERVER['PHP_AUTH_USER']));
$output .= t('$_SERVER[\'PHP_AUTH_PW\'] is @password.', array('@password' => $_SERVER['PHP_AUTH_PW']));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment