| 3 |
liveuser |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace Guzzle\Stream;
|
|
|
4 |
|
|
|
5 |
use Guzzle\Common\Exception\InvalidArgumentException;
|
|
|
6 |
use Guzzle\Common\Exception\RuntimeException;
|
|
|
7 |
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
|
|
|
8 |
use Guzzle\Http\Message\RequestInterface;
|
|
|
9 |
use Guzzle\Http\Url;
|
|
|
10 |
|
|
|
11 |
/**
|
|
|
12 |
* Factory used to create fopen streams using PHP's http and https stream wrappers
|
|
|
13 |
*
|
|
|
14 |
* Note: PHP's http stream wrapper only supports streaming downloads. It does not support streaming uploads.
|
|
|
15 |
*/
|
|
|
16 |
class PhpStreamRequestFactory implements StreamRequestFactoryInterface
|
|
|
17 |
{
|
|
|
18 |
/** @var resource Stream context options */
|
|
|
19 |
protected $context;
|
|
|
20 |
|
|
|
21 |
/** @var array Stream context */
|
|
|
22 |
protected $contextOptions;
|
|
|
23 |
|
|
|
24 |
/** @var Url Stream URL */
|
|
|
25 |
protected $url;
|
|
|
26 |
|
|
|
27 |
/** @var array Last response headers received by the HTTP request */
|
|
|
28 |
protected $lastResponseHeaders;
|
|
|
29 |
|
|
|
30 |
/**
|
|
|
31 |
* {@inheritdoc}
|
|
|
32 |
*
|
|
|
33 |
* The $params array can contain the following custom keys specific to the PhpStreamRequestFactory:
|
|
|
34 |
* - stream_class: The name of a class to create instead of a Guzzle\Stream\Stream object
|
|
|
35 |
*/
|
|
|
36 |
public function fromRequest(RequestInterface $request, $context = array(), array $params = array())
|
|
|
37 |
{
|
|
|
38 |
if (is_resource($context)) {
|
|
|
39 |
$this->contextOptions = stream_context_get_options($context);
|
|
|
40 |
$this->context = $context;
|
|
|
41 |
} elseif (is_array($context) || !$context) {
|
|
|
42 |
$this->contextOptions = $context;
|
|
|
43 |
$this->createContext($params);
|
|
|
44 |
} elseif ($context) {
|
|
|
45 |
throw new InvalidArgumentException('$context must be an array or resource');
|
|
|
46 |
}
|
|
|
47 |
|
|
|
48 |
// Dispatch the before send event
|
|
|
49 |
$request->dispatch('request.before_send', array(
|
|
|
50 |
'request' => $request,
|
|
|
51 |
'context' => $this->context,
|
|
|
52 |
'context_options' => $this->contextOptions
|
|
|
53 |
));
|
|
|
54 |
|
|
|
55 |
$this->setUrl($request);
|
|
|
56 |
$this->addDefaultContextOptions($request);
|
|
|
57 |
$this->addSslOptions($request);
|
|
|
58 |
$this->addBodyOptions($request);
|
|
|
59 |
$this->addProxyOptions($request);
|
|
|
60 |
|
|
|
61 |
// Create the file handle but silence errors
|
|
|
62 |
return $this->createStream($params)
|
|
|
63 |
->setCustomData('request', $request)
|
|
|
64 |
->setCustomData('response_headers', $this->getLastResponseHeaders());
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
/**
|
|
|
68 |
* Set an option on the context and the internal options array
|
|
|
69 |
*
|
|
|
70 |
* @param string $wrapper Stream wrapper name of http
|
|
|
71 |
* @param string $name Context name
|
|
|
72 |
* @param mixed $value Context value
|
|
|
73 |
* @param bool $overwrite Set to true to overwrite an existing value
|
|
|
74 |
*/
|
|
|
75 |
protected function setContextValue($wrapper, $name, $value, $overwrite = false)
|
|
|
76 |
{
|
|
|
77 |
if (!isset($this->contextOptions[$wrapper])) {
|
|
|
78 |
$this->contextOptions[$wrapper] = array($name => $value);
|
|
|
79 |
} elseif (!$overwrite && isset($this->contextOptions[$wrapper][$name])) {
|
|
|
80 |
return;
|
|
|
81 |
}
|
|
|
82 |
$this->contextOptions[$wrapper][$name] = $value;
|
|
|
83 |
stream_context_set_option($this->context, $wrapper, $name, $value);
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
/**
|
|
|
87 |
* Create a stream context
|
|
|
88 |
*
|
|
|
89 |
* @param array $params Parameter array
|
|
|
90 |
*/
|
|
|
91 |
protected function createContext(array $params)
|
|
|
92 |
{
|
|
|
93 |
$options = $this->contextOptions;
|
|
|
94 |
$this->context = $this->createResource(function () use ($params, $options) {
|
|
|
95 |
return stream_context_create($options, $params);
|
|
|
96 |
});
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
/**
|
|
|
100 |
* Get the last response headers received by the HTTP request
|
|
|
101 |
*
|
|
|
102 |
* @return array
|
|
|
103 |
*/
|
|
|
104 |
public function getLastResponseHeaders()
|
|
|
105 |
{
|
|
|
106 |
return $this->lastResponseHeaders;
|
|
|
107 |
}
|
|
|
108 |
|
|
|
109 |
/**
|
|
|
110 |
* Adds the default context options to the stream context options
|
|
|
111 |
*
|
|
|
112 |
* @param RequestInterface $request Request
|
|
|
113 |
*/
|
|
|
114 |
protected function addDefaultContextOptions(RequestInterface $request)
|
|
|
115 |
{
|
|
|
116 |
$this->setContextValue('http', 'method', $request->getMethod());
|
|
|
117 |
$headers = $request->getHeaderLines();
|
|
|
118 |
|
|
|
119 |
// "Connection: close" is required to get streams to work in HTTP 1.1
|
|
|
120 |
if (!$request->hasHeader('Connection')) {
|
|
|
121 |
$headers[] = 'Connection: close';
|
|
|
122 |
}
|
|
|
123 |
|
|
|
124 |
$this->setContextValue('http', 'header', $headers);
|
|
|
125 |
$this->setContextValue('http', 'protocol_version', $request->getProtocolVersion());
|
|
|
126 |
$this->setContextValue('http', 'ignore_errors', true);
|
|
|
127 |
}
|
|
|
128 |
|
|
|
129 |
/**
|
|
|
130 |
* Set the URL to use with the factory
|
|
|
131 |
*
|
|
|
132 |
* @param RequestInterface $request Request that owns the URL
|
|
|
133 |
*/
|
|
|
134 |
protected function setUrl(RequestInterface $request)
|
|
|
135 |
{
|
|
|
136 |
$this->url = $request->getUrl(true);
|
|
|
137 |
|
|
|
138 |
// Check for basic Auth username
|
|
|
139 |
if ($request->getUsername()) {
|
|
|
140 |
$this->url->setUsername($request->getUsername());
|
|
|
141 |
}
|
|
|
142 |
|
|
|
143 |
// Check for basic Auth password
|
|
|
144 |
if ($request->getPassword()) {
|
|
|
145 |
$this->url->setPassword($request->getPassword());
|
|
|
146 |
}
|
|
|
147 |
}
|
|
|
148 |
|
|
|
149 |
/**
|
|
|
150 |
* Add SSL options to the stream context
|
|
|
151 |
*
|
|
|
152 |
* @param RequestInterface $request Request
|
|
|
153 |
*/
|
|
|
154 |
protected function addSslOptions(RequestInterface $request)
|
|
|
155 |
{
|
|
|
156 |
if ($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)) {
|
|
|
157 |
$this->setContextValue('ssl', 'verify_peer', true, true);
|
|
|
158 |
if ($cafile = $request->getCurlOptions()->get(CURLOPT_CAINFO)) {
|
|
|
159 |
$this->setContextValue('ssl', 'cafile', $cafile, true);
|
|
|
160 |
}
|
|
|
161 |
} else {
|
|
|
162 |
$this->setContextValue('ssl', 'verify_peer', false, true);
|
|
|
163 |
}
|
|
|
164 |
}
|
|
|
165 |
|
|
|
166 |
/**
|
|
|
167 |
* Add body (content) specific options to the context options
|
|
|
168 |
*
|
|
|
169 |
* @param RequestInterface $request
|
|
|
170 |
*/
|
|
|
171 |
protected function addBodyOptions(RequestInterface $request)
|
|
|
172 |
{
|
|
|
173 |
// Add the content for the request if needed
|
|
|
174 |
if (!($request instanceof EntityEnclosingRequestInterface)) {
|
|
|
175 |
return;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
if (count($request->getPostFields())) {
|
|
|
179 |
$this->setContextValue('http', 'content', (string) $request->getPostFields(), true);
|
|
|
180 |
} elseif ($request->getBody()) {
|
|
|
181 |
$this->setContextValue('http', 'content', (string) $request->getBody(), true);
|
|
|
182 |
}
|
|
|
183 |
|
|
|
184 |
// Always ensure a content-length header is sent
|
|
|
185 |
if (isset($this->contextOptions['http']['content'])) {
|
|
|
186 |
$headers = isset($this->contextOptions['http']['header']) ? $this->contextOptions['http']['header'] : array();
|
|
|
187 |
$headers[] = 'Content-Length: ' . strlen($this->contextOptions['http']['content']);
|
|
|
188 |
$this->setContextValue('http', 'header', $headers, true);
|
|
|
189 |
}
|
|
|
190 |
}
|
|
|
191 |
|
|
|
192 |
/**
|
|
|
193 |
* Add proxy parameters to the context if needed
|
|
|
194 |
*
|
|
|
195 |
* @param RequestInterface $request Request
|
|
|
196 |
*/
|
|
|
197 |
protected function addProxyOptions(RequestInterface $request)
|
|
|
198 |
{
|
|
|
199 |
if ($proxy = $request->getCurlOptions()->get(CURLOPT_PROXY)) {
|
|
|
200 |
$this->setContextValue('http', 'proxy', $proxy);
|
|
|
201 |
}
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
/**
|
|
|
205 |
* Create the stream for the request with the context options
|
|
|
206 |
*
|
|
|
207 |
* @param array $params Parameters of the stream
|
|
|
208 |
*
|
|
|
209 |
* @return StreamInterface
|
|
|
210 |
*/
|
|
|
211 |
protected function createStream(array $params)
|
|
|
212 |
{
|
|
|
213 |
$http_response_header = null;
|
|
|
214 |
$url = $this->url;
|
|
|
215 |
$context = $this->context;
|
|
|
216 |
$fp = $this->createResource(function () use ($context, $url, &$http_response_header) {
|
|
|
217 |
return fopen((string) $url, 'r', false, $context);
|
|
|
218 |
});
|
|
|
219 |
|
|
|
220 |
// Determine the class to instantiate
|
|
|
221 |
$className = isset($params['stream_class']) ? $params['stream_class'] : __NAMESPACE__ . '\\Stream';
|
|
|
222 |
|
|
|
223 |
/** @var $stream StreamInterface */
|
|
|
224 |
$stream = new $className($fp);
|
|
|
225 |
|
|
|
226 |
// Track the response headers of the request
|
|
|
227 |
if (isset($http_response_header)) {
|
|
|
228 |
$this->lastResponseHeaders = $http_response_header;
|
|
|
229 |
$this->processResponseHeaders($stream);
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
return $stream;
|
|
|
233 |
}
|
|
|
234 |
|
|
|
235 |
/**
|
|
|
236 |
* Process response headers
|
|
|
237 |
*
|
|
|
238 |
* @param StreamInterface $stream
|
|
|
239 |
*/
|
|
|
240 |
protected function processResponseHeaders(StreamInterface $stream)
|
|
|
241 |
{
|
|
|
242 |
// Set the size on the stream if it was returned in the response
|
|
|
243 |
foreach ($this->lastResponseHeaders as $header) {
|
|
|
244 |
if ((stripos($header, 'Content-Length:')) === 0) {
|
|
|
245 |
$stream->setSize(trim(substr($header, 15)));
|
|
|
246 |
}
|
|
|
247 |
}
|
|
|
248 |
}
|
|
|
249 |
|
|
|
250 |
/**
|
|
|
251 |
* Create a resource and check to ensure it was created successfully
|
|
|
252 |
*
|
|
|
253 |
* @param callable $callback Closure to invoke that must return a valid resource
|
|
|
254 |
*
|
|
|
255 |
* @return resource
|
|
|
256 |
* @throws RuntimeException on error
|
|
|
257 |
*/
|
|
|
258 |
protected function createResource($callback)
|
|
|
259 |
{
|
|
|
260 |
// Turn off error reporting while we try to initiate the request
|
|
|
261 |
$level = error_reporting(0);
|
|
|
262 |
$resource = call_user_func($callback);
|
|
|
263 |
error_reporting($level);
|
|
|
264 |
|
|
|
265 |
// If the resource could not be created, then grab the last error and throw an exception
|
|
|
266 |
if (false === $resource) {
|
|
|
267 |
$message = 'Error creating resource. ';
|
|
|
268 |
foreach (error_get_last() as $key => $value) {
|
|
|
269 |
$message .= "[{$key}] {$value} ";
|
|
|
270 |
}
|
|
|
271 |
throw new RuntimeException(trim($message));
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
return $resource;
|
|
|
275 |
}
|
|
|
276 |
}
|