diff --git a/httprl.module b/httprl.module index b49f33f..fc07364 100755 --- a/httprl.module +++ b/httprl.module @@ -6,6 +6,8 @@ * */ +define('HTTP_REQUEST_FWRITE_FAIL', -2); + /** * Perform an HTTP request * @@ -57,16 +59,10 @@ function httprl_request($url, array $options = array()) { 'method' => 'GET', 'data' => NULL, 'max_redirects' => 3, - 'timeout' => 10.0, + 'timeout' => 30.0, 'context' => NULL, 'blocking' => TRUE, ); - // Using http 1.0 so set the connection to be closed by default. - // Sending referrer as well. - $options['headers'] += array( - 'Connection' => 'close', - 'Referer' => $base_root . request_uri(), - ); // stream_socket_client() requires timeout to be a float. $options['timeout'] = (float) $options['timeout']; @@ -118,10 +114,11 @@ function httprl_request($url, array $options = array()) { $result->code = -$errno; $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket)); + // Exit if a stream was not created. return $result; } - // Non blocking stream. + // Set the stream to be non blocking. stream_set_blocking($fp, 0); // Construct the path to act on. @@ -131,8 +128,13 @@ function httprl_request($url, array $options = array()) { } // Merge the default headers. + // Set user agent to drupal. + // Set connection to closed to prevent keep-alive from causing a timeout. + // Set referrer to current page. $options['headers'] += array( 'User-Agent' => 'Drupal (+http://drupal.org/)', + 'Connection' => 'close', + 'Referer' => $base_root . request_uri(), ); // Only add Content-Length if we actually have any content or if it is a POST @@ -160,6 +162,7 @@ function httprl_request($url, array $options = array()) { $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); } + // Assemble the request together. $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n"; foreach ($options['headers'] as $name => $value) { $request .= $name . ': ' . trim($value) . "\r\n"; @@ -167,12 +170,12 @@ function httprl_request($url, array $options = array()) { $request .= "\r\n" . $options['data']; $result->request = $request; + // Put this request into the queue to be processed. return httprl_send_request($fp, $url, $request, $options); } /** - * Perform an HTTP request; does not wait for reply & you never will get it - * back. + * Perform an HTTP request. * * @see drupal_http_request() * @@ -193,8 +196,9 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' static $streams = array(); static $timeout = 0; static $counter = 0; + static $stream_write_count = 0; - // Store data in a static and exit. + // If given a file pointer, store data in a static and exit. if (!empty($fp)) { $streams[$counter] = $fp; $timeout = max($options['timeout'], $timeout); @@ -207,6 +211,7 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $result->chunk_size = 1024; $responses[$counter] = $result; $counter++; + $stream_write_count++; return TRUE; } @@ -215,10 +220,12 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' return FALSE; } - timer_start(__FUNCTION__); + // Start the timer. + timer_start('httprl_send_request'); - // Run the loop as long as we have a stream to write to. + // Run the loop as long as we have a stream to read/write to. while (!empty($streams)) { + // Set the read and write vars to the streams var. $read = $write = $streams; $except = array(); @@ -226,49 +233,63 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' // Do some voodoo and open all streams at once. $n = stream_select($read, $write, $except, $timeout); - // We have some streams to write to. + // We have some streams to read/write to. if (!empty($n)) { // Readable sockets either have data for us, or are failed connection attempts. foreach ($read as $r) { $id = array_search($r, $streams); + // Calculate how much time is left of the original timeout value. + $timeout = $responses[$id]->options['timeout'] - timer_read(__FUNCTION__) / 1000; + if ($timeout <= 0) { + // Stream timed out. + $responses[$id]->error = 'request timed out'; + $responses[$id]->code = HTTP_REQUEST_TIMEOUT; + $responses[$id]->status = 'Done.'; + $responses[$id]->options['timeout'] = $timeout; + fclose($w); + unset($streams[$id]); + continue; + } // Do not read from the non blocking sockets. if (empty($responses[$id]->options['blocking'])) { continue; } + // Read socket. $chunk = fread($r, $responses[$id]->chunk_size); $responses[$id]->data .= $chunk; // Get stream data. $info = stream_get_meta_data($r); - // See if the headers have come back yet. - if (empty($responses[$id]->headers) && + // Process the headers if we have some data. + if (!empty($responses[$id]->data) && empty($responses[$id]->headers) && ( strpos($responses[$id]->data, "\r\n\r\n") || strpos($responses[$id]->data, "\n\n") || strpos($responses[$id]->data, "\r\r") ) ) { + // See if the headers are in the data stream. httprl_parse_data($responses[$id]); if (!empty($responses[$id]->headers)) { // Stream timed out, close connection. if (isset($responses[$id]->error) && $responses[$id]->error == 'request timed out') { + $responses[$id]->status = 'Done.'; fclose($r); unset($streams[$id]); - $responses[$id]->status = 'Done.'; continue; } // Sream was a redirect, close this connection; redirect is being followed. if (!empty($responses[$id]->redirect_url)) { + $responses[$id]->status = 'Done.'; fclose($r); unset($streams[$id]); - $responses[$id]->status = 'Done.'; continue; } + // Now that we have the headers, increase the chunk size. + $responses[$id]->chunk_size = 32768; } - // Now that we have the headers, increase the chunk size. - $responses[$id]->chunk_size = 32768; } $alive = !$info['eof'] && !$info['timed_out'] && strlen($chunk); @@ -288,25 +309,65 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' } // Write to each stream if it is available. - foreach ($write as $w) { - $id = array_search($w, $streams); - if (isset($streams[$id]) && $responses[$id]->status == 'in progress') { - $len = strlen($responses[$id]->request); - $bytes = fwrite($w, $responses[$id]->request, $len); - if ($bytes >= $len) { - $responses[$id]->status = "Request sent, waiting for response."; - } - else { - $responses[$id]->request = substr($responses[$id]->request, $bytes); - } - if (empty($responses[$id]->options['blocking'])) { - fclose($w); - unset($streams[$id]); - $responses[$id]->status = 'Non-Blocking request sent out. Not waiting for the response.'; + if ($stream_write_count > 0) { + foreach ($write as $w) { + $id = array_search($w, $streams); + if (isset($streams[$id]) && $responses[$id]->status == 'in progress') { + // Calculate how much time is left of the original timeout value. + $timeout = $responses[$id]->options['timeout'] - timer_read(__FUNCTION__) / 1000; + if ($timeout <= 0) { + // Stream timed out. + $responses[$id]->error = 'request timed out'; + $responses[$id]->code = HTTP_REQUEST_TIMEOUT; + $responses[$id]->status = 'Done.'; + $responses[$id]->options['timeout'] = $timeout; + $stream_write_count--; + fclose($w); + unset($streams[$id]); + continue; + } + // Calculate the number of bytes we need to write to the stream. + $len = strlen($responses[$id]->request); + if ($len > 0) { + // Write to the stream. + $bytes = fwrite($w, $responses[$id]->request, $len); + } + else { + // Nothing to write. + $bytes = $len; + } + + // See if we are done with writing. + if ($bytes === FALSE) { + // fwrite failed. + $responses[$id]->error = 'fwrite failed'; + $responses[$id]->code = HTTP_REQUEST_FWRITE_FAIL; + $responses[$id]->status = 'Done.'; + $stream_write_count--; + fclose($w); + unset($streams[$id]); + } + elseif ($bytes >= $len) { + $stream_write_count--; + // If this is a non blocking request then close the connection and destroy the stream. + if (empty($responses[$id]->options['blocking'])) { + fclose($w); + unset($streams[$id]); + $responses[$id]->status = 'Non-Blocking request sent out. Not waiting for the response.'; + } + else { + // All data has been written to the socket. We are read only from here on out. + $responses[$id]->status = "Request sent, waiting for response."; + } + } + else { + // There is more data to write to this socket. Cut what was sent + // accross the stream and resend whats left next time in the loop. + $responses[$id]->request = substr($responses[$id]->request, $bytes); + } } } } - } else { break; @@ -351,6 +412,7 @@ function httprl_send_request($fp = NULL, $url = '', $request = '', $options = '' $streams = array(); $timeout = 0; $counter = 0; + $stream_write_count = 0; return $output; } @@ -459,7 +521,7 @@ function httprl_parse_data(&$result) { case 302: // Moved temporarily case 307: // Moved temporarily $location = $result->headers['location']; - $result->options['timeout'] -= timer_read(__FUNCTION__) / 1000; + $result->options['timeout'] -= timer_read('httprl_send_request') / 1000; if ($result->options['timeout'] <= 0) { $result->code = HTTP_REQUEST_TIMEOUT; $result->error = 'request timed out';