Huymada

RollingCurlX

Aug 22nd, 2019
388
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 12.84 KB | None | 0 0
  1. <?php
  2. /*
  3.         ---------- RollingCurlX 3.0.0 -----------
  4.         an easy to use curl_multi wrapper for php
  5.  
  6.             Copyright (c) 2015-2017 Marcus Leath
  7.                     License: MIT
  8.         https://github.com/marcushat/RollingCurlX
  9. */
  10. Class RollingCurlX {
  11.     private $_curl_version;
  12.  
  13.     private $_maxConcurrent = 0; //max. number of simultaneous connections allowed
  14.     private $_options = []; //shared cURL options
  15.     private $_headers = []; //shared cURL request headers
  16.     private $_callback = NULL; //default callback
  17.     private $_timeout = 5000; //all requests must be completed by this time
  18.     public $requests = []; //request_queue
  19.  
  20.  
  21.     function __construct($max_concurrent = 10) {
  22.         $this->setMaxConcurrent($max_concurrent);
  23.  
  24.         $this->_curl_version = curl_version()['version'];
  25.     }
  26.  
  27.     public function setMaxConcurrent($max_requests) {
  28.         if($max_requests > 0) {
  29.             $this->_maxConcurrent = $max_requests;
  30.         }
  31.     }
  32.  
  33.     public function setOptions(array $options) {
  34.         $this->_options = $options;
  35.     }
  36.  
  37.     public function setHeaders(array $headers) {
  38.         if(is_array($headers) && count($headers)) {
  39.             $this->_headers = $headers;
  40.         }
  41.     }
  42.  
  43.     public function setCallback(callable $callback) {
  44.         $this->_callback = $callback;
  45.     }
  46.  
  47.     public function setTimeout($timeout) { //in milliseconds
  48.         if($timeout > 0) {
  49.             $this->_timeout = $timeout;
  50.         }
  51.     }
  52.  
  53.     //Add a request to the request queue
  54.     public function addRequest(
  55.                         $url,
  56.                         $post_data = NULL,
  57.                         callable $callback = NULL, //individual callback
  58.                         $user_data = NULL,
  59.                         array $options = NULL, //individual cURL options
  60.                         array $headers = NULL //individual cURL request headers
  61.     ) { //Add to request queue
  62.         $this->requests[] = [
  63.             'url' => $url,
  64.             'post_data' => ($post_data) ? $post_data : NULL,
  65.             'callback' => ($callback) ? $callback : $this->_callback,
  66.             'user_data' => ($user_data) ? $user_data : NULL,
  67.             'options' => ($options) ? $options : NULL,
  68.             'headers' => ($headers) ? $headers : NULL
  69.         ];
  70.         return count($this->requests) - 1; //return request number/index
  71.     }
  72.  
  73.     //Reset request queue
  74.     public function reset() {
  75.         $this->requests = [];
  76.     }
  77.  
  78.     private function normalize_headers(array $headers) {
  79.         $normalized = [];
  80.         foreach($headers as $key => $header) {
  81.             if(is_string($key)) {
  82.                 $normal = "$key: $header";
  83.             } else {
  84.                 $header;
  85.             }
  86.             $normalized = [];
  87.         }
  88.     }
  89.  
  90.     //Execute the request queue
  91.     public function execute() {
  92.         //the request map that maps the request queue to request curl handles
  93.         $requests_map = [];
  94.         $multi_handle = curl_multi_init();
  95.         $num_outstanding = 0;
  96.  
  97.         //start processing the initial request queue
  98.         $num_initial_requests = min($this->_maxConcurrent, count($this->requests));
  99.         for($i = 0; $i < $num_initial_requests; $i++) {
  100.             $this->init_request($i, $multi_handle, $requests_map);
  101.             $num_outstanding++;
  102.         }
  103.  
  104.         do{
  105.             do{
  106.                 $mh_status = curl_multi_exec($multi_handle, $active);
  107.             } while($mh_status == CURLM_CALL_MULTI_PERFORM);
  108.             if($mh_status != CURLM_OK) {
  109.                 break;
  110.             }
  111.  
  112.             //a request is just completed, find out which one
  113.             while($completed = curl_multi_info_read($multi_handle)) {
  114.                 $this->process_request($completed, $multi_handle, $requests_map);
  115.                 $num_outstanding--;
  116.  
  117.                 //try to add/start a new requests to the request queue
  118.                 while(
  119.                     $num_outstanding < $this->_maxConcurrent && //under the limit
  120.                     $i < count($this->requests) && isset($this->requests[$i]) // requests left
  121.                 ) {
  122.                     $this->init_request($i, $multi_handle, $requests_map);
  123.                     $num_outstanding++;
  124.                     $i++;
  125.                 }
  126.             }
  127.  
  128.             usleep(15); //save CPU cycles, prevent continuous checking
  129.         } while ($active || count($requests_map)); //End do-while
  130.  
  131.         $this->reset();
  132.         curl_multi_close($multi_handle);
  133.     }
  134.  
  135.  
  136.  
  137.     //Build individual cURL options for a request
  138.     private function buildOptions(array $request) {
  139.         $url = $request['url'];
  140.         $post_data = $request['post_data'];
  141.         $individual_opts = $request['options'];
  142.         $individual_headers = $request['headers'];
  143.  
  144.         $options = ($individual_opts) ? $individual_opts + $this->_options : $this->_options; //merge shared and individual request options
  145.         $headers = ($individual_headers) ? $individual_headers + $this->_headers : $this->_headers; //merge shared and individual request headers
  146.  
  147.         //the below will overide the corresponding default or individual options
  148.         $options[CURLOPT_RETURNTRANSFER] = true;
  149.  
  150.         $options[CURLOPT_NOSIGNAL] = 1;
  151.  
  152.         if(version_compare($this->_curl_version, '7.16.2') >= 0) {
  153.             $options[CURLOPT_CONNECTTIMEOUT_MS] = $this->_timeout;
  154.             $options[CURLOPT_TIMEOUT_MS] = $this->_timeout;
  155.             unset($options[CURLOPT_CONNECTTIMEOUT]);
  156.             unset($options[CURLOPT_TIMEOUT]);
  157.         } else {
  158.             $options[CURLOPT_CONNECTTIMEOUT] = round($this->_timeout / 1000);
  159.             $options[CURLOPT_TIMEOUT] = round($this->_timeout / 1000);
  160.             unset($options[CURLOPT_CONNECTTIMEOUT_MS]);
  161.             unset($options[CURLOPT_TIMEOUT_MS]);
  162.         }
  163.  
  164.         if($url) {
  165.             $options[CURLOPT_URL] = $url;
  166.         }
  167.  
  168.         if($headers) {
  169.             $options[CURLOPT_HTTPHEADER] = $headers;
  170.         }
  171.  
  172.         // enable POST method and set POST parameters
  173.         if($post_data !== null) {
  174.             $options[CURLOPT_POST] = 1;
  175.             $options[CURLOPT_POSTFIELDS] = is_array($post_data)? http_build_query($post_data) : $post_data;
  176.         }
  177.         return $options;
  178.     }
  179.  
  180.  
  181.     private function init_request($request_num, $multi_handle, &$requests_map) {
  182.         $request =& $this->requests[$request_num];
  183.         $this->addTimer($request);
  184.  
  185.         $ch = curl_init();
  186.         $options = $this->buildOptions($request);
  187.         $request['options_set'] = $options; //merged options
  188.         $opts_set = curl_setopt_array($ch, $options);
  189.         if(!$opts_set) {
  190.             echo 'options not set';
  191.             exit;
  192.         }
  193.         curl_multi_add_handle($multi_handle, $ch);
  194.  
  195.         //add curl handle of a new request to the request map
  196.         $ch_hash = (string) $ch;
  197.         $requests_map[$ch_hash] = $request_num;
  198.     }
  199.  
  200.  
  201.     private function process_request($completed, $multi_handle, array &$requests_map) {
  202.         $ch = $completed['handle'];
  203.         $ch_hash = (string) $ch;
  204.         $request =& $this->requests[$requests_map[$ch_hash]]; //map handler to request index to get request info
  205.  
  206.         $request_info = curl_getinfo($ch);
  207.         $request_info['curle'] = $completed['result'];
  208.         $request_info['curle_msg'] = isset($this->curle_msgs[$completed['result']]) ? $this->curle_msgs[$completed['result']] : curl_strerror($completed['result']);
  209.         $request_info['handle'] = $ch;
  210.         $request_info['time'] = $time = $this->stopTimer($request); //record request time
  211.         $request_info['url_raw'] = $url = $request['url'];
  212.         $request_info['user_data'] = $user_data = $request['user_data'];
  213.  
  214.         if(curl_errno($ch) !== 0 || intval($request_info['http_code']) !== 200) { //if server responded with http error
  215.             $response = false;
  216.         } else { //sucessful response
  217.             $response = curl_multi_getcontent($ch);
  218.         }
  219.  
  220.         //get request info
  221.         $callback = $request['callback'];
  222.         $options = $request['options_set'];
  223.  
  224.         if($response && !empty($options[CURLOPT_HEADER])) {
  225.             $k = intval($request_info['header_size']);
  226.             $request_info['response_header'] = substr($response, 0, $k);
  227.             $response = substr($response, $k);
  228.         }
  229.  
  230.         //remove completed request and its curl handle
  231.         unset($requests_map[$ch_hash]);
  232.         curl_multi_remove_handle($multi_handle, $ch);
  233.  
  234.         //call the callback function and pass request info and user data to it
  235.         if($callback) {
  236.             call_user_func($callback, $response, $url, $request_info, $user_data, $time);
  237.         }
  238.         $request = NULL; //free up memory now just incase response was large
  239.     }
  240.  
  241.  
  242.     private function check_for_timeouts($mh) {
  243.         $now = microtime(true);
  244.         $request_map = $this->_request_map;
  245.         $requests = $this->_request_map;
  246.         foreach($request_maps as $ch_hash => $request_num) {
  247.             $request = $requests[$request_num];
  248.             $timeout = $request->timeout;
  249.             $start_time = $request->start_time;
  250.             $ch = $request->handle;
  251.             if($now >=  $start_time + $timeout) {
  252.                 curl_multi_remove_handle($mh, $ch);
  253.             }
  254.         }
  255.     }
  256.  
  257.  
  258.     private function addTimer(array &$request) { //adds timer object to request
  259.         $request['timer'] = microtime(true); //start time
  260.         $request['time'] = false; //default if not overridden by time later
  261.     }
  262.  
  263.     private function stopTimer(array &$request) {
  264.         $elapsed = $request['timer'] - microtime(true);
  265.         $request['time'] = $elapsed;
  266.         unset($request['timer']);
  267.         return $elapsed;
  268.     }
  269.  
  270.     private $curle_msgs = [CURLE_OK => 'OK', CURLE_UNSUPPORTED_PROTOCOL => 'UNSUPPORTED_PROTOCOL', CURLE_FAILED_INIT => 'FAILED_INIT', CURLE_URL_MALFORMAT => 'URL_MALFORMAT', CURLE_URL_MALFORMAT_USER => 'URL_MALFORMAT_USER', CURLE_COULDNT_RESOLVE_PROXY => 'COULDNT_RESOLVE_PROXY', CURLE_COULDNT_RESOLVE_HOST => 'COULDNT_RESOLVE_HOST', CURLE_COULDNT_CONNECT => 'COULDNT_CONNECT', CURLE_FTP_WEIRD_SERVER_REPLY => 'FTP_WEIRD_SERVER_REPLY', CURLE_FTP_ACCESS_DENIED => 'FTP_ACCESS_DENIED', CURLE_FTP_USER_PASSWORD_INCORRECT => 'FTP_USER_PASSWORD_INCORRECT', CURLE_FTP_WEIRD_PASS_REPLY => 'FTP_WEIRD_PASS_REPLY', CURLE_FTP_WEIRD_USER_REPLY => 'FTP_WEIRD_USER_REPLY', CURLE_FTP_WEIRD_PASV_REPLY => 'FTP_WEIRD_PASV_REPLY', CURLE_FTP_WEIRD_227_FORMAT => 'FTP_WEIRD_227_FORMAT', CURLE_FTP_CANT_GET_HOST => 'FTP_CANT_GET_HOST', CURLE_FTP_CANT_RECONNECT => 'FTP_CANT_RECONNECT', CURLE_FTP_COULDNT_SET_BINARY => 'FTP_COULDNT_SET_BINARY', CURLE_PARTIAL_FILE => 'PARTIAL_FILE', CURLE_FTP_COULDNT_RETR_FILE => 'FTP_COULDNT_RETR_FILE', CURLE_FTP_WRITE_ERROR => 'FTP_WRITE_ERROR', CURLE_FTP_QUOTE_ERROR => 'FTP_QUOTE_ERROR', CURLE_HTTP_NOT_FOUND => 'HTTP_NOT_FOUND', CURLE_WRITE_ERROR => 'WRITE_ERROR', CURLE_MALFORMAT_USER => 'MALFORMAT_USER', CURLE_FTP_COULDNT_STOR_FILE => 'FTP_COULDNT_STOR_FILE', CURLE_READ_ERROR => 'READ_ERROR', CURLE_OUT_OF_MEMORY => 'OUT_OF_MEMORY', CURLE_OPERATION_TIMEOUTED => 'OPERATION_TIMEOUTED', CURLE_FTP_COULDNT_SET_ASCII => 'FTP_COULDNT_SET_ASCII', CURLE_FTP_PORT_FAILED => 'FTP_PORT_FAILED', CURLE_FTP_COULDNT_USE_REST => 'FTP_COULDNT_USE_REST', CURLE_FTP_COULDNT_GET_SIZE => 'FTP_COULDNT_GET_SIZE', CURLE_HTTP_RANGE_ERROR => 'HTTP_RANGE_ERROR', CURLE_HTTP_POST_ERROR => 'HTTP_POST_ERROR', CURLE_SSL_CONNECT_ERROR => 'SSL_CONNECT_ERROR', CURLE_FTP_BAD_DOWNLOAD_RESUME => 'FTP_BAD_DOWNLOAD_RESUME', CURLE_FILE_COULDNT_READ_FILE => 'FILE_COULDNT_READ_FILE', CURLE_LDAP_CANNOT_BIND => 'LDAP_CANNOT_BIND', CURLE_LDAP_SEARCH_FAILED => 'LDAP_SEARCH_FAILED', CURLE_LIBRARY_NOT_FOUND => 'LIBRARY_NOT_FOUND', CURLE_FUNCTION_NOT_FOUND => 'FUNCTION_NOT_FOUND', CURLE_ABORTED_BY_CALLBACK => 'ABORTED_BY_CALLBACK', CURLE_BAD_FUNCTION_ARGUMENT => 'BAD_FUNCTION_ARGUMENT', CURLE_BAD_CALLING_ORDER => 'BAD_CALLING_ORDER', CURLE_HTTP_PORT_FAILED => 'HTTP_PORT_FAILED', CURLE_BAD_PASSWORD_ENTERED => 'BAD_PASSWORD_ENTERED', CURLE_TOO_MANY_REDIRECTS => 'TOO_MANY_REDIRECTS', CURLE_UNKNOWN_TELNET_OPTION => 'UNKNOWN_TELNET_OPTION', CURLE_TELNET_OPTION_SYNTAX => 'TELNET_OPTION_SYNTAX', CURLE_OBSOLETE => 'OBSOLETE', CURLE_SSL_PEER_CERTIFICATE => 'SSL_PEER_CERTIFICATE', CURLE_GOT_NOTHING => 'GOT_NOTHING', CURLE_SSL_ENGINE_NOTFOUND => 'SSL_ENGINE_NOTFOUND', CURLE_SSL_ENGINE_SETFAILED => 'SSL_ENGINE_SETFAILED', CURLE_SEND_ERROR => 'SEND_ERROR', CURLE_RECV_ERROR => 'RECV_ERROR', CURLE_SHARE_IN_USE => 'SHARE_IN_USE', CURLE_SSL_CERTPROBLEM => 'SSL_CERTPROBLEM', CURLE_SSL_CIPHER => 'SSL_CIPHER', CURLE_SSL_CACERT => 'SSL_CACERT', CURLE_BAD_CONTENT_ENCODING => 'BAD_CONTENT_ENCODING', CURLE_LDAP_INVALID_URL => 'LDAP_INVALID_URL', CURLE_FILESIZE_EXCEEDED => 'FILESIZE_EXCEEDED', CURLE_FTP_SSL_FAILED => 'FTP_SSL_FAILED', CURLE_SSH => 'SSH'
  271.     ];
  272. }
  273.  
  274. $rcx = new RollingCurlX(20);
  275.  
  276. ?>
Advertisement
Add Comment
Please, Sign In to add comment