[code]<?php
/**
 * A class to send very easy an HTTP request.
 *
 * With this class, you can easily send an HTTP request to a given url. Just
 * give the url and, if necessary, an array with arguments and the port.
 * With another function you send the request, and finaly, you can recieve the
 * respons with a last function.
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 2.0-augustus 2008
 * @acces public
 */
class httpRequest {
	/* ########## The variables ########## */
	/* ################################### */
	
	/**
	 * @var string The variable that contains the given url, excepted 'http://'.
	 */
	protected $strUrl;
	/**
	 * @var string The variable that contains the host, stripped from the givin url.
	 */
	protected $strHost;
	/**
	 * @var string The variable that contains the path to the file, stripped from the given url.
	 */
	protected $strPath;
	/**
	 * @var int The variable that contains the port of the connection.
	 */
	protected $intPort;
	/**
	 * @var string The variable that tells which protocol will be used.
	 */
	protected $strProtocol;
	
	
	/**
	 * @var resource The variable that contains the resource opened by fsockopen.
	 */
	protected $resFilePointer;
	
	
	/**
	 * @var object The variable that contains an object of httpHeaders.
	 */
	public $headers;
	
	/**
	 * @var object The variable that contains an object of httpArguments.
	 */
	public $arguments;
	
	/**
	 * @var object The variable that contains an object of httpResponse.
	 */
	public $response;
	
	/* ########## The public functions ########## */
	/* ########################################## */
	
	/**
	 * @desc Constructor.
	 *
	 * @param string URL to the script where the request will be send to.
	 * @param string [optional] The protocol which will be used.
	 * @param int [optional] The port which will be used for connecting. Default: 80.
	 *
	 * @return bool
	 */
	public function __construct($strUrl, $strProtocol="post", $intPort=80) {
		// Check the parameters.
		if (!is_string($strUrl)) {
			throw new HttpParameterException("\$strUrl must be a string", __METHOD__);
		}
		if (!is_string($strProtocol)) {
			throw new HttpParameterException("\$strProtocol must be a string", __METHOD__);
		}
		if (!is_int($intPort)) {
			throw new HttpParameterException("\$intPort must be an integer", __METHOD__);
		}
		
		// Process the url and put it in a private variable.
		$this->processUrl($strUrl);
		
		// Process the port and put it in a private variable.
		$this->processPort($intPort);
		
		// Process the protocol and put it in a private variable.
		$this->processProtocol($strProtocol);
		
		// Prepare the headers.
		$this->headers = new httpHeaders();
		
		// Prepare the arguments.
		$this->arguments = new httpArguments();
		
		return true;
	}
	
	/**
	 * @desc Destructor. Closes the connection if it's still open.
	 *
	 * @return bool
	 */
	public function __destruct() {
		if ($this->resFilePointer) {
			if (!fclose($this->resFilePointer)) {
				throw new HttpConnectionException ("cannot close the connection");
			}
		}
		
		return true;
	}
	
	/**
	 * @desc The function that opens the connection and sends the arguments.
	 *
	 * @param int [optional] The connection timeout, in secconds. Default: 30.
	 *
	 * @return object
	 */
	public function execute($intTimeout = 30) {
		// Checks the parameter
		if (!is_int($intTimeout)) {
			throw new HttpParameterException("\$intTimeout must be an integer", __METHOD__);
		}
		
		// Add the default headers.
		// Tell what protocol will be used for the request, and to which path.
		$this->headers->add($this->strProtocol." ".$this->strPath." HTTP/1.1");
		// Tell which content type it is.
		$this->headers->add("Content-Type: application/x-www-form-urlencoded");
		// Tell the length of the string with arguments.
		$this->headers->add("Content-Length: ".$this->arguments->length());
		// Tell that the connection must be closed afterwards.
		$this->headers->add("Connection: Close");
		
		// Open the connection.
		$this->resFilePointer = fsockopen($this->strHost, $this->intPort, $intErrNumber, $strErrMessage, $intTimeout);
		if (!$this->resFilePointer) {
			$strErrMessage = (empty($strErrMessage)) ? "probably wrong hostname" : $strErrMessage;
			throw new HttpConnectionException ($strErrMessage);
		}
		// Send the header.
		if (fputs($this->resFilePointer, $this->headers) === false) {
			throw new HttpConnectionException ("cannot send header");
		}
		// Send the arguments.
		if (fputs($this->resFilePointer, $this->arguments) === false) {
			throw new HttpConnectionException ("cannot send arguments");
		}
		
		// Get the response and put it in object
		$this->getResponse();
		
		return $this->response;
	}
	
	
	/* ########## The protected functions ########## */
	/* ############################################# */
	
	/**
	 * @desc The function that processes the url and put it in a private variable.
	 *
	 * @param string URL to the script where the request will be send to.
	 *
	 * @return bool
	 */
	protected function processUrl($strUrl) {
		// Checks the parameter.
		if (!is_string($strUrl)) {
			throw new HttpParameterException("\$strUrl must be a string", __METHOD__);
		}
		
		// Removes 'http://' from the url.
		if (substr($strUrl, 0, 7) == "http://") {
			$strUrl = substr($strUrl, 7);
		}
		
		// Retrieves the host and path from the url.
		list($strHost, $strPath) = explode("/", $strUrl, 2);
		
		// Add a slash in front of the path.
		$strPath = "/".$strPath;
		
		// Removes the port from the host
		$intPosSeparator = strpos($strHost, ":");
		if ($intPosSeparator) {
			$strPort = substr($strHost, $intPosSeparator+1);
			$strHost = substr($strHost, 0, $intPosSeparator);
		}
		
		// Checks the port
		if (!empty($strPort)) {
			$this->processPort($strPort);
		}
		
		// Puts the founded url, host and path in private variables.
		$this->strUrl = $strUrl;
		$this->strHost = $strHost;
		$this->strPath = $strPath;
		
		return true;
	}
	
	/**
	 * @desc The function that checks the port and put it in a private variable if it's correct.
	 *
	 * @param int The port.
	 *
	 * @return bool
	 */
	protected function processPort($intPort) {
		// Checks the parameter.
		if (!is_int($intPort)) {
			throw new HttpParameterException("\$intPort must be an integer", __METHOD__);
		}
		
		// Check if it's between 0 and 65535.
		if ($intPort >= 0 && $intPort <= 65535) {
			// The port is correct, put it in a private variable.
			$this->intPort = $intPort;
			
			return true;
		}
		else {
			throw new HttpPortException ("the port must be between 0 and 65535");
		}
		
		return false;
	}
	
	/**
	 * @desc The function that checks the protocol and put it in a private variable if it's correct.
	 *
	 * @param string The protocol.
	 *
	 * @return bool
	 */
	protected function processProtocol($strProtocol) {
		// Checks the parameter.
		if (!is_string($intPort)) {
			throw new HttpParameterException("\$strProtocol must be a string", __METHOD__);
		}
		
		// The array of accepted protocols (in uppercase!!)
		// Source: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
		$arrAcceptedProtocols = array ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT");
		
		// Make given protocol uppercase.
		$strProtocol = strtoupper($strProtocol);
		
		// Checks if the given protocol is accepted.
		if (in_array($strProtocol, $arrAcceptedProtocols)) {
			// The protocol is correct, put it in a private variable.
			$this->strProtocol = $strProtocol;
			
			return true;
		}
		else {
			// Throw Exception
			// Make message (use $arrAcceptedProtocols)
			$strExceptionMessage = "the protocol must be ";
			foreach($arrAcceptedProtocols as $intKey => $strProtocol) {
				if ($intKey != 0 && $intKey < (count($arrAcceptedProtocols)-1)) {
					$strExceptionMessage .= ", ";
				}
				elseif ($intKey == (count($arrAcceptedProtocols)-1)) {
					$strExceptionMessage .= " or ";
				}
				$strExceptionMessage .= $strProtocol;
			}
			throw new HttpProtocolException($strExceptionMessage);
		}
		
		return false;
	}
	
	/**
	 * @desc The function that retrieves the response and puts it in an object.
	 *
	 * @return bool
	 */
	protected function getResponse () {
		$strResponse = "";
		while (!feof($this->resFilePointer)) {
			$strResponse .= fread($this->resFilePointer, 4096);
		}
		
		// Process the response...
		list($strResponseHeader, $strResponseBody) = explode("\r\n\r\n", $strResponse, 2);
		// ...and puts it in an object
		$this->response = new httpResponse ($strResponseHeader, $strResponseBody);
		
		return true;
	}
}

/**
 * A class which contains the headers of the httpRequest-class.
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class httpHeaders {
	/* ########## The variables ########## */
	/* ################################### */
	
	/**
	 * @var array The variable that contains an array with all headers.
	 */
	protected $arrHeaders;
	
	/* ########## The public functions ########## */
	/* ########################################## */
	
	/**
	 * @desc Constructor.
	 *
	 * @return void
	 */
	public function __construct () {
		$this->arrHeaders = array();
	}
	
	/**
	 * @desc toString.
	 *
	 * @return string
	 */
	public function __toString() {
		return implode("\r\n", $this->arrHeaders)."\r\n";
	}
	
	/**
	 * @desc The function to add a header.
	 *
	 * @param string The header to add.
	 *
	 * @return bool
	 */
	public function add($strHeader) {
		// Checks the parameter.
		if (!is_string($strHeader)) {
			throw new HttpParameterException("\$strHeader must be a string", __METHOD__);
		}
		
		$this->arrHeaders[] = $strHeader;
	}
	
	/**
	 * @desc The function to get the array with headers.
	 *
	 * @return array
	 */
	public function getArray() {
		return $this->arrHeaders;
	}
	
	/**
	 * @desc The function that removes all the headers.
	 *
	 * @return bool
	 */
	public function clear() {
		$this->arrHeaders = array();
		return true;
	}
}

/**
 * A class which contains the arguments of the httpRequest-class.
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class httpArguments {
	/* ########## The variables ########## */
	/* ################################### */
	
	/**
	 * @var array The variable that contains an array with all arguments.
	 */
	protected $arrArguments;
	
	/* ########## The public functions ########## */
	/* ########################################## */
	
	/**
	 * @desc Constructor.
	 *
	 * @return void
	 */
	public function __construct () {
		$this->arrArguments = array();
	}
	
	/**
	 * @desc toString.
	 *
	 * @return string
	 */
	public function __toString() {
		$strArguments = "";
		foreach ($this->arrArguments as $mixName => $mixValue) {
			$strArguments .= "&".$mixName."=".$mixValue;
		}
		
		// Remove the first ampersand (&).
		return $strArguments = substr($strArguments, 1);
	}
	
	/**
	 * @desc The function to add arguments.
	 *
	 * @param array The arguments to add.
	 *
	 * @return bool
	 */
	public function add($arrArguments) {
		// Checks the parameter.
		if (!is_array($arrArguments)) {
			throw new HttpParameterException("\$arrArguments must be an array", __METHOD__);
		}
		
		// Encode the values of the arguments.
		// DON'T ENCODE THE NAMES!!!
		$arrArgumentsEncoded = array();
		foreach ($arrArguments as $mixName => $mixValue) {
			$arrArgumentsEncoded[$mixName] = urlencode($mixValue);
		}
		
		$this->arrArguments = array_merge($this->arrArguments, $arrArgumentsEncoded);
		
		return true;
	}
	
	/**
	 * @desc The function that returns the length of the string.
	 *
	 * @return int
	 */
	public function length() {
		return strlen($this->__toString());
	}
	
	/**
	 * @desc The function to get the array with arguments.
	 *
	 * @return array
	 */
	public function getArray() {
		return $this->arrArguments;
	}
	
	/**
	 * @desc The function that removes all the arguments.
	 *
	 * @return bool
	 */
	public function clear() {
		$this->arrArguments = array();
		return true;
	}
}

/**
 * A class which contains the respons of the httpRequest-class.
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class httpResponse {
	/* ########## The variables ########## */
	/* ################################### */
	
	/**
	 * @var object The variable that contains an object of httpHeaders to return.
	 */
	protected $objHeaders;
	
	/**
	 * @var string The variable that contains the response body.
	 */
	protected $strBody;
	
	
	/* ########## The public functions ########## */
	/* ########################################## */
	
	/**
	 * @desc Constructor.
	 *
	 * @param string The response headers.
	 * @param string The response body.
	 *
	 * @return bool
	 */
	public function __construct ($strHeaders, $strBody) {
		// Checks the parameters.
		if (!is_string($strHeaders)) {
			throw new HttpParameterException("\$strHeaders must be a string", __METHOD__);
		}
		if (!is_string($strBody)) {
			throw new HttpParameterException("\$strBody must be a string", __METHOD__);
		}
		
		// There has to be given a header.
		if (empty($strHeaders)) {
			// If not:
			throw new HttpResponseException ("The response doesn't contain a header.");
		}
		else {
			$this->objHeaders = $this->createHeadersObject($strHeaders);
			$this->strBody = $strBody;
		}
	}
	
	/**
	 * @desc The function that returns the headers.
	 *
	 * @return string
	 */
	public function getHeaders() {
		return $this->objHeaders;
	}
	
	/**
	 * @desc The function that returns the body.
	 *
	 * @return string
	 */
	public function getBody() {
		return $this->strBody;
	}
	
	
	/* ########## The protected functions ########## */
	/* ############################################# */
	
	/**
	 * @desc The function that makes the httpHeaders object to return.
	 *
	 * @param string The variable that contains the headers as a string.
	 *
	 * @return object
	 */
	protected function createHeadersObject($strHeaders) {
		// Checks the parameter
		if (!is_string($strHeaders)) {
			throw new HttpParameterException("\$strHeaders must be a string", __METHOD__);
		}
		
		// Create the object.
		$objHeaders = new httpHeaders();
		// Make an array with headers
		$arrHeaders = explode("\r\n", $strHeaders);
		// Put them into the object
		foreach ($arrHeaders as $strHeader) {
			$objHeaders->add($strHeader);
		}
		// Return the object
		return $objHeaders;
	}
}

/**
 * A class to create a new HttpException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpException extends Exception {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName;
	
	/**
	 * @desc The function that overwrites the construct-function.
	 *
	 * @param string The message.
	 * @param int [optional] The code.
	 *
	 * @return void
	 */
	public function __construct($strMessage, $intCode=0) {
		// Checks the parameters.
		if (!is_string($strMessage)) {
			throw new HttpParameterException("\$strMessage must be a string", __METHOD__);
		}
		if (!is_int($intCode)) {
			throw new HttpParameterException("\$intCode must be an integer", __METHOD__);
		}
		
		// Call parent constructor.
		parent::__construct($strMessage, $intCode);
	}
	
	/**
	 * @desc The function to display the exception.
	 *
	 * @return string
	 */
	public function __toString() {
		return "<br />\n<b>".$this->strExceptionName."</b>: ".$this->strErrorName.", ".$this->message." in <b>".$this->file."</b> on line <b>".$this->line."</b>";
	}
}

/**
 * A class to create a new HttpConnectionException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpConnectionException extends HttpException {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName = __CLASS__;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName = "connection error";
}

/**
 * A class to create a new HttpPortException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpPortException extends HttpException {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName = __CLASS__;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName = "port error";
}

/**
 * A class to create a new HttpProtocolException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpProtocolException extends HttpException {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName = __CLASS__;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName = "protocol error";
}

/**
 * A class to create a new HttpResponseException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpResponseException extends HttpException {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName = __CLASS__;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName = "response error";
}

/**
 * A class to create a new HttpParameterException
 *
 * @package httpRequest
 * @author Daan van Marsbergen
 * @version 1.0-augustus 2008
 * @acces public
 */
class HttpParameterException extends HttpException {
	/**
	 * @var string The exception name.
	 */
	protected $strExceptionName = __CLASS__;
	
	/**
	 * @var string The error name.
	 */
	protected $strErrorName = "parameter error";
	
	/**
	 * @desc The function that overwrites the construct-function.
	 *
	 * @param string The message.
	 * @param string [optional] The method name where the error occured.
	 * @param int [optional] The code.
	 *
	 * @return void
	 */
	public function __construct($strMessage, $strMethod=NULL, $intCode=0) {
		// Checks the parameters.
		if (!is_string($strMessage)) {
			throw new HttpParameterException("\$strMessage must be a string", __METHOD__);
		}
		if (!is_string($strMethod) && !empty($strMethod)) {
			throw new HttpParameterException("\$strMethod must be a string", __METHOD__);
		}
		if (!is_int($intCode)) {
			throw new HttpParameterException("\$intCode must be an integer", __METHOD__);
		}
		
		// Add the method name to the message.
		if ($strMethod != NULL) {
			$strMessage .= " [<i>".$strMethod."()</i>]";
		}
		
		// Call parent constructor.
		parent::__construct($strMessage, $intCode);
	}
}
?>[/code]

[b]Voorbeeld hoe te gebruiken:[/b]
[code]<?php
// Bij het maken van het object, wordt de url, het protocol en de poort gegeven.
// Dat kan op twee manieren: (let op de poort)
$httpRequest = new httpRequest("http://www.website.nl/script.php", "post", 80);
// Of:
$httpRequest = new httpRequest("http://www.website.nl:80/script.php", "post");

// Daarna worden de argumenten toegevoegd:
$arguments = array ('var1' => 'value1', 'var2' = 'value2');
$httpRequest->arguments->add($arguments);
// De argumenten kunnen ook weer worden weggehaald met:
$httpRequest->arguments->clear();

// Vervolgens worden de headers toegevoegd:
$httpRequest->headers->add('Keep-Alive: 300');
// En dit kan ook weer worden weggehaald met:
$httpRequest->headers->clear();
// De standaard headers (die altijd verzonden worden) staan op regel 122-129.

// Als alles klaar is, verzend je de HTTP request
$httpRequest->execute()
// Eventueel kan je ook vertellen hoe lang hij daar over mag doen:
$httpRequest->execute(30) // 30 seconden (standaard)

// Tot slot kan je het resultaat ophalen.
// De headers die teruggestuurd werden:
$objHeaders = $httpRequest->response->getHeaders();
echo $objHeaders;
print_r($objHeaders->getArray());
// De body (rest) die teruggestuurd werd:
echo $httpRequest->response->getBody();

// Ook kan je de verzonden headers en argumenten altijd ophalen.
// Headers:
echo $httpRequest->headers; // Als string
print_r($httpRequest->headers->getArray()); // Als array
// Argumenten:
echo $httpRequest->arguments; // Als string
print_r($httpRequest->arguments->getArray()); // Als array

?>[/code]

[b]Gebruik van excepties:[/b]
[code]<?php
// Bijvoorbeeld bij het uitvoeren van de request.
try {
    $httpRequest->execute(30);
}
catch (HttpParameterException $e) {
    // Verkeerde parameter meegegeven, bijvoorbeeld een string ipv een int.
}
catch (HttpException $e) {
    // Elke exceptie die door deze classe wordt gegooid
}
catch (Exception $e) {
    // Elke exceptie
}
?>[/code]
Je hebt dus drie niveaus van excepties, waarvan twee door mij zijn gemaakt.
[list][li]Exception: elke exceptie, maakt niet uit waar vandaan.[/li]
[li]HttpException: elke exceptie die door deze class wordt gegooid, voldoet hieraan.[/li]
[li]Gedetailleerdere excepties. Dit zijn:
[list][li]HttpConnectionException (fout bij het versturen van de request)[/li]
[li]HttpPortException (ongeldige port opgegeven)[/li]
[li]HttpProtocolException (ongeldig protocol opgegeven)[/li]
[li]HttpResponseException (een fout in de response)[/li]
[li]HttpParameterException (een verkeerde parameter meegegeven)[/li][/list][/li][/list]