Chris@0
|
1 <?php
|
Chris@0
|
2 namespace GuzzleHttp\Handler;
|
Chris@0
|
3
|
Chris@0
|
4 use GuzzleHttp\Exception\RequestException;
|
Chris@0
|
5 use GuzzleHttp\Exception\ConnectException;
|
Chris@0
|
6 use GuzzleHttp\Promise\FulfilledPromise;
|
Chris@0
|
7 use GuzzleHttp\Promise\PromiseInterface;
|
Chris@0
|
8 use GuzzleHttp\Psr7;
|
Chris@0
|
9 use GuzzleHttp\TransferStats;
|
Chris@0
|
10 use Psr\Http\Message\RequestInterface;
|
Chris@0
|
11 use Psr\Http\Message\ResponseInterface;
|
Chris@0
|
12 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
13
|
Chris@0
|
14 /**
|
Chris@0
|
15 * HTTP handler that uses PHP's HTTP stream wrapper.
|
Chris@0
|
16 */
|
Chris@0
|
17 class StreamHandler
|
Chris@0
|
18 {
|
Chris@0
|
19 private $lastHeaders = [];
|
Chris@0
|
20
|
Chris@0
|
21 /**
|
Chris@0
|
22 * Sends an HTTP request.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @param RequestInterface $request Request to send.
|
Chris@0
|
25 * @param array $options Request transfer options.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @return PromiseInterface
|
Chris@0
|
28 */
|
Chris@0
|
29 public function __invoke(RequestInterface $request, array $options)
|
Chris@0
|
30 {
|
Chris@0
|
31 // Sleep if there is a delay specified.
|
Chris@0
|
32 if (isset($options['delay'])) {
|
Chris@0
|
33 usleep($options['delay'] * 1000);
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36 $startTime = isset($options['on_stats']) ? microtime(true) : null;
|
Chris@0
|
37
|
Chris@0
|
38 try {
|
Chris@0
|
39 // Does not support the expect header.
|
Chris@0
|
40 $request = $request->withoutHeader('Expect');
|
Chris@0
|
41
|
Chris@0
|
42 // Append a content-length header if body size is zero to match
|
Chris@0
|
43 // cURL's behavior.
|
Chris@0
|
44 if (0 === $request->getBody()->getSize()) {
|
Chris@0
|
45 $request = $request->withHeader('Content-Length', 0);
|
Chris@0
|
46 }
|
Chris@0
|
47
|
Chris@0
|
48 return $this->createResponse(
|
Chris@0
|
49 $request,
|
Chris@0
|
50 $options,
|
Chris@0
|
51 $this->createStream($request, $options),
|
Chris@0
|
52 $startTime
|
Chris@0
|
53 );
|
Chris@0
|
54 } catch (\InvalidArgumentException $e) {
|
Chris@0
|
55 throw $e;
|
Chris@0
|
56 } catch (\Exception $e) {
|
Chris@0
|
57 // Determine if the error was a networking error.
|
Chris@0
|
58 $message = $e->getMessage();
|
Chris@0
|
59 // This list can probably get more comprehensive.
|
Chris@0
|
60 if (strpos($message, 'getaddrinfo') // DNS lookup failed
|
Chris@0
|
61 || strpos($message, 'Connection refused')
|
Chris@0
|
62 || strpos($message, "couldn't connect to host") // error on HHVM
|
Chris@13
|
63 || strpos($message, "connection attempt failed")
|
Chris@0
|
64 ) {
|
Chris@0
|
65 $e = new ConnectException($e->getMessage(), $request, $e);
|
Chris@0
|
66 }
|
Chris@0
|
67 $e = RequestException::wrapException($request, $e);
|
Chris@0
|
68 $this->invokeStats($options, $request, $startTime, null, $e);
|
Chris@0
|
69
|
Chris@0
|
70 return \GuzzleHttp\Promise\rejection_for($e);
|
Chris@0
|
71 }
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 private function invokeStats(
|
Chris@0
|
75 array $options,
|
Chris@0
|
76 RequestInterface $request,
|
Chris@0
|
77 $startTime,
|
Chris@0
|
78 ResponseInterface $response = null,
|
Chris@0
|
79 $error = null
|
Chris@0
|
80 ) {
|
Chris@0
|
81 if (isset($options['on_stats'])) {
|
Chris@0
|
82 $stats = new TransferStats(
|
Chris@0
|
83 $request,
|
Chris@0
|
84 $response,
|
Chris@0
|
85 microtime(true) - $startTime,
|
Chris@0
|
86 $error,
|
Chris@0
|
87 []
|
Chris@0
|
88 );
|
Chris@0
|
89 call_user_func($options['on_stats'], $stats);
|
Chris@0
|
90 }
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 private function createResponse(
|
Chris@0
|
94 RequestInterface $request,
|
Chris@0
|
95 array $options,
|
Chris@0
|
96 $stream,
|
Chris@0
|
97 $startTime
|
Chris@0
|
98 ) {
|
Chris@0
|
99 $hdrs = $this->lastHeaders;
|
Chris@0
|
100 $this->lastHeaders = [];
|
Chris@0
|
101 $parts = explode(' ', array_shift($hdrs), 3);
|
Chris@0
|
102 $ver = explode('/', $parts[0])[1];
|
Chris@0
|
103 $status = $parts[1];
|
Chris@0
|
104 $reason = isset($parts[2]) ? $parts[2] : null;
|
Chris@0
|
105 $headers = \GuzzleHttp\headers_from_lines($hdrs);
|
Chris@13
|
106 list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
|
Chris@0
|
107 $stream = Psr7\stream_for($stream);
|
Chris@0
|
108 $sink = $stream;
|
Chris@0
|
109
|
Chris@0
|
110 if (strcasecmp('HEAD', $request->getMethod())) {
|
Chris@0
|
111 $sink = $this->createSink($stream, $options);
|
Chris@0
|
112 }
|
Chris@0
|
113
|
Chris@0
|
114 $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
|
Chris@0
|
115
|
Chris@0
|
116 if (isset($options['on_headers'])) {
|
Chris@0
|
117 try {
|
Chris@0
|
118 $options['on_headers']($response);
|
Chris@0
|
119 } catch (\Exception $e) {
|
Chris@0
|
120 $msg = 'An error was encountered during the on_headers event';
|
Chris@0
|
121 $ex = new RequestException($msg, $request, $response, $e);
|
Chris@0
|
122 return \GuzzleHttp\Promise\rejection_for($ex);
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 // Do not drain when the request is a HEAD request because they have
|
Chris@0
|
127 // no body.
|
Chris@0
|
128 if ($sink !== $stream) {
|
Chris@0
|
129 $this->drain(
|
Chris@0
|
130 $stream,
|
Chris@0
|
131 $sink,
|
Chris@0
|
132 $response->getHeaderLine('Content-Length')
|
Chris@0
|
133 );
|
Chris@0
|
134 }
|
Chris@0
|
135
|
Chris@0
|
136 $this->invokeStats($options, $request, $startTime, $response, null);
|
Chris@0
|
137
|
Chris@0
|
138 return new FulfilledPromise($response);
|
Chris@0
|
139 }
|
Chris@0
|
140
|
Chris@0
|
141 private function createSink(StreamInterface $stream, array $options)
|
Chris@0
|
142 {
|
Chris@0
|
143 if (!empty($options['stream'])) {
|
Chris@0
|
144 return $stream;
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 $sink = isset($options['sink'])
|
Chris@0
|
148 ? $options['sink']
|
Chris@0
|
149 : fopen('php://temp', 'r+');
|
Chris@0
|
150
|
Chris@0
|
151 return is_string($sink)
|
Chris@0
|
152 ? new Psr7\LazyOpenStream($sink, 'w+')
|
Chris@0
|
153 : Psr7\stream_for($sink);
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 private function checkDecode(array $options, array $headers, $stream)
|
Chris@0
|
157 {
|
Chris@0
|
158 // Automatically decode responses when instructed.
|
Chris@0
|
159 if (!empty($options['decode_content'])) {
|
Chris@0
|
160 $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
|
Chris@0
|
161 if (isset($normalizedKeys['content-encoding'])) {
|
Chris@0
|
162 $encoding = $headers[$normalizedKeys['content-encoding']];
|
Chris@0
|
163 if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
|
Chris@0
|
164 $stream = new Psr7\InflateStream(
|
Chris@0
|
165 Psr7\stream_for($stream)
|
Chris@0
|
166 );
|
Chris@0
|
167 $headers['x-encoded-content-encoding']
|
Chris@0
|
168 = $headers[$normalizedKeys['content-encoding']];
|
Chris@0
|
169 // Remove content-encoding header
|
Chris@0
|
170 unset($headers[$normalizedKeys['content-encoding']]);
|
Chris@0
|
171 // Fix content-length header
|
Chris@0
|
172 if (isset($normalizedKeys['content-length'])) {
|
Chris@0
|
173 $headers['x-encoded-content-length']
|
Chris@0
|
174 = $headers[$normalizedKeys['content-length']];
|
Chris@0
|
175
|
Chris@0
|
176 $length = (int) $stream->getSize();
|
Chris@0
|
177 if ($length === 0) {
|
Chris@0
|
178 unset($headers[$normalizedKeys['content-length']]);
|
Chris@0
|
179 } else {
|
Chris@0
|
180 $headers[$normalizedKeys['content-length']] = [$length];
|
Chris@0
|
181 }
|
Chris@0
|
182 }
|
Chris@0
|
183 }
|
Chris@0
|
184 }
|
Chris@0
|
185 }
|
Chris@0
|
186
|
Chris@0
|
187 return [$stream, $headers];
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 /**
|
Chris@0
|
191 * Drains the source stream into the "sink" client option.
|
Chris@0
|
192 *
|
Chris@0
|
193 * @param StreamInterface $source
|
Chris@0
|
194 * @param StreamInterface $sink
|
Chris@0
|
195 * @param string $contentLength Header specifying the amount of
|
Chris@0
|
196 * data to read.
|
Chris@0
|
197 *
|
Chris@0
|
198 * @return StreamInterface
|
Chris@0
|
199 * @throws \RuntimeException when the sink option is invalid.
|
Chris@0
|
200 */
|
Chris@0
|
201 private function drain(
|
Chris@0
|
202 StreamInterface $source,
|
Chris@0
|
203 StreamInterface $sink,
|
Chris@0
|
204 $contentLength
|
Chris@0
|
205 ) {
|
Chris@0
|
206 // If a content-length header is provided, then stop reading once
|
Chris@0
|
207 // that number of bytes has been read. This can prevent infinitely
|
Chris@0
|
208 // reading from a stream when dealing with servers that do not honor
|
Chris@0
|
209 // Connection: Close headers.
|
Chris@0
|
210 Psr7\copy_to_stream(
|
Chris@0
|
211 $source,
|
Chris@0
|
212 $sink,
|
Chris@0
|
213 (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
|
Chris@0
|
214 );
|
Chris@0
|
215
|
Chris@0
|
216 $sink->seek(0);
|
Chris@0
|
217 $source->close();
|
Chris@0
|
218
|
Chris@0
|
219 return $sink;
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 /**
|
Chris@0
|
223 * Create a resource and check to ensure it was created successfully
|
Chris@0
|
224 *
|
Chris@0
|
225 * @param callable $callback Callable that returns stream resource
|
Chris@0
|
226 *
|
Chris@0
|
227 * @return resource
|
Chris@0
|
228 * @throws \RuntimeException on error
|
Chris@0
|
229 */
|
Chris@0
|
230 private function createResource(callable $callback)
|
Chris@0
|
231 {
|
Chris@0
|
232 $errors = null;
|
Chris@0
|
233 set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
|
Chris@0
|
234 $errors[] = [
|
Chris@0
|
235 'message' => $msg,
|
Chris@0
|
236 'file' => $file,
|
Chris@0
|
237 'line' => $line
|
Chris@0
|
238 ];
|
Chris@0
|
239 return true;
|
Chris@0
|
240 });
|
Chris@0
|
241
|
Chris@0
|
242 $resource = $callback();
|
Chris@0
|
243 restore_error_handler();
|
Chris@0
|
244
|
Chris@0
|
245 if (!$resource) {
|
Chris@0
|
246 $message = 'Error creating resource: ';
|
Chris@0
|
247 foreach ($errors as $err) {
|
Chris@0
|
248 foreach ($err as $key => $value) {
|
Chris@0
|
249 $message .= "[$key] $value" . PHP_EOL;
|
Chris@0
|
250 }
|
Chris@0
|
251 }
|
Chris@0
|
252 throw new \RuntimeException(trim($message));
|
Chris@0
|
253 }
|
Chris@0
|
254
|
Chris@0
|
255 return $resource;
|
Chris@0
|
256 }
|
Chris@0
|
257
|
Chris@0
|
258 private function createStream(RequestInterface $request, array $options)
|
Chris@0
|
259 {
|
Chris@0
|
260 static $methods;
|
Chris@0
|
261 if (!$methods) {
|
Chris@0
|
262 $methods = array_flip(get_class_methods(__CLASS__));
|
Chris@0
|
263 }
|
Chris@0
|
264
|
Chris@0
|
265 // HTTP/1.1 streams using the PHP stream wrapper require a
|
Chris@0
|
266 // Connection: close header
|
Chris@0
|
267 if ($request->getProtocolVersion() == '1.1'
|
Chris@0
|
268 && !$request->hasHeader('Connection')
|
Chris@0
|
269 ) {
|
Chris@0
|
270 $request = $request->withHeader('Connection', 'close');
|
Chris@0
|
271 }
|
Chris@0
|
272
|
Chris@0
|
273 // Ensure SSL is verified by default
|
Chris@0
|
274 if (!isset($options['verify'])) {
|
Chris@0
|
275 $options['verify'] = true;
|
Chris@0
|
276 }
|
Chris@0
|
277
|
Chris@0
|
278 $params = [];
|
Chris@13
|
279 $context = $this->getDefaultContext($request);
|
Chris@0
|
280
|
Chris@0
|
281 if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
|
Chris@0
|
282 throw new \InvalidArgumentException('on_headers must be callable');
|
Chris@0
|
283 }
|
Chris@0
|
284
|
Chris@0
|
285 if (!empty($options)) {
|
Chris@0
|
286 foreach ($options as $key => $value) {
|
Chris@0
|
287 $method = "add_{$key}";
|
Chris@0
|
288 if (isset($methods[$method])) {
|
Chris@0
|
289 $this->{$method}($request, $context, $value, $params);
|
Chris@0
|
290 }
|
Chris@0
|
291 }
|
Chris@0
|
292 }
|
Chris@0
|
293
|
Chris@0
|
294 if (isset($options['stream_context'])) {
|
Chris@0
|
295 if (!is_array($options['stream_context'])) {
|
Chris@0
|
296 throw new \InvalidArgumentException('stream_context must be an array');
|
Chris@0
|
297 }
|
Chris@0
|
298 $context = array_replace_recursive(
|
Chris@0
|
299 $context,
|
Chris@0
|
300 $options['stream_context']
|
Chris@0
|
301 );
|
Chris@0
|
302 }
|
Chris@0
|
303
|
Chris@0
|
304 // Microsoft NTLM authentication only supported with curl handler
|
Chris@0
|
305 if (isset($options['auth'])
|
Chris@0
|
306 && is_array($options['auth'])
|
Chris@0
|
307 && isset($options['auth'][2])
|
Chris@0
|
308 && 'ntlm' == $options['auth'][2]
|
Chris@0
|
309 ) {
|
Chris@0
|
310 throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
|
Chris@0
|
311 }
|
Chris@0
|
312
|
Chris@0
|
313 $uri = $this->resolveHost($request, $options);
|
Chris@0
|
314
|
Chris@0
|
315 $context = $this->createResource(
|
Chris@0
|
316 function () use ($context, $params) {
|
Chris@0
|
317 return stream_context_create($context, $params);
|
Chris@0
|
318 }
|
Chris@0
|
319 );
|
Chris@0
|
320
|
Chris@0
|
321 return $this->createResource(
|
Chris@0
|
322 function () use ($uri, &$http_response_header, $context, $options) {
|
Chris@0
|
323 $resource = fopen((string) $uri, 'r', null, $context);
|
Chris@0
|
324 $this->lastHeaders = $http_response_header;
|
Chris@0
|
325
|
Chris@0
|
326 if (isset($options['read_timeout'])) {
|
Chris@0
|
327 $readTimeout = $options['read_timeout'];
|
Chris@0
|
328 $sec = (int) $readTimeout;
|
Chris@0
|
329 $usec = ($readTimeout - $sec) * 100000;
|
Chris@0
|
330 stream_set_timeout($resource, $sec, $usec);
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 return $resource;
|
Chris@0
|
334 }
|
Chris@0
|
335 );
|
Chris@0
|
336 }
|
Chris@0
|
337
|
Chris@0
|
338 private function resolveHost(RequestInterface $request, array $options)
|
Chris@0
|
339 {
|
Chris@0
|
340 $uri = $request->getUri();
|
Chris@0
|
341
|
Chris@0
|
342 if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
|
Chris@0
|
343 if ('v4' === $options['force_ip_resolve']) {
|
Chris@0
|
344 $records = dns_get_record($uri->getHost(), DNS_A);
|
Chris@0
|
345 if (!isset($records[0]['ip'])) {
|
Chris@0
|
346 throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
|
Chris@0
|
347 }
|
Chris@0
|
348 $uri = $uri->withHost($records[0]['ip']);
|
Chris@0
|
349 } elseif ('v6' === $options['force_ip_resolve']) {
|
Chris@0
|
350 $records = dns_get_record($uri->getHost(), DNS_AAAA);
|
Chris@0
|
351 if (!isset($records[0]['ipv6'])) {
|
Chris@0
|
352 throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
|
Chris@0
|
353 }
|
Chris@0
|
354 $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
|
Chris@0
|
355 }
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 return $uri;
|
Chris@0
|
359 }
|
Chris@0
|
360
|
Chris@0
|
361 private function getDefaultContext(RequestInterface $request)
|
Chris@0
|
362 {
|
Chris@0
|
363 $headers = '';
|
Chris@0
|
364 foreach ($request->getHeaders() as $name => $value) {
|
Chris@0
|
365 foreach ($value as $val) {
|
Chris@0
|
366 $headers .= "$name: $val\r\n";
|
Chris@0
|
367 }
|
Chris@0
|
368 }
|
Chris@0
|
369
|
Chris@0
|
370 $context = [
|
Chris@0
|
371 'http' => [
|
Chris@0
|
372 'method' => $request->getMethod(),
|
Chris@0
|
373 'header' => $headers,
|
Chris@0
|
374 'protocol_version' => $request->getProtocolVersion(),
|
Chris@0
|
375 'ignore_errors' => true,
|
Chris@0
|
376 'follow_location' => 0,
|
Chris@0
|
377 ],
|
Chris@0
|
378 ];
|
Chris@0
|
379
|
Chris@0
|
380 $body = (string) $request->getBody();
|
Chris@0
|
381
|
Chris@0
|
382 if (!empty($body)) {
|
Chris@0
|
383 $context['http']['content'] = $body;
|
Chris@0
|
384 // Prevent the HTTP handler from adding a Content-Type header.
|
Chris@0
|
385 if (!$request->hasHeader('Content-Type')) {
|
Chris@0
|
386 $context['http']['header'] .= "Content-Type:\r\n";
|
Chris@0
|
387 }
|
Chris@0
|
388 }
|
Chris@0
|
389
|
Chris@0
|
390 $context['http']['header'] = rtrim($context['http']['header']);
|
Chris@0
|
391
|
Chris@0
|
392 return $context;
|
Chris@0
|
393 }
|
Chris@0
|
394
|
Chris@0
|
395 private function add_proxy(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
396 {
|
Chris@0
|
397 if (!is_array($value)) {
|
Chris@0
|
398 $options['http']['proxy'] = $value;
|
Chris@0
|
399 } else {
|
Chris@0
|
400 $scheme = $request->getUri()->getScheme();
|
Chris@0
|
401 if (isset($value[$scheme])) {
|
Chris@0
|
402 if (!isset($value['no'])
|
Chris@0
|
403 || !\GuzzleHttp\is_host_in_noproxy(
|
Chris@0
|
404 $request->getUri()->getHost(),
|
Chris@0
|
405 $value['no']
|
Chris@0
|
406 )
|
Chris@0
|
407 ) {
|
Chris@0
|
408 $options['http']['proxy'] = $value[$scheme];
|
Chris@0
|
409 }
|
Chris@0
|
410 }
|
Chris@0
|
411 }
|
Chris@0
|
412 }
|
Chris@0
|
413
|
Chris@0
|
414 private function add_timeout(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
415 {
|
Chris@0
|
416 if ($value > 0) {
|
Chris@0
|
417 $options['http']['timeout'] = $value;
|
Chris@0
|
418 }
|
Chris@0
|
419 }
|
Chris@0
|
420
|
Chris@0
|
421 private function add_verify(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
422 {
|
Chris@0
|
423 if ($value === true) {
|
Chris@0
|
424 // PHP 5.6 or greater will find the system cert by default. When
|
Chris@0
|
425 // < 5.6, use the Guzzle bundled cacert.
|
Chris@0
|
426 if (PHP_VERSION_ID < 50600) {
|
Chris@0
|
427 $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
|
Chris@0
|
428 }
|
Chris@0
|
429 } elseif (is_string($value)) {
|
Chris@0
|
430 $options['ssl']['cafile'] = $value;
|
Chris@0
|
431 if (!file_exists($value)) {
|
Chris@0
|
432 throw new \RuntimeException("SSL CA bundle not found: $value");
|
Chris@0
|
433 }
|
Chris@0
|
434 } elseif ($value === false) {
|
Chris@0
|
435 $options['ssl']['verify_peer'] = false;
|
Chris@0
|
436 $options['ssl']['verify_peer_name'] = false;
|
Chris@0
|
437 return;
|
Chris@0
|
438 } else {
|
Chris@0
|
439 throw new \InvalidArgumentException('Invalid verify request option');
|
Chris@0
|
440 }
|
Chris@0
|
441
|
Chris@0
|
442 $options['ssl']['verify_peer'] = true;
|
Chris@0
|
443 $options['ssl']['verify_peer_name'] = true;
|
Chris@0
|
444 $options['ssl']['allow_self_signed'] = false;
|
Chris@0
|
445 }
|
Chris@0
|
446
|
Chris@0
|
447 private function add_cert(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
448 {
|
Chris@0
|
449 if (is_array($value)) {
|
Chris@0
|
450 $options['ssl']['passphrase'] = $value[1];
|
Chris@0
|
451 $value = $value[0];
|
Chris@0
|
452 }
|
Chris@0
|
453
|
Chris@0
|
454 if (!file_exists($value)) {
|
Chris@0
|
455 throw new \RuntimeException("SSL certificate not found: {$value}");
|
Chris@0
|
456 }
|
Chris@0
|
457
|
Chris@0
|
458 $options['ssl']['local_cert'] = $value;
|
Chris@0
|
459 }
|
Chris@0
|
460
|
Chris@0
|
461 private function add_progress(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
462 {
|
Chris@0
|
463 $this->addNotification(
|
Chris@0
|
464 $params,
|
Chris@0
|
465 function ($code, $a, $b, $c, $transferred, $total) use ($value) {
|
Chris@0
|
466 if ($code == STREAM_NOTIFY_PROGRESS) {
|
Chris@0
|
467 $value($total, $transferred, null, null);
|
Chris@0
|
468 }
|
Chris@0
|
469 }
|
Chris@0
|
470 );
|
Chris@0
|
471 }
|
Chris@0
|
472
|
Chris@0
|
473 private function add_debug(RequestInterface $request, &$options, $value, &$params)
|
Chris@0
|
474 {
|
Chris@0
|
475 if ($value === false) {
|
Chris@0
|
476 return;
|
Chris@0
|
477 }
|
Chris@0
|
478
|
Chris@0
|
479 static $map = [
|
Chris@0
|
480 STREAM_NOTIFY_CONNECT => 'CONNECT',
|
Chris@0
|
481 STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
|
Chris@0
|
482 STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
|
Chris@0
|
483 STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
|
Chris@0
|
484 STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
|
Chris@0
|
485 STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
|
Chris@0
|
486 STREAM_NOTIFY_PROGRESS => 'PROGRESS',
|
Chris@0
|
487 STREAM_NOTIFY_FAILURE => 'FAILURE',
|
Chris@0
|
488 STREAM_NOTIFY_COMPLETED => 'COMPLETED',
|
Chris@0
|
489 STREAM_NOTIFY_RESOLVE => 'RESOLVE',
|
Chris@0
|
490 ];
|
Chris@0
|
491 static $args = ['severity', 'message', 'message_code',
|
Chris@0
|
492 'bytes_transferred', 'bytes_max'];
|
Chris@0
|
493
|
Chris@0
|
494 $value = \GuzzleHttp\debug_resource($value);
|
Chris@0
|
495 $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
|
Chris@0
|
496 $this->addNotification(
|
Chris@0
|
497 $params,
|
Chris@0
|
498 function () use ($ident, $value, $map, $args) {
|
Chris@0
|
499 $passed = func_get_args();
|
Chris@0
|
500 $code = array_shift($passed);
|
Chris@0
|
501 fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
|
Chris@0
|
502 foreach (array_filter($passed) as $i => $v) {
|
Chris@0
|
503 fwrite($value, $args[$i] . ': "' . $v . '" ');
|
Chris@0
|
504 }
|
Chris@0
|
505 fwrite($value, "\n");
|
Chris@0
|
506 }
|
Chris@0
|
507 );
|
Chris@0
|
508 }
|
Chris@0
|
509
|
Chris@0
|
510 private function addNotification(array &$params, callable $notify)
|
Chris@0
|
511 {
|
Chris@0
|
512 // Wrap the existing function if needed.
|
Chris@0
|
513 if (!isset($params['notification'])) {
|
Chris@0
|
514 $params['notification'] = $notify;
|
Chris@0
|
515 } else {
|
Chris@0
|
516 $params['notification'] = $this->callArray([
|
Chris@0
|
517 $params['notification'],
|
Chris@0
|
518 $notify
|
Chris@0
|
519 ]);
|
Chris@0
|
520 }
|
Chris@0
|
521 }
|
Chris@0
|
522
|
Chris@0
|
523 private function callArray(array $functions)
|
Chris@0
|
524 {
|
Chris@0
|
525 return function () use ($functions) {
|
Chris@0
|
526 $args = func_get_args();
|
Chris@0
|
527 foreach ($functions as $fn) {
|
Chris@0
|
528 call_user_func_array($fn, $args);
|
Chris@0
|
529 }
|
Chris@0
|
530 };
|
Chris@0
|
531 }
|
Chris@0
|
532 }
|