1 : <?php
2 : /**
3 : * File: RequestCore
4 : * Handles all linear and parallel HTTP requests using cURL and manages the responses.
5 : *
6 : * Version:
7 : * 2010.03.21
8 : *
9 : * Copyright:
10 : * 2006-2010 Ryan Parman, Foleeo Inc., and contributors.
11 : *
12 : * License:
13 : * Simplified BSD License - http://opensource.org/licenses/bsd-license.php
14 : */
15 :
16 :
17 : /*%******************************************************************************************%*/
18 : // EXCEPTIONS
19 :
20 : /**
21 : * Exception: RequestCore_Exception
22 : * Default RequestCore Exception.
23 : */
24 52 : class RequestCore_Exception extends Exception {}
25 :
26 :
27 : /*%******************************************************************************************%*/
28 : // CLASS
29 :
30 : /**
31 : * Class: RequestCore
32 : * Container for all request-related methods.
33 : */
34 : class RequestCore
35 52 : {
36 : /**
37 : * Property: request_url
38 : * The URL being requested.
39 : */
40 : var $request_url;
41 :
42 : /**
43 : * Property: request_headers
44 : * The headers being sent in the request.
45 : */
46 : var $request_headers;
47 :
48 : /**
49 : * Property: request_body
50 : * The body being sent in the request.
51 : */
52 : var $request_body;
53 :
54 : /**
55 : * Property: response
56 : * The response returned by the request.
57 : */
58 : var $response;
59 :
60 : /**
61 : * Property: response_headers
62 : * The headers returned by the request.
63 : */
64 : var $response_headers;
65 :
66 : /**
67 : * Property: response_body
68 : * The body returned by the request.
69 : */
70 : var $response_body;
71 :
72 : /**
73 : * Property: response_code
74 : * The HTTP status code returned by the request.
75 : */
76 : var $response_code;
77 :
78 : /**
79 : * Property: response_info
80 : * Additional response data.
81 : */
82 : var $response_info;
83 :
84 : /**
85 : * Property: curl_handle
86 : * The handle for the cURL object.
87 : */
88 : var $curl_handle;
89 :
90 : /**
91 : * Property: method
92 : * The method by which the request is being made.
93 : */
94 : var $method;
95 :
96 : /**
97 : * Property: proxy
98 : * Stores the proxy settings to use for the request.
99 : */
100 : var $proxy = null;
101 :
102 : /**
103 : * Property: username
104 : * The username to use for the request.
105 : */
106 : var $username = null;
107 :
108 : /**
109 : * Property: password
110 : * The password to use for the request.
111 : */
112 : var $password = null;
113 :
114 : /**
115 : * Property: curlopts
116 : * Custom CURLOPT settings.
117 : */
118 : var $curlopts = null;
119 :
120 : /**
121 : * Property: request_class
122 : * The default class to use for HTTP Requests (defaults to <RequestCore>).
123 : */
124 : var $request_class = 'RequestCore';
125 :
126 : /**
127 : * Property: response_class
128 : * The default class to use for HTTP Responses (defaults to <ResponseCore>).
129 : */
130 : var $response_class = 'ResponseCore';
131 :
132 : /**
133 : * Property: useragent
134 : * Default useragent string to use.
135 : */
136 : var $useragent = 'RequestCore/1.1';
137 :
138 : /**
139 : * Constant: HTTP_GET
140 : * GET HTTP Method
141 : */
142 : const HTTP_GET = 'GET';
143 :
144 : /**
145 : * Constant: HTTP_POST
146 : * POST HTTP Method
147 : */
148 : const HTTP_POST = 'POST';
149 :
150 : /**
151 : * Constant: HTTP_PUT
152 : * PUT HTTP Method
153 : */
154 : const HTTP_PUT = 'PUT';
155 :
156 : /**
157 : * Constant: HTTP_DELETE
158 : * DELETE HTTP Method
159 : */
160 : const HTTP_DELETE = 'DELETE';
161 :
162 : /**
163 : * Constant: HTTP_HEAD
164 : * HEAD HTTP Method
165 : */
166 : const HTTP_HEAD = 'HEAD';
167 :
168 :
169 : /*%******************************************************************************************%*/
170 : // CONSTRUCTOR
171 :
172 : /**
173 : * Method: __construct()
174 : * The constructor
175 : *
176 : * Access:
177 : * public
178 : *
179 : * Parameters:
180 : * $url - _string_ (Optional) The URL to request or service endpoint to query.
181 : * $proxy - _string_ (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
182 : * $helpers - _array_ (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class.
183 : *
184 : * Returns:
185 : * `$this`
186 : */
187 : public function __construct($url = null, $proxy = null, $helpers = null)
188 : {
189 : // Set some default values.
190 44 : $this->request_url = $url;
191 44 : $this->method = $this::HTTP_GET;
192 44 : $this->request_headers = array();
193 44 : $this->request_body = '';
194 :
195 : // Set a new Request class if one was set.
196 44 : if (isset($helpers['request']) && !empty($helpers['request']))
197 44 : {
198 1 : $this->request_class = $helpers['request'];
199 1 : }
200 :
201 : // Set a new Request class if one was set.
202 44 : if (isset($helpers['response']) && !empty($helpers['response']))
203 44 : {
204 1 : $this->response_class = $helpers['response'];
205 1 : }
206 :
207 : if ($proxy)
208 44 : {
209 1 : $this->set_proxy($proxy);
210 1 : }
211 :
212 44 : return $this;
213 : }
214 :
215 :
216 : /*%******************************************************************************************%*/
217 : // REQUEST METHODS
218 :
219 : /**
220 : * Method: set_credentials()
221 : * Sets the credentials to use for authentication.
222 : *
223 : * Access:
224 : * public
225 : *
226 : * Parameters:
227 : * $user - _string_ (Required) The username to authenticate with.
228 : * $pass - _string_ (Required) The password to authenticate with.
229 : *
230 : * Returns:
231 : * `$this`
232 : */
233 : public function set_credentials($user, $pass)
234 : {
235 2 : $this->username = $user;
236 2 : $this->password = $pass;
237 2 : return $this;
238 : }
239 :
240 : /**
241 : * Method: add_header()
242 : * Adds a custom HTTP header to the cURL request.
243 : *
244 : * Access:
245 : * public
246 : *
247 : * Parameters:
248 : * $key - _string_ (Required) The custom HTTP header to set.
249 : * $value - _mixed_ (Required) The value to assign to the custom HTTP header.
250 : *
251 : * Returns:
252 : * `$this`
253 : */
254 : public function add_header($key, $value)
255 : {
256 35 : $this->request_headers[$key] = $value;
257 35 : return $this;
258 : }
259 :
260 : /**
261 : * Method: remove_header()
262 : * Removes an HTTP header from the cURL request.
263 : *
264 : * Access:
265 : * public
266 : *
267 : * Parameters:
268 : * $key - _string_ (Required) The custom HTTP header to set.
269 : *
270 : * Returns:
271 : * `$this`
272 : */
273 : public function remove_header($key)
274 : {
275 1 : if (isset($this->request_headers[$key]))
276 1 : {
277 1 : unset($this->request_headers[$key]);
278 1 : }
279 1 : return $this;
280 : }
281 :
282 : /**
283 : * Method: set_method()
284 : * Set the method type for the request.
285 : *
286 : * Access:
287 : * public
288 : *
289 : * Parameters:
290 : * $method - _string_ (Required) One of the following constants: <HTTP_GET>, <HTTP_POST>, <HTTP_PUT>, <HTTP_HEAD>, <HTTP_DELETE>.
291 : *
292 : * Returns:
293 : * `$this`
294 : */
295 : public function set_method($method)
296 : {
297 5 : $this->method = strtoupper($method);
298 5 : return $this;
299 : }
300 :
301 : /**
302 : * Method: set_useragent()
303 : * Sets a custom useragent string for the class.
304 : *
305 : * Access:
306 : * public
307 : *
308 : * Parameters:
309 : * $ua - _string_ (Required) The useragent string to use.
310 : *
311 : * Returns:
312 : * `$this`
313 : */
314 : public function set_useragent($ua)
315 : {
316 1 : $this->useragent = $ua;
317 1 : return $this;
318 : }
319 :
320 : /**
321 : * Method: set_body()
322 : * Set the body to send in the request.
323 : *
324 : * Access:
325 : * public
326 : *
327 : * Parameters:
328 : * $body - _string_ (Required) The textual content to send along in the body of the request.
329 : *
330 : * Returns:
331 : * `$this`
332 : */
333 : public function set_body($body)
334 : {
335 1 : $this->request_body = $body;
336 1 : return $this;
337 : }
338 :
339 : /**
340 : * Method: set_request_url()
341 : * Set the URL to make the request to.
342 : *
343 : * Access:
344 : * public
345 : *
346 : * Parameters:
347 : * $url - _string_ (Required) The URL to make the request to.
348 : *
349 : * Returns:
350 : * `$this`
351 : */
352 : public function set_request_url($url)
353 : {
354 5 : $this->request_url = $url;
355 5 : return $this;
356 : }
357 :
358 : /**
359 : * Method: set_curlopts()
360 : * Set additional CURLOPT settings. These will merge with the default settings, and override if there is a duplicate.
361 : *
362 : * Access:
363 : * public
364 : *
365 : * Parameters:
366 : * $curlopts - _array_ (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
367 : *
368 : * Returns:
369 : * `$this`
370 : */
371 : public function set_curlopts($curlopts)
372 : {
373 0 : $this->curlopts = $curlopts;
374 0 : return $this;
375 : }
376 :
377 : /**
378 : * Method: set_proxy()
379 : * Set the proxy to use for making requests.
380 : *
381 : * Access:
382 : * public
383 : *
384 : * Parameters:
385 : * $proxy - _string_ (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
386 : *
387 : * Returns:
388 : * `$this`
389 : */
390 : public function set_proxy($proxy)
391 : {
392 2 : $proxy = parse_url($proxy);
393 2 : $proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null;
394 2 : $proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null;
395 2 : $proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null;
396 2 : $this->proxy = $proxy;
397 2 : return $this;
398 : }
399 :
400 :
401 : /*%******************************************************************************************%*/
402 : // PREPARE, SEND, AND PROCESS REQUEST
403 :
404 : /**
405 : * Method: prep_request()
406 : * Prepares and adds the details of the cURL request. This can be passed along to a `curl_multi_exec()` function.
407 : *
408 : * Access:
409 : * public
410 : *
411 : * Returns:
412 : * The handle for the cURL object.
413 : */
414 : public function prep_request()
415 : {
416 35 : $this->add_header('Expect', '100-continue');
417 35 : $this->add_header('Connection', 'close');
418 :
419 35 : $curl_handle = curl_init();
420 :
421 : // Set default options.
422 35 : curl_setopt($curl_handle, CURLOPT_URL, $this->request_url);
423 35 : curl_setopt($curl_handle, CURLOPT_FILETIME, true);
424 35 : curl_setopt($curl_handle, CURLOPT_FRESH_CONNECT, false);
425 35 : curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
426 35 : curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, true);
427 35 : curl_setopt($curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED);
428 35 : curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
429 35 : curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5);
430 35 : curl_setopt($curl_handle, CURLOPT_HEADER, true);
431 35 : curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
432 35 : curl_setopt($curl_handle, CURLOPT_TIMEOUT, 5184000);
433 35 : curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 120);
434 35 : curl_setopt($curl_handle, CURLOPT_NOSIGNAL, true);
435 35 : curl_setopt($curl_handle, CURLOPT_REFERER, $this->request_url);
436 35 : curl_setopt($curl_handle, CURLOPT_USERAGENT, $this->useragent);
437 :
438 : // Merge in the CURLOPTs
439 35 : if (isset($this->curlopts) && sizeof($this->curlopts) > 0)
440 35 : {
441 0 : foreach ($this->curlopts as $k => $v)
442 : {
443 0 : curl_setopt($curl_handle, $k, $v);
444 0 : }
445 0 : }
446 :
447 : // Enable a proxy connection if requested.
448 35 : if ($this->proxy)
449 35 : {
450 1 : curl_setopt($curl_handle, CURLOPT_HTTPPROXYTUNNEL, true);
451 :
452 1 : $host = $this->proxy['host'];
453 1 : $host .= ($this->proxy['port']) ? ':' . $this->proxy['port'] : '';
454 1 : curl_setopt($curl_handle, CURLOPT_PROXY, $host);
455 :
456 1 : if (isset($this->proxy['user']) && isset($this->proxy['pass']))
457 1 : {
458 1 : curl_setopt($curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']);
459 1 : }
460 1 : }
461 :
462 : // Set credentials for HTTP Basic/Digest Authentication.
463 35 : if ($this->username && $this->password)
464 35 : {
465 2 : curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
466 2 : curl_setopt($curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password);
467 2 : }
468 :
469 : // Handle the encoding if we can.
470 35 : if (extension_loaded('zlib'))
471 35 : {
472 35 : curl_setopt($curl_handle, CURLOPT_ENCODING, '');
473 35 : }
474 :
475 : // Process custom headers
476 35 : if (isset($this->request_headers) && count($this->request_headers))
477 35 : {
478 35 : $temp_headers = array();
479 :
480 35 : foreach ($this->request_headers as $k => $v)
481 : {
482 35 : $temp_headers[] = $k . ': ' . $v;
483 35 : }
484 :
485 35 : curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $temp_headers);
486 35 : }
487 :
488 35 : switch ($this->method)
489 : {
490 35 : case $this::HTTP_PUT:
491 3 : curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT');
492 3 : curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
493 3 : break;
494 :
495 32 : case $this::HTTP_POST:
496 1 : curl_setopt($curl_handle, CURLOPT_POST, true);
497 1 : curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
498 1 : break;
499 :
500 31 : case $this::HTTP_HEAD:
501 1 : curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this::HTTP_HEAD);
502 1 : curl_setopt($curl_handle, CURLOPT_NOBODY, 1);
503 1 : break;
504 :
505 30 : default:
506 30 : curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this->method);
507 30 : curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
508 30 : break;
509 35 : }
510 :
511 35 : return $curl_handle;
512 : }
513 :
514 : /**
515 : * Method: process_response()
516 : * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the data stored in the <curl_handle> and <response> properties unless replacement data is passed in via parameters.
517 : *
518 : * Access:
519 : * public
520 : *
521 : * Parameters:
522 : * $curl_handle - _string_ (Optional) The reference to the already executed cURL request.
523 : * $response - _string_ (Optional) The actual response content itself that needs to be parsed.
524 : *
525 : * Returns:
526 : * <ResponseCore> object
527 : */
528 : public function process_response($curl_handle = null, $response = null)
529 : {
530 : // Accept a custom one if it's passed.
531 28 : if ($curl_handle && $response)
532 28 : {
533 25 : $this->curl_handle = $curl_handle;
534 25 : $this->response = $response;
535 25 : }
536 :
537 : // As long as this came back as a valid resource...
538 28 : if (is_resource($this->curl_handle))
539 28 : {
540 : // Determine what's what.
541 25 : $header_size = curl_getinfo($this->curl_handle, CURLINFO_HEADER_SIZE);
542 25 : $this->response_headers = substr($this->response, 0, $header_size);
543 25 : $this->response_body = substr($this->response, $header_size);
544 25 : $this->response_code = curl_getinfo($this->curl_handle, CURLINFO_HTTP_CODE);
545 25 : $this->response_info = curl_getinfo($this->curl_handle);
546 :
547 : // Parse out the headers
548 25 : $this->response_headers = explode("\r\n\r\n", trim($this->response_headers));
549 25 : $this->response_headers = array_pop($this->response_headers);
550 25 : $this->response_headers = explode("\r\n", $this->response_headers);
551 25 : array_shift($this->response_headers);
552 :
553 : // Loop through and split up the headers.
554 25 : $header_assoc = array();
555 25 : foreach ($this->response_headers as $header)
556 : {
557 25 : $kv = explode(': ', $header);
558 25 : $header_assoc[strtolower($kv[0])] = $kv[1];
559 25 : }
560 :
561 : // Reset the headers to the appropriate property.
562 25 : $this->response_headers = $header_assoc;
563 25 : $this->response_headers['_info'] = $this->response_info;
564 25 : $this->response_headers['_info']['method'] = $this->method;
565 :
566 25 : if ($curl_handle && $response)
567 25 : {
568 25 : return new $this->response_class($this->response_headers, $this->response_body, $this->response_code);
569 : }
570 0 : }
571 :
572 : // Return false
573 4 : return false;
574 : }
575 :
576 : /**
577 : * Method: send_request()
578 : * Sends the request, calling necessary utility functions to update built-in properties.
579 : *
580 : * Access:
581 : * public
582 : *
583 : * Parameters:
584 : * $parse - _boolean_ (Optional) Whether to parse the response with ResponseCore or not.
585 : *
586 : * Returns:
587 : * _string_ The resulting unparsed data from the request.
588 : */
589 : public function send_request($parse = false)
590 : {
591 26 : $curl_handle = $this->prep_request();
592 26 : $this->response = curl_exec($curl_handle);
593 26 : $parsed_response = $this->process_response($curl_handle, $this->response);
594 :
595 26 : curl_close($curl_handle);
596 :
597 : if ($parse)
598 26 : {
599 8 : return $parsed_response;
600 : }
601 :
602 18 : return $this->response;
603 : }
604 :
605 : /**
606 : * Method: send_multi_request()
607 : * Sends the request using curl_multi_exec(), enabling parallel requests.
608 : *
609 : * Access:
610 : * public
611 : *
612 : * Parameters:
613 : * $handles - _array_ (Required) An indexed array of cURL handles to process simultaneously.
614 : *
615 : * Returns:
616 : * _array_ Post-processed cURL responses.
617 : */
618 : public function send_multi_request($handles)
619 : {
620 : // Initialize MultiCURL
621 2 : $multi_handle = curl_multi_init();
622 :
623 : // Loop through each of the CURL handles and add them to the MultiCURL request.
624 2 : foreach ($handles as $handle)
625 : {
626 2 : curl_multi_add_handle($multi_handle, $handle);
627 2 : }
628 :
629 2 : $count = 0;
630 :
631 : // Execute
632 : do
633 : {
634 2 : $status = curl_multi_exec($multi_handle, $active);
635 : }
636 2 : while ($status == CURLM_CALL_MULTI_PERFORM || $active);
637 :
638 : // Define this.
639 2 : $handles_post = array();
640 :
641 : // Retrieve each handle response
642 2 : foreach ($handles as $handle)
643 : {
644 2 : if (curl_errno($handle) == CURLE_OK)
645 2 : {
646 2 : $http = new $this->request_class(null);
647 2 : $handles_post[] = $http->process_response($handle, curl_multi_getcontent($handle));
648 2 : }
649 : else
650 : {
651 0 : throw new RequestCore_Exception(curl_error($handle));
652 : }
653 :
654 : // Explicitly close each cURL handle.
655 2 : curl_multi_remove_handle($multi_handle, $handle);
656 2 : curl_close($handle);
657 2 : }
658 :
659 2 : return $handles_post;
660 : }
661 :
662 :
663 : /*%******************************************************************************************%*/
664 : // RESPONSE METHODS
665 :
666 : /**
667 : * Method: get_response_header()
668 : * Get the HTTP response headers from the request.
669 : *
670 : * Access:
671 : * public
672 : *
673 : * Parameters:
674 : * $header - _string_ (Optional) A specific header value to return. Defaults to all headers.
675 : *
676 : * Returns:
677 : * _string_|_array_ All or selected header values.
678 : */
679 : public function get_response_header($header = null)
680 : {
681 : if ($header)
682 7 : {
683 2 : return $this->response_headers[strtolower($header)];
684 : }
685 5 : return $this->response_headers;
686 : }
687 :
688 : /**
689 : * Method: get_response_body()
690 : * Get the HTTP response body from the request.
691 : *
692 : * Access:
693 : * public
694 : *
695 : * Returns:
696 : * _string_ The response body.
697 : */
698 : public function get_response_body()
699 : {
700 6 : return $this->response_body;
701 : }
702 :
703 : /**
704 : * Method: get_response_code()
705 : * Get the HTTP response code from the request.
706 : *
707 : * Access:
708 : * public
709 : *
710 : * Returns:
711 : * _string_ The HTTP response code.
712 : */
713 : public function get_response_code()
714 : {
715 5 : return $this->response_code;
716 : }
717 : }
718 :
719 :
720 : /**
721 : * Class: ResponseCore
722 : * Container for all response-related methods.
723 : */
724 : class ResponseCore
725 52 : {
726 : /**
727 : * Property: header
728 : * Stores the HTTP header information.
729 : */
730 : var $header;
731 :
732 : /**
733 : * Property: body
734 : * Stores the SimpleXML response.
735 : */
736 : var $body;
737 :
738 : /**
739 : * Property: status
740 : * Stores the HTTP response code.
741 : */
742 : var $status;
743 :
744 : /**
745 : * Method: __construct()
746 : * The constructor
747 : *
748 : * Access:
749 : * public
750 : *
751 : * Parameters:
752 : * $header - _array_ (Required) Associative array of HTTP headers (typically returned by <RequestCore::get_response_header()>).
753 : * $body - _string_ (Required) XML-formatted response from AWS.
754 : * $status - _integer_ (Optional) HTTP response status code from the request.
755 : *
756 : * Returns:
757 : * _object_ Contains an _array_ `header` property (HTTP headers as an associative array), a _SimpleXMLElement_ or _string_ `body` property, and an _integer_ `status` code.
758 : */
759 : public function __construct($header, $body, $status = null)
760 : {
761 25 : $this->header = $header;
762 25 : $this->body = $body;
763 25 : $this->status = $status;
764 25 : return $this;
765 : }
766 :
767 : /**
768 : * Method: isOK()
769 : * Did we receive the status code we expected?
770 : *
771 : * Access:
772 : * public
773 : *
774 : * Parameters:
775 : * $codes - _integer|array_ (Optional) The status code(s) to expect. Pass an _integer_ for a single acceptable value, or an _array_ of integers for multiple acceptable values. Defaults to _array_.
776 : *
777 : * Returns:
778 : * _boolean_ Whether we received the expected status code or not.
779 : */
780 : public function isOK($codes = array(200, 201, 204))
781 : {
782 4 : if (is_array($codes))
783 4 : {
784 3 : return in_array($this->status, $codes);
785 : }
786 : else
787 : {
788 1 : return ($this->status == $codes);
789 : }
790 : }
791 : }
|