scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2  
3 namespace GuzzleHttp\Adapter;
4  
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;
16  
17 /**
18 * HTTP adapter that uses PHP's HTTP stream wrapper.
19 *
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.
24 */
25 class StreamAdapter implements AdapterInterface
26 {
27 /** @var MessageFactoryInterface */
28 private $messageFactory;
29  
30 /**
31 * @param MessageFactoryInterface $messageFactory
32 */
33 public function __construct(MessageFactoryInterface $messageFactory)
34 {
35 $this->messageFactory = $messageFactory;
36 }
37  
38 public function send(TransactionInterface $transaction)
39 {
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')
46 ) {
47 $transaction->getRequest()->setHeader('Connection', 'close');
48 }
49  
50 RequestEvents::emitBefore($transaction);
51 if (!$transaction->getResponse()) {
52 $this->createResponse($transaction);
53 RequestEvents::emitComplete($transaction);
54 }
55  
56 return $transaction->getResponse();
57 }
58  
59 private function createResponse(TransactionInterface $transaction)
60 {
61 $request = $transaction->getRequest();
62 $stream = $this->createStream($request, $http_response_header);
63 $this->createResponseObject(
64 $request,
65 $http_response_header,
66 $transaction,
67 new Stream($stream)
68 );
69 }
70  
71 private function createResponseObject(
72 RequestInterface $request,
73 array $headers,
74 TransactionInterface $transaction,
75 StreamInterface $stream
76 ) {
77 $parts = explode(' ', array_shift($headers), 3);
78 $options = ['protocol_version' => substr($parts[0], -3)];
79  
80 if (isset($parts[2])) {
81 $options['reason_phrase'] = $parts[2];
82 }
83  
84 $response = $this->messageFactory->createResponse(
85 $parts[1],
86 $this->headersFromLines($headers),
87 null,
88 $options
89 );
90  
91 // Automatically decode responses when instructed.
92 if ($request->getConfig()->get('decode_content')) {
93 switch ($response->getHeader('Content-Encoding')) {
94 case 'gzip':
95 case 'deflate':
96 $stream = new InflateStream($stream);
97 break;
98 }
99 }
100  
101 // Drain the stream immediately if 'stream' was not enabled.
102 if (!$request->getConfig()['stream']) {
103 $stream = $this->getSaveToBody($request, $stream);
104 }
105  
106 $response->setBody($stream);
107 $transaction->setResponse($response);
108 RequestEvents::emitHeaders($transaction);
109  
110 return $response;
111 }
112  
113 /**
114 * Drain the stream into the destination stream
115 */
116 private function getSaveToBody(
117 RequestInterface $request,
118 StreamInterface $stream
119 ) {
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);
125 } else {
126 // Stream into the default temp stream
127 $saveTo = Stream::factory();
128 }
129  
130 Utils::copyToStream($stream, $saveTo);
131 $saveTo->seek(0);
132 $stream->close();
133  
134 return $saveTo;
135 }
136  
137 private function headersFromLines(array $lines)
138 {
139 $responseHeaders = [];
140  
141 foreach ($lines as $line) {
142 $headerParts = explode(':', $line, 2);
143 $responseHeaders[$headerParts[0]][] = isset($headerParts[1])
144 ? trim($headerParts[1])
145 : '';
146 }
147  
148 return $responseHeaders;
149 }
150  
151 /**
152 * Create a resource and check to ensure it was created successfully
153 *
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
157 *
158 * @return resource
159 * @throws RequestException on error
160 */
161 private function createResource(callable $callback, RequestInterface $request, $options)
162 {
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);
167  
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']} ";
174 }
175 foreach ((array) error_get_last() as $key => $value) {
176 $message .= "[{$key}] {$value} ";
177 }
178 throw new RequestException(trim($message), $request);
179 }
180  
181 return $resource;
182 }
183  
184 /**
185 * Create the stream for the request with the context options.
186 *
187 * @param RequestInterface $request Request being sent
188 * @param mixed $http_response_header Populated by stream wrapper
189 *
190 * @return resource
191 */
192 private function createStream(
193 RequestInterface $request,
194 &$http_response_header
195 ) {
196 static $methods;
197 if (!$methods) {
198 $methods = array_flip(get_class_methods(__CLASS__));
199 }
200  
201 $params = [];
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);
207 }
208 }
209  
210 $this->applyCustomOptions($request, $options);
211 $context = $this->createStreamContext($request, $options, $params);
212  
213 return $this->createStreamResource(
214 $request,
215 $options,
216 $context,
217 $http_response_header
218 );
219 }
220  
221 private function getDefaultOptions(RequestInterface $request)
222 {
223 $headers = AbstractMessage::getHeadersAsString($request);
224  
225 $context = [
226 'http' => [
227 'method' => $request->getMethod(),
228 'header' => trim($headers),
229 'protocol_version' => $request->getProtocolVersion(),
230 'ignore_errors' => true,
231 'follow_location' => 0
232 ]
233 ];
234  
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:";
240 }
241 }
242  
243 return $context;
244 }
245  
246 private function add_proxy(RequestInterface $request, &$options, $value, &$params)
247 {
248 if (!is_array($value)) {
249 $options['http']['proxy'] = $value;
250 } else {
251 $scheme = $request->getScheme();
252 if (isset($value[$scheme])) {
253 $options['http']['proxy'] = $value[$scheme];
254 }
255 }
256 }
257  
258 private function add_timeout(RequestInterface $request, &$options, $value, &$params)
259 {
260 $options['http']['timeout'] = $value;
261 }
262  
263 private function add_verify(RequestInterface $request, &$options, $value, &$params)
264 {
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}");
270 }
271 $options['http']['allow_self_signed'] = true;
272 $options['http']['cafile'] = $value;
273 }
274 } elseif ($value === false) {
275 $options['http']['verify_peer'] = false;
276 }
277 }
278  
279 private function add_cert(RequestInterface $request, &$options, $value, &$params)
280 {
281 if (is_array($value)) {
282 $options['http']['passphrase'] = $value[1];
283 $value = $value[0];
284 }
285  
286 if (!file_exists($value)) {
287 throw new \RuntimeException("SSL certificate not found: {$value}");
288 }
289  
290 $options['http']['local_cert'] = $value;
291 }
292  
293 private function add_debug(RequestInterface $request, &$options, $value, &$params)
294 {
295 static $map = [
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'
306 ];
307  
308 static $args = ['severity', 'message', 'message_code',
309 'bytes_transferred', 'bytes_max'];
310  
311 if (!is_resource($value)) {
312 $value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
313 }
314  
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 . '" ');
321 }
322 fwrite($value, "\n");
323 };
324 }
325  
326 private function applyCustomOptions(
327 RequestInterface $request,
328 array &$options
329 ) {
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');
334 }
335 $options = array_replace_recursive($options, $custom);
336 }
337 }
338  
339 private function createStreamContext(
340 RequestInterface $request,
341 array $options,
342 array $params
343 ) {
344 return $this->createResource(
345 function () use ($request, $options, $params) {
346 return stream_context_create($options, $params);
347 },
348 $request,
349 $options
350 );
351 }
352  
353 private function createStreamResource(
354 RequestInterface $request,
355 array $options,
356 $context,
357 &$http_response_header
358 ) {
359 $url = $request->getUrl();
360  
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);
365 return null;
366 }
367 return fopen($url, 'r', null, $context);
368 },
369 $request,
370 $options
371 );
372 }
373 }