3 * PHP HTTP Tools is a library for working with the http protocol
4 * php_http_client_generic represents a basic http client
5 * @package php-http-tools
7 * @copyright (C) 2004 John Heinstein. All rights reserved
8 * @license http://www.gnu.org/copyleft/lesser.html LGPL License
9 * @author John Heinstein <johnkarl@nbnet.nb.ca>
10 * @link http://www.engageinteractive.com/php_http_tools/ PHP HTTP Tools Home Page
11 * PHP HTTP Tools are Free Software
14 if (!defined('PHP_HTTP_TOOLS_INCLUDE_PATH')) {
15 define('PHP_HTTP_TOOLS_INCLUDE_PATH', (dirname(__FILE__
) . "/"));
18 /** end-of-line character sequence as defined in HTTP spec */
19 define ('CRLF', "\r\n");
20 /** carriage return character */
22 /** line feed character */
25 //http read states for client
26 /** beginning read state */
27 define('HTTP_READ_STATE_BEGIN', 1);
28 /** state when reading headers */
29 define('HTTP_READ_STATE_HEADERS', 2);
30 /** state when reading body of message */
31 define('HTTP_READ_STATE_BODY', 3);
33 require_once(PHP_HTTP_TOOLS_INCLUDE_PATH
. 'php_http_exceptions.php');
37 * An HTTP Request class
39 * @package php-http-tools
40 * @author John Heinstein <johnkarl@nbnet.nb.ca>
42 class php_http_request
{
43 /** @var object A reference to the headers object */
45 /** @var string The requested method, e.g. GET, POST, HEAD */
46 var $requestMethod = 'POST';
47 /** @var string The requested path */
48 var $requestPath = '';
49 /** @var string The requested protocol */
50 var $protocol = 'HTTP';
51 /** @var string The version of the requested protocol */
52 var $protocolVersion= '1.1';
55 * Returns the headers object
56 * @return object The headers object
58 function &getHeaders() {
59 return $this->headers
;
63 * Sets the header to the specified value
64 * @param string The header name
65 * @param string The header value
66 * @param boolean True if multiple headers with the same name are allowed
68 function setHeader($name, $value, $allowMultipleHeaders = false) {
69 $this->headers
->setHeader($name, $value, $allowMultipleHeaders);
73 * Default method for setting headers; meant to be overridden in subclasses
75 function setHeaders() {
76 //you will want to override this method
77 $this->setHeader('User-Agent', 'PHP-HTTP-Client(Generic)/0.1');
78 $this->setHeader('Connection', 'Close');
82 * Sets the request method, e.g., GET
83 * @param string The name of the request method
84 * @return boolean True if the version number is valid
86 function setRequestMethod($method) {
87 $method = strtoupper($method);
94 $this->requestMethod
= $method;
103 * Sets the request path, e.g., http://www.engageinteractive.com/domit/test.xml
104 * @param string The request path
106 function setRequestPath($path) {
107 $this->requestPath
= $path;
111 * Sets the version number of the protocol
112 * @param string The version number
113 * @return boolean True if the version number is valid
115 function setProtocolVersion($version) {
116 if (($version == '1.0') ||
($version == '1.1')) {
117 $this->protocolVersion
= $version;
122 } //setProtocolVersion
125 * Specifies a user name and password for basic authentication
126 * @param string The user name
127 * @param string The password
129 function setAuthorization($user, $password) {
130 $encodedChallengeResponse = 'Basic ' . base64_encode($this->user
. ':' . $this->password
);
131 $this->setHeader('Authorization', $encodedChallengeResponse);
136 class php_http_client_generic
extends php_http_request
{
137 /** @var object A reference to the connection object */
139 /** @var string True if response headers are to be generated as an object */
140 var $responseHeadersAsObject = false;
141 /** @var object The http response */
142 var $response = null;
143 /** @var string A list of event names that can be fired by the client */
144 var $events = array('onRequest' => null, 'onRead' => null,
145 'onResponse' => null, 'onResponseHeaders' => null,
146 'onResponseBody' => null);
149 * HTTP Client constructor
150 * @param string The client connection host name, with or without its protocol prefix
151 * @param string The client connection path, not including the host name
152 * @param int The port to establish the client connection on
153 * @param int The timeout value for the client connection
155 function php_http_client_generic($host = '', $path = '/', $port = 80, $timeout = 0) {
156 $this->connection
=& new php_http_connection($host, $path, $port, $timeout);
157 $this->headers
=& new php_http_headers();
158 $this->requestPath
= $path;
159 $this->response
=& new php_http_response();
161 } //php_http_client_generic
164 * Specifies that the response headers array should be generated
165 * @param boolean True if the response headers array should be built
167 function generateResponseHeadersAsObject($responseHeadersAsObject) {
168 $this->responseHeadersAsObject
= $responseHeadersAsObject;
170 if ($responseHeadersAsObject) {
171 $this->response
->headers
=& new php_http_headers();
173 } //generateResponseHeadersAsObject
176 * Fires an http event that has been registered
177 * @param string The name of the event, e.g., onRead
178 * @param string The data to be passed to the event
180 function fireEvent($target, $data) {
181 if ($this->events
[$target] != null) {
182 call_user_func($this->events
[$target], $data);
187 * Sets which http events are to be fired
188 * @param string The http event option to be set
189 * @param string True if the event is to be fired
190 * @param object A reference to a custom handler for the http event data
192 function setHTTPEvent($option, $truthVal, $customHandler = null) {
193 if ($customHandler != null) {
194 $handler =& $customHandler;
197 $handler = array(&$this, 'defaultHTTPEventHandler');
204 case 'onResponseHeaders':
205 case 'onResponseBody':
206 $truthVal ?
($this->events
[$option] =& $handler) : ($this->events
[$option] = null);
212 * Evaluates whether the specified http event option is active
213 * @param string The http event option to evaluate
214 * @return boolean True if the specified option is active
216 function getHTTPEvent($option) {
221 case 'onResponseHeaders':
222 case 'onResponseBody':
223 return ($this->events
[$option] != null);
229 * The default http event handler; fired if no custom handler has been registered
230 * @param string The event data
232 function defaultHTTPEventHandler($data) {
233 $this->printHTML($data);
234 } //defaultHTTPEventHandler
237 * Prints the data to the browser as preformatted, htmlentified output
238 * @param string The data to be printed
240 function printHTML($html) {
241 print('<pre>' . htmlentities($html) . '</pre>');
245 * Establishes a client connection
248 if (!$this->headers
->headerExists('Host')) {
249 $this->setHeader('Host', $this->connection
->host
);
252 return $this->connection
->connect();
256 * Disconnects the current client connection if one exists
258 function disconnect() {
259 return $this->connection
->disconnect();
263 * Evaluated whether the current client is connected
264 * @return boolean True if a connection exists
266 function isConnected() {
267 return $this->connection
->isOpen();
271 * Performs an HTTP GET
272 * @param string The target url
274 function &get($url) {
275 $this->setRequestMethod('GET');
276 $this->setRequestPath($url);
278 $this->get_custom($url);
282 $result = $this->send('');
288 * Handler for customizing the HTTP GET call
289 * @param string The target url
291 function get_custom($url) {
292 //do nothing; meant to be overridden
296 * Performs an HTTP POST
297 * @param string The posted data
299 function &post($data) {
300 $this->setRequestMethod('POST');
301 $this->setHeader('Content-Type', 'text/html');
302 $this->post_custom($data);
306 return $this->send($data);
310 * Handler for customizing the HTTP POST call
311 * @param string The post data
313 function post_custom($data) {
314 //do nothing; meant to be overridden
318 * Performs an HTTP HEAD
319 * @param string The target url
321 function &head($url) {
322 $this->setRequestMethod('HEAD');
323 $this->head_custom($url);
327 return $this->send('');
331 * Handler for customizing the HTTP HEAD call
332 * @param string The target url
334 function head_custom($url) {
335 //do nothing; meant to be overridden
339 * Sends data through the client connection
340 * @return string The body of the http response
342 function &send($message) {
343 $conn =& $this->connection
;
345 if ($conn->isOpen()) {
347 $request = $this->requestMethod
. ' ' . $this->requestPath
. ' ' . $this->protocol
.
348 '/' . $this->protocolVersion
. CRLF
;
349 $request .= $this->headers
->toString() . CRLF
;
350 $request .= $message;
353 $response = $headers = $body = '';
354 $readState = HTTP_READ_STATE_BEGIN
;
356 $this->fireEvent('onRequest', $request);
359 $connResource =& $conn->connection
;
360 fputs ($connResource, $request);
363 while (!feof($connResource)) {
364 $data = fgets($connResource, 4096);
365 $this->fireEvent('onRead', $data);
367 switch ($readState) {
368 case HTTP_READ_STATE_BEGIN
:
369 $this->response
->statusLine
= $data;
370 $readState = HTTP_READ_STATE_HEADERS
;
373 case HTTP_READ_STATE_HEADERS
:
374 if (trim($data) == '') { //end of headers is signalled by a blank line
375 $readState = HTTP_READ_STATE_BODY
;
378 if ($this->responseHeadersAsObject
) {
379 $this->response
->setUnformattedHeader($data);
382 $this->response
->headers
.= $data;
387 case HTTP_READ_STATE_BODY
:
388 $this->response
->message
.= $data;
393 $this->normalizeResponseIfChunked();
395 $headerString = is_object($this->response
->headers
) ?
396 $this->response
->headers
->toString() : $this->response
->headers
;
398 $this->fireEvent('onResponseHeaders', $headerString);
399 $this->fireEvent('onResponseBody', $this->response
->message
);
401 $this->fireEvent('onResponse', $this->response
->headers
. $this->response
->message
);
403 return $this->response
;
406 HTTPExceptions
::raiseException(HTTP_SOCKET_CONNECTION_ERR
, ('HTTP Transport Error - Unable to establish connection to host ' .
412 * Determines if response data is transfer encoding chunked, then decodes
414 function normalizeResponseIfChunked() {
415 if (($this->protocolVersion
= '1.1') && (!$this->response
->isResponseChunkDecoded
)) {
416 if ($this->responseHeadersAsObject
) {
417 if ($this->response
->headers
->headerExists('Transfer-Encoding') &&
418 ($this->response
->headers
->getHeader('Transfer-Encoding') == 'chunked')) {
419 $this->response
->message
= $this->decodeChunkedData($this->response
->getResponse());
420 $this->response
->isResponseChunkDecoded
= true;
424 if ((strpos($this->response
->headers
, 'Transfer-Encoding') !== false) &&
425 (strpos($this->response
->headers
, 'chunked') !== false)){
426 $this->response
->message
= $this->decodeChunkedData($this->response
->getResponse());
427 $this->response
->isResponseChunkDecoded
= true;
431 } //normalizeResponseIfChunked
434 * Decodes data if transfer encoding chunked
435 * @param string The encoded data
436 * @return string The decoded data
438 function decodeChunkedData($data) {
439 $chunkStart = $chunkEnd = strpos($data, CRLF
) +
2;
440 $chunkLengthInHex = substr($data, 0, $chunkEnd);
441 $chunkLength = hexdec(trim($chunkLengthInHex));
445 while ($chunkLength > 0) {
446 $chunkEnd = strpos($data, CRLF
, ($chunkStart +
$chunkLength));
449 //if the trailing CRLF is missing, return all the remaining data
450 $decodedData .= substr($data, $chunkStart);
454 $decodedData .= substr($data, $chunkStart, ($chunkEnd - $chunkStart));
455 $chunkStart = $chunkEnd +
2;
456 $chunkEnd = strpos($data, CRLF
, $chunkStart) +
2;
458 if (!$chunkEnd) break;
460 $chunkLengthInHex = substr($data, $chunkStart, ($chunkEnd - $chunkStart));
461 $chunkLength = hexdec(trim($chunkLengthInHex));
462 $chunkStart = $chunkEnd;
466 } //decodeChunkedData
467 } //php_http_client_generic
471 * An HTTP Connection class
473 * @package php-http-tools
474 * @author John Heinstein <johnkarl@nbnet.nb.ca>
476 class php_http_connection
{
477 /** @var object A reference to the current connection */
478 var $connection = null;
479 /** @var string The host of the connection */
481 /** @var string The path of the connection */
483 /** @var int The port of the connection */
485 /** @var int The timeout value for the connection */
487 /** @var int The error number of the connection */
488 var $errorNumber = 0;
489 /** @var string The error string of the connection */
490 var $errorString = '';
493 * HTTP Connection constructor
494 * @param string The connection host name, with or without its protocol prefix
495 * @param string The connection path, not including the host name
496 * @param int The port to establish the client connection on
497 * @param int The timeout value for the client connection
499 function php_http_connection($host = '', $path = '/', $port = 80, $timeout = 0) {
500 $this->host
= $this->formatHost($host);
501 $this->path
= $this->formatPath($path);
503 $this->timeout
= $timeout;
504 } //php_http_connection
507 * Formats a host string by stripping off the http:// prefix
508 * @param string The host name
509 * @return string The formatted host name
511 function formatHost($hostString) {
512 $hasProtocol = (substr(strtoupper($hostString), 0, 7) == 'HTTP://');
515 $hostString = substr($hostString, 7);
522 * Formats a path string
523 * @param string The path
524 * @return string The formatted path
526 function formatPath($pathString) {
527 if (($pathString == '') ||
($pathString == null)) {
535 * Establishes a socket connection
536 * @return boolean True if the connection was successful
539 if ($this->timeout
== 0) {
540 $this->connection
= @fsockopen
($this->host
, $this->port
, $errorNumber, $errorString);
543 $this->connection
= @fsockopen
($this->host
, $this->port
, $errorNumber, $errorString, $this->timeout
);
546 $this->errorNumber
= $errorNumber;
547 $this->errorString
= $errorString;
549 return is_resource($this->connection
);
553 * Determines whether the connection is still open
554 * @return boolean True if the connection is still open
557 return (is_resource($this->connection
) && (!feof($this->connection
)));
561 * Disconnects the current connection
562 * @return boolean True if the connection has been disconnected
564 function disconnect() {
565 fclose($this->connection
);
566 $this->connection
= null;
569 } //php_http_connection
572 * An HTTP Headers class
574 * @package php-http-tools
575 * @author John Heinstein <johnkarl@nbnet.nb.ca>
577 class php_http_headers
{
578 /** @var object An array of headers */
582 * HTTP Headers constructor
584 function php_http_headers() {
585 $this->headers
= array();
589 * Returns the specified header value
590 * @param string The header name
591 * @return mixed The header value, or an array of header values
593 function &getHeader($name) {
594 if ($this->headerExists($name)) {
595 return $this->headers
[$name];
602 * Sets the named header to the specified value
603 * @param string The header name
604 * @param string The header value
605 * @param boolean True if multiple headers with the same name are allowed
607 function setHeader($name, $value, $allowMultipleHeaders = false) {
608 if ($allowMultipleHeaders) {
609 if (isset($this->headers
[$name])) {
610 if (is_array($this->headers
[$name])) {
611 $this->headers
[$name][count($this->headers
)] = $value;
614 $tempVal = $this->headers
[$name];
615 $this->headers
[$name] = array($tempVal, $value);
619 $this->headers
[$name] = array();
620 $this->headers
[$name][0] = $value;
624 $this->headers
[$name] = $value;
629 * Determines whether the specified header exists
630 * @param string The header name
631 * @return boolean True if the specified header exists
633 function headerExists($name) {
634 return isset($this->headers
[$name]);
638 * Removes the specified header
639 * @param string The header name
640 * @return boolean True if the specified header has been removed
642 function removeHeader($name) {
643 if ($this->headerExists($name)) {
644 unset($this->headers
[$name]);
652 * Returns a reference to the headers array
653 * @return array The headers array
655 function getHeaders() {
656 return $this->headers
;
660 * Returns a list of existing headers names
661 * @return array A list of existing header names
663 function getHeaderList() {
664 return array_keys($this->headers
);
668 * Returns a string representation of the headers array
669 * @return string A string representation of the headers array
671 function toString() {
674 foreach ($this->headers
as $key => $value) {
675 if (is_array($value)) {
676 foreach ($value as $key2 => $value2) {
677 $retString .= $key . ': ' . $value2 . CRLF
;
681 $retString .= $key . ': ' . $value . CRLF
;
690 * An HTTP Response class
692 * @package php-http-tools
693 * @author John Heinstein <johnkarl@nbnet.nb.ca>
695 class php_http_response
{
696 /** @var string Response number */
697 var $statusLine = '';
698 /** @var mixed Response headers, either as object or string */
700 /** @var string Response message */
702 /** @var boolean True if the chunked transfer-encoding of the response has been decoded */
703 var $isResponseChunkDecoded = false;
706 * Returns a reference to the headers array
707 * @return array The headers array
709 function getResponse() {
710 return $this->message
;
714 * Returns the response status line
715 * @return string The response status line
717 function getStatusLine() {
718 return $this->statusLine
;
722 * Returns the response status code
723 * @return int The response status code
725 function getStatusCode() {
726 $statusArray = split(' ', $this->statusLine
);
728 if (count($statusArray > 1)) {
729 return intval($statusArray[1], 10);
736 * Returns a reference to the headers array
737 * @return array The headers array
739 function &getHeaders() {
740 return $this->headers
;
744 * Converts a header string into a key value pair and sets header
746 function setUnformattedHeader($headerString) {
747 $colonIndex = strpos($headerString, ':');
749 if ($colonIndex !== false) {
750 $key = trim(substr($headerString, 0, $colonIndex));
751 $value = trim(substr($headerString, ($colonIndex +
1)));
752 $this->headers
->setHeader($key, $value, true);
754 } //setUnformattedHeader
755 } //php_http_response