diff --git a/README.txt b/README.txt index be85c2e..745923b 100644 --- a/README.txt +++ b/README.txt @@ -142,6 +142,8 @@ processing in the background. @@ -367,7 +369,7 @@ Get 2 results from 2 different queries at the hook_boot bootstrap level in D6. // Second Query. array( 'type' => 'function', - 'call' => 'db_query', + 'call' => 'db_query_range', 'args' => array('SELECT filename FROM {system} ORDER BY filename DESC', 0, 1), ), array( @@ -536,8 +538,9 @@ non blocking background request. Hit 4 different URLs, Using at least 2 that has a status code of 200 and -erroring out on the others that didn't return. Data is truncated as well. +erroring out the others that didn't return fast. Data is truncated as well. + &$result) { // Skip if we got a 200. if ($result->code == 200) { - $counter = $counter + 1; + $counter += 1; continue; } if ($result->status == 'Done.') { @@ -576,12 +579,14 @@ erroring out on the others that didn't return. Data is truncated as well. if ($counter >= 2) { // Set the code to request was aborted. $result->code = HTTPRL_REQUEST_ABORTED; + $result->error = 'Software caused connection abort.'; + // Set status to done and set timeout. $result->status = 'Done.'; - $result->options['timeout'] = $result->options['timeout'] - $current_time; + $result->options['timeout'] -= $result->running_time; // Close the file pointer and remove from the stream from the array. - fclose($streams[$id]); - unset($streams[$id]); + fclose($result->fp); + unset($result->fp); } } } @@ -590,5 +595,5 @@ erroring out on the others that didn't return. Data is truncated as well. // Only use the first and last 256 characters in the data array. $result->data = substr($result->data, 0, 256) . "\n\n ... \n\n" . substr($result->data, strlen($result->data)-256); } - + ?> diff --git a/httprl.module b/httprl.module index 3a9ee7a..39961e8 100644 --- a/httprl.module +++ b/httprl.module @@ -68,6 +68,14 @@ define('HTTPRL_ERROR_INITIALIZING_STREAM', -1004); define('HTTPRL_REQUEST_ABORTED', -10053); /** + * Error code indicating that the connection was forcibly closed by the remote + * host. + * + * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx + */ +define('HTTPRL_CONNECTION_RESET', -10054); + +/** * Error code indicating that the request exceeded the specified timeout. * * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx @@ -244,12 +252,12 @@ function httprl_parse_url($url, &$result) { if (empty($uri)) { // Set error code for failed request. - $result->error = 'Unable to parse URL.'; + $result->error = t('Unable to parse URL.'); $result->code = HTTPRL_URL_PARSE_ERROR; } elseif (!isset($uri['scheme'])) { // Set error code for failed request. - $result->error = 'Missing schema.'; + $result->error = t('Missing schema.'); $result->code = HTTPRL_URL_MISSING_SCHEMA; } @@ -399,7 +407,7 @@ function httprl_set_socket($uri, &$options, $proxy_server, &$result) { break; default: - $result->error = 'Invalid schema ' . $uri['scheme'] . '.'; + $result->error = t('Invalid schema @scheme.', array('@scheme' => $uri['scheme'])); $result->code = HTTPRL_URL_INVALID_SCHEMA; } @@ -419,8 +427,8 @@ function httprl_set_socket($uri, &$options, $proxy_server, &$result) { * STREAM_CLIENT_CONNECT or STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT. */ function httprl_set_connection_flag(&$options, $uri) { - // Set connection flag. - if ($options['async_connect']) { + // async_connect only works if stream is in blocking mode. + if ($options['async_connect'] && $options['blocking']) { // Workaround for PHP bug with STREAM_CLIENT_ASYNC_CONNECT and SSL // https://bugs.php.net/bug.php?id=48182 - Fixed in PHP 5.2.11 and 5.3.1 if ($uri['scheme'] == 'https' && (version_compare(PHP_VERSION, '5.2.11', '<') || version_compare(PHP_VERSION, '5.3.0', '='))) { @@ -432,6 +440,7 @@ function httprl_set_connection_flag(&$options, $uri) { } } else { + $options['async_connect'] = FALSE; $flags = STREAM_CLIENT_CONNECT; } return $flags; @@ -613,7 +622,7 @@ function httprl_build_request_string($uri, $options) { * @return $result * An object for httprl_send_request. */ -function httprl_stream_connection_error_formatter($errno, $errstr, $socket, $result) { +function httprl_stream_connection_error_formatter($errno, $errstr, &$result) { // Make sure drupal_convert_to_utf8() is available. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) { require_once DRUPAL_ROOT . '/includes/unicode.inc'; @@ -629,15 +638,14 @@ function httprl_stream_connection_error_formatter($errno, $errstr, $socket, $res // before the connect() call. This is most likely due to a problem // initializing the stream. $result->code = HTTPRL_ERROR_INITIALIZING_STREAM; - $result->error = !empty($errstr) ? $errstr : t('Error initializing socket @socket.', array('@socket' => $socket)); + $result->error = !empty($errstr) ? $errstr : t('Error initializing socket @socket.', array('@socket' => $result->socket)); } else { // When a network error occurs, we use a negative number so it does not // clash with the HTTP status codes. $result->code = (int) -$errno; - $result->error = !empty($errstr) ? $errstr : t('Error opening socket @socket.', array('@socket' => $socket)); + $result->error = !empty($errstr) ? $errstr : t('Error opening socket @socket.', array('@socket' => $result->socket)); } - return $result; } /** @@ -654,20 +662,19 @@ function httprl_stream_connection_error_formatter($errno, $errstr, $socket, $res * @return array * array($fp, $options, $errno, $errstr). */ -function httprl_establish_stream_connection($socket, $flags, $uri, $options) { - // Start the timer. - $timer_name = mt_rand(); - timer_start($timer_name); - $fp = FALSE; +function httprl_establish_stream_connection(&$result) { + // Record start time. + $start_time = microtime(TRUE); + $result->fp = FALSE; // Try to make a connection, 3 max tries in loop. $count = 0; - while (!$fp && $count < 3) { + while (!$result->fp && $count < 3) { // Try the connection again not using async if in https mode. if ($count > 0) { - if ($flags === STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT && $uri['scheme'] == 'https') { - $flags = STREAM_CLIENT_CONNECT; - $options['async_connect'] = FALSE; + if ($result->flags === STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT && $result->uri['scheme'] == 'https') { + $result->flags = STREAM_CLIENT_CONNECT; + $result->options['async_connect'] = FALSE; } else { // Break out of while loop if we can't connect. @@ -676,22 +683,45 @@ function httprl_establish_stream_connection($socket, $flags, $uri, $options) { } // Open the connection. - if (empty($options['context'])) { - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags); + if (empty($result->options['context'])) { + $result->fp = @stream_socket_client($result->socket, $errno, $errstr, $result->options['timeout'], $result->flags); } else { // Create a stream with context. Context allows for the verification of // a SSL certificate. - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']); + $result->fp = @stream_socket_client($result->socket, $errno, $errstr, $result->options['timeout'], $result->flags, $result->options['context']); } $count++; - $options['timeout'] = $options['timeout'] - timer_read($timer_name) / 1000; } - // Stop the timer. - timer_stop($timer_name); + // Make sure the stream opened properly. This check doesn't work if + // async_connect is used, so only check it if async_connect is FALSE. Making + // sure that stream_socket_get_name returns a "TRUE" value. + if ( $result->fp + && !$result->options['async_connect'] + && !stream_socket_get_name($result->fp, TRUE) + ) { + $errno = HTTPRL_CONNECTION_REFUSED; + $errstr = 'Connection refused. No connection could be made because the target machine actively refused it.'; + $result->fp = FALSE; + } - return array($fp, $options, $errno, $errstr); + // Report any errors or set the steram to non blocking mode. + if (!$result->fp) { + httprl_stream_connection_error_formatter($errno, $errstr, $result); + } + else { + stream_set_blocking($result->fp, 0); + } + + // Record end time. + $end_time = microtime(TRUE); + $extra = 0; + if (isset($result->options['internal_states']['running_time'])) { + $extra = $result->options['internal_states']['running_time']; + unset($result->options['internal_states']['running_time']); + } + $result->running_time = $end_time - $start_time + $extra; } /** @@ -757,9 +787,9 @@ function httprl_establish_stream_connection($socket, $flags, $uri, $options) { * will be available to the parent. * - alter_all_streams_function: Function name. This function runs at the end * of httprl_post_processing() so that one can alter the $responses and - * $streams variables inside of httprl_send_request. Defined function + * $output variables inside of httprl_send_request. Defined function * should have the following parameters: - * ($id, &$responses, &$streams, $current_time). + * ($id, &$responses). * * @return array * Array where key is the URL and the value is the return value from @@ -790,12 +820,19 @@ function httprl_request($urls, $options = array()) { // Set things up; but do not perform any IO. foreach ($urls as $url) { $result = new stdClass(); + $result->url = $url; + $result->status = 'in progress'; + $result->code = 0; + $result->chunk_size = 1024; + $result->data = ''; // Parse the given URL and skip if an error occurred. $uri = httprl_parse_url($url, $result); if (isset($result->error)) { - // Add the failed request to the output. - $return[$url] = httprl_send_request(FALSE, $url, $result, $options); + // Put all variables into an array for easy alterations. + $connections[] = array($socket, $flags, $uri, $url, $options, $result, $request); + $return[$url] = FALSE; + // Stop processing this request as we have encountered an error. continue; } // Setup the default options. @@ -806,8 +843,10 @@ function httprl_request($urls, $options = array()) { // Create the socket string and skip if an error occurred. $socket = httprl_set_socket($uri, $options, $proxy_server, $result, $return, $url); if (isset($result->error)) { - // Add the failed request to the output. - $return[$url] = httprl_send_request(FALSE, $url, $result, $options); + // Put all variables into an array for easy alterations. + $connections[] = array($socket, $flags, $uri, $url, $options, $result, $request); + $return[$url] = FALSE; + // Stop processing this request as we have encountered an error. continue; } @@ -820,6 +859,7 @@ function httprl_request($urls, $options = array()) { // Put all variables into an array for easy alterations. $connections[] = array($socket, $flags, $uri, $url, $options, $result, $request); + $return[$url] = TRUE; } if ($full_bootstrap) { @@ -828,37 +868,18 @@ function httprl_request($urls, $options = array()) { drupal_alter('httprl_request', $connections); } - // Make the connection and queue up the request. + $results = array(); foreach ($connections as $connection) { list($socket, $flags, $uri, $url, $options, $result, $request) = $connection; - - if (isset($result->error)) { - // Add in failed request to the output. - // Continue if an error was triggered in the alter hook. - $return[$url] = httprl_send_request(FALSE, $url, $result, $options); - continue; - } - - // Establish a connection to the server. - list($fp, $options, $errno, $errstr) = httprl_establish_stream_connection($socket, $flags, $uri, $options); - - // Make sure the stream opened properly. - if (!$fp) { - $result = httprl_stream_connection_error_formatter($errno, $errstr, $socket, $result); - - // Add in failed request to the output. - // Continue the loop if a stream was not created. - $return[$url] = httprl_send_request(FALSE, $url, $result, $options); - continue; - } - - // Set the stream to be non blocking. - stream_set_blocking($fp, 0); - - // Put this request into the queue to be processed. - $return[$url] = httprl_send_request($fp, $url, $request, $options); - continue; + $result->request = $request; + $result->options = $options; + $result->socket = $socket; + $result->flags = $flags; + $result->uri = $uri; + $results[] = $result; } + + httprl_send_request($results); return $return; } @@ -870,55 +891,22 @@ function httprl_request($urls, $options = array()) { * This is a flexible and powerful HTTP client implementation. Correctly * handles GET, POST, PUT or any other HTTP requests. * - * @param $fp + * @param $connections * (optional) A file pointer. - * @param $request - * (optional) A string containing the request headers to send to the server. - * @param $timeout - * (optional) An integer holding the stream timeout value. * @return bool * TRUE if function worked as planed. */ -function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '') { +function httprl_send_request($results = NULL) { static $responses = array(); - static $streams = array(); - static $global_timeout = 0; static $counter = 0; - static $stream_write_count = 0; - static $global_connection_count = 0; - static $global_connection_limit = 0; - static $domain_connection_count = array(); - static $domain_connection_limit = array(); - - // If given a file pointer, store data in a static and exit. - if (!is_null($fp)) { - $result = new stdClass(); - $result->url = $url; - $result->request = $request; - $result->status = 'in progress'; - $result->data = ''; - $result->options = $options; - $result->chunk_size = 1024; - $responses[$counter] = $result; - // If file pointer is not empty add it to the connection pool. - if (!empty($fp)) { - $streams[$counter] = $fp; - $stream_write_count++; + if (!is_null($results)) { + // Put the connection information into the responses array. + foreach ($results as $result) { + $responses[$counter] = $result; $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; - } - // Connection was never made. - else { - $result->status = 'Connection not made.'; - $result->code = $request->code; - $result->error = $request->error; - $counter++; - return FALSE; } + return TRUE; } // Exit if there is nothing to do. @@ -926,99 +914,128 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' return FALSE; } - // Start the timer. - $timer_name = mt_rand(); - timer_start($timer_name); + // Create output array. + $output = array(); + // Remove errors from responses array and set the global timeout. + foreach ($responses as $id => &$result) { + if (!empty($result->error)) { + $result->status = 'Connection not made.'; + // Copy the result to the output array. + if (isset($result->url)) { + $output[$result->url] = $result; + } + unset($responses[$id]); + continue; + } + + // Get connection limits. + $global_connection_limit = max($global_connection_limit, $result->options['global_connections']); + if (!isset($domain_connection_limit[$result->options['headers']['Host']])) { + $domain_connection_limit[$result->options['headers']['Host']] = max(1, $result->options['domain_connections']); + } + else { + $domain_connection_limit[$result->options['headers']['Host']] = max($domain_connection_limit[$result->options['headers']['Host']], $result->options['domain_connections']); + } + + $global_timeout = max($global_timeout, $result->options['global_timeout']); + } + + // Record start time. + $start_time_this_run = $start_time_global = microtime(TRUE); // Run the loop as long as we have a stream to read/write to. $empty_runs = 0; $stream_select_timeout = 1; - while (!empty($streams)) { - // Get time. - $current_time = timer_read($timer_name) / 1000; - $global_time = $global_timeout - timer_read($timer_name) / 1000; + + while (!empty($responses)) { // Initialize connection limits. $this_run = array(); $global_connection_count = 0; $domain_connection_count = array(); + $restart_timers = FALSE; - // Inspect each stream, checking for timeouts and connection limits. - foreach ($streams as $id => $fp) { - // Calculate how much time is left of the original timeout value. - $timeout = $responses[$id]->options['timeout'] - $current_time; - if ($timeout <= 0) { - // Stream timed out & the request is not done. - if ($responses[$id]->status == 'in progress') { - $responses[$id]->error = 'Request timed out. Write.'; - // If stream is not done writing, then remove one from the write count. - $stream_write_count--; - } - else { - $responses[$id]->error = 'Request timed out.'; - } - $responses[$id]->code = HTTPRL_REQUEST_TIMEOUT; - $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $timeout; - fclose($fp); - unset($streams[$id]); + // Get time. + $now = microtime(TRUE); - // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); - continue; - } + // Calculate times. + $elapsed_time = $now - $start_time_this_run; + $start_time_this_run = $now; + $global_time = $global_timeout - (($start_time_this_run - $start_time_global) / 1000); + // Inspect each stream, checking for timeouts and connection limits. + foreach ($responses as $id => &$result) { // See if function timed out. if ($global_time <= 0) { // Function timed out & the request is not done. - if ($responses[$id]->status == 'in progress') { - $responses[$id]->error = 'Function timed out. Write.'; + if ($result->status == 'in progress') { + $result->error = t('Function timed out. Write.'); // If stream is not done writing, then remove one from the write count. - $stream_write_count--; + if (isset($result->fp)) { + $stream_write_count--; + } } else { - $responses[$id]->error = 'Function timed out.'; + $result->error = t('Function timed out.'); } - $responses[$id]->code = HTTPRL_FUNCTION_TIMEOUT; - $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $global_time; - fclose($fp); - unset($streams[$id]); + $result->code = HTTPRL_FUNCTION_TIMEOUT; + $result->status = 'Done.'; - // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); + // Do post processing on the stream and close it. + httprl_post_processing($id, $responses, $output, $global_time); continue; } + // Do not calculate local timeout if a file pointer doesn't exist. + if (isset($result->fp)) { + // Add the elapsed time to this stream. + $result->running_time += $elapsed_time; + // Calculate how much time is left of the original timeout value. + $timeout = $result->options['timeout'] - $result->running_time; + // No streams are ready from stream_select, See if end server has + // dropped the connection, or has failed to make the connection. + $socket_name = 'Not Empty'; + if ($empty_runs > 10) { + $socket_name = stream_socket_get_name($result->fp, TRUE); + } - // See if end server has dropped the connection. - if ($empty_runs > 10) { - $socket_name = stream_socket_get_name($fp, TRUE); - if (empty($socket_name)) { - // Connection was dropped. - if ($responses[$id]->status == 'in progress') { + // Connection was dropped or connection timed out. + if ($timeout <= 0 || empty($socket_name)) { + $result->error = t('Connection timed out. If you believe this is a false error, turn off async_connect in the httprl options array and try again.'); + // Stream timed out & the request is not done. + if ($result->status == 'in progress') { + $result->error .= t(' Write.'); // If stream is not done writing, then remove one from the write count. - $responses[$id]->error = 'Connection refused by destination. Write.'; $stream_write_count--; } else { - $responses[$id]->error = 'Connection refused by destination.'; + $result->error = t(' Read.'); } - $responses[$id]->code = HTTPRL_CONNECTION_REFUSED; - $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $timeout; - fclose($fp); - unset($streams[$id]); + $result->code = HTTPRL_REQUEST_TIMEOUT; + $result->status = 'Done.'; // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); + httprl_post_processing($id, $responses, $output, $timeout); continue; } } + // Connection was handled elsewhere. Copy to output and remove. + if (!isset($result->fp) && $result->status != 'in progress') { + // Copy the result to the output array. + if (isset($result->url)) { + $output[$result->url] = $result; + } + unset($responses[$id]); + } + // Set the connection limits for this run. // Get the host name. - $host = $responses[$id]->options['headers']['Host']; + $host = $result->options['headers']['Host']; + // Set the domain connection limit if none has been defined yet. + if (!isset($domain_connection_limit[$host])) { + $domain_connection_limit[$host] = max(1, $result->options['domain_connections']); + } // Count up the number of connections. $global_connection_count++; if (empty($domain_connection_count[$host])) { @@ -1029,14 +1046,48 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' } // 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; + // Establish a new connection. + if (!isset($result->fp) && $result->status == 'in progress') { + // Establish a connection to the server. + httprl_establish_stream_connection($result); + + // Reset timer. + $restart_timers = TRUE; + + // If connection can not be established bail out here. + if (!$result->fp) { + // Copy the result to the output array. + if (isset($result->url)) { + $output[$result->url] = $result; + } + unset($responses[$id]); + $domain_connection_count[$host]--; + $global_connection_count--; + continue; + } + $stream_write_count++; + + } + if (!empty($result->fp)) { + $this_run[$id] = $result->fp; + } } } // All streams removed; exit loop. - if (empty($this_run) || empty($streams)) { + if (empty($responses)) { break; } + // Restart timers. + if ($restart_timers) { + $start_time_this_run = microtime(TRUE); + } + // No streams selected; restart loop from the top. + if (empty($this_run)) { + continue; + } + + // Set the read and write vars to the streams var. $read = $write = $this_run; @@ -1054,14 +1105,16 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // Readable sockets either have data for us, or are failed connection // attempts. foreach ($read as $r) { - $id = array_search($r, $streams); + $id = array_search($r, $this_run); // Make sure ID is in the streams. if ($id === FALSE) { + @fclose($r); continue; } // Do not read from the non blocking sockets. if (empty($responses[$id]->options['blocking'])) { - fclose($r); + // Do post processing on the stream and close it. + httprl_post_processing($id, $responses, $output); continue; } @@ -1086,7 +1139,6 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // being followed now. if (!empty($responses[$id]->options['internal_states']['kill'])) { fclose($r); - unset($streams[$id]); unset($responses[$id]); continue; } @@ -1105,12 +1157,9 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $responses[$id]->code = HTTPRL_CONNECTION_REFUSED; } $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $responses[$id]->options['timeout'] - $current_time; - fclose($r); - unset($streams[$id]); // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); + httprl_post_processing($id, $responses, $output); } else { $responses[$id]->status = 'Reading data'; @@ -1120,7 +1169,7 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // Write to each stream if it is available. if ($stream_write_count > 0) { foreach ($write as $w) { - $id = array_search($w, $streams); + $id = array_search($w, $this_run); // Make sure ID is in the streams & status is for writing. if ($id === FALSE || empty($responses[$id]->status) || $responses[$id]->status != 'in progress') { continue; @@ -1149,13 +1198,10 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $responses[$id]->error = 'fwrite() failed.'; $responses[$id]->code = HTTPRL_REQUEST_FWRITE_FAIL; $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $responses[$id]->options['timeout'] - $current_time; $stream_write_count--; - @fclose($w); - unset($streams[$id]); // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); + httprl_post_processing($id, $responses, $output); } elseif ($bytes >= $len) { $stream_write_count--; @@ -1167,10 +1213,9 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // If this is a non blocking request then close the connection and destroy the stream. if (empty($responses[$id]->options['blocking'])) { - $responses[$id]->options['timeout'] = $responses[$id]->options['timeout'] - $current_time; - fclose($w); - unset($streams[$id]); $responses[$id]->status = 'Non-Blocking request sent out. Not waiting for the response.'; + // Do post processing on the stream. + httprl_post_processing($id, $responses, $output); } else { // All data has been written to the socket. We are read only from here on out. @@ -1193,46 +1238,25 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' if ($empty_runs > 100) { // If stream_select hasn't returned a valid read or write stream after // 2.5+ seconds, error out. - foreach ($streams as $id => $fp) { + foreach ($this_run as $id => $fp) { // stream_select timed out & the request is not done. $responses[$id]->error = 'stream_select() timed out.'; $responses[$id]->code = HTTPRL_STREAM_SELECT_TIMEOUT; $responses[$id]->status = 'Done.'; - $responses[$id]->options['timeout'] = $responses[$id]->options['timeout'] - $current_time; - fclose($fp); - unset($streams[$id]); // Do post processing on the stream. - httprl_post_processing($id, $responses, $streams, $timer_name); + httprl_post_processing($id, $responses, $output); } - break; - } - } - - // Stop the timer. - timer_stop($timer_name); - - $output = array(); - foreach ($responses as $id => &$result) { - if (isset($result->options['internal_states']['background_function_return'])) { - continue; } - // Copy the result to the output array. - if (isset($result->url)) { - $output[$result->url] = $result; + if (!$rw_done) { + // Wait 5ms for data buffers. + usleep(5000); } } // Free memory/reset static variables. $responses = array(); - $streams = array(); - $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; } @@ -1398,7 +1422,7 @@ function httprl_parse_data(&$result) { // Error out if we hit the max redirect. if ($result->options['max_redirects'] <= 0) { $result->code = HTTPRL_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED; - $result->error = 'Maximum allowed redirects exhausted.'; + $result->error = t('Maximum allowed redirects exhausted.'); } else { // Redirect to the new location. @@ -1409,6 +1433,10 @@ function httprl_parse_data(&$result) { } // Remove the host from the header. unset($result->options['headers']['Host']); + + // Pass along running time. + $result->options['internal_states']['running_time'] = $result->running_time; + // Send new request. httprl_request($location, $result->options); // Kill this request. @@ -1431,10 +1459,21 @@ function httprl_parse_data(&$result) { * @param $result * An object from httprl_send_request. */ -function httprl_post_processing($id, &$responses, &$streams, $timer_name) { +function httprl_post_processing($id, &$responses, &$output, $time_left = NULL) { // Create the result reference. $result = &$responses[$id]; + // Close file. + if (isset($result->fp)) { + @fclose($result->fp); + } + + // Set timeout. + if (is_null($time_left)) { + $time_left = $result->options['timeout'] - $result->running_time; + } + $result->options['timeout'] = $time_left; + // Assemble redirects. httprl_reconstruct_redirects($result); @@ -1447,6 +1486,7 @@ function httprl_post_processing($id, &$responses, &$streams, $timer_name) { // If this is a background callback request, extract the data and return. if (isset($result->options['internal_states']['background_function_return']) && isset($result->headers['content-type']) && $result->headers['content-type'] == 'application/x-www-form-urlencoded') { httprl_extract_background_callback_data($result); + unset($responses[$id]); return; } @@ -1474,11 +1514,16 @@ function httprl_post_processing($id, &$responses, &$streams, $timer_name) { httprl_queue_background_callback($result->options['background_callback'], $result); } - // Allow a user defined function to alter all $responses and $streams. + // Allow a user defined function to alter all $responses. if ($full_bootstrap && !empty($result->options['alter_all_streams_function']) && function_exists($result->options['alter_all_streams_function'])) { - $current_time = timer_read($timer_name) / 1000; - $result->options['alter_all_streams_function']($id, $responses, $streams, $current_time); + $result->options['alter_all_streams_function']($id, $responses); + } + + // Copy the result to the output array. + if (isset($result->url)) { + $output[$result->url] = $result; } + unset($responses[$id]); } /** @@ -1781,31 +1826,38 @@ function httprl_background_processing($output, $wait = TRUE, $content_type = "te return FALSE; } - // Calculate Content Length - if ($length == 0) { - $output .= "\n"; - $length = (httprl_strlen($output)-1); - } - // Prime php for background operations + // Prime php for background operations. + // Remove any output buffers. + @ob_end_clean(); $loop = 0; while (ob_get_level() && $loop < 25) { - ob_end_clean(); + @ob_end_clean(); $loop++; } - header("Connection: close"); - ignore_user_abort(); - // Output headers & data + // Ignore user aborts. + ignore_user_abort(TRUE); + + // Output headers & data. ob_start(); + header("HTTP/1.0 200 OK"); header("Content-type: " . $content_type); header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); header("Cache-Control: no-cache"); header("Cache-Control: must-revalidate"); - header("Content-Length: " . $length); header("Connection: close"); + header('Etag: "' . microtime(TRUE) . '"'); print($output); - ob_end_flush(); - flush(); + $size = ob_get_length(); + header("Content-Length: " . $size); + @ob_end_flush(); + @ob_flush(); + @flush(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } + // wait for 1 second if ($wait) { sleep(1);