summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-06-06 15:43:05 +0000
committerDries Buytaert <dries@buytaert.net>2009-06-06 15:43:05 +0000
commit36e3d552cfe211cba22d1fc8cf0a7f4b13627179 (patch)
tree2350df7fe55052c45329e3818ec77421669525f6
parente9f8dc82b0a33c9d8c5a78267f79762ff5d884da (diff)
downloadbrdo-36e3d552cfe211cba22d1fc8cf0a7f4b13627179.tar.gz
brdo-36e3d552cfe211cba22d1fc8cf0a7f4b13627179.tar.bz2
- Patch #156582 by c960657, Damien Tournoud, townxelliot: added support for timeouts to drupal_http_request().
-rw-r--r--includes/common.inc52
-rw-r--r--modules/simpletest/tests/common.test13
-rw-r--r--modules/simpletest/tests/system_test.module10
3 files changed, 62 insertions, 13 deletions
diff --git a/includes/common.inc b/includes/common.inc
index 299af56b8..35a1ccf46 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -56,6 +56,12 @@ define('JS_DEFAULT', 0);
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.
*
* @param $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);
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index 9b91d5775..6b767a527 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -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() {
diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module
index 432737f52..07a86afdb 100644
--- a/modules/simpletest/tests/system_test.module
+++ b/modules/simpletest/tests/system_test.module
@@ -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']));