Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}