diff --git a/httprl.module b/httprl.module index 44e02f4..d85e049 100644 --- a/httprl.module +++ b/httprl.module @@ -6,6 +6,12 @@ */ /** + * Error code indicating that all requests made by httprl_send_request() + * exceeded the specified timeout. + */ +define('HTTP_FUNCTION_TIMEOUT', -3); + +/** * Implement hook_menu(). */ function httprl_menu() { @@ -131,9 +137,9 @@ function httprl_build_url_self($path = '', $detect_schema = FALSE) { * 'param=value¶m=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. + * - timeout: A float representing the maximum number of seconds a connection + * 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(). * - blocking: set to FALSE to make this not care about the returned data. * - version: HTTP Version 1.0 or 1.1. Default is 1.0 for a good reason. @@ -143,6 +149,10 @@ function httprl_build_url_self($path = '', $detect_schema = FALSE) { * domain name. Default is 8. * - global_connections: Maximum number of simultaneous connections that can * be open on the server. Default is 128. + * - global_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_FUNCTION_TIMEOUT constant. Default is + * 120 seconds. * @return bool * return value from httprl_send_request(). */ @@ -185,6 +195,7 @@ function httprl_request($url, $options = array()) { 'referrer' => FALSE, 'domain_connections' => 8, 'global_connections' => 128, + 'global_timeout' => 120.0, ); // stream_socket_client() requires timeout to be a float. @@ -345,7 +356,7 @@ function httprl_request($url, $options = array()) { function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '') { static $responses = array(); static $streams = array(); - static $timeout = 0; + static $global_timeout = 0; static $counter = 0; static $stream_write_count = 0; static $global_connection_count = 0; @@ -367,9 +378,9 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // If file pointer is not empty add it to the connection pool. if (!empty($fp)) { $streams[$counter] = $fp; - $timeout = max($options['timeout'], $timeout); $stream_write_count++; $counter++; + $global_timeout = max(1, $options['global_timeout']); $global_connection_limit = max(1, $options['global_connections']); $domain_connection_limit[$options['headers']['Host']] = max(1, $options['domain_connections']); return TRUE; @@ -390,7 +401,8 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' } // Start the timer. - timer_start('httprl_send_request'); + $timer_name = mt_rand(); + timer_start($timer_name); // Run the loop as long as we have a stream to read/write to. while (!empty($streams)) { @@ -398,7 +410,7 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // Check for timeouts. foreach ($streams as $id => $fp) { // Calculate how much time is left of the original timeout value. - $timeout = $responses[$id]->options['timeout'] - timer_read(__FUNCTION__) / 1000; + $timeout = $responses[$id]->options['timeout'] - timer_read($timer_name) / 1000; if ($timeout <= 0) { if (defined('HTTP_REQUEST_TIMEOUT')) { $http_request_timeout = HTTP_REQUEST_TIMEOUT; @@ -420,6 +432,26 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' } } } + + // See if function timed out. + $global_time = $global_timeout - timer_read($timer_name) / 1000; + if ($global_time <= 0) { + foreach ($streams as $id => $fp) { + // Function timed out & the request is not done. + $responses[$id]->error = 'function timed out'; + $responses[$id]->code = HTTP_FUNCTION_TIMEOUT; + $responses[$id]->status = 'Done.'; + $responses[$id]->options['timeout'] = $timeout; + fclose($fp); + unset($streams[$id]); + // If stream is not done writing, then remove one from the write count. + if ($responses[$id]->status == 'in progress') { + $responses[$id]->error = 'request timed out, write'; + $stream_write_count--; + } + } + } + // All streams timed out. if (empty($streams)) { break; @@ -442,7 +474,7 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $domain_connection_count[$host]++; } - // If the condtions are correct, let the stream be ran in this loop. + // If the conditions are correct, let the stream be ran in this loop. if ($global_connection_limit >= $global_connection_count && $domain_connection_limit[$host] >= $domain_connection_count[$host]) { $this_run[$id] = $fp; } @@ -579,6 +611,9 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' } } + // Stop the timer. + timer_stop($timer_name); + // HTTP_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED is defined in // http://drupal.org/node/1096890 if (defined('HTTP_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED')) { @@ -647,12 +682,16 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $output[$result->url] = $result; } - // Free memory. + // Free memory/reset static variables. $responses = array(); $streams = array(); - $timeout = 0; + $global_timeout = 0; $counter = 0; $stream_write_count = 0; + $global_connection_count = 0; + $global_connection_limit = 0; + $domain_connection_count = array(); + $domain_connection_limit = array(); return $output; }