sabre/http
This library provides a toolkit to make working with the HTTP protocol easier., (*1)
Most PHP scripts run within a HTTP request but accessing information about the
HTTP request is cumbersome at least, mainly do to superglobals and the CGI
standard., (*2)
There's bad practices, inconsistencies and confusion. This library is
effectively a wrapper around the following PHP constructs:, (*3)
For Input:, (*4)
$_GET
$_POST
$_SERVER
-
php://input or $HTTP_RAW_POST_DATA.
For output:, (*5)
-
php://output or echo.
header()
What this library provides, is a Request object, and a Response object., (*6)
The objects are extendable and easily mockable., (*7)
Build status
| branch |
status |
| master |
 |
Installation
Make sure you have composer installed. In your project directory, create,
or edit a composer.json file, and make sure it contains something like this:, (*8)
{
"require" : {
"sabre/http" : "~2.0.0"
}
}
After that, just hit composer install and you should be rolling., (*9)
Quick history
This library came to existence in 2009, as a part of the SabreDAV
project, which uses it heavily., (*10)
It got split off into a separate library to make it easier to manage
releases and hopefully giving it use outside of the scope of just SabreDAV., (*11)
Although completely independently developed, this library has a LOT of
overlap with symfony's HttpFoundation., (*12)
Said library does a lot more stuff and is significantly more popular,
so if you are looking for something to fulfill this particular requirement,
I'd recommend also considering HttpFoundation., (*13)
Getting started
First and foremost, this library wraps the superglobals. The easiest way to
instantiate a request object is as follows:, (*14)
use Sabre\HTTP;
include 'vendor/autoload.php';
$request = HTTP\Sapi::getRequest();
This line should only happen once in your entire application. Everywhere else
you should pass this request object around using dependency injection., (*15)
You should always typehint on it's interface:, (*16)
function handleRequest(HTTP\RequestInterface $request) {
// Do something with this request :)
}
A response object you can just create as such:, (*17)
use Sabre\HTTP;
include 'vendor/autoload.php';
$response = new HTTP\Response();
$response->setStatus(201); // created !
$response->setHeader('X-Foo', 'bar');
$response->setBody(
'success!'
);
After you fully constructed your response, you must call:, (*18)
HTTP\Sapi::sendResponse($response);
This line should generally also appear once in your application (at the very
end)., (*19)
Decorators
It may be useful to extend the Request and Response objects in your
application, if you for example would like them to carry a bit more
information about the current request., (*20)
For instance, you may want to add an isLoggedIn() method to the Request
object., (*21)
Simply extending Request and Response may pose some problems:, (*22)
- You may want to extend the objects with new behavior differently, in
different subsystems of your application.
- The
Sapi::getRequest factory always returns a instance of
Request so you would have to override the factory method as well.
- By controlling the instantation and depend on specific
Request and
Response instances in your library or application, you make it harder to
work with other applications which also use sabre/http.
In short: it would be bad design. Instead, it's recommended to use the
decorator pattern to add new behavior where you need it. sabre/http
provides helper classes to quickly do this., (*23)
Example:, (*24)
use Sabre\HTTP;
class MyRequest extends HTTP\RequestDecorator {
function isLoggedIn() {
return true;
}
}
Our application assumes that the true Request object was instantiated
somewhere else, by some other subsystem. This could simply be a call like
$request = Sapi::getRequest() at the top of your application,
but could also be somewhere in a unittest., (*25)
All we know in the current subsystem, is that we received a $request and
that it implements Sabre\HTTP\RequestInterface. To decorate this object,
all we need to do is:, (*26)
$request = new MyRequest($request);
And that's it, we now have an isLoggedIn method, without having to mess
with the core instances., (*27)
Client
This package also contains a simple wrapper around cURL, which will allow
you to write simple clients, using the Request and Response objects you're
already familiar with., (*28)
It's by no means a replacement for something like guzzle, but it provides
a simple and lightweight api for making the occasional API call., (*29)
Usage
use Sabre\HTTP;
$request = new HTTP\Request('GET', 'http://example.org/');
$request->setHeader('X-Foo', 'Bar');
$client = new HTTP\Client();
$response = $client->send($request);
echo $response->getBodyAsString();
The client emits 3 event using sabre/event. beforeRequest,
afterRequest and error., (*30)
$client = new HTTP\Client();
$client->on('beforeRequest', function($request) {
// You could use beforeRequest to for example inject a few extra headers.
// into the Request object.
});
$client->on('afterRequest', function($request, $response) {
// The afterRequest event could be a good time to do some logging, or
// do some rewriting in the response.
});
$client->on('error', function($request, $response, &$retry, $retryCount) {
// The error event is triggered for every response with a HTTP code higher
// than 399.
});
$client->on('error:401', function($request, $response, &$retry, $retryCount) {
// You can also listen for specific error codes. This example shows how
// to inject HTTP authentication headers if a 401 was returned.
if ($retryCount > 1) {
// We're only going to retry exactly once.
}
$request->setHeader('Authorization', 'Basic xxxxxxxxxx');
$retry = true;
});
Asynchronous requests
The Client also supports doing asynchronous requests. This is especially handy
if you need to perform a number of requests, that are allowed to be executed
in parallel., (*31)
The underlying system for this is simply [curl's multi request handler][8],
but this provides a much nicer API to handle this., (*32)
Sample usage:, (*33)
use Sabre\HTTP;
$request = new Request('GET', 'http://localhost/');
$client = new Client();
// Executing 1000 requests
for ($i = 0; $i < 1000; $i++) {
$client->sendAsync(
$request,
function(ResponseInterface $response) {
// Success handler
},
function($error) {
// Error handler
}
);
}
// Wait for all requests to get a result.
$client->wait();
Check out examples/asyncclient.php for more information., (*34)
Writing a reverse proxy
With all these tools combined, it becomes very easy to write a simple reverse
http proxy., (*35)
use
Sabre\HTTP\Sapi,
Sabre\HTTP\Client;
// The url we're proxying to.
$remoteUrl = 'http://example.org/';
// The url we're proxying from. Please note that this must be a relative url,
// and basically acts as the base url.
//
// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't
// either.
$myBaseUrl = '/reverseproxy.php';
// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/';
$request = Sapi::getRequest();
$request->setBaseUrl($myBaseUrl);
$subRequest = clone $request;
// Removing the Host header.
$subRequest->removeHeader('Host');
// Rewriting the url.
$subRequest->setUrl($remoteUrl . $request->getPath());
$client = new Client();
// Sends the HTTP request to the server
$response = $client->send($subRequest);
// Sends the response back to the client that connected to the proxy.
Sapi::sendResponse($response);
The Request and Response API's
Request
/**
* Creates the request object
*
* @param string $method
* @param string $url
* @param array $headers
* @param resource $body
*/
public function __construct($method = null, $url = null, array $headers = null, $body = null);
/**
* Returns the current HTTP method
*
* @return string
*/
function getMethod();
/**
* Sets the HTTP method
*
* @param string $method
* @return void
*/
function setMethod($method);
/**
* Returns the request url.
*
* @return string
*/
function getUrl();
/**
* Sets the request url.
*
* @param string $url
* @return void
*/
function setUrl($url);
/**
* Returns the absolute url.
*
* @return string
*/
function getAbsoluteUrl();
/**
* Sets the absolute url.
*
* @param string $url
* @return void
*/
function setAbsoluteUrl($url);
/**
* Returns the current base url.
*
* @return string
*/
function getBaseUrl();
/**
* Sets a base url.
*
* This url is used for relative path calculations.
*
* The base url should default to /
*
* @param string $url
* @return void
*/
function setBaseUrl($url);
/**
* Returns the relative path.
*
* This is being calculated using the base url. This path will not start
* with a slash, so it will always return something like
* 'example/path.html'.
*
* If the full path is equal to the base url, this method will return an
* empty string.
*
* This method will also urldecode the path, and if the url was incoded as
* ISO-8859-1, it will convert it to UTF-8.
*
* If the path is outside of the base url, a LogicException will be thrown.
*
* @return string
*/
function getPath();
/**
* Returns the list of query parameters.
*
* This is equivalent to PHP's $_GET superglobal.
*
* @return array
*/
function getQueryParameters();
/**
* Returns the POST data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* @return array
*/
function getPostData();
/**
* Sets the post data.
*
* This is equivalent to PHP's $_POST superglobal.
*
* This would not have been needed, if POST data was accessible as
* php://input, but unfortunately we need to special case it.
*
* @param array $postData
* @return void
*/
function setPostData(array $postData);
/**
* Returns an item from the _SERVER array.
*
* If the value does not exist in the array, null is returned.
*
* @param string $valueName
* @return string|null
*/
function getRawServerValue($valueName);
/**
* Sets the _SERVER array.
*
* @param array $data
* @return void
*/
function setRawServerData(array $data);
/**
* Returns the body as a readable stream resource.
*
* Note that the stream may not be rewindable, and therefore may only be
* read once.
*
* @return resource
*/
function getBodyAsStream();
/**
* Returns the body as a string.
*
* Note that because the underlying data may be based on a stream, this
* method could only work correctly the first time.
*
* @return string
*/
function getBodyAsString();
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* @return array
*/
function getHeaders();
/**
* Returns a specific HTTP header, based on it's name.
*
* The name must be treated as case-insensitive.
*
* If the header does not exist, this method must return null.
*
* @param string $name
* @return string|null
*/
function getHeader($name);
/**
* Updates a HTTP header.
*
* The case-sensitity of the name value must be retained as-is.
*
* @param string $name
* @param string $value
* @return void
*/
function setHeader($name, $value);
/**
* Resets HTTP headers
*
* This method overwrites all existing HTTP headers
*
* @param array $headers
* @return void
*/
function setHeaders(array $headers);
/**
* Adds a new set of HTTP headers.
*
* Any header specified in the array that already exists will be
* overwritten, but any other existing headers will be retained.
*
* @param array $headers
* @return void
*/
function addHeaders(array $headers);
/**
* Removes a HTTP header.
*
* The specified header name must be treated as case-insenstive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*
* @return bool
*/
function removeHeader($name);
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version);
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion();
Response
/**
* Returns the current HTTP status.
*
* This is the status-code as well as the human readable string.
*
* @return string
*/
function getStatus();
/**
* Sets the HTTP status code.
*
* This can be either the full HTTP status code with human readable string,
* for example: "403 I can't let you do that, Dave".
*
* Or just the code, in which case the appropriate default message will be
* added.
*
* @param string|int $status
* @throws \InvalidArgumentExeption
* @return void
*/
function setStatus($status);
/**
* Returns the body as a readable stream resource.
*
* Note that the stream may not be rewindable, and therefore may only be
* read once.
*
* @return resource
*/
function getBodyAsStream();
/**
* Returns the body as a string.
*
* Note that because the underlying data may be based on a stream, this
* method could only work correctly the first time.
*
* @return string
*/
function getBodyAsString();
/**
* Returns the message body, as it's internal representation.
*
* This could be either a string or a stream.
*
* @return resource|string
*/
function getBody();
/**
* Updates the body resource with a new stream.
*
* @param resource $body
* @return void
*/
function setBody($body);
/**
* Returns all the HTTP headers as an array.
*
* @return array
*/
function getHeaders();
/**
* Returns a specific HTTP header, based on it's name.
*
* The name must be treated as case-insensitive.
*
* If the header does not exist, this method must return null.
*
* @param string $name
* @return string|null
*/
function getHeader($name);
/**
* Updates a HTTP header.
*
* The case-sensitity of the name value must be retained as-is.
*
* @param string $name
* @param string $value
* @return void
*/
function setHeader($name, $value);
/**
* Resets HTTP headers
*
* This method overwrites all existing HTTP headers
*
* @param array $headers
* @return void
*/
function setHeaders(array $headers);
/**
* Adds a new set of HTTP headers.
*
* Any header specified in the array that already exists will be
* overwritten, but any other existing headers will be retained.
*
* @param array $headers
* @return void
*/
function addHeaders(array $headers);
/**
* Removes a HTTP header.
*
* The specified header name must be treated as case-insenstive.
* This method should return true if the header was successfully deleted,
* and false if the header did not exist.
*
* @return bool
*/
function removeHeader($name);
/**
* Sets the HTTP version.
*
* Should be 1.0 or 1.1.
*
* @param string $version
* @return void
*/
function setHttpVersion($version);
/**
* Returns the HTTP version.
*
* @return string
*/
function getHttpVersion();
Made at fruux
This library is being developed by fruux. Drop us a line for commercial services or enterprise support., (*36)