scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2  
3 namespace GuzzleHttp;
4  
5 use GuzzleHttp\Adapter\AdapterInterface;
6 use GuzzleHttp\Adapter\Curl\CurlAdapter;
7 use GuzzleHttp\Adapter\Curl\MultiAdapter;
8 use GuzzleHttp\Adapter\FakeParallelAdapter;
9 use GuzzleHttp\Adapter\ParallelAdapterInterface;
10 use GuzzleHttp\Adapter\StreamAdapter;
11 use GuzzleHttp\Adapter\StreamingProxyAdapter;
12 use GuzzleHttp\Adapter\Transaction;
13 use GuzzleHttp\Adapter\TransactionIterator;
14 use GuzzleHttp\Event\HasEmitterTrait;
15 use GuzzleHttp\Exception\RequestException;
16 use GuzzleHttp\Message\MessageFactory;
17 use GuzzleHttp\Message\MessageFactoryInterface;
18 use GuzzleHttp\Message\RequestInterface;
19  
20 /**
21 * HTTP client
22 */
23 class Client implements ClientInterface
24 {
25 use HasEmitterTrait;
26  
27 const DEFAULT_CONCURRENCY = 25;
28  
29 /** @var MessageFactoryInterface Request factory used by the client */
30 private $messageFactory;
31  
32 /** @var AdapterInterface */
33 private $adapter;
34  
35 /** @var ParallelAdapterInterface */
36 private $parallelAdapter;
37  
38 /** @var Url Base URL of the client */
39 private $baseUrl;
40  
41 /** @var array Default request options */
42 private $defaults;
43  
44 /**
45 * Clients accept an array of constructor parameters.
46 *
47 * Here's an example of creating a client using an URI template for the
48 * client's base_url and an array of default request options to apply
49 * to each request:
50 *
51 * $client = new Client([
52 * 'base_url' => [
53 * 'http://www.foo.com/{version}/',
54 * ['version' => '123']
55 * ],
56 * 'defaults' => [
57 * 'timeout' => 10,
58 * 'allow_redirects' => false,
59 * 'proxy' => '192.168.16.1:10'
60 * ]
61 * ]);
62 *
63 * @param array $config Client configuration settings
64 * - base_url: Base URL of the client that is merged into relative URLs.
65 * Can be a string or an array that contains a URI template followed
66 * by an associative array of expansion variables to inject into the
67 * URI template.
68 * - adapter: Adapter used to transfer requests
69 * - parallel_adapter: Adapter used to transfer requests in parallel
70 * - message_factory: Factory used to create request and response object
71 * - defaults: Default request options to apply to each request
72 * - emitter: Event emitter used for request events
73 */
74 public function __construct(array $config = [])
75 {
76 $this->configureBaseUrl($config);
77 $this->configureDefaults($config);
78 $this->configureAdapter($config);
79 if (isset($config['emitter'])) {
80 $this->emitter = $config['emitter'];
81 }
82 }
83  
84 /**
85 * Get the default User-Agent string to use with Guzzle
86 *
87 * @return string
88 */
89 public static function getDefaultUserAgent()
90 {
91 static $defaultAgent = '';
92 if (!$defaultAgent) {
93 $defaultAgent = 'Guzzle/' . self::VERSION;
94 if (extension_loaded('curl')) {
95 $defaultAgent .= ' curl/' . curl_version()['version'];
96 }
97 $defaultAgent .= ' PHP/' . PHP_VERSION;
98 }
99  
100 return $defaultAgent;
101 }
102  
103 public function __call($name, $arguments)
104 {
105 return \GuzzleHttp\deprecation_proxy(
106 $this,
107 $name,
108 $arguments,
109 ['getEventDispatcher' => 'getEmitter']
110 );
111 }
112  
113 public function getDefaultOption($keyOrPath = null)
114 {
115 return $keyOrPath === null
116 ? $this->defaults
117 : \GuzzleHttp\get_path($this->defaults, $keyOrPath);
118 }
119  
120 public function setDefaultOption($keyOrPath, $value)
121 {
122 \GuzzleHttp\set_path($this->defaults, $keyOrPath, $value);
123 }
124  
125 public function getBaseUrl()
126 {
127 return (string) $this->baseUrl;
128 }
129  
130 public function createRequest($method, $url = null, array $options = [])
131 {
132 $headers = $this->mergeDefaults($options);
133 // Use a clone of the client's emitter
134 $options['config']['emitter'] = clone $this->getEmitter();
135  
136 $request = $this->messageFactory->createRequest(
137 $method,
138 $url ? (string) $this->buildUrl($url) : (string) $this->baseUrl,
139 $options
140 );
141  
142 // Merge in default headers
143 if ($headers) {
144 foreach ($headers as $key => $value) {
145 if (!$request->hasHeader($key)) {
146 $request->setHeader($key, $value);
147 }
148 }
149 }
150  
151 return $request;
152 }
153  
154 public function get($url = null, $options = [])
155 {
156 return $this->send($this->createRequest('GET', $url, $options));
157 }
158  
159 public function head($url = null, array $options = [])
160 {
161 return $this->send($this->createRequest('HEAD', $url, $options));
162 }
163  
164 public function delete($url = null, array $options = [])
165 {
166 return $this->send($this->createRequest('DELETE', $url, $options));
167 }
168  
169 public function put($url = null, array $options = [])
170 {
171 return $this->send($this->createRequest('PUT', $url, $options));
172 }
173  
174 public function patch($url = null, array $options = [])
175 {
176 return $this->send($this->createRequest('PATCH', $url, $options));
177 }
178  
179 public function post($url = null, array $options = [])
180 {
181 return $this->send($this->createRequest('POST', $url, $options));
182 }
183  
184 public function options($url = null, array $options = [])
185 {
186 return $this->send($this->createRequest('OPTIONS', $url, $options));
187 }
188  
189 public function send(RequestInterface $request)
190 {
191 $transaction = new Transaction($this, $request);
192 try {
193 if ($response = $this->adapter->send($transaction)) {
194 return $response;
195 }
196 throw new \LogicException('No response was associated with the transaction');
197 } catch (RequestException $e) {
198 throw $e;
199 } catch (\Exception $e) {
200 // Wrap exceptions in a RequestException to adhere to the interface
201 throw new RequestException($e->getMessage(), $request, null, $e);
202 }
203 }
204  
205 public function sendAll($requests, array $options = [])
206 {
207 if (!($requests instanceof TransactionIterator)) {
208 $requests = new TransactionIterator($requests, $this, $options);
209 }
210  
211 $this->parallelAdapter->sendAll(
212 $requests,
213 isset($options['parallel'])
214 ? $options['parallel']
215 : self::DEFAULT_CONCURRENCY
216 );
217 }
218  
219 /**
220 * Get an array of default options to apply to the client
221 *
222 * @return array
223 */
224 protected function getDefaultOptions()
225 {
226 $settings = [
227 'allow_redirects' => true,
228 'exceptions' => true,
229 'decode_content' => true,
230 'verify' => __DIR__ . '/cacert.pem'
231 ];
232  
233 // Use the bundled cacert if it is a regular file, or set to true if
234 // using a phar file (because curL and the stream wrapper can't read
235 // cacerts from the phar stream wrapper). Favor the ini setting over
236 // the system's cacert.
237 if (substr(__FILE__, 0, 7) == 'phar://') {
238 $settings['verify'] = ini_get('openssl.cafile') ?: true;
239 }
240  
241 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
242 // We can only trust the HTTP_PROXY environment variable in a CLI
243 // process due to the fact that PHP has no reliable mechanism to
244 // get environment variables that start with "HTTP_".
245 if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
246 $settings['proxy']['http'] = getenv('HTTP_PROXY');
247 }
248  
249 if ($proxy = getenv('HTTPS_PROXY')) {
250 $settings['proxy']['https'] = $proxy;
251 }
252  
253 return $settings;
254 }
255  
256 /**
257 * Expand a URI template and inherit from the base URL if it's relative
258 *
259 * @param string|array $url URL or URI template to expand
260 *
261 * @return string
262 */
263 private function buildUrl($url)
264 {
265 if (!is_array($url)) {
266 if (strpos($url, '://')) {
267 return (string) $url;
268 }
269 return (string) $this->baseUrl->combine($url);
270 } elseif (strpos($url[0], '://')) {
271 return \GuzzleHttp\uri_template($url[0], $url[1]);
272 }
273  
274 return (string) $this->baseUrl->combine(
275 \GuzzleHttp\uri_template($url[0], $url[1])
276 );
277 }
278  
279 /**
280 * Get a default parallel adapter to use based on the environment
281 *
282 * @return ParallelAdapterInterface
283 */
284 private function getDefaultParallelAdapter()
285 {
286 return extension_loaded('curl')
287 ? new MultiAdapter($this->messageFactory)
288 : new FakeParallelAdapter($this->adapter);
289 }
290  
291 /**
292 * Create a default adapter to use based on the environment
293 * @throws \RuntimeException
294 */
295 private function getDefaultAdapter()
296 {
297 if (extension_loaded('curl')) {
298 $this->parallelAdapter = new MultiAdapter($this->messageFactory);
299 $this->adapter = function_exists('curl_reset')
300 ? new CurlAdapter($this->messageFactory)
301 : $this->parallelAdapter;
302 if (ini_get('allow_url_fopen')) {
303 $this->adapter = new StreamingProxyAdapter(
304 $this->adapter,
305 new StreamAdapter($this->messageFactory)
306 );
307 }
308 } elseif (ini_get('allow_url_fopen')) {
309 $this->adapter = new StreamAdapter($this->messageFactory);
310 } else {
311 throw new \RuntimeException('Guzzle requires cURL, the '
312 . 'allow_url_fopen ini setting, or a custom HTTP adapter.');
313 }
314 }
315  
316 private function configureBaseUrl(&$config)
317 {
318 if (!isset($config['base_url'])) {
319 $this->baseUrl = new Url('', '');
320 } elseif (is_array($config['base_url'])) {
321 $this->baseUrl = Url::fromString(
322 \GuzzleHttp\uri_template(
323 $config['base_url'][0],
324 $config['base_url'][1]
325 )
326 );
327 $config['base_url'] = (string) $this->baseUrl;
328 } else {
329 $this->baseUrl = Url::fromString($config['base_url']);
330 }
331 }
332  
333 private function configureDefaults($config)
334 {
335 if (!isset($config['defaults'])) {
336 $this->defaults = $this->getDefaultOptions();
337 } else {
338 $this->defaults = array_replace(
339 $this->getDefaultOptions(),
340 $config['defaults']
341 );
342 }
343  
344 // Add the default user-agent header
345 if (!isset($this->defaults['headers'])) {
346 $this->defaults['headers'] = [
347 'User-Agent' => static::getDefaultUserAgent()
348 ];
349 } elseif (!isset(array_change_key_case($this->defaults['headers'])['user-agent'])) {
350 // Add the User-Agent header if one was not already set
351 $this->defaults['headers']['User-Agent'] = static::getDefaultUserAgent();
352 }
353 }
354  
355 private function configureAdapter(&$config)
356 {
357 if (isset($config['message_factory'])) {
358 $this->messageFactory = $config['message_factory'];
359 } else {
360 $this->messageFactory = new MessageFactory();
361 }
362 if (isset($config['adapter'])) {
363 $this->adapter = $config['adapter'];
364 } else {
365 $this->getDefaultAdapter();
366 }
367 // If no parallel adapter was explicitly provided and one was not
368 // defaulted when creating the default adapter, then create one now.
369 if (isset($config['parallel_adapter'])) {
370 $this->parallelAdapter = $config['parallel_adapter'];
371 } elseif (!$this->parallelAdapter) {
372 $this->parallelAdapter = $this->getDefaultParallelAdapter();
373 }
374 }
375  
376 /**
377 * Merges default options into the array passed by reference and returns
378 * an array of headers that need to be merged in after the request is
379 * created.
380 *
381 * @param array $options Options to modify by reference
382 *
383 * @return array|null
384 */
385 private function mergeDefaults(&$options)
386 {
387 // Merging optimization for when no headers are present
388 if (!isset($options['headers'])
389 || !isset($this->defaults['headers'])) {
390 $options = array_replace_recursive($this->defaults, $options);
391 return null;
392 }
393  
394 $defaults = $this->defaults;
395 unset($defaults['headers']);
396 $options = array_replace_recursive($defaults, $options);
397  
398 return $this->defaults['headers'];
399 }
400 }