From: maxious Date: Sun, 10 Apr 2011 10:57:41 +0000 Subject: Parallelise http transfers for trip planner tester X-Git-Url: https://maxious.lambdacomplex.org/git/?p=busui.git&a=commitdiff&h=94242a5095cccf5150d731e5102df0af306a8de5 --- Parallelise http transfers for trip planner tester --- --- a/labs/tripPlannerTester.kml.php +++ b/labs/tripPlannerTester.kml.php @@ -8,6 +8,50 @@ } else { return (($pBegin - $pEnd) * (1 - ($pStep / $pMax))) + $pEnd; + } +} +require ("../lib/rolling-curl/RollingCurl.php"); +function processResult_cb($response, $info, $request) +{ + global $testRegions, $regionTimes,$csv,$kml, $latdeltasize,$londeltasize; + $md = $request->metadata; + $tripplan = json_decode($response); + $plans = Array(); + //var_dump(Array($info, $request)); + if (is_array($tripplan->plan->itineraries->itinerary)) { + foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) { + $plans[floor($itinerary->duration / 60000) ] = $itinerary; + } + } + else { + $plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary; + } + if ($csv) echo "{$md['i']},{$md['j']}," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,{$md['key']}\n"; + if ($kml) { + $time = min(array_keys($plans)); + $plan = ""; + if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) { + foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) { + $plan.= processLeg($legNumber, $leg) . ","; + } + } + else { + $plan.= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg); + } + if (isset($tripplan->error) && $tripplan->error->id == 404) { + $time = 999; + $plan = "Trip not possible without excessive walking from nearest bus stop"; + } + $testRegions[] = Array( + "lat" => $md['i'], + "lon" => $md['j'], + "time" => $time, + "latdeltasize" => $latdeltasize, + "londeltasize" => $londeltasize, + "regionname" => $md['key'], + "plan" => $plan . "
original plan" + ); + $regionTimes[] = $time; } } function Gradient($HexFrom, $HexTo, $ColorSteps) @@ -49,18 +93,17 @@ //} //$walkingstep.= floor($step->distance) . "m"; //return $walkingstep; + } } $csv = false; $kml = true; if ($kml) { - //header('Content-Type: application/vnd.google-earth.kml+xml'); + header('Content-Type: application/vnd.google-earth.kml+xml'); echo ' '; } include ('../include/common.inc.php'); -//Test code to grab transit times -// make sure to sleep(10); $boundingBoxes = Array( "belconnen" => Array( "startlat" => - 35.1928, @@ -105,72 +148,29 @@ $useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"; if ($csv) echo "
";
 if ($csv) echo "lat,lon,time,latdeltasize, londeltasize, region key name\n";
+$rc = new RollingCurl("processResult_cb");
+$rc->window_size = 3;
 foreach ($boundingBoxes as $key => $boundingBox) {
 	for ($i = $boundingBox['startlat']; $i >= $boundingBox['finishlat']; $i-= $latdeltasize) {
 		for ($j = $boundingBox['startlon']; $j <= $boundingBox['finishlon']; $j+= $londeltasize) {
 			$url = $otpAPIurl . "ws/plan?date=" . urlencode($startDate) . "&time=" . urlencode($startTime) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=440&wheelchair=false&toPlace=" . $i . "," . $j . "&fromPlace=$fromPlace";
-			$ch = curl_init($url);
-			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-			curl_setopt($ch, CURLOPT_HEADER, 0);
-			curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+			$request = new RollingCurlRequest($url);
+			$request->headers = Array(
 				"Accept: application/json"
-			));
-			curl_setopt($ch, CURLOPT_TIMEOUT, 5);
-			$page = curl_exec($ch);
-			if (curl_errno($ch)) {
-				if ($csv) echo "Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch);
-			}
-			else {
-				$tripplan = json_decode($page); 
-				$plans = Array();
-				if (is_array($tripplan->plan->itineraries->itinerary)) {
-					foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
-						$plans[floor($itinerary->duration / 60000) ] = $itinerary;
-					}
-				}
-				else {
-					$plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
-				}
-				if ($csv) echo "$i,$j," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,$key\n";
-				if ($kml) {
-					$time = min(array_keys($plans));
-					$plan = "";
-					if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
-						foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
-							$plan .= processLeg($legNumber, $leg).",";
-						}
-					}
-					else {
-						$plan .= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
-					}
-						if (isset($tripplan->error) && $tripplan->error->id == 404) {
-							$time = 999;
-							$plan = "Trip not possible without excessive walking from nearest bus stop";
-						}
-					$testRegions[] = Array(
-						"lat" => $i,
-						"lon" => $j,
-						"time" => $time,
-						"latdeltasize" => $latdeltasize,
-						"londeltasize" => $londeltasize,
-						"regionname" => $key,
-						"plan" => $plan . "
original plan" - ); - $regionTimes[] = $time; - } - } - flush(); @ob_flush(); - curl_close($ch); - } - } -} + ); + $request->metadata = Array( "i" => $i, "j" => $j, "key" => $key); + $rc->add($request); + } + } +} +$rc->execute(); if ($kml) { $colorSteps = 9; //$minTime = min($regionTimes); //$maxTime = max($regionTimes); //$rangeTime = $maxTime - $minTime; //$deltaTime = $rangeTime / $colorSteps; - $Gradients = Gradient(strrev("66FF00"), strrev("FF0000"), $colorSteps); // KML is BGR not RGB so strrev + $Gradients = Gradient(strrev("66FF00") , strrev("FF0000") , $colorSteps); // KML is BGR not RGB so strrev foreach ($testRegions as $testRegion) { //$band = (floor(($testRegion[time] - $minTime) / $deltaTime)); $band = (floor($testRegion[time] / 10)); --- /dev/null +++ b/lib/rolling-curl/.svn/all-wcprops @@ -1,1 +1,42 @@ +K 25 +svn:wc:ra_dav:version-url +V 22 +/svn/!svn/ver/20/trunk +END +RollingCurlGroup.php +K 25 +svn:wc:ra_dav:version-url +V 43 +/svn/!svn/ver/20/trunk/RollingCurlGroup.php +END +example_groups.php +K 25 +svn:wc:ra_dav:version-url +V 41 +/svn/!svn/ver/20/trunk/example_groups.php +END +example.php +K 25 +svn:wc:ra_dav:version-url +V 34 +/svn/!svn/ver/20/trunk/example.php +END +RollingCurl.php +K 25 +svn:wc:ra_dav:version-url +V 38 +/svn/!svn/ver/20/trunk/RollingCurl.php +END +CHANGELOG.txt +K 25 +svn:wc:ra_dav:version-url +V 36 +/svn/!svn/ver/20/trunk/CHANGELOG.txt +END +README.txt +K 25 +svn:wc:ra_dav:version-url +V 33 +/svn/!svn/ver/20/trunk/README.txt +END --- /dev/null +++ b/lib/rolling-curl/.svn/entries @@ -1,1 +1,233 @@ - +10 + +dir +20 +http://rolling-curl.googlecode.com/svn/trunk +http://rolling-curl.googlecode.com/svn + + + +2010-09-12T20:39:22.711474Z +20 +alexander.makarow + + + + + + + + + + + + + + +74aa2acc-2e27-11de-b2a4-4f96ceaaac44 + +RollingCurlGroup.php +file + + + + +2011-04-10T08:32:48.081650Z +73c08d9e9e24b4adc89816624c7aca30 +2010-09-12T20:39:22.711474Z +20 +alexander.makarow +has-props + + + + + + + + + + + + + + + + + + + + +5152 + +example_groups.php +file + + + + +2011-04-10T08:32:48.082650Z +907ed82a47d346c39acbd5578e1d0230 +2010-09-12T20:39:22.711474Z +20 +alexander.makarow +has-props + + + + + + + + + + + + + + + + + + + + +1367 + +example.php +file + + + + +2011-04-10T08:32:48.083650Z +87aa845abfaffc09ed4eca024f2a8b8a +2010-09-12T20:39:22.711474Z +20 +alexander.makarow + + + + + + + + + + + + + + + + + + + + + +1860 + +RollingCurl.php +file + + + + +2011-04-10T08:32:48.084650Z +205391c449f3f3ee050004dadc374dc8 +2010-09-12T20:39:22.711474Z +20 +alexander.makarow + + + + + + + + + + + + + + + + + + + + + +10444 + +CHANGELOG.txt +file + + + + +2011-04-10T08:32:48.085650Z +d0452f6f9530ed04580159121d0fd5f7 +2010-09-12T20:39:22.711474Z +20 +alexander.makarow +has-props + + + + + + + + + + + + + + + + + + + + +662 + +README.txt +file + + + + +2011-04-10T08:32:48.085650Z +60dd357081431c0f2b82989cdbce8615 +2010-09-12T20:39:22.711474Z +20 +alexander.makarow + + + + + + + + + + + + + + + + + + + + + +6355 + + --- /dev/null +++ b/lib/rolling-curl/.svn/prop-base/CHANGELOG.txt.svn-base @@ -1,1 +1,6 @@ +K 13 +svn:eol-style +V 6 +native +END --- /dev/null +++ b/lib/rolling-curl/.svn/prop-base/RollingCurlGroup.php.svn-base @@ -1,1 +1,6 @@ +K 13 +svn:eol-style +V 6 +native +END --- /dev/null +++ b/lib/rolling-curl/.svn/prop-base/example_groups.php.svn-base @@ -1,1 +1,6 @@ +K 13 +svn:eol-style +V 6 +native +END --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/CHANGELOG.txt.svn-base @@ -1,1 +1,15 @@ +Rolling Curl changelog +====================== +September 13, 2010 +------------------ +- Bug #12, #14: Fixed default options overriding (LionsAd) +- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd) +- Enh #6, #9: Added $request as parameter to callback function (LionsAd) +- Chg: Request renamed to RollingCurlRequest (LionsAd) +- Added RollingCurlGroup class that allows processing groups of requests (LionsAd) +- More cleanup at unsetting a class (LionsAd) +- Timeout parameter for curl_multi_select is now configurable (LionsAd) +- single_curl now returns true (LionsAd) +- Readme corrections (Alexander Makarov) +- Code cleanup (Alexander Makarov) --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/README.txt.svn-base @@ -1,1 +1,210 @@ - +Rolling Curl +============ + +RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library. + +Released under the Apache License 2.0. + +Authors +------- +- Was originally written by [Josh Fraser](joshfraser.com). +- Currently maintained by [Alexander Makarov](http://rmcreative.ru/). +- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl). + +Overview +-------- +RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP. +curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is +very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or +fail entirely when asked to handle more than a few hundred requests. + +The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests +to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for +the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of +requests you are dealing with, the more noticeable this latency becomes. + +The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of +cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing +links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending. +The result is a faster and more efficient way of processing large quantities of cURL requests in parallel. + +Callbacks +--------- + +Each of requests usually do have a callback to process results that is being executed when request is done +(both successfully or not). + +Callback accepts three parameters and can look like the following one: +~~~ +[php] +function request_callback($response, $info, $request){ + // doing something with the data received +} +~~~ + +- $response contains received page body. +- $info is an associative array that holds various information about response such as HTTP response code, content type, +time taken to make request etc. +- $request contains RollingCurlRequest that was used to make request. + +Examples +-------- +### Hello world + +~~~ +[php] +// an array of URL's to fetch +$urls = array("http://www.google.com", + "http://www.facebook.com", + "http://www.yahoo.com"); + +// a function that will process the returned responses +function request_callback($response, $info, $request) { + // parse the page title out of the returned HTML + if (preg_match("~(.*?)~i", $response, $out)) { + $title = $out[1]; + } + echo "$title
"; + print_r($info); + echo "
"; +} + +// create a new RollingCurl object and pass it the name of your custom callback function +$rc = new RollingCurl("request_callback"); +// the window size determines how many simultaneous requests to allow. +$rc->window_size = 20; +foreach ($urls as $url) { + // add each request to the RollingCurl object + $request = new RollingCurlRequest($url); + $rc->add($request); +} +$rc->execute(); +~~~ + + +### Setting custom options + +Set custom options for EVERY request: + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true); +$rc->execute(); +~~~ + +Set custom options for A SINGLE request: + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$request = new RollingCurlRequest($url); +$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true); +$rc->add($request); +$rc->execute(); +~~~ + +### Shortcuts + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$rc->get("http://www.google.com"); +$rc->get("http://www.yahoo.com"); +$rc->execute(); +~~~ + +### Class callbacks + +~~~ +[php] +class MyInfoCollector { + private $rc; + + function __construct(){ + $this->rc = new RollingCurl(array($this, 'processPage')); + } + + function processPage($response, $info, $request){ + //... + } + + function run($urls){ + foreach ($urls as $url){ + $request = new RollingCurlRequest($url); + $this->rc->add($request); + } + $this->rc->execute(); + } +} + +$collector = new MyInfoCollector(); +$collector->run(array( + 'http://google.com/', + 'http://yahoo.com/' +)); +~~~ + +### Using RollingCurlGroup + +~~~ +[php] +class TestCurlRequest extends RollingCurlGroupRequest { + public $test_verbose = true; + + function process($output, $info) { + echo "Processing " . $this->url . "\n"; + if ($this->test_verbose) + print_r($info); + + parent::process($output, $info); + } +} + +class TestCurlGroup extends RollingCurlGroup { + function process($output, $info, $request) { + echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n"; + parent::process($output, $info, $request); + } + + function finished() { + echo "Group CB: Finished" . $this->name . "\n"; + parent::finished(); + } +} + +$group = new TestCurlGroup("High"); +$group->add(new TestCurlRequest("www.google.de")); +$group->add(new TestCurlRequest("www.yahoo.de")); +$group->add(new TestCurlRequest("www.newyorktimes.com")); +$reqs[] = $group; + +$group = new TestCurlGroup("Normal"); +$group->add(new TestCurlRequest("twitter.com")); +$group->add(new TestCurlRequest("www.bing.com")); +$group->add(new TestCurlRequest("m.facebook.com")); +$reqs[] = $group; + +$reqs[] = new TestCurlRequest("www.kernel.org"); + +// No callback here, as its done in Request class +$rc = new GroupRollingCurl(); + +foreach ($reqs as $req) +$rc->add($req); + +$rc->execute(); +~~~ + +The same function (add) can be used both for adding requests and groups of requests. +The "callback" in request and groups is: + +process($output, $info) + +and + +process($output, $info, $request) + +Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing. + +$Id$ --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/RollingCurl.php.svn-base @@ -1,1 +1,375 @@ - +url = $url; + $this->method = $method; + $this->post_data = $post_data; + $this->headers = $headers; + $this->options = $options; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->url, $this->method, $this->post_data, $this->headers, $this->options); + } +} + +/** + * RollingCurl custom exception + */ +class RollingCurlException extends Exception { +} + +/** + * Class that holds a rolling queue of curl requests. + * + * @throws RollingCurlException + */ +class RollingCurl { + /** + * @var int + * + * Window size is the max number of simultaneous connections allowed. + * + * REMEMBER TO RESPECT THE SERVERS: + * Sending too many requests at one time can easily be perceived + * as a DOS attack. Increase this window_size if you are making requests + * to multiple servers or have permission from the receving server admins. + */ + private $window_size = 5; + + /** + * @var float + * + * Timeout is the timeout used for curl_multi_select. + */ + private $timeout = 10; + + /** + * @var string|array + * + * Callback function to be applied to each result. + */ + private $callback; + + /** + * @var array + * + * Set your base options that you want to be used with EVERY request. + */ + protected $options = array( + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 30, + CURLOPT_TIMEOUT => 30 + ); + + /** + * @var array + */ + private $headers = array(); + + /** + * @var Request[] + * + * The request queue + */ + private $requests = array(); + + /** + * @var RequestMap[] + * + * Maps handles to request indexes + */ + private $requestMap = array(); + + /** + * @param $callback + * Callback function to be applied to each result. + * + * Can be specified as 'my_callback_function' + * or array($object, 'my_callback_method'). + * + * Function should take three parameters: $response, $info, $request. + * $response is response body, $info is additional curl info. + * $request is the original request + * + * @return void + */ + function __construct($callback = null) { + $this->callback = $callback; + } + + /** + * @param string $name + * @return mixed + */ + public function __get($name) { + return (isset($this->{$name})) ? $this->{$name} : null; + } + + /** + * @param string $name + * @param mixed $value + * @return bool + */ + public function __set($name, $value) { + // append the base options & headers + if ($name == "options" || $name == "headers") { + $this->{$name} = $value + $this->{$name}; + } else { + $this->{$name} = $value; + } + return true; + } + + /** + * Add a request to the request queue + * + * @param Request $request + * @return bool + */ + public function add($request) { + $this->requests[] = $request; + return true; + } + + /** + * Create new Request and add it to the request queue + * + * @param string $url + * @param string $method + * @param $post_data + * @param $headers + * @param $options + * @return bool + */ + public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) { + $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options); + return true; + } + + /** + * Perform GET request + * + * @param string $url + * @param $headers + * @param $options + * @return bool + */ + public function get($url, $headers = null, $options = null) { + return $this->request($url, "GET", null, $headers, $options); + } + + /** + * Perform POST request + * + * @param string $url + * @param $post_data + * @param $headers + * @param $options + * @return bool + */ + public function post($url, $post_data = null, $headers = null, $options = null) { + return $this->request($url, "POST", $post_data, $headers, $options); + } + + /** + * Execute processing + * + * @param int $window_size Max number of simultaneous connections + * @return string|bool + */ + public function execute($window_size = null) { + // rolling curl window must always be greater than 1 + if (sizeof($this->requests) == 1) { + return $this->single_curl(); + } else { + // start the rolling curl. window_size is the max number of simultaneous connections + return $this->rolling_curl($window_size); + } + } + + /** + * Performs a single curl request + * + * @access private + * @return string + */ + private function single_curl() { + $ch = curl_init(); + $request = array_shift($this->requests); + $options = $this->get_options($request); + curl_setopt_array($ch, $options); + $output = curl_exec($ch); + $info = curl_getinfo($ch); + + // it's not neccesary to set a callback for one-off requests + if ($this->callback) { + $callback = $this->callback; + if (is_callable($this->callback)) { + call_user_func($callback, $output, $info, $request); + } + } + else + return $output; + return true; + } + + /** + * Performs multiple curl requests + * + * @access private + * @throws RollingCurlException + * @param int $window_size Max number of simultaneous connections + * @return bool + */ + private function rolling_curl($window_size = null) { + if ($window_size) + $this->window_size = $window_size; + + // make sure the rolling window isn't greater than the # of urls + if (sizeof($this->requests) < $this->window_size) + $this->window_size = sizeof($this->requests); + + if ($this->window_size < 2) { + throw new RollingCurlException("Window size must be greater than 1"); + } + + $master = curl_multi_init(); + + // start the first batch of requests + for ($i = 0; $i < $this->window_size; $i++) { + $ch = curl_init(); + + $options = $this->get_options($this->requests[$i]); + + curl_setopt_array($ch, $options); + curl_multi_add_handle($master, $ch); + + // Add to our request Maps + $key = (string) $ch; + $this->requestMap[$key] = $i; + } + + do { + while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ; + if ($execrun != CURLM_OK) + break; + // a request was just completed -- find out which one + while ($done = curl_multi_info_read($master)) { + + // get the info and content returned on the request + $info = curl_getinfo($done['handle']); + $output = curl_multi_getcontent($done['handle']); + + // send the return values to the callback function. + $callback = $this->callback; + if (is_callable($callback)) { + $key = (string) $done['handle']; + $request = $this->requests[$this->requestMap[$key]]; + unset($this->requestMap[$key]); + call_user_func($callback, $output, $info, $request); + } + + // start a new request (it's important to do this before removing the old one) + if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) { + $ch = curl_init(); + $options = $this->get_options($this->requests[$i]); + curl_setopt_array($ch, $options); + curl_multi_add_handle($master, $ch); + + // Add to our request Maps + $key = (string) $ch; + $this->requestMap[$key] = $i; + $i++; + } + + // remove the curl handle that just completed + curl_multi_remove_handle($master, $done['handle']); + + } + + // Block for data in / output; error handling is done by curl_multi_exec + if ($running) + curl_multi_select($master, $this->timeout); + + } while ($running); + curl_multi_close($master); + return true; + } + + + /** + * Helper function to set up a new request by setting the appropriate options + * + * @access private + * @param Request $request + * @return array + */ + private function get_options($request) { + // options for this entire curl object + $options = $this->__get('options'); + if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) { + $options[CURLOPT_FOLLOWLOCATION] = 1; + $options[CURLOPT_MAXREDIRS] = 5; + } + $headers = $this->__get('headers'); + + // append custom options for this specific request + if ($request->options) { + $options = $request->options + $options; + } + + // set the request URL + $options[CURLOPT_URL] = $request->url; + + // posting data w/ this request? + if ($request->post_data) { + $options[CURLOPT_POST] = 1; + $options[CURLOPT_POSTFIELDS] = $request->post_data; + } + if ($headers) { + $options[CURLOPT_HEADER] = 0; + $options[CURLOPT_HTTPHEADER] = $headers; + } + + return $options; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests); + } +} + --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/RollingCurlGroup.php.svn-base @@ -1,1 +1,218 @@ - +group = $group; + } + + /** + * Process the request + * + * + */ + function process($output, $info) { + if ($this->group) + $this->group->process($output, $info, $this); + } + + /** + * @return void + */ + public function __destruct() { + unset($this->group); + parent::__destruct(); + } + +} + +/** + * A group of curl requests. + * + * @throws RollingCurlGroupException * + */ +class RollingCurlGroup { + /** + * @var string group name + */ + protected $name; + + /** + * @var int total number of requests in a group + */ + protected $num_requests = 0; + + /** + * @var int total number of finished requests in a group + */ + protected $finished_requests = 0; + + /** + * @var array requests array + */ + private $requests = array(); + + /** + * @param string $name group name + * @return void + */ + function __construct($name) { + $this->name = $name; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->name, $this->num_requests, $this->finished_requests, $this->requests); + } + + /** + * Adds request to a group + * + * @throws RollingCurlGroupException + * @param RollingCurlGroupRequest|array $request + * @return bool + */ + function add($request) { + if ($request instanceof RollingCurlGroupRequest) { + $request->setGroup($this); + $this->num_requests++; + $this->requests[] = $request; + } + else if (is_array($request)) { + foreach ($request as $req) + $this->add($req); + } + else + throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest"); + + return true; + } + + /** + * @throws RollingCurlGroupException + * @param RollingCurl $rc + * @return bool + */ + function addToRC(RollingCurl $rc){ + $ret = true; + + while (count($this->requests) > 0){ + $ret1 = $rc->add(array_shift($this->requests)); + if (!$ret1) + $ret = false; + } + + return $ret; + } + + /** + * Override to implement custom response processing. + * + * Don't forget to call parent::process(). + * + * @param string $output received page body + * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc. + * @param RollingCurlRequest $request request used + * @return void + */ + function process($output, $info, $request) { + $this->finished_requests++; + + if ($this->finished_requests >= $this->num_requests) + $this->finished(); + } + + /** + * Override to execute code after all requests in a group are processed. + * + * @return void + */ + function finished() { + } + +} + +/** + * Group version of rolling curl + */ +class GroupRollingCurl extends RollingCurl { + + /** + * @var mixed common callback for all groups + */ + private $group_callback = null; + + /** + * @param string $output received page body + * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc. + * @param RollingCurlRequest $request request used + * @return void + */ + protected function process($output, $info, $request) { + if ($request instanceof RollingCurlGroupRequest) + $request->process($output, $info); + + if (is_callable($this->group_callback)) + call_user_func($this->group_callback, $output, $info, $request); + } + + /** + * @param mixed $callback common callback for all groups + * @return void + */ + function __construct($callback = null) { + $this->group_callback = $callback; + + parent::__construct(array(&$this, "process")); + } + + /** + * Adds a group to processing queue + * + * @param RollingCurlGroup|Request $request + * @return bool + */ + public function add($request) { + if ($request instanceof RollingCurlGroup) + return $request->addToRC($this); + else + return parent::add($request); + } + + /** + * Execute processing + * + * @param int $window_size Max number of simultaneous connections + * @return bool|string + */ + public function execute($window_size = null) { + if (count($this->requests) == 0) + return false; + + return parent::execute($window_size); + } +} + --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/example.php.svn-base @@ -1,1 +1,66 @@ +(.*?)~i", $response, $out)) { + $title = $out[1]; + } + echo "$title
"; + print_r($info); + print_r($request); + echo "
"; +} + +// single curl request +$rc = new RollingCurl("request_callback"); +$rc->request("http://www.msn.com"); +$rc->execute(); + +// another single curl request +$rc = new RollingCurl("request_callback"); +$rc->request("http://www.google.com"); +$rc->execute(); + +echo "
"; + +// top 20 sites according to alexa (11/5/09) +$urls = array("http://www.google.com", + "http://www.facebook.com", + "http://www.yahoo.com", + "http://www.youtube.com", + "http://www.live.com", + "http://www.wikipedia.com", + "http://www.blogger.com", + "http://www.msn.com", + "http://www.baidu.com", + "http://www.yahoo.co.jp", + "http://www.myspace.com", + "http://www.qq.com", + "http://www.google.co.in", + "http://www.twitter.com", + "http://www.google.de", + "http://www.microsoft.com", + "http://www.google.cn", + "http://www.sina.com.cn", + "http://www.wordpress.com", + "http://www.google.co.uk"); + +$rc = new RollingCurl("request_callback"); +$rc->window_size = 20; +foreach ($urls as $url) { + $request = new RollingCurlRequest($url); + $rc->add($request); +} +$rc->execute(); + --- /dev/null +++ b/lib/rolling-curl/.svn/text-base/example_groups.php.svn-base @@ -1,1 +1,49 @@ +url . "\n"; + if ($this->test_verbose) + print_r($info); + + parent::process($output, $info); + } +} + +class TestCurlGroup extends RollingCurlGroup { + function process($output, $info, $request) { + echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n"; + parent::process($output, $info, $request); + } + + function finished() { + echo "Group CB: Finished" . $this->name . "\n"; + parent::finished(); + } +} + +$group = new TestCurlGroup("High"); +$group->add(new TestCurlRequest("www.google.de")); +$group->add(new TestCurlRequest("www.yahoo.de")); +$group->add(new TestCurlRequest("www.newyorktimes.com")); +$reqs[] = $group; + +$group = new TestCurlGroup("Normal"); +$group->add(new TestCurlRequest("twitter.com")); +$group->add(new TestCurlRequest("www.bing.com")); +$group->add(new TestCurlRequest("m.facebook.com")); +$reqs[] = $group; + +$reqs[] = new TestCurlRequest("www.kernel.org"); + +// No callback here, as its done in Request class +$rc = new GroupRollingCurl(); + +foreach ($reqs as $req) + $rc->add($req); + +$rc->execute(); --- /dev/null +++ b/lib/rolling-curl/CHANGELOG.txt @@ -1,1 +1,15 @@ +Rolling Curl changelog +====================== +September 13, 2010 +------------------ +- Bug #12, #14: Fixed default options overriding (LionsAd) +- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd) +- Enh #6, #9: Added $request as parameter to callback function (LionsAd) +- Chg: Request renamed to RollingCurlRequest (LionsAd) +- Added RollingCurlGroup class that allows processing groups of requests (LionsAd) +- More cleanup at unsetting a class (LionsAd) +- Timeout parameter for curl_multi_select is now configurable (LionsAd) +- single_curl now returns true (LionsAd) +- Readme corrections (Alexander Makarov) +- Code cleanup (Alexander Makarov) --- /dev/null +++ b/lib/rolling-curl/README.txt @@ -1,1 +1,210 @@ - +Rolling Curl +============ + +RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library. + +Released under the Apache License 2.0. + +Authors +------- +- Was originally written by [Josh Fraser](joshfraser.com). +- Currently maintained by [Alexander Makarov](http://rmcreative.ru/). +- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl). + +Overview +-------- +RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP. +curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is +very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or +fail entirely when asked to handle more than a few hundred requests. + +The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests +to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for +the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of +requests you are dealing with, the more noticeable this latency becomes. + +The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of +cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing +links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending. +The result is a faster and more efficient way of processing large quantities of cURL requests in parallel. + +Callbacks +--------- + +Each of requests usually do have a callback to process results that is being executed when request is done +(both successfully or not). + +Callback accepts three parameters and can look like the following one: +~~~ +[php] +function request_callback($response, $info, $request){ + // doing something with the data received +} +~~~ + +- $response contains received page body. +- $info is an associative array that holds various information about response such as HTTP response code, content type, +time taken to make request etc. +- $request contains RollingCurlRequest that was used to make request. + +Examples +-------- +### Hello world + +~~~ +[php] +// an array of URL's to fetch +$urls = array("http://www.google.com", + "http://www.facebook.com", + "http://www.yahoo.com"); + +// a function that will process the returned responses +function request_callback($response, $info, $request) { + // parse the page title out of the returned HTML + if (preg_match("~(.*?)~i", $response, $out)) { + $title = $out[1]; + } + echo "$title
"; + print_r($info); + echo "
"; +} + +// create a new RollingCurl object and pass it the name of your custom callback function +$rc = new RollingCurl("request_callback"); +// the window size determines how many simultaneous requests to allow. +$rc->window_size = 20; +foreach ($urls as $url) { + // add each request to the RollingCurl object + $request = new RollingCurlRequest($url); + $rc->add($request); +} +$rc->execute(); +~~~ + + +### Setting custom options + +Set custom options for EVERY request: + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true); +$rc->execute(); +~~~ + +Set custom options for A SINGLE request: + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$request = new RollingCurlRequest($url); +$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true); +$rc->add($request); +$rc->execute(); +~~~ + +### Shortcuts + +~~~ +[php] +$rc = new RollingCurl("request_callback"); +$rc->get("http://www.google.com"); +$rc->get("http://www.yahoo.com"); +$rc->execute(); +~~~ + +### Class callbacks + +~~~ +[php] +class MyInfoCollector { + private $rc; + + function __construct(){ + $this->rc = new RollingCurl(array($this, 'processPage')); + } + + function processPage($response, $info, $request){ + //... + } + + function run($urls){ + foreach ($urls as $url){ + $request = new RollingCurlRequest($url); + $this->rc->add($request); + } + $this->rc->execute(); + } +} + +$collector = new MyInfoCollector(); +$collector->run(array( + 'http://google.com/', + 'http://yahoo.com/' +)); +~~~ + +### Using RollingCurlGroup + +~~~ +[php] +class TestCurlRequest extends RollingCurlGroupRequest { + public $test_verbose = true; + + function process($output, $info) { + echo "Processing " . $this->url . "\n"; + if ($this->test_verbose) + print_r($info); + + parent::process($output, $info); + } +} + +class TestCurlGroup extends RollingCurlGroup { + function process($output, $info, $request) { + echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n"; + parent::process($output, $info, $request); + } + + function finished() { + echo "Group CB: Finished" . $this->name . "\n"; + parent::finished(); + } +} + +$group = new TestCurlGroup("High"); +$group->add(new TestCurlRequest("www.google.de")); +$group->add(new TestCurlRequest("www.yahoo.de")); +$group->add(new TestCurlRequest("www.newyorktimes.com")); +$reqs[] = $group; + +$group = new TestCurlGroup("Normal"); +$group->add(new TestCurlRequest("twitter.com")); +$group->add(new TestCurlRequest("www.bing.com")); +$group->add(new TestCurlRequest("m.facebook.com")); +$reqs[] = $group; + +$reqs[] = new TestCurlRequest("www.kernel.org"); + +// No callback here, as its done in Request class +$rc = new GroupRollingCurl(); + +foreach ($reqs as $req) +$rc->add($req); + +$rc->execute(); +~~~ + +The same function (add) can be used both for adding requests and groups of requests. +The "callback" in request and groups is: + +process($output, $info) + +and + +process($output, $info, $request) + +Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing. + +$Id$ --- /dev/null +++ b/lib/rolling-curl/RollingCurl.php @@ -1,1 +1,376 @@ - +url = $url; + $this->method = $method; + $this->post_data = $post_data; + $this->headers = $headers; + $this->options = $options; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->url, $this->method, $this->post_data, $this->headers, $this->options); + } +} + +/** + * RollingCurl custom exception + */ +class RollingCurlException extends Exception { +} + +/** + * Class that holds a rolling queue of curl requests. + * + * @throws RollingCurlException + */ +class RollingCurl { + /** + * @var int + * + * Window size is the max number of simultaneous connections allowed. + * + * REMEMBER TO RESPECT THE SERVERS: + * Sending too many requests at one time can easily be perceived + * as a DOS attack. Increase this window_size if you are making requests + * to multiple servers or have permission from the receving server admins. + */ + private $window_size = 5; + + /** + * @var float + * + * Timeout is the timeout used for curl_multi_select. + */ + private $timeout = 10; + + /** + * @var string|array + * + * Callback function to be applied to each result. + */ + private $callback; + + /** + * @var array + * + * Set your base options that you want to be used with EVERY request. + */ + protected $options = array( + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 60, + CURLOPT_TIMEOUT => 60 + ); + + /** + * @var array + */ + private $headers = array(); + + /** + * @var Request[] + * + * The request queue + */ + private $requests = array(); + + /** + * @var RequestMap[] + * + * Maps handles to request indexes + */ + private $requestMap = array(); + + /** + * @param $callback + * Callback function to be applied to each result. + * + * Can be specified as 'my_callback_function' + * or array($object, 'my_callback_method'). + * + * Function should take three parameters: $response, $info, $request. + * $response is response body, $info is additional curl info. + * $request is the original request + * + * @return void + */ + function __construct($callback = null) { + $this->callback = $callback; + } + + /** + * @param string $name + * @return mixed + */ + public function __get($name) { + return (isset($this->{$name})) ? $this->{$name} : null; + } + + /** + * @param string $name + * @param mixed $value + * @return bool + */ + public function __set($name, $value) { + // append the base options & headers + if ($name == "options" || $name == "headers") { + $this->{$name} = $value + $this->{$name}; + } else { + $this->{$name} = $value; + } + return true; + } + + /** + * Add a request to the request queue + * + * @param Request $request + * @return bool + */ + public function add($request) { + $this->requests[] = $request; + return true; + } + + /** + * Create new Request and add it to the request queue + * + * @param string $url + * @param string $method + * @param $post_data + * @param $headers + * @param $options + * @return bool + */ + public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) { + $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options); + return true; + } + + /** + * Perform GET request + * + * @param string $url + * @param $headers + * @param $options + * @return bool + */ + public function get($url, $headers = null, $options = null) { + return $this->request($url, "GET", null, $headers, $options); + } + + /** + * Perform POST request + * + * @param string $url + * @param $post_data + * @param $headers + * @param $options + * @return bool + */ + public function post($url, $post_data = null, $headers = null, $options = null) { + return $this->request($url, "POST", $post_data, $headers, $options); + } + + /** + * Execute processing + * + * @param int $window_size Max number of simultaneous connections + * @return string|bool + */ + public function execute($window_size = null) { + // rolling curl window must always be greater than 1 + if (sizeof($this->requests) == 1) { + return $this->single_curl(); + } else { + // start the rolling curl. window_size is the max number of simultaneous connections + return $this->rolling_curl($window_size); + } + } + + /** + * Performs a single curl request + * + * @access private + * @return string + */ + private function single_curl() { + $ch = curl_init(); + $request = array_shift($this->requests); + $options = $this->get_options($request); + curl_setopt_array($ch, $options); + $output = curl_exec($ch); + $info = curl_getinfo($ch); + + // it's not neccesary to set a callback for one-off requests + if ($this->callback) { + $callback = $this->callback; + if (is_callable($this->callback)) { + call_user_func($callback, $output, $info, $request); + } + } + else + return $output; + return true; + } + + /** + * Performs multiple curl requests + * + * @access private + * @throws RollingCurlException + * @param int $window_size Max number of simultaneous connections + * @return bool + */ + private function rolling_curl($window_size = null) { + if ($window_size) + $this->window_size = $window_size; + + // make sure the rolling window isn't greater than the # of urls + if (sizeof($this->requests) < $this->window_size) + $this->window_size = sizeof($this->requests); + + if ($this->window_size < 2) { + throw new RollingCurlException("Window size must be greater than 1"); + } + + $master = curl_multi_init(); + + // start the first batch of requests + for ($i = 0; $i < $this->window_size; $i++) { + $ch = curl_init(); + + $options = $this->get_options($this->requests[$i]); + + curl_setopt_array($ch, $options); + curl_multi_add_handle($master, $ch); + + // Add to our request Maps + $key = (string) $ch; + $this->requestMap[$key] = $i; + } + + do { + while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ; + if ($execrun != CURLM_OK) + break; + // a request was just completed -- find out which one + while ($done = curl_multi_info_read($master)) { + + // get the info and content returned on the request + $info = curl_getinfo($done['handle']); + $output = curl_multi_getcontent($done['handle']); + + // send the return values to the callback function. + $callback = $this->callback; + if (is_callable($callback)) { + $key = (string) $done['handle']; + $request = $this->requests[$this->requestMap[$key]]; + unset($this->requestMap[$key]); + call_user_func($callback, $output, $info, $request); + } + + // start a new request (it's important to do this before removing the old one) + if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) { + $ch = curl_init(); + $options = $this->get_options($this->requests[$i]); + curl_setopt_array($ch, $options); + curl_multi_add_handle($master, $ch); + + // Add to our request Maps + $key = (string) $ch; + $this->requestMap[$key] = $i; + $i++; + } + + // remove the curl handle that just completed + curl_multi_remove_handle($master, $done['handle']); + + } + + // Block for data in / output; error handling is done by curl_multi_exec + if ($running) + curl_multi_select($master, $this->timeout); + + } while ($running); + curl_multi_close($master); + return true; + } + + + /** + * Helper function to set up a new request by setting the appropriate options + * + * @access private + * @param Request $request + * @return array + */ + private function get_options($request) { + // options for this entire curl object + $options = $this->__get('options'); + if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) { + $options[CURLOPT_FOLLOWLOCATION] = 1; + $options[CURLOPT_MAXREDIRS] = 5; + } + $headers = $this->__get('headers'); + + // append custom options for this specific request + if ($request->options) { + $options = $request->options + $options; + } + + // set the request URL + $options[CURLOPT_URL] = $request->url; + + // posting data w/ this request? + if ($request->post_data) { + $options[CURLOPT_POST] = 1; + $options[CURLOPT_POSTFIELDS] = $request->post_data; + } + if ($headers) { + $options[CURLOPT_HEADER] = 0; + $options[CURLOPT_HTTPHEADER] = $headers; + } + + return $options; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests); + } +} + --- /dev/null +++ b/lib/rolling-curl/RollingCurlGroup.php @@ -1,1 +1,218 @@ - +group = $group; + } + + /** + * Process the request + * + * + */ + function process($output, $info) { + if ($this->group) + $this->group->process($output, $info, $this); + } + + /** + * @return void + */ + public function __destruct() { + unset($this->group); + parent::__destruct(); + } + +} + +/** + * A group of curl requests. + * + * @throws RollingCurlGroupException * + */ +class RollingCurlGroup { + /** + * @var string group name + */ + protected $name; + + /** + * @var int total number of requests in a group + */ + protected $num_requests = 0; + + /** + * @var int total number of finished requests in a group + */ + protected $finished_requests = 0; + + /** + * @var array requests array + */ + private $requests = array(); + + /** + * @param string $name group name + * @return void + */ + function __construct($name) { + $this->name = $name; + } + + /** + * @return void + */ + public function __destruct() { + unset($this->name, $this->num_requests, $this->finished_requests, $this->requests); + } + + /** + * Adds request to a group + * + * @throws RollingCurlGroupException + * @param RollingCurlGroupRequest|array $request + * @return bool + */ + function add($request) { + if ($request instanceof RollingCurlGroupRequest) { + $request->setGroup($this); + $this->num_requests++; + $this->requests[] = $request; + } + else if (is_array($request)) { + foreach ($request as $req) + $this->add($req); + } + else + throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest"); + + return true; + } + + /** + * @throws RollingCurlGroupException + * @param RollingCurl $rc + * @return bool + */ + function addToRC(RollingCurl $rc){ + $ret = true; + + while (count($this->requests) > 0){ + $ret1 = $rc->add(array_shift($this->requests)); + if (!$ret1) + $ret = false; + } + + return $ret; + } + + /** + * Override to implement custom response processing. + * + * Don't forget to call parent::process(). + * + * @param string $output received page body + * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc. + * @param RollingCurlRequest $request request used + * @return void + */ + function process($output, $info, $request) { + $this->finished_requests++; + + if ($this->finished_requests >= $this->num_requests) + $this->finished(); + } + + /** + * Override to execute code after all requests in a group are processed. + * + * @return void + */ + function finished() { + } + +} + +/** + * Group version of rolling curl + */ +class GroupRollingCurl extends RollingCurl { + + /** + * @var mixed common callback for all groups + */ + private $group_callback = null; + + /** + * @param string $output received page body + * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc. + * @param RollingCurlRequest $request request used + * @return void + */ + protected function process($output, $info, $request) { + if ($request instanceof RollingCurlGroupRequest) + $request->process($output, $info); + + if (is_callable($this->group_callback)) + call_user_func($this->group_callback, $output, $info, $request); + } + + /** + * @param mixed $callback common callback for all groups + * @return void + */ + function __construct($callback = null) { + $this->group_callback = $callback; + + parent::__construct(array(&$this, "process")); + } + + /** + * Adds a group to processing queue + * + * @param RollingCurlGroup|Request $request + * @return bool + */ + public function add($request) { + if ($request instanceof RollingCurlGroup) + return $request->addToRC($this); + else + return parent::add($request); + } + + /** + * Execute processing + * + * @param int $window_size Max number of simultaneous connections + * @return bool|string + */ + public function execute($window_size = null) { + if (count($this->requests) == 0) + return false; + + return parent::execute($window_size); + } +} + --- /dev/null +++ b/lib/rolling-curl/example.php @@ -1,1 +1,66 @@ +(.*?)~i", $response, $out)) { + $title = $out[1]; + } + echo "$title
"; + print_r($info); + print_r($request); + echo "
"; +} + +// single curl request +$rc = new RollingCurl("request_callback"); +$rc->request("http://www.msn.com"); +$rc->execute(); + +// another single curl request +$rc = new RollingCurl("request_callback"); +$rc->request("http://www.google.com"); +$rc->execute(); + +echo "
"; + +// top 20 sites according to alexa (11/5/09) +$urls = array("http://www.google.com", + "http://www.facebook.com", + "http://www.yahoo.com", + "http://www.youtube.com", + "http://www.live.com", + "http://www.wikipedia.com", + "http://www.blogger.com", + "http://www.msn.com", + "http://www.baidu.com", + "http://www.yahoo.co.jp", + "http://www.myspace.com", + "http://www.qq.com", + "http://www.google.co.in", + "http://www.twitter.com", + "http://www.google.de", + "http://www.microsoft.com", + "http://www.google.cn", + "http://www.sina.com.cn", + "http://www.wordpress.com", + "http://www.google.co.uk"); + +$rc = new RollingCurl("request_callback"); +$rc->window_size = 20; +foreach ($urls as $url) { + $request = new RollingCurlRequest($url); + $rc->add($request); +} +$rc->execute(); + --- /dev/null +++ b/lib/rolling-curl/example_groups.php @@ -1,1 +1,49 @@ +url . "\n"; + if ($this->test_verbose) + print_r($info); + + parent::process($output, $info); + } +} + +class TestCurlGroup extends RollingCurlGroup { + function process($output, $info, $request) { + echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n"; + parent::process($output, $info, $request); + } + + function finished() { + echo "Group CB: Finished" . $this->name . "\n"; + parent::finished(); + } +} + +$group = new TestCurlGroup("High"); +$group->add(new TestCurlRequest("www.google.de")); +$group->add(new TestCurlRequest("www.yahoo.de")); +$group->add(new TestCurlRequest("www.newyorktimes.com")); +$reqs[] = $group; + +$group = new TestCurlGroup("Normal"); +$group->add(new TestCurlRequest("twitter.com")); +$group->add(new TestCurlRequest("www.bing.com")); +$group->add(new TestCurlRequest("m.facebook.com")); +$reqs[] = $group; + +$reqs[] = new TestCurlRequest("www.kernel.org"); + +// No callback here, as its done in Request class +$rc = new GroupRollingCurl(); + +foreach ($reqs as $req) + $rc->add($req); + +$rc->execute(); --- a/tripPlanner.php +++ b/tripPlanner.php @@ -131,7 +131,7 @@ curl_setopt($ch, CURLOPT_HTTPHEADER, array( "Accept: application/json" )); - curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); $page = curl_exec($ch); if (curl_errno($ch)) { tripPlanForm("Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch) .(isDebug() ? $url : ""));