HPCloud-PHP  1.2.0
PHP bindings for HPCloud and OpenStack services.
 All Classes Namespaces Files Functions Variables Pages
Response.php
Go to the documentation of this file.
1 <?php
2 /* ============================================================================
3 (c) Copyright 2012 Hewlett-Packard Development Company, L.P.
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of
8 the Software, and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 ============================================================================ */
22 /**
23  * @file
24  *
25  * A response from a transport.
26  */
27 namespace HPCloud\Transport;
28 
29 /**
30  * A Transport response.
31  *
32  * When one of the transporters makes a request, it will
33  * return one of these as a response. The response is simplified
34  * to the needs of the HP Cloud services and isn't a
35  * general purpose HTTP response object.
36  *
37  * The transport wraps three pieces of information:
38  * - The response body.
39  * - The HTTP headers.
40  * - Other metadata about the request.
41  *
42  * There are two different "modes" for working with a response
43  * object. Either you can work with the raw file data directly
44  * or you can work with the string content of the file.
45  *
46  * You should work with the raw file directly (using file())
47  * if you are working with large objects, such as those returned
48  * from Object Storage.
49  *
50  * You may prefer to work with string data when you are working
51  * with the JSON data returned by the vast majority of requests.
52  */
53 class Response {
54 
55  protected $handle;
56  protected $metadata;
57  protected $headers;
58 
59  /**
60  * Handle error response.
61  *
62  * When a response is a failure, it should pass through this function,
63  * which generates the appropriate exception and then throws it.
64  *
65  * @param int $code
66  * The HTTP status code, e.g. 404, 500.
67  * @param string $err
68  * The error string, as bubbled up.
69  * @param string $uri
70  * The URI.
71  * @param string $method
72  * The HTTP method, e.g. 'HEAD', 'GET', 'DELETE'.
73  * @param string $extra
74  * An extra string of debugging information. (NOT USED)
75  * @throws HPCloud::Exception
76  * A wide variety of HPCloud::Transport exceptions.
77  */
78  public static function failure($code, $err = 'Unknown', $uri = '', $method = '', $extra = '') {
79 
80  // syslog(LOG_WARNING, print_r($extra, TRUE));
81  switch ($code) {
82 
83  case '403':
84  throw new \HPCloud\Transport\ForbiddenException($err);
85  case '401':
86  throw new \HPCloud\Transport\UnauthorizedException($err);
87  case '404':
88  throw new \HPCloud\Transport\FileNotFoundException($err . " ($uri)");
89  case '405':
90  throw new \HPCloud\Transport\MethodNotAllowedException($err . " ($method $uri)");
91  case '409':
92  throw new \HPCloud\Transport\ConflictException($err);
93  case '412':
94  throw new \HPCloud\Transport\LengthRequiredException($err);
95  case '422':
96  throw new \HPCloud\Transport\UnprocessableEntityException($err);
97  case '423':
98  throw new \HPCloud\Transport\LockedException($err);
99  case '500':
100  throw new \HPCloud\Transport\ServerException($err);
101  default:
102  throw new \HPCloud\Exception($err);
103 
104  }
105 
106  }
107 
108  /**
109  * Construct a new Response.
110  *
111  * The Transporter implementations use this to
112  * construct a response.
113  */
114  public function __construct($handle, $metadata, $headers = NULL) {
115  $this->handle = $handle;
116  $this->metadata = $metadata;
117 
118  if (!isset($headers) && isset($metadata['wrapper_data'])) {
119  $headers = $metadata['wrapper_data'];
120  }
121 
122  $this->headers = $this->parseHeaders($headers);
123  }
124 
125  /**
126  * Destroy the object.
127  */
128  public function __destruct() {
129  // There are two issues with fclosing here:
130  // 1. file()'s handle can get closed on it.
131  // 2. If anything else closes the handle, this generates a warning.
132 
133  //fclose($this->handle);
134  }
135 
136  /**
137  * Get the file handle.
138  * This provides raw access to the IO stream. Users
139  * are responsible for all IO management.
140  *
141  * Note that if the handle is closed through this object,
142  * the handle returned by file() will also be closed
143  * (they are one and the same).
144  *
145  * @retval resource
146  * @return resource
147  * A file handle.
148  */
149  public function file() {
150  return $this->handle;
151  }
152 
153  /**
154  * Get the contents of this response as a string.
155  *
156  * This returns the body of the response (no HTTP headers)
157  * as a single string.
158  *
159  * @attention
160  * IMPORTANT: This can only be called once. HTTP streams
161  * handled by PHP's stream wrapper cannot be rewound, and
162  * to keep memory usage low, we don't want to store the
163  * entire content in a string.
164  *
165  * @retval string
166  * @return string
167  * The contents of the response body.
168  */
169  public function content() {
170  $out = '';
171 
172  // XXX: The addition of the Content-Length check is a workaround
173  // for an issue with using PHP Stream Wrappers to communicate with
174  // Identity Service. Apparently, the remote does not provide
175  // an EOF marker, and PHP is too dumb to truncate at Content-Length,
176  // so we have to do it manually.
177  $max = $this->header('Content-Length', NULL);
178  if (isset($this->metadata['unread_bytes']) && isset($max)) {
179  while (!feof($this->handle) && strlen($out) < $max) {
180  $out .= fread($this->handle, 8192);
181  }
182  }
183  else {
184  // XXX: This works fine with CURL, but will not
185  // work with PHP HTTP Stream Wrapper b/c the
186  // wrapper has a bug that will cause this to
187  // hang.
188  $out = stream_get_contents($this->handle);
189  }
190 
191  // Should we close or rewind?
192  // Cannot rewind PHP HTTP streams.
193  fclose($this->handle);
194  //rewind($this->handle);
195 
196  return $out;
197  }
198 
199  /**
200  * Get metadata.
201  *
202  * This returns any available metadata on the file. Not
203  * all Transporters will have any associated metadata.
204  * Some return extra information on the processing of the
205  * data.
206  *
207  * @retval array
208  * @return array
209  * An associative array of metadata about the
210  * transaction resulting in this response.
211  */
212  public function metadata() {
213  return $this->metadata;
214  }
215 
216  /**
217  * Convenience function to retrieve a single header.
218  *
219  * @param string $name
220  * The name of the header.
221  * @param mixed $default
222  * An optional default value.
223  *
224  * @retval mixed
225  * @return mixed
226  * The value, if found, or the default, is specified, or NULL.
227  */
228  public function header($name, $default = NULL) {
229  if (isset($this->headers[$name])) {
230  return $this->headers[$name];
231  }
232  return $default;
233  }
234 
235 
236  /**
237  * Get the HTTP headers.
238  *
239  * This returns an associative array of all of the
240  * headers returned by the remote server.
241  *
242  * These are available even if the stream has been closed.
243  *
244  * @retval array
245  * @return array
246  * The array of headers.
247  */
248  public function headers() {
249  return $this->headers;
250  }
251 
252  /**
253  * Get the HTTP status code.
254  *
255  * This will give the HTTP status codes on successful
256  * transactions.
257  *
258  * A successful transaction is one that does not generate an HTTP
259  * error. This does not necessarily mean that the REST-level request
260  * was fulfilled in the desired way.
261  *
262  * Example: Attempting to create a container in object storage when
263  * such a container already exists results in a 202 response code,
264  * which is an HTTP success code, but indicates failure to fulfill the
265  * requested action.
266  *
267  * Unsuccessful transactions throw exceptions and do not return
268  * Response objects. Example codes of this sort: 403, 404, 500.
269  *
270  * Redirects are typically followed, and thus rarely (if ever)
271  * appear in a Response object.
272  *
273  * @retval int
274  * @return int
275  * The HTTP code, e.g. 200 or 202.
276  */
277  public function status() {
278  return $this->code;
279  }
280 
281  /**
282  * The server-returned status message.
283  *
284  * Typically these follow the HTTP protocol specification's
285  * recommendations. e.g. 200 returns 'OK'.
286  *
287  * @retval string
288  * @return string
289  * A server-generated status message.
290  */
291  public function statusMessage() {
292  return $this->message;
293  }
294 
295  /**
296  * The protocol and version used for this transaction.
297  *
298  * Example: HTTP/1.1
299  *
300  * @retval string
301  * @return string
302  * The protocol name and version.
303  */
304  public function protocol() {
305  return $this->protocol;
306  }
307 
308  public function __toString() {
309  return $this->content();
310  }
311 
312  /**
313  * Parse the HTTP headers.
314  *
315  * @param array $headerArray
316  * An indexed array of headers, as returned by the PHP stream
317  * library.
318  * @retval array
319  * @return array
320  * An associative array of header name/value pairs.
321  */
322  protected function parseHeaders($headerArray) {
323  $ret = array_shift($headerArray);
324  $responseLine = preg_split('/\s/', $ret);
325 
326  $count = count($headerArray);
327  $this->protocol = $responseLine[0];
328  $this->code = (int) $responseLine[1];
329  $this->message = $responseLine[2];
330 
331  // A CONTINUE response means that we will get
332  // a second HTTP status code. Since we have
333  // shifted it off, we recurse. Note that
334  // only CURL returns the 100. PHP's stream
335  // wrapper eats the 100 for us.
336  if ($this->code == 100) {
337  return $this->parseHeaders($headerArray);
338  }
339 
340  $buffer = array();
341  //syslog(LOG_WARNING, $ret);
342  //syslog(LOG_WARNING, print_r($headerArray, TRUE));
343 
344  for ($i = 0; $i < $count; ++$i) {
345  list($name, $value) = explode(':', $headerArray[$i], 2);
346  $name = filter_var($name, FILTER_SANITIZE_STRING);
347  $value = filter_var(trim($value), FILTER_SANITIZE_STRING);
348  $buffer[$name] = $value;
349  }
350 
351  return $buffer;
352  }
353 
354 }