3
namespace GuzzleHttp\Adapter;
5
use GuzzleHttp\Event\RequestEvents;
6
use GuzzleHttp\Exception\AdapterException;
7
use GuzzleHttp\Exception\RequestException;
8
use GuzzleHttp\Message\AbstractMessage;
9
use GuzzleHttp\Message\MessageFactoryInterface;
10
use GuzzleHttp\Message\RequestInterface;
11
use GuzzleHttp\Stream\InflateStream;
12
use GuzzleHttp\Stream\LazyOpenStream;
13
use GuzzleHttp\Stream\Stream;
14
use GuzzleHttp\Stream\StreamInterface;
15
use GuzzleHttp\Stream\Utils;
18
* HTTP adapter that uses PHP's HTTP stream wrapper.
20
* When using the StreamAdapter, custom stream context options can be specified
21
* using the **stream_context** option in a request's **config** option. The
22
* structure of the "stream_context" option is an associative array where each
23
* key is a transport name and each option is an associative array of options.
25
class StreamAdapter implements AdapterInterface
27
/** @var MessageFactoryInterface */
28
private $messageFactory;
31
* @param MessageFactoryInterface $messageFactory
33
public function __construct(MessageFactoryInterface $messageFactory)
35
$this->messageFactory = $messageFactory;
38
public function send(TransactionInterface $transaction)
40
// HTTP/1.1 streams using the PHP stream wrapper require a
41
// Connection: close header. Setting here so that it is added before
42
// emitting the request.before_send event.
43
$request = $transaction->getRequest();
44
if ($request->getProtocolVersion() == '1.1' &&
45
!$request->hasHeader('Connection')
47
$transaction->getRequest()->setHeader('Connection', 'close');
50
RequestEvents::emitBefore($transaction);
51
if (!$transaction->getResponse()) {
52
$this->createResponse($transaction);
53
RequestEvents::emitComplete($transaction);
56
return $transaction->getResponse();
59
private function createResponse(TransactionInterface $transaction)
61
$request = $transaction->getRequest();
62
$stream = $this->createStream($request, $http_response_header);
63
$this->createResponseObject(
65
$http_response_header,
71
private function createResponseObject(
72
RequestInterface $request,
74
TransactionInterface $transaction,
75
StreamInterface $stream
77
$parts = explode(' ', array_shift($headers), 3);
78
$options = ['protocol_version' => substr($parts[0], -3)];
80
if (isset($parts[2])) {
81
$options['reason_phrase'] = $parts[2];
84
$response = $this->messageFactory->createResponse(
86
$this->headersFromLines($headers),
91
// Automatically decode responses when instructed.
92
if ($request->getConfig()->get('decode_content')) {
93
switch ($response->getHeader('Content-Encoding')) {
96
$stream = new InflateStream($stream);
101
// Drain the stream immediately if 'stream' was not enabled.
102
if (!$request->getConfig()['stream']) {
103
$stream = $this->getSaveToBody($request, $stream);
106
$response->setBody($stream);
107
$transaction->setResponse($response);
108
RequestEvents::emitHeaders($transaction);
114
* Drain the stream into the destination stream
116
private function getSaveToBody(
117
RequestInterface $request,
118
StreamInterface $stream
120
if ($saveTo = $request->getConfig()['save_to']) {
121
// Stream the response into the destination stream
122
$saveTo = is_string($saveTo)
123
? new Stream(Utils::open($saveTo, 'r+'))
124
: Stream::factory($saveTo);
126
// Stream into the default temp stream
127
$saveTo = Stream::factory();
130
Utils::copyToStream($stream, $saveTo);
137
private function headersFromLines(array $lines)
139
$responseHeaders = [];
141
foreach ($lines as $line) {
142
$headerParts = explode(':', $line, 2);
143
$responseHeaders[$headerParts[0]][] = isset($headerParts[1])
144
? trim($headerParts[1])
148
return $responseHeaders;
152
* Create a resource and check to ensure it was created successfully
154
* @param callable $callback Callable that returns stream resource
155
* @param RequestInterface $request Request used when throwing exceptions
156
* @param array $options Options used when throwing exceptions
159
* @throws RequestException on error
161
private function createResource(callable $callback, RequestInterface $request, $options)
163
// Turn off error reporting while we try to initiate the request
164
$level = error_reporting(0);
165
$resource = call_user_func($callback);
166
error_reporting($level);
168
// If the resource could not be created, then grab the last error and
169
// throw an exception.
170
if (!is_resource($resource)) {
171
$message = 'Error creating resource. [url] ' . $request->getUrl() . ' ';
172
if (isset($options['http']['proxy'])) {
173
$message .= "[proxy] {$options['http']['proxy']} ";
175
foreach ((array) error_get_last() as $key => $value) {
176
$message .= "[{$key}] {$value} ";
178
throw new RequestException(trim($message), $request);
185
* Create the stream for the request with the context options.
187
* @param RequestInterface $request Request being sent
188
* @param mixed $http_response_header Populated by stream wrapper
192
private function createStream(
193
RequestInterface $request,
194
&$http_response_header
198
$methods = array_flip(get_class_methods(__CLASS__));
202
$options = $this->getDefaultOptions($request);
203
foreach ($request->getConfig()->toArray() as $key => $value) {
204
$method = "add_{$key}";
205
if (isset($methods[$method])) {
206
$this->{$method}($request, $options, $value, $params);
210
$this->applyCustomOptions($request, $options);
211
$context = $this->createStreamContext($request, $options, $params);
213
return $this->createStreamResource(
217
$http_response_header
221
private function getDefaultOptions(RequestInterface $request)
223
$headers = AbstractMessage::getHeadersAsString($request);
227
'method' => $request->getMethod(),
228
'header' => trim($headers),
229
'protocol_version' => $request->getProtocolVersion(),
230
'ignore_errors' => true,
231
'follow_location' => 0
235
if ($body = $request->getBody()) {
236
$context['http']['content'] = (string) $body;
237
// Prevent the HTTP adapter from adding a Content-Type header.
238
if (!$request->hasHeader('Content-Type')) {
239
$context['http']['header'] .= "\r\nContent-Type:";
246
private function add_proxy(RequestInterface $request, &$options, $value, &$params)
248
if (!is_array($value)) {
249
$options['http']['proxy'] = $value;
251
$scheme = $request->getScheme();
252
if (isset($value[$scheme])) {
253
$options['http']['proxy'] = $value[$scheme];
258
private function add_timeout(RequestInterface $request, &$options, $value, &$params)
260
$options['http']['timeout'] = $value;
263
private function add_verify(RequestInterface $request, &$options, $value, &$params)
265
if ($value === true || is_string($value)) {
266
$options['http']['verify_peer'] = true;
267
if ($value !== true) {
268
if (!file_exists($value)) {
269
throw new \RuntimeException("SSL certificate authority file not found: {$value}");
271
$options['http']['allow_self_signed'] = true;
272
$options['http']['cafile'] = $value;
274
} elseif ($value === false) {
275
$options['http']['verify_peer'] = false;
279
private function add_cert(RequestInterface $request, &$options, $value, &$params)
281
if (is_array($value)) {
282
$options['http']['passphrase'] = $value[1];
286
if (!file_exists($value)) {
287
throw new \RuntimeException("SSL certificate not found: {$value}");
290
$options['http']['local_cert'] = $value;
293
private function add_debug(RequestInterface $request, &$options, $value, &$params)
296
STREAM_NOTIFY_CONNECT => 'CONNECT',
297
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
298
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
299
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
300
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
301
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
302
STREAM_NOTIFY_PROGRESS => 'PROGRESS',
303
STREAM_NOTIFY_FAILURE => 'FAILURE',
304
STREAM_NOTIFY_COMPLETED => 'COMPLETED',
305
STREAM_NOTIFY_RESOLVE => 'RESOLVE'
308
static $args = ['severity', 'message', 'message_code',
309
'bytes_transferred', 'bytes_max'];
311
if (!is_resource($value)) {
312
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
315
$params['notification'] = function () use ($request, $value, $map, $args) {
316
$passed = func_get_args();
317
$code = array_shift($passed);
318
fprintf($value, '<%s> [%s] ', $request->getUrl(), $map[$code]);
319
foreach (array_filter($passed) as $i => $v) {
320
fwrite($value, $args[$i] . ': "' . $v . '" ');
322
fwrite($value, "\n");
326
private function applyCustomOptions(
327
RequestInterface $request,
330
// Overwrite any generated options with custom options
331
if ($custom = $request->getConfig()['stream_context']) {
332
if (!is_array($custom)) {
333
throw new AdapterException('stream_context must be an array');
335
$options = array_replace_recursive($options, $custom);
339
private function createStreamContext(
340
RequestInterface $request,
344
return $this->createResource(
345
function () use ($request, $options, $params) {
346
return stream_context_create($options, $params);
353
private function createStreamResource(
354
RequestInterface $request,
357
&$http_response_header
359
$url = $request->getUrl();
361
return $this->createResource(
362
function () use ($url, &$http_response_header, $context) {
363
if (false === strpos($url, 'http')) {
364
trigger_error("URL is invalid: {$url}", E_USER_WARNING);
367
return fopen($url, 'r', null, $context);