You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
746 lines
16 KiB
746 lines
16 KiB
sabre/http |
|
========== |
|
|
|
This library provides a toolkit to make working with the HTTP protocol easier. |
|
|
|
Most PHP scripts run within a HTTP request but accessing information about the |
|
HTTP request is cumbersome at least. |
|
|
|
There's bad practices, inconsistencies and confusion. This library is |
|
effectively a wrapper around the following PHP constructs: |
|
|
|
For Input: |
|
|
|
* `$_GET`, |
|
* `$_POST`, |
|
* `$_SERVER`, |
|
* `php://input` or `$HTTP_RAW_POST_DATA`. |
|
|
|
For output: |
|
|
|
* `php://output` or `echo`, |
|
* `header()`. |
|
|
|
What this library provides, is a `Request` object, and a `Response` object. |
|
|
|
The objects are extendable and easily mockable. |
|
|
|
Build status |
|
------------ |
|
|
|
| branch | status | |
|
| ------ | ------ | |
|
| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) | |
|
| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) | |
|
|
|
Installation |
|
------------ |
|
|
|
Make sure you have [composer][1] installed. In your project directory, create, |
|
or edit a `composer.json` file, and make sure it contains something like this: |
|
|
|
```json |
|
{ |
|
"require" : { |
|
"sabre/http" : "~3.0.0" |
|
} |
|
} |
|
``` |
|
|
|
After that, just hit `composer install` and you should be rolling. |
|
|
|
Quick history |
|
------------- |
|
|
|
This library came to existence in 2009, as a part of the [`sabre/dav`][2] |
|
project, which uses it heavily. |
|
|
|
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 `sabre/dav`. |
|
|
|
Although completely independently developed, this library has a LOT of |
|
overlap with [Symfony's `HttpFoundation`][3]. |
|
|
|
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`][3]. |
|
|
|
|
|
Getting started |
|
--------------- |
|
|
|
First and foremost, this library wraps the superglobals. The easiest way to |
|
instantiate a request object is as follows: |
|
|
|
```php |
|
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. |
|
|
|
You should always typehint on it's interface: |
|
|
|
```php |
|
function handleRequest(HTTP\RequestInterface $request) { |
|
|
|
// Do something with this request :) |
|
|
|
} |
|
``` |
|
|
|
A response object you can just create as such: |
|
|
|
```php |
|
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: |
|
|
|
```php |
|
HTTP\Sapi::sendResponse($response); |
|
``` |
|
|
|
This line should generally also appear once in your application (at the very |
|
end). |
|
|
|
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. |
|
|
|
For instance, you may want to add an `isLoggedIn` method to the Request |
|
object. |
|
|
|
Simply extending Request and Response may pose some problems: |
|
|
|
1. You may want to extend the objects with new behaviors differently, in |
|
different subsystems of your application, |
|
2. The `Sapi::getRequest` factory always returns a instance of |
|
`Request` so you would have to override the factory method as well, |
|
3. 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][6] to add new behavior where you need it. `sabre/http` |
|
provides helper classes to quickly do this. |
|
|
|
Example: |
|
|
|
```php |
|
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. |
|
|
|
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: |
|
|
|
```php |
|
$request = new MyRequest($request); |
|
``` |
|
|
|
And that's it, we now have an `isLoggedIn` method, without having to mess |
|
with the core instances. |
|
|
|
|
|
Client |
|
------ |
|
|
|
This package also contains a simple wrapper around [cURL][4], which will allow |
|
you to write simple clients, using the `Request` and `Response` objects you're |
|
already familiar with. |
|
|
|
It's by no means a replacement for something like [Guzzle][7], but it provides |
|
a simple and lightweight API for making the occasional API call. |
|
|
|
### Usage |
|
|
|
```php |
|
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`][5]. `beforeRequest`, |
|
`afterRequest` and `error`. |
|
|
|
```php |
|
$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. |
|
|
|
The underlying system for this is simply [cURL's multi request handler][8], |
|
but this provides a much nicer API to handle this. |
|
|
|
Sample usage: |
|
|
|
```php |
|
|
|
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. |
|
|
|
Writing a reverse proxy |
|
----------------------- |
|
|
|
With all these tools combined, it becomes very easy to write a simple reverse |
|
http proxy. |
|
|
|
```php |
|
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 |
|
|
|
```php |
|
|
|
/** |
|
* 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 |
|
|
|
```php |
|
/** |
|
* 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](https://fruux.com/). Drop us a line for commercial services or enterprise support. |
|
|
|
[1]: http://getcomposer.org/ |
|
[2]: http://sabre.io/ |
|
[3]: https://github.com/symfony/HttpFoundation |
|
[4]: http://php.net/curl |
|
[5]: https://github.com/fruux/sabre-event |
|
[6]: http://en.wikipedia.org/wiki/Decorator_pattern |
|
[7]: http://guzzlephp.org/ |
|
[8]: http://php.net/curl_multi_init
|
|
|