1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright 2013 Google Inc. |
4
|
|
|
* |
5
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
6
|
|
|
* you may not use this file except in compliance with the License. |
7
|
|
|
* You may obtain a copy of the License at |
8
|
|
|
* |
9
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
10
|
|
|
* |
11
|
|
|
* Unless required by applicable law or agreed to in writing, software |
12
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
13
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14
|
|
|
* See the License for the specific language governing permissions and |
15
|
|
|
* limitations under the License. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Http Streams based implementation of Google_IO. |
20
|
|
|
* |
21
|
|
|
* @author Stuart Langley <[email protected]> |
22
|
|
|
*/ |
23
|
|
|
|
24
|
|
|
if (!class_exists('Google_Client')) { |
25
|
|
|
require_once dirname(__FILE__) . '/../autoload.php'; |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
class Google_IO_Stream extends Google_IO_Abstract |
29
|
|
|
{ |
30
|
|
|
const TIMEOUT = "timeout"; |
31
|
|
|
const ZLIB = "compress.zlib://"; |
32
|
|
|
private $options = array(); |
33
|
|
|
private $trappedErrorNumber; |
34
|
|
|
private $trappedErrorString; |
35
|
|
|
|
36
|
|
|
private static $DEFAULT_HTTP_CONTEXT = array( |
37
|
|
|
"follow_location" => 0, |
38
|
|
|
"ignore_errors" => 1, |
39
|
|
|
); |
40
|
|
|
|
41
|
|
|
private static $DEFAULT_SSL_CONTEXT = array( |
42
|
|
|
"verify_peer" => true, |
43
|
|
|
); |
44
|
|
|
|
45
|
|
View Code Duplication |
public function __construct(Google_Client $client) |
|
|
|
|
46
|
|
|
{ |
47
|
|
|
if (!ini_get('allow_url_fopen')) { |
48
|
|
|
$error = 'The stream IO handler requires the allow_url_fopen runtime ' . |
49
|
|
|
'configuration to be enabled'; |
50
|
|
|
$client->getLogger()->critical($error); |
51
|
|
|
throw new Google_IO_Exception($error); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
parent::__construct($client); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Execute an HTTP Request |
59
|
|
|
* |
60
|
|
|
* @param Google_Http_Request $request the http request to be executed |
61
|
|
|
* @return array containing response headers, body, and http code |
62
|
|
|
* @throws Google_IO_Exception on curl or IO error |
63
|
|
|
*/ |
64
|
|
|
public function executeRequest(Google_Http_Request $request) |
65
|
|
|
{ |
66
|
|
|
$default_options = stream_context_get_options(stream_context_get_default()); |
67
|
|
|
|
68
|
|
|
$requestHttpContext = array_key_exists('http', $default_options) ? |
69
|
|
|
$default_options['http'] : array(); |
70
|
|
|
|
71
|
|
|
if ($request->getPostBody()) { |
72
|
|
|
$requestHttpContext["content"] = $request->getPostBody(); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$requestHeaders = $request->getRequestHeaders(); |
76
|
|
|
if ($requestHeaders && is_array($requestHeaders)) { |
|
|
|
|
77
|
|
|
$headers = ""; |
78
|
|
|
foreach ($requestHeaders as $k => $v) { |
79
|
|
|
$headers .= "$k: $v\r\n"; |
80
|
|
|
} |
81
|
|
|
$requestHttpContext["header"] = $headers; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$requestHttpContext["method"] = $request->getRequestMethod(); |
85
|
|
|
$requestHttpContext["user_agent"] = $request->getUserAgent(); |
86
|
|
|
|
87
|
|
|
$requestSslContext = array_key_exists('ssl', $default_options) ? |
88
|
|
|
$default_options['ssl'] : array(); |
89
|
|
|
|
90
|
|
|
if (!array_key_exists("cafile", $requestSslContext)) { |
91
|
|
|
$requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem'; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$options = array( |
95
|
|
|
"http" => array_merge( |
96
|
|
|
self::$DEFAULT_HTTP_CONTEXT, |
97
|
|
|
$requestHttpContext |
98
|
|
|
), |
99
|
|
|
"ssl" => array_merge( |
100
|
|
|
self::$DEFAULT_SSL_CONTEXT, |
101
|
|
|
$requestSslContext |
102
|
|
|
) |
103
|
|
|
); |
104
|
|
|
|
105
|
|
|
$context = stream_context_create($options); |
106
|
|
|
|
107
|
|
|
$url = $request->getUrl(); |
108
|
|
|
|
109
|
|
|
if ($request->canGzip()) { |
110
|
|
|
$url = self::ZLIB . $url; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$this->client->getLogger()->debug( |
114
|
|
|
'Stream request', |
115
|
|
|
array( |
116
|
|
|
'url' => $url, |
117
|
|
|
'method' => $request->getRequestMethod(), |
118
|
|
|
'headers' => $requestHeaders, |
119
|
|
|
'body' => $request->getPostBody() |
120
|
|
|
) |
121
|
|
|
); |
122
|
|
|
|
123
|
|
|
// We are trapping any thrown errors in this method only and |
124
|
|
|
// throwing an exception. |
125
|
|
|
$this->trappedErrorNumber = null; |
126
|
|
|
$this->trappedErrorString = null; |
127
|
|
|
|
128
|
|
|
// START - error trap. |
129
|
|
|
set_error_handler(array($this, 'trapError')); |
130
|
|
|
$fh = fopen($url, 'r', false, $context); |
131
|
|
|
restore_error_handler(); |
132
|
|
|
// END - error trap. |
133
|
|
|
|
134
|
|
|
if ($this->trappedErrorNumber) { |
135
|
|
|
$error = sprintf( |
136
|
|
|
"HTTP Error: Unable to connect: '%s'", |
137
|
|
|
$this->trappedErrorString |
138
|
|
|
); |
139
|
|
|
|
140
|
|
|
$this->client->getLogger()->error('Stream ' . $error); |
141
|
|
|
throw new Google_IO_Exception($error, $this->trappedErrorNumber); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
$response_data = false; |
145
|
|
|
$respHttpCode = self::UNKNOWN_CODE; |
146
|
|
|
if ($fh) { |
147
|
|
|
if (isset($this->options[self::TIMEOUT])) { |
148
|
|
|
stream_set_timeout($fh, $this->options[self::TIMEOUT]); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$response_data = stream_get_contents($fh); |
152
|
|
|
fclose($fh); |
153
|
|
|
|
154
|
|
|
$respHttpCode = $this->getHttpResponseCode($http_response_header); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
if (false === $response_data) { |
158
|
|
|
$error = sprintf( |
159
|
|
|
"HTTP Error: Unable to connect: '%s'", |
160
|
|
|
$respHttpCode |
161
|
|
|
); |
162
|
|
|
|
163
|
|
|
$this->client->getLogger()->error('Stream ' . $error); |
164
|
|
|
throw new Google_IO_Exception($error, $respHttpCode); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$responseHeaders = $this->getHttpResponseHeaders($http_response_header); |
168
|
|
|
|
169
|
|
|
$this->client->getLogger()->debug( |
170
|
|
|
'Stream response', |
171
|
|
|
array( |
172
|
|
|
'code' => $respHttpCode, |
173
|
|
|
'headers' => $responseHeaders, |
174
|
|
|
'body' => $response_data, |
175
|
|
|
) |
176
|
|
|
); |
177
|
|
|
|
178
|
|
|
return array($response_data, $responseHeaders, $respHttpCode); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Set options that update the transport implementation's behavior. |
183
|
|
|
* @param $options |
184
|
|
|
*/ |
185
|
|
|
public function setOptions($options) |
186
|
|
|
{ |
187
|
|
|
$this->options = $options + $this->options; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Method to handle errors, used for error handling around |
192
|
|
|
* stream connection methods. |
193
|
|
|
*/ |
194
|
|
|
public function trapError($errno, $errstr) |
195
|
|
|
{ |
196
|
|
|
$this->trappedErrorNumber = $errno; |
197
|
|
|
$this->trappedErrorString = $errstr; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Set the maximum request time in seconds. |
202
|
|
|
* @param $timeout in seconds |
203
|
|
|
*/ |
204
|
|
|
public function setTimeout($timeout) |
205
|
|
|
{ |
206
|
|
|
$this->options[self::TIMEOUT] = $timeout; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Get the maximum request time in seconds. |
211
|
|
|
* @return timeout in seconds |
212
|
|
|
*/ |
213
|
|
|
public function getTimeout() |
214
|
|
|
{ |
215
|
|
|
return $this->options[self::TIMEOUT]; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Test for the presence of a cURL header processing bug |
220
|
|
|
* |
221
|
|
|
* {@inheritDoc} |
222
|
|
|
* |
223
|
|
|
* @return boolean |
224
|
|
|
*/ |
225
|
|
|
protected function needsQuirk() |
226
|
|
|
{ |
227
|
|
|
return false; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
protected function getHttpResponseCode($response_headers) |
231
|
|
|
{ |
232
|
|
|
$header_count = count($response_headers); |
233
|
|
|
|
234
|
|
|
for ($i = 0; $i < $header_count; $i++) { |
235
|
|
|
$header = $response_headers[$i]; |
236
|
|
|
if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) { |
237
|
|
|
$response = explode(' ', $header); |
238
|
|
|
return $response[1]; |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
return self::UNKNOWN_CODE; |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.