1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
<?php
/**
* Phergie
*
* PHP version 5
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.
* It is also available through the world-wide-web at this URL:
* http://phergie.org/license
*
* @category Phergie
* @package Phergie_Plugin_Http
* @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Http
*/
/**
* Provides an HTTP client for plugins to use in contacting web services or
* retrieving feeds or web pages.
*
* @category Phergie
* @package Phergie_Plugin_Http
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Http
* @uses extension simplexml optional
* @uses extension json optional
*/
class Phergie_Plugin_Http extends Phergie_Plugin_Abstract
{
/**
* Response to the last executed HTTP request
*
* @var Phergie_Plugin_Http_Response
*/
protected $response;
/**
* Mapping of content types to handlers for them
*
* @var array
*/
protected $handlers;
/**
* Initializes the handler lookup table.
*
* @return void
*/
public function onLoad()
{
$this->handlers = array(
'(?:application|text)/xml(?:;.*)?' => 'simplexml_load_string',
'(?:(?:application|text)/(?:x-)?json)|text/javascript.*' => 'json_decode',
);
if (is_array($this->config['http.handlers'])) {
$this->handlers = array_merge(
$this->handlers,
$this->config['http.handlers']
);
}
}
/**
* Sets a handler callback for a content type, which is called when a
* response of that content type is received to perform any needed
* transformations on the response body content before storing it in the
* response object. Note that the calling plugin is responsible for
* indicating any dependencies related to specified handler callbacks.
*
* @param string $type PCRE regular expression (without delimiters) that
* matches one or more MIME types
* @param callback $callback Callback to execute when a response of a content
* type matched by $type is encountered
*
* @return Phergie_Plugin_Http Provides a fluent interface
*/
public function setHandler($type, $callback)
{
if (!is_callable($callback)) {
throw new Phergie_Plugin_Exception(
'Invalid callback specified',
Phergie_Plugin_Exception::ERR_FATAL_ERROR
);
}
$this->handlers[$type] = $callback;
return $this;
}
/**
* Supporting method that parses the status line of an HTTP response
* message.
*
* @param string $status Status line
*
* @return array Associative array containing the HTTP version, response
* code, and response description
*/
protected function parseStatusLine($status)
{
$parts = explode(' ', $status, 3);
$parsed = array(
'version' => str_replace('HTTP/', '', $parts[0]),
'code' => $parts[1],
'message' => rtrim($parts[2])
);
return $parsed;
}
/**
* Supporting method that acts as an error handler to intercept HTTP
* responses resulting in PHP-level errors.
*
* @param int $errno Level of the error raised
* @param string $errstr Error message
* @param string $errfile Name of the file in which the error was raised
* @param string $errline Line number on which the error was raised
*
* @return bool Always returns TRUE to allow normal execution to
* continue once this method terminates
*/
protected function handleError($errno, $errstr, $errfile, $errline)
{
if ($httperr = strstr($errstr, 'HTTP/')) {
$parts = $this->parseStatusLine($httperr);
$this->response
->setCode($parts['code'])
->setMessage($parts['message']);
}
return true;
}
/**
* Supporting method that executes a request and handles the response.
*
* @param string $url URL to request
* @param array $context Associative array of stream context parameters
*
* @return Phergie_Plugin_Http_Response Object representing the response
* resulting from the request
*/
public function request($url, array $context)
{
$this->response = new Phergie_Plugin_Http_Response;
$url = (string) $url;
$context = stream_context_create(array('http' => $context));
set_error_handler(array($this, 'handleError'), E_WARNING);
$stream = fopen($url, 'r', false, $context);
if ($stream) {
$meta = stream_get_meta_data($stream);
$status = $this->parseStatusLine($meta['wrapper_data'][0]);
$code = $status['code'];
$message = $status['message'];
$headers = array();
foreach (array_slice($meta['wrapper_data'], 1) as $header) {
list($name, $value) = explode(': ', $header, 2);
$headers[$name] = $value;
}
unset($meta['wrapper_data']);
$this->response
->setCode($code)
->setMessage($message)
->setHeaders($headers)
->setMeta($meta);
$body = stream_get_contents($stream);
$type = $this->response->getHeaders('content-type');
foreach ($this->handlers as $expr => $handler) {
if (preg_match('#^' . $expr . '$#i', $type)) {
$body = call_user_func($handler, $body);
}
}
$this->response->setContent($body);
}
restore_error_handler();
return $this->response;
}
/**
* Performs a GET request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function get($url, array $query = array(), array $context = array())
{
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'GET';
return $this->request($url, $context);
}
/**
* Performs a HEAD request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function head($url, array $query = array(), array $context = array())
{
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'HEAD';
return $this->request($url, $context);
}
/**
* Performs a POST request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $post Optional associative array of parameters
* constituting the POST request body if it is using the
* traditional URL-encoded format
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function post($url, array $query = array(), array $post = array(), array $context = array())
{
if (!empty($params)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'POST';
if (!empty($post)
&& (!empty($context['header'])
xor stripos($context['header'], 'Content-Type'))
) {
if (!empty($context['header'])) {
$context['header'] .= "\r\n";
} else {
$context['header'] = '';
}
$context['header'] .=
'Content-Type: application/x-www-form-urlencoded';
$context['content'] = http_build_query($post);
}
return $this->request($url, $context);
}
}
|