annotate vendor/guzzlehttp/guzzle/src/Client.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 5fb285c0d0e3
children
rev   line source
Chris@0 1 <?php
Chris@0 2 namespace GuzzleHttp;
Chris@0 3
Chris@0 4 use GuzzleHttp\Cookie\CookieJar;
Chris@0 5 use GuzzleHttp\Promise;
Chris@0 6 use GuzzleHttp\Psr7;
Chris@0 7 use Psr\Http\Message\UriInterface;
Chris@0 8 use Psr\Http\Message\RequestInterface;
Chris@0 9 use Psr\Http\Message\ResponseInterface;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * @method ResponseInterface get(string|UriInterface $uri, array $options = [])
Chris@0 13 * @method ResponseInterface head(string|UriInterface $uri, array $options = [])
Chris@0 14 * @method ResponseInterface put(string|UriInterface $uri, array $options = [])
Chris@0 15 * @method ResponseInterface post(string|UriInterface $uri, array $options = [])
Chris@0 16 * @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
Chris@0 17 * @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
Chris@0 18 * @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
Chris@0 19 * @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
Chris@0 20 * @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
Chris@0 21 * @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
Chris@0 22 * @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
Chris@0 23 * @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
Chris@0 24 */
Chris@0 25 class Client implements ClientInterface
Chris@0 26 {
Chris@0 27 /** @var array Default request options */
Chris@0 28 private $config;
Chris@0 29
Chris@0 30 /**
Chris@0 31 * Clients accept an array of constructor parameters.
Chris@0 32 *
Chris@0 33 * Here's an example of creating a client using a base_uri and an array of
Chris@0 34 * default request options to apply to each request:
Chris@0 35 *
Chris@0 36 * $client = new Client([
Chris@0 37 * 'base_uri' => 'http://www.foo.com/1.0/',
Chris@0 38 * 'timeout' => 0,
Chris@0 39 * 'allow_redirects' => false,
Chris@0 40 * 'proxy' => '192.168.16.1:10'
Chris@0 41 * ]);
Chris@0 42 *
Chris@0 43 * Client configuration settings include the following options:
Chris@0 44 *
Chris@0 45 * - handler: (callable) Function that transfers HTTP requests over the
Chris@0 46 * wire. The function is called with a Psr7\Http\Message\RequestInterface
Chris@0 47 * and array of transfer options, and must return a
Chris@0 48 * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
Chris@0 49 * Psr7\Http\Message\ResponseInterface on success. "handler" is a
Chris@0 50 * constructor only option that cannot be overridden in per/request
Chris@0 51 * options. If no handler is provided, a default handler will be created
Chris@0 52 * that enables all of the request options below by attaching all of the
Chris@0 53 * default middleware to the handler.
Chris@0 54 * - base_uri: (string|UriInterface) Base URI of the client that is merged
Chris@0 55 * into relative URIs. Can be a string or instance of UriInterface.
Chris@0 56 * - **: any request option
Chris@0 57 *
Chris@0 58 * @param array $config Client configuration settings.
Chris@0 59 *
Chris@0 60 * @see \GuzzleHttp\RequestOptions for a list of available request options.
Chris@0 61 */
Chris@0 62 public function __construct(array $config = [])
Chris@0 63 {
Chris@0 64 if (!isset($config['handler'])) {
Chris@0 65 $config['handler'] = HandlerStack::create();
Chris@0 66 } elseif (!is_callable($config['handler'])) {
Chris@0 67 throw new \InvalidArgumentException('handler must be a callable');
Chris@0 68 }
Chris@0 69
Chris@0 70 // Convert the base_uri to a UriInterface
Chris@0 71 if (isset($config['base_uri'])) {
Chris@0 72 $config['base_uri'] = Psr7\uri_for($config['base_uri']);
Chris@0 73 }
Chris@0 74
Chris@0 75 $this->configureDefaults($config);
Chris@0 76 }
Chris@0 77
Chris@0 78 public function __call($method, $args)
Chris@0 79 {
Chris@0 80 if (count($args) < 1) {
Chris@0 81 throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
Chris@0 82 }
Chris@0 83
Chris@0 84 $uri = $args[0];
Chris@0 85 $opts = isset($args[1]) ? $args[1] : [];
Chris@0 86
Chris@0 87 return substr($method, -5) === 'Async'
Chris@0 88 ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
Chris@0 89 : $this->request($method, $uri, $opts);
Chris@0 90 }
Chris@0 91
Chris@0 92 public function sendAsync(RequestInterface $request, array $options = [])
Chris@0 93 {
Chris@0 94 // Merge the base URI into the request URI if needed.
Chris@0 95 $options = $this->prepareDefaults($options);
Chris@0 96
Chris@0 97 return $this->transfer(
Chris@0 98 $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
Chris@0 99 $options
Chris@0 100 );
Chris@0 101 }
Chris@0 102
Chris@0 103 public function send(RequestInterface $request, array $options = [])
Chris@0 104 {
Chris@0 105 $options[RequestOptions::SYNCHRONOUS] = true;
Chris@0 106 return $this->sendAsync($request, $options)->wait();
Chris@0 107 }
Chris@0 108
Chris@0 109 public function requestAsync($method, $uri = '', array $options = [])
Chris@0 110 {
Chris@0 111 $options = $this->prepareDefaults($options);
Chris@0 112 // Remove request modifying parameter because it can be done up-front.
Chris@0 113 $headers = isset($options['headers']) ? $options['headers'] : [];
Chris@0 114 $body = isset($options['body']) ? $options['body'] : null;
Chris@0 115 $version = isset($options['version']) ? $options['version'] : '1.1';
Chris@0 116 // Merge the URI into the base URI.
Chris@0 117 $uri = $this->buildUri($uri, $options);
Chris@0 118 if (is_array($body)) {
Chris@0 119 $this->invalidBody();
Chris@0 120 }
Chris@0 121 $request = new Psr7\Request($method, $uri, $headers, $body, $version);
Chris@0 122 // Remove the option so that they are not doubly-applied.
Chris@0 123 unset($options['headers'], $options['body'], $options['version']);
Chris@0 124
Chris@0 125 return $this->transfer($request, $options);
Chris@0 126 }
Chris@0 127
Chris@0 128 public function request($method, $uri = '', array $options = [])
Chris@0 129 {
Chris@0 130 $options[RequestOptions::SYNCHRONOUS] = true;
Chris@0 131 return $this->requestAsync($method, $uri, $options)->wait();
Chris@0 132 }
Chris@0 133
Chris@0 134 public function getConfig($option = null)
Chris@0 135 {
Chris@0 136 return $option === null
Chris@0 137 ? $this->config
Chris@0 138 : (isset($this->config[$option]) ? $this->config[$option] : null);
Chris@0 139 }
Chris@0 140
Chris@0 141 private function buildUri($uri, array $config)
Chris@0 142 {
Chris@0 143 // for BC we accept null which would otherwise fail in uri_for
Chris@0 144 $uri = Psr7\uri_for($uri === null ? '' : $uri);
Chris@0 145
Chris@0 146 if (isset($config['base_uri'])) {
Chris@0 147 $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
Chris@0 148 }
Chris@0 149
Chris@0 150 return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
Chris@0 151 }
Chris@0 152
Chris@0 153 /**
Chris@0 154 * Configures the default options for a client.
Chris@0 155 *
Chris@0 156 * @param array $config
Chris@0 157 */
Chris@0 158 private function configureDefaults(array $config)
Chris@0 159 {
Chris@0 160 $defaults = [
Chris@0 161 'allow_redirects' => RedirectMiddleware::$defaultSettings,
Chris@0 162 'http_errors' => true,
Chris@0 163 'decode_content' => true,
Chris@0 164 'verify' => true,
Chris@0 165 'cookies' => false
Chris@0 166 ];
Chris@0 167
Chris@0 168 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
Chris@0 169
Chris@0 170 // We can only trust the HTTP_PROXY environment variable in a CLI
Chris@0 171 // process due to the fact that PHP has no reliable mechanism to
Chris@0 172 // get environment variables that start with "HTTP_".
Chris@0 173 if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
Chris@0 174 $defaults['proxy']['http'] = getenv('HTTP_PROXY');
Chris@0 175 }
Chris@0 176
Chris@0 177 if ($proxy = getenv('HTTPS_PROXY')) {
Chris@0 178 $defaults['proxy']['https'] = $proxy;
Chris@0 179 }
Chris@0 180
Chris@0 181 if ($noProxy = getenv('NO_PROXY')) {
Chris@0 182 $cleanedNoProxy = str_replace(' ', '', $noProxy);
Chris@0 183 $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
Chris@0 184 }
Chris@0 185
Chris@0 186 $this->config = $config + $defaults;
Chris@0 187
Chris@0 188 if (!empty($config['cookies']) && $config['cookies'] === true) {
Chris@0 189 $this->config['cookies'] = new CookieJar();
Chris@0 190 }
Chris@0 191
Chris@0 192 // Add the default user-agent header.
Chris@0 193 if (!isset($this->config['headers'])) {
Chris@0 194 $this->config['headers'] = ['User-Agent' => default_user_agent()];
Chris@0 195 } else {
Chris@0 196 // Add the User-Agent header if one was not already set.
Chris@0 197 foreach (array_keys($this->config['headers']) as $name) {
Chris@0 198 if (strtolower($name) === 'user-agent') {
Chris@0 199 return;
Chris@0 200 }
Chris@0 201 }
Chris@0 202 $this->config['headers']['User-Agent'] = default_user_agent();
Chris@0 203 }
Chris@0 204 }
Chris@0 205
Chris@0 206 /**
Chris@0 207 * Merges default options into the array.
Chris@0 208 *
Chris@0 209 * @param array $options Options to modify by reference
Chris@0 210 *
Chris@0 211 * @return array
Chris@0 212 */
Chris@0 213 private function prepareDefaults($options)
Chris@0 214 {
Chris@0 215 $defaults = $this->config;
Chris@0 216
Chris@0 217 if (!empty($defaults['headers'])) {
Chris@0 218 // Default headers are only added if they are not present.
Chris@0 219 $defaults['_conditional'] = $defaults['headers'];
Chris@0 220 unset($defaults['headers']);
Chris@0 221 }
Chris@0 222
Chris@0 223 // Special handling for headers is required as they are added as
Chris@0 224 // conditional headers and as headers passed to a request ctor.
Chris@0 225 if (array_key_exists('headers', $options)) {
Chris@0 226 // Allows default headers to be unset.
Chris@0 227 if ($options['headers'] === null) {
Chris@0 228 $defaults['_conditional'] = null;
Chris@0 229 unset($options['headers']);
Chris@0 230 } elseif (!is_array($options['headers'])) {
Chris@0 231 throw new \InvalidArgumentException('headers must be an array');
Chris@0 232 }
Chris@0 233 }
Chris@0 234
Chris@0 235 // Shallow merge defaults underneath options.
Chris@0 236 $result = $options + $defaults;
Chris@0 237
Chris@0 238 // Remove null values.
Chris@0 239 foreach ($result as $k => $v) {
Chris@0 240 if ($v === null) {
Chris@0 241 unset($result[$k]);
Chris@0 242 }
Chris@0 243 }
Chris@0 244
Chris@0 245 return $result;
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * Transfers the given request and applies request options.
Chris@0 250 *
Chris@0 251 * The URI of the request is not modified and the request options are used
Chris@0 252 * as-is without merging in default options.
Chris@0 253 *
Chris@0 254 * @param RequestInterface $request
Chris@0 255 * @param array $options
Chris@0 256 *
Chris@0 257 * @return Promise\PromiseInterface
Chris@0 258 */
Chris@0 259 private function transfer(RequestInterface $request, array $options)
Chris@0 260 {
Chris@0 261 // save_to -> sink
Chris@0 262 if (isset($options['save_to'])) {
Chris@0 263 $options['sink'] = $options['save_to'];
Chris@0 264 unset($options['save_to']);
Chris@0 265 }
Chris@0 266
Chris@0 267 // exceptions -> http_errors
Chris@0 268 if (isset($options['exceptions'])) {
Chris@0 269 $options['http_errors'] = $options['exceptions'];
Chris@0 270 unset($options['exceptions']);
Chris@0 271 }
Chris@0 272
Chris@0 273 $request = $this->applyOptions($request, $options);
Chris@0 274 $handler = $options['handler'];
Chris@0 275
Chris@0 276 try {
Chris@0 277 return Promise\promise_for($handler($request, $options));
Chris@0 278 } catch (\Exception $e) {
Chris@0 279 return Promise\rejection_for($e);
Chris@0 280 }
Chris@0 281 }
Chris@0 282
Chris@0 283 /**
Chris@0 284 * Applies the array of request options to a request.
Chris@0 285 *
Chris@0 286 * @param RequestInterface $request
Chris@0 287 * @param array $options
Chris@0 288 *
Chris@0 289 * @return RequestInterface
Chris@0 290 */
Chris@0 291 private function applyOptions(RequestInterface $request, array &$options)
Chris@0 292 {
Chris@13 293 $modify = [
Chris@13 294 'set_headers' => [],
Chris@13 295 ];
Chris@13 296
Chris@13 297 if (isset($options['headers'])) {
Chris@13 298 $modify['set_headers'] = $options['headers'];
Chris@13 299 unset($options['headers']);
Chris@13 300 }
Chris@0 301
Chris@0 302 if (isset($options['form_params'])) {
Chris@0 303 if (isset($options['multipart'])) {
Chris@0 304 throw new \InvalidArgumentException('You cannot use '
Chris@0 305 . 'form_params and multipart at the same time. Use the '
Chris@0 306 . 'form_params option if you want to send application/'
Chris@0 307 . 'x-www-form-urlencoded requests, and the multipart '
Chris@0 308 . 'option to send multipart/form-data requests.');
Chris@0 309 }
Chris@0 310 $options['body'] = http_build_query($options['form_params'], '', '&');
Chris@0 311 unset($options['form_params']);
Chris@13 312 // Ensure that we don't have the header in different case and set the new value.
Chris@13 313 $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
Chris@0 314 $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
Chris@0 315 }
Chris@0 316
Chris@0 317 if (isset($options['multipart'])) {
Chris@0 318 $options['body'] = new Psr7\MultipartStream($options['multipart']);
Chris@0 319 unset($options['multipart']);
Chris@0 320 }
Chris@0 321
Chris@0 322 if (isset($options['json'])) {
Chris@0 323 $options['body'] = \GuzzleHttp\json_encode($options['json']);
Chris@0 324 unset($options['json']);
Chris@13 325 // Ensure that we don't have the header in different case and set the new value.
Chris@13 326 $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
Chris@0 327 $options['_conditional']['Content-Type'] = 'application/json';
Chris@0 328 }
Chris@0 329
Chris@0 330 if (!empty($options['decode_content'])
Chris@0 331 && $options['decode_content'] !== true
Chris@0 332 ) {
Chris@13 333 // Ensure that we don't have the header in different case and set the new value.
Chris@13 334 $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
Chris@0 335 $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
Chris@0 336 }
Chris@0 337
Chris@0 338 if (isset($options['body'])) {
Chris@0 339 if (is_array($options['body'])) {
Chris@0 340 $this->invalidBody();
Chris@0 341 }
Chris@0 342 $modify['body'] = Psr7\stream_for($options['body']);
Chris@0 343 unset($options['body']);
Chris@0 344 }
Chris@0 345
Chris@0 346 if (!empty($options['auth']) && is_array($options['auth'])) {
Chris@0 347 $value = $options['auth'];
Chris@0 348 $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
Chris@0 349 switch ($type) {
Chris@0 350 case 'basic':
Chris@13 351 // Ensure that we don't have the header in different case and set the new value.
Chris@13 352 $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
Chris@0 353 $modify['set_headers']['Authorization'] = 'Basic '
Chris@0 354 . base64_encode("$value[0]:$value[1]");
Chris@0 355 break;
Chris@0 356 case 'digest':
Chris@0 357 // @todo: Do not rely on curl
Chris@0 358 $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
Chris@0 359 $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
Chris@0 360 break;
Chris@0 361 case 'ntlm':
Chris@0 362 $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
Chris@0 363 $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
Chris@0 364 break;
Chris@0 365 }
Chris@0 366 }
Chris@0 367
Chris@0 368 if (isset($options['query'])) {
Chris@0 369 $value = $options['query'];
Chris@0 370 if (is_array($value)) {
Chris@0 371 $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
Chris@0 372 }
Chris@0 373 if (!is_string($value)) {
Chris@0 374 throw new \InvalidArgumentException('query must be a string or array');
Chris@0 375 }
Chris@0 376 $modify['query'] = $value;
Chris@0 377 unset($options['query']);
Chris@0 378 }
Chris@0 379
Chris@0 380 // Ensure that sink is not an invalid value.
Chris@0 381 if (isset($options['sink'])) {
Chris@0 382 // TODO: Add more sink validation?
Chris@0 383 if (is_bool($options['sink'])) {
Chris@0 384 throw new \InvalidArgumentException('sink must not be a boolean');
Chris@0 385 }
Chris@0 386 }
Chris@0 387
Chris@0 388 $request = Psr7\modify_request($request, $modify);
Chris@0 389 if ($request->getBody() instanceof Psr7\MultipartStream) {
Chris@0 390 // Use a multipart/form-data POST if a Content-Type is not set.
Chris@13 391 // Ensure that we don't have the header in different case and set the new value.
Chris@13 392 $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
Chris@0 393 $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
Chris@0 394 . $request->getBody()->getBoundary();
Chris@0 395 }
Chris@0 396
Chris@0 397 // Merge in conditional headers if they are not present.
Chris@0 398 if (isset($options['_conditional'])) {
Chris@0 399 // Build up the changes so it's in a single clone of the message.
Chris@0 400 $modify = [];
Chris@0 401 foreach ($options['_conditional'] as $k => $v) {
Chris@0 402 if (!$request->hasHeader($k)) {
Chris@0 403 $modify['set_headers'][$k] = $v;
Chris@0 404 }
Chris@0 405 }
Chris@0 406 $request = Psr7\modify_request($request, $modify);
Chris@0 407 // Don't pass this internal value along to middleware/handlers.
Chris@0 408 unset($options['_conditional']);
Chris@0 409 }
Chris@0 410
Chris@0 411 return $request;
Chris@0 412 }
Chris@0 413
Chris@0 414 private function invalidBody()
Chris@0 415 {
Chris@0 416 throw new \InvalidArgumentException('Passing in the "body" request '
Chris@0 417 . 'option as an array to send a POST request has been deprecated. '
Chris@0 418 . 'Please use the "form_params" request option to send a '
Chris@0 419 . 'application/x-www-form-urlencoded request, or the "multipart" '
Chris@0 420 . 'request option to send a multipart/form-data request.');
Chris@0 421 }
Chris@0 422 }