diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-06-06 15:43:05 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-06-06 15:43:05 +0000 |
commit | 36e3d552cfe211cba22d1fc8cf0a7f4b13627179 (patch) | |
tree | 2350df7fe55052c45329e3818ec77421669525f6 | |
parent | e9f8dc82b0a33c9d8c5a78267f79762ff5d884da (diff) | |
download | brdo-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.inc | 52 | ||||
-rw-r--r-- | modules/simpletest/tests/common.test | 13 | ||||
-rw-r--r-- | modules/simpletest/tests/system_test.module | 10 |
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'])); |