Skip to content

Instantly share code, notes, and snippets.

@dedalozzo
Created October 9, 2012 19:46
Show Gist options
  • Save dedalozzo/3860996 to your computer and use it in GitHub Desktop.
Save dedalozzo/3860996 to your computer and use it in GitHub Desktop.
<?php
//! @file Client.class.php
//! @brief This file contains the Client class.
//! @details
//! @author Filippo F. Fadda
//! @brief TODO
namespace Rest;
// If PHP is not properly recognizing the line endings when reading files either on or created by a Macintosh computer,
// enabling the auto_detect_line_endings run-time configuration option may help resolve the problem.
ini_set("auto_detect_line_endings", true);
//! @brief TODO ajkhdakjhda
class Client {
//! HTTP protocol version used by the Client.
const HTTP_PROTOCOL_VERSION = "HTTP/1.1";
//! CR+LF (0x0D 0x0A). A Carriage Return followed by a Line Feed. We don't use PHP_EOL because HTTP wants CR+LF.
const CRLF = "\r\n";
//! Maximum period to wait before the response is sent.
const DEFAULT_TIMEOUT = 60000;
const CURL_TRANSPORT = "cURL";
const SOCKET_TRANSPORT = "socket";
private $userAgent;
private $host;
private $port;
private $userName;
private $password;
// URI specifying address of proxy server. (e.g. tcp://proxy.example.com:5100).
protected $proxy;
// When set to TRUE, the entire URI will be used when constructing the request. While this is a non-standard request
// format, some proxy servers require it.
// TODO not used actually.
private $requestFullUri;
// Socket connection timeout in seconds, specified by a float. By default the default_socket_timeout php.ini setting
// is used.
private $timeout;
// Stores the transport mode. This library can use cURL or sockets.
private $transport;
//! @brief TODO
//! @param[in] string $server TODO
//! @param[in] string $userName TODO
//! @param[in] string $password TODO
public function __construct($server, $userName = "", $password = "") {
$this->setServer($server);
$this->authorization = NULL;
$this->userName = $userName;
$this->password = $password;
// Proxy parameters.
$this->proxy = NULL;
$this->requestFullUri = FALSE;
// Uses the default socket's timeout.
$this->timeout = ini_get("default_socket_timeout");
$this->useSocket();
}
//! @brief Sets server host and port.
private function setServer($server) {
if (preg_match(
'/^
(?P<host>[a-z0-9\-._~%]+ # Host
|\[[a-f0-9:.]+\]
|\[v[a-f0-9][a-z0-9\-._~%!$&\'()*+,;=:]+)
(?P<port>:[0-9]+)?
$/ix', $server, $matches)) {
$this->host = $matches['host']; // Sets host.
$this->port = substr($matches['port'], 1); // Sets port.
}
else // Match attempt failed.
throw new \Exception("'$server' is not a valid host.");
}
//! @brief Set the user agent name.
//! @todo regex
protected function setUserAgent($name) {
$this->userAgent = $name;
}
//! @brief This method executes the provided request, using sockets.
private function socketSendRequest(Request $request) {
$command = $request->getMethod()." ".$request->getPath().$request->getQueryStr()." ".self::HTTP_PROTOCOL_VERSION.self::CRLF;
echo PHP_EOL."==============================================================================================================";
echo PHP_EOL.$command.PHP_EOL;
$request->setHeaderField(Request::HOST_HF, $this->host.":".$this->port);
if (!empty($this->userName))
$request->setBasicAuthorization($this->userName, $this->password);
// Sets the Content-Lenght header only when the given request has a message body.
if ($request->hasBody())
$request->setHeaderField(Message::CONTENT_LENGTH_HF, $request->getBodyLength());
$header = $request->getHeaderAsString();
$socket = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
if (!is_resource($socket))
throw new \ErrorException($errstr, $errno);
echo $header.PHP_EOL.PHP_EOL;
if ($request->hasBody())
echo $request->getBody().PHP_EOL.PHP_EOL;
// Writes the request over the socket.
fputs($socket, $command);
fputs($socket, $header.self::CRLF.self::CRLF);
fputs($socket, $request->getBody());
fputs($socket, self::CRLF);
// Reads the response.
$buffer = "";
while (!feof($socket)) {
$buffer .= fgets($socket);
}
@fclose($socket);
return new Response($buffer);
}
//! This method executes the provided request, using cURL library. To use it, cURL must be installed on server.
private function curlSendRequest(Request $request) {
$url = "http://".$this->host.":".$this->port.$request->getPath().$request->getQueryStr();
$curl = curl_init();
// TODO debug only
$command = $request->getMethod()." ".$request->getPath().$request->getQueryStr()." ".self::HTTP_PROTOCOL_VERSION.self::CRLF;
echo PHP_EOL."==============================================================================================================";
echo PHP_EOL.$command.PHP_EOL;
// Sets the request Uniform Resource Locator.
curl_setopt($curl, CURLOPT_URL, $url);
// Sets the request HTTP header.
curl_setopt($curl, CURLOPT_HTTPHEADER, $request->getHeaderAsArray());
// I'm not sure we need this. TODO
if (!empty($this->userName)) {
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC) ;
curl_setopt($curl, CURLOPT_USERPWD, $this->userName.":".$this->password);
}
//curl_setopt($curl, CURLOPT_VERBOSE, TRUE);
// TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// Includes the header in the output. We need this because our Response object will parse them.
curl_setopt($curl, CURLOPT_HEADER, TRUE);
//curl_setopt($curl, CURLINFO_HEADER_OUT, TRUE);
// SSL options.
// curl_setopt($curl, CURLOPT_SSLVERSION, 3);
// curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
// curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
switch ($request->getMethod()) {
case Request::GET_METHOD:
curl_setopt($curl, CURLOPT_HTTPGET, TRUE);
break;
case Request::POST_METHOD:
// @bug The following instruction doesn't work; it's probably a cURL bug.
//curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, Request::POST_METHOD);
// The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with @ and use the full
// path. This can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with
// the field name as key and field data as value. If value is an array, the Content-Type header will be set to
// multipart/form-data.
curl_setopt($curl, CURLOPT_POSTFIELDS, ltrim($request->getQueryStr(), "?"));
break;
case Request::PUT_METHOD:
curl_setopt($curl, CURLOPT_PUT, TRUE);
// Often a request contains data in the form of a JSON object. Since cURL is just able to read data from a file,
// but we can't create a temporary file because it's just too much expensive, the code below uses a faster and
// efficient memory stream.
if ($request->hasBody()) {
if ($fd = fopen("php://memory", "r+")) { // Try to create a temporary file in memory.
fputs($fd, $request->getBody()); // Writes the message body.
rewind($fd); // Sets the pointer to the beginning of the file stream.
curl_setopt($curl, CURLOPT_INFILE, $fd);
curl_setopt($curl, CURLOPT_INFILESIZE, $request->getBodyLength());
}
else
throw new \Exception("Cannot create the temporary file in memory.");
}
break;
case Request::DELETE_METHOD:
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, Request::DELETE_METHOD);
break;
default:
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getMethod());
}
//echo curl_getinfo($curl, CURLINFO_HEADER_OUT);
echo $request->getHeaderAsString().PHP_EOL.PHP_EOL;
if ($request->hasBody())
echo $request->getBody().PHP_EOL.PHP_EOL;
if ($result = curl_exec($curl)) {
curl_close($curl);
return new Response($result);
}
else {
$error = curl_error($curl);
curl_close($curl);
throw new \Exception($error);
}
}
//! @name Request Factory and Execution Methods
//@{
//! @brief This is a factory method to create a new Request.
//! @details This method is used to create a Request object. You can still create a Request instance using the appropriate
//! constructor, but I recommend you to use this factory method, because it does a lot of dirty work. You should use
//! this method combined with sendRequest.
public function newRequest($method, $path) {
$request = new Request($method, $path);
$request->setHeaderField(Request::ACCEPT_HF, "application/json"); // default accept header value
$request->setHeaderField(Request::USER_AGENT_HF, $this->userAgent);
return $request;
}
//! @brief This method is used to send a Request to the server. See details for more informations.
//! @details You can use this method in conjunction with newRequest factory method to build and execute a new request.
public function sendRequest(Request $request) {
$request->setHeaderField(Message::CONNECTION_HF, "close");
if ($this->transport == self::SOCKET_TRANSPORT)
return $this->socketSendRequest($request);
else
return $this->curlSendRequest($request);
}
//! @}
//! @name Transport Mode Selection Methods
//@{
//! @brief Selects the cURL transport method.
public function useCurl() {
if (extension_loaded("curl"))
$this->transport = self::CURL_TRANSPORT;
else
throw new \Exception("The cURL extension is not loaded.");
}
//! @brief Selects socket transport method.
public function useSocket() {
$this->transport = self::SOCKET_TRANSPORT;
}
//! @}
//! @brief Uses the specified proxy.
public function setProxy($proxyAddress) {
if (!empty($proxyAddress)) // TODO regex
$this->proxy = $proxyAddress;
else
throw new \Exception("The \$proxy is not valid.");
}
public function unsetProxy() {
$this->proxy = NULL;
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment