request($method, $url, $settings); } catch (\Throwable $e) { $response = new Helpers\HTTPHelper($e); } // Measure elapsed time and add a debug entry. $status_code = $response->getStatusCode() ?: 0; $elapsed_time = microtime(true) - $start_time; self::_debug($url, $status_code, $elapsed_time); return $response; } /** * Make any type of request asynchronously. * * @param string $url * @param string $method * @param string|array $data * @param array $headers * @param array $cookies * @param array $settings * @return \GuzzleHttp\Promise\PromiseInterface */ public static function async(string $url, string $method = 'GET', $data = null, array $headers = [], array $cookies = [], array $settings = []): \GuzzleHttp\Promise\PromiseInterface { // Create a new Guzzle client, or reuse a cached one if available. $guzzle = self::_createClient(); // Populate settings. $settings = self::_populateSettings($url, $method, $data, $headers, $cookies, $settings); // Send the request. $promise = $guzzle->requestAsync($method, $url, $settings); self::_debug($url, 0, 0, true); return $promise; } /** * Make multiple concurrent requests. * * @param array $requests[url, method, data, headers, cookies, settings] * @return array */ public static function multiple(array $requests): array { // Create a new Guzzle client, or reuse a cached one if available. $guzzle = self::_createClient(); // Add settings for each request. $promises = []; foreach ($requests as $key => $val) { $settings = self::_populateSettings($val['url'], $val['method'] ?? 'GET', $val['data'] ?? null, $val['headers'] ?? [], $val['cookies'] ?? [], $val['settings'] ?? []); $promise = $guzzle->requestAsync($val['method'] ?? 'GET', $val['url'], $settings); $promises[$key] = $promise; self::_debug($val['url'], 0, 0, true); } // Wait for each request. $responses = \GuzzleHttp\Promise\Utils::settle($promises)->wait(); $result = []; foreach ($responses as $key => $val) { if ($val['state'] === 'fulfilled') { $result[$key] = $val['value']; } else { $result[$key] = new Helpers\HTTPHelper($val['reason']); } } return $result; } /** * Create a Guzzle client with default options. * * @return \GuzzleHttp\Client */ protected static function _createClient(): \GuzzleHttp\Client { if (!self::$_client) { self::$_client = new \GuzzleHttp\Client([ 'http_errors' => false, 'timeout' => self::DEFAULT_TIMEOUT, 'verify' => \RX_BASEDIR . 'common/vendor/composer/ca-bundle/res/cacert.pem', ]); } return self::$_client; } /** * Populate a settings array with various options. * * @param string $url * @param string $method * @param string|array $data * @param array $headers * @param array $cookies * @param array $settings * @return array */ protected static function _populateSettings(string $url, string $method = 'GET', $data = null, array $headers = [], array $cookies = [], array $settings = []): array { // Set headers. if ($headers) { $settings['headers'] = $headers; } // Set cookies. if ($cookies && !isset($settings['cookies'])) { $jar = \GuzzleHttp\Cookie\CookieJar::fromArray($cookies, parse_url($url, \PHP_URL_HOST)); $settings['cookies'] = $jar; } // Set the body or POST data. if (is_string($data) && strlen($data) > 0) { $settings['body'] = $data; } elseif (is_array($data) && count($data) > 0) { if (isset($headers['Content-Type']) && preg_match('!^multipart/form-data\b!i', $headers['Content-Type'])) { $settings['multipart'] = []; foreach ($data as $key => $val) { $settings['multipart'][] = ['name' => $key, 'contents' => $val]; } } elseif (isset($headers['Content-Type']) && preg_match('!^application/json\b!i', $headers['Content-Type'])) { $settings['json'] = $data; } elseif ($method !== 'GET') { $settings['form_params'] = $data; } else { $settings['query'] = $data; } } // Set the proxy. if (!isset($settings['proxy']) && defined('__PROXY_SERVER__')) { $proxy = parse_url(constant('__PROXY_SERVER__')); $proxy_scheme = preg_match('/^socks/', $proxy['scheme'] ?? '') ? ($proxy['scheme'] . '://') : 'http://'; $proxy_auth = (!empty($proxy['user']) && !empty($proxy['pass'])) ? ($proxy['user'] . ':' . $proxy['pass'] . '@') : ''; $settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']); } return $settings; } /** * Record a request with the Debug class. * * @param string $url * @param int $status_code * @param float $elapsed_time * @return void */ protected static function _debug(string $url, int $status_code = 0, float $elapsed_time = 0, bool $async = true): void { if (!isset($GLOBALS['__remote_request_elapsed__'])) { $GLOBALS['__remote_request_elapsed__'] = 0; } $GLOBALS['__remote_request_elapsed__'] += $elapsed_time; if (Debug::isEnabledForCurrentUser()) { $log = array(); $log['url'] = $url; $log['status'] = $async ? 'ASYNC' : $status_code; $log['elapsed_time'] = $async ? 'ASYNC' : $elapsed_time; $log['called_file'] = $log['called_line'] = $log['called_method'] = null; $log['backtrace'] = []; if (in_array('slow_remote_requests', config('debug.display_content'))) { $bt = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); foreach ($bt as $no => $call) { if($call['file'] !== __FILE__ && $call['file'] !== \RX_BASEDIR . 'classes/file/FileHandler.class.php') { $log['called_file'] = $bt[$no]['file']; $log['called_line'] = $bt[$no]['line']; $next = $no + 1; $log['called_method'] = $bt[$next]['class'].$bt[$next]['type'].$bt[$next]['function']; $log['backtrace'] = array_slice($bt, $next, 1); break; } } } Debug::addRemoteRequest($log); } } }