1sabre/http
2==========
3
4This library provides a toolkit to make working with the HTTP protocol easier.
5
6Most PHP scripts run within a HTTP request but accessing information about the
7HTTP request is cumbersome at least.
8
9There's bad practices, inconsistencies and confusion. This library is
10effectively a wrapper around the following PHP constructs:
11
12For Input:
13
14* `$_GET`,
15* `$_POST`,
16* `$_SERVER`,
17* `php://input` or `$HTTP_RAW_POST_DATA`.
18
19For output:
20
21* `php://output` or `echo`,
22* `header()`.
23
24What this library provides, is a `Request` object, and a `Response` object.
25
26The objects are extendable and easily mockable.
27
28Build status
29------------
30
31| branch | status |
32| ------ | ------ |
33| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) |
34| 3.0    | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) |
35
36Installation
37------------
38
39Make sure you have [composer][1] installed. In your project directory, create,
40or edit a `composer.json` file, and make sure it contains something like this:
41
42```json
43{
44    "require" : {
45        "sabre/http" : "~3.0.0"
46    }
47}
48```
49
50After that, just hit `composer install` and you should be rolling.
51
52Quick history
53-------------
54
55This library came to existence in 2009, as a part of the [`sabre/dav`][2]
56project, which uses it heavily.
57
58It got split off into a separate library to make it easier to manage
59releases and hopefully giving it use outside of the scope of just `sabre/dav`.
60
61Although completely independently developed, this library has a LOT of
62overlap with [Symfony's `HttpFoundation`][3].
63
64Said library does a lot more stuff and is significantly more popular,
65so if you are looking for something to fulfill this particular requirement,
66I'd recommend also considering [`HttpFoundation`][3].
67
68
69Getting started
70---------------
71
72First and foremost, this library wraps the superglobals. The easiest way to
73instantiate a request object is as follows:
74
75```php
76use Sabre\HTTP;
77
78include 'vendor/autoload.php';
79
80$request = HTTP\Sapi::getRequest();
81```
82
83This line should only happen once in your entire application. Everywhere else
84you should pass this request object around using dependency injection.
85
86You should always typehint on it's interface:
87
88```php
89function handleRequest(HTTP\RequestInterface $request) {
90
91    // Do something with this request :)
92
93}
94```
95
96A response object you can just create as such:
97
98```php
99use Sabre\HTTP;
100
101include 'vendor/autoload.php';
102
103$response = new HTTP\Response();
104$response->setStatus(201); // created !
105$response->setHeader('X-Foo', 'bar');
106$response->setBody(
107    'success!'
108);
109
110```
111
112After you fully constructed your response, you must call:
113
114```php
115HTTP\Sapi::sendResponse($response);
116```
117
118This line should generally also appear once in your application (at the very
119end).
120
121Decorators
122----------
123
124It may be useful to extend the `Request` and `Response` objects in your
125application, if you for example would like them to carry a bit more
126information about the current request.
127
128For instance, you may want to add an `isLoggedIn` method to the Request
129object.
130
131Simply extending Request and Response may pose some problems:
132
1331. You may want to extend the objects with new behaviors differently, in
134   different subsystems of your application,
1352. The `Sapi::getRequest` factory always returns a instance of
136   `Request` so you would have to override the factory method as well,
1373. By controlling the instantation and depend on specific `Request` and
138   `Response` instances in your library or application, you make it harder to
139   work with other applications which also use `sabre/http`.
140
141In short: it would be bad design. Instead, it's recommended to use the
142[decorator pattern][6] to add new behavior where you need it. `sabre/http`
143provides helper classes to quickly do this.
144
145Example:
146
147```php
148use Sabre\HTTP;
149
150class MyRequest extends HTTP\RequestDecorator {
151
152    function isLoggedIn() {
153
154        return true;
155
156    }
157
158}
159```
160
161Our application assumes that the true `Request` object was instantiated
162somewhere else, by some other subsystem. This could simply be a call like
163`$request = Sapi::getRequest()` at the top of your application,
164but could also be somewhere in a unittest.
165
166All we know in the current subsystem, is that we received a `$request` and
167that it implements `Sabre\HTTP\RequestInterface`. To decorate this object,
168all we need to do is:
169
170```php
171$request = new MyRequest($request);
172```
173
174And that's it, we now have an `isLoggedIn` method, without having to mess
175with the core instances.
176
177
178Client
179------
180
181This package also contains a simple wrapper around [cURL][4], which will allow
182you to write simple clients, using the `Request` and `Response` objects you're
183already familiar with.
184
185It's by no means a replacement for something like [Guzzle][7], but it provides
186a simple and lightweight API for making the occasional API call.
187
188### Usage
189
190```php
191use Sabre\HTTP;
192
193$request = new HTTP\Request('GET', 'http://example.org/');
194$request->setHeader('X-Foo', 'Bar');
195
196$client = new HTTP\Client();
197$response = $client->send($request);
198
199echo $response->getBodyAsString();
200```
201
202The client emits 3 event using [`sabre/event`][5]. `beforeRequest`,
203`afterRequest` and `error`.
204
205```php
206$client = new HTTP\Client();
207$client->on('beforeRequest', function($request) {
208
209    // You could use beforeRequest to for example inject a few extra headers.
210    // into the Request object.
211
212});
213
214$client->on('afterRequest', function($request, $response) {
215
216    // The afterRequest event could be a good time to do some logging, or
217    // do some rewriting in the response.
218
219});
220
221$client->on('error', function($request, $response, &$retry, $retryCount) {
222
223    // The error event is triggered for every response with a HTTP code higher
224    // than 399.
225
226});
227
228$client->on('error:401', function($request, $response, &$retry, $retryCount) {
229
230    // You can also listen for specific error codes. This example shows how
231    // to inject HTTP authentication headers if a 401 was returned.
232
233    if ($retryCount > 1) {
234        // We're only going to retry exactly once.
235    }
236
237    $request->setHeader('Authorization', 'Basic xxxxxxxxxx');
238    $retry = true;
239
240});
241```
242
243### Asynchronous requests
244
245The `Client` also supports doing asynchronous requests. This is especially handy
246if you need to perform a number of requests, that are allowed to be executed
247in parallel.
248
249The underlying system for this is simply [cURL's multi request handler][8],
250but this provides a much nicer API to handle this.
251
252Sample usage:
253
254```php
255
256use Sabre\HTTP;
257
258$request = new Request('GET', 'http://localhost/');
259$client = new Client();
260
261// Executing 1000 requests
262for ($i = 0; $i < 1000; $i++) {
263    $client->sendAsync(
264        $request,
265        function(ResponseInterface $response) {
266            // Success handler
267        },
268        function($error) {
269            // Error handler
270        }
271    );
272}
273
274// Wait for all requests to get a result.
275$client->wait();
276
277```
278
279Check out `examples/asyncclient.php` for more information.
280
281Writing a reverse proxy
282-----------------------
283
284With all these tools combined, it becomes very easy to write a simple reverse
285http proxy.
286
287```php
288use
289    Sabre\HTTP\Sapi,
290    Sabre\HTTP\Client;
291
292// The url we're proxying to.
293$remoteUrl = 'http://example.org/';
294
295// The url we're proxying from. Please note that this must be a relative url,
296// and basically acts as the base url.
297//
298// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't
299// either.
300$myBaseUrl = '/reverseproxy.php';
301// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/';
302
303$request = Sapi::getRequest();
304$request->setBaseUrl($myBaseUrl);
305
306$subRequest = clone $request;
307
308// Removing the Host header.
309$subRequest->removeHeader('Host');
310
311// Rewriting the url.
312$subRequest->setUrl($remoteUrl . $request->getPath());
313
314$client = new Client();
315
316// Sends the HTTP request to the server
317$response = $client->send($subRequest);
318
319// Sends the response back to the client that connected to the proxy.
320Sapi::sendResponse($response);
321```
322
323The Request and Response API's
324------------------------------
325
326### Request
327
328```php
329
330/**
331 * Creates the request object
332 *
333 * @param string $method
334 * @param string $url
335 * @param array $headers
336 * @param resource $body
337 */
338public function __construct($method = null, $url = null, array $headers = null, $body = null);
339
340/**
341 * Returns the current HTTP method
342 *
343 * @return string
344 */
345function getMethod();
346
347/**
348 * Sets the HTTP method
349 *
350 * @param string $method
351 * @return void
352 */
353function setMethod($method);
354
355/**
356 * Returns the request url.
357 *
358 * @return string
359 */
360function getUrl();
361
362/**
363 * Sets the request url.
364 *
365 * @param string $url
366 * @return void
367 */
368function setUrl($url);
369
370/**
371 * Returns the absolute url.
372 *
373 * @return string
374 */
375function getAbsoluteUrl();
376
377/**
378 * Sets the absolute url.
379 *
380 * @param string $url
381 * @return void
382 */
383function setAbsoluteUrl($url);
384
385/**
386 * Returns the current base url.
387 *
388 * @return string
389 */
390function getBaseUrl();
391
392/**
393 * Sets a base url.
394 *
395 * This url is used for relative path calculations.
396 *
397 * The base url should default to /
398 *
399 * @param string $url
400 * @return void
401 */
402function setBaseUrl($url);
403
404/**
405 * Returns the relative path.
406 *
407 * This is being calculated using the base url. This path will not start
408 * with a slash, so it will always return something like
409 * 'example/path.html'.
410 *
411 * If the full path is equal to the base url, this method will return an
412 * empty string.
413 *
414 * This method will also urldecode the path, and if the url was incoded as
415 * ISO-8859-1, it will convert it to UTF-8.
416 *
417 * If the path is outside of the base url, a LogicException will be thrown.
418 *
419 * @return string
420 */
421function getPath();
422
423/**
424 * Returns the list of query parameters.
425 *
426 * This is equivalent to PHP's $_GET superglobal.
427 *
428 * @return array
429 */
430function getQueryParameters();
431
432/**
433 * Returns the POST data.
434 *
435 * This is equivalent to PHP's $_POST superglobal.
436 *
437 * @return array
438 */
439function getPostData();
440
441/**
442 * Sets the post data.
443 *
444 * This is equivalent to PHP's $_POST superglobal.
445 *
446 * This would not have been needed, if POST data was accessible as
447 * php://input, but unfortunately we need to special case it.
448 *
449 * @param array $postData
450 * @return void
451 */
452function setPostData(array $postData);
453
454/**
455 * Returns an item from the _SERVER array.
456 *
457 * If the value does not exist in the array, null is returned.
458 *
459 * @param string $valueName
460 * @return string|null
461 */
462function getRawServerValue($valueName);
463
464/**
465 * Sets the _SERVER array.
466 *
467 * @param array $data
468 * @return void
469 */
470function setRawServerData(array $data);
471
472/**
473 * Returns the body as a readable stream resource.
474 *
475 * Note that the stream may not be rewindable, and therefore may only be
476 * read once.
477 *
478 * @return resource
479 */
480function getBodyAsStream();
481
482/**
483 * Returns the body as a string.
484 *
485 * Note that because the underlying data may be based on a stream, this
486 * method could only work correctly the first time.
487 *
488 * @return string
489 */
490function getBodyAsString();
491
492/**
493 * Returns the message body, as it's internal representation.
494 *
495 * This could be either a string or a stream.
496 *
497 * @return resource|string
498 */
499function getBody();
500
501/**
502 * Updates the body resource with a new stream.
503 *
504 * @param resource $body
505 * @return void
506 */
507function setBody($body);
508
509/**
510 * Returns all the HTTP headers as an array.
511 *
512 * @return array
513 */
514function getHeaders();
515
516/**
517 * Returns a specific HTTP header, based on it's name.
518 *
519 * The name must be treated as case-insensitive.
520 *
521 * If the header does not exist, this method must return null.
522 *
523 * @param string $name
524 * @return string|null
525 */
526function getHeader($name);
527
528/**
529 * Updates a HTTP header.
530 *
531 * The case-sensitity of the name value must be retained as-is.
532 *
533 * @param string $name
534 * @param string $value
535 * @return void
536 */
537function setHeader($name, $value);
538
539/**
540 * Resets HTTP headers
541 *
542 * This method overwrites all existing HTTP headers
543 *
544 * @param array $headers
545 * @return void
546 */
547function setHeaders(array $headers);
548
549/**
550 * Adds a new set of HTTP headers.
551 *
552 * Any header specified in the array that already exists will be
553 * overwritten, but any other existing headers will be retained.
554 *
555 * @param array $headers
556 * @return void
557 */
558function addHeaders(array $headers);
559
560/**
561 * Removes a HTTP header.
562 *
563 * The specified header name must be treated as case-insenstive.
564 * This method should return true if the header was successfully deleted,
565 * and false if the header did not exist.
566 *
567 * @return bool
568 */
569function removeHeader($name);
570
571/**
572 * Sets the HTTP version.
573 *
574 * Should be 1.0 or 1.1.
575 *
576 * @param string $version
577 * @return void
578 */
579function setHttpVersion($version);
580
581/**
582 * Returns the HTTP version.
583 *
584 * @return string
585 */
586function getHttpVersion();
587```
588
589### Response
590
591```php
592/**
593 * Returns the current HTTP status.
594 *
595 * This is the status-code as well as the human readable string.
596 *
597 * @return string
598 */
599function getStatus();
600
601/**
602 * Sets the HTTP status code.
603 *
604 * This can be either the full HTTP status code with human readable string,
605 * for example: "403 I can't let you do that, Dave".
606 *
607 * Or just the code, in which case the appropriate default message will be
608 * added.
609 *
610 * @param string|int $status
611 * @throws \InvalidArgumentExeption
612 * @return void
613 */
614function setStatus($status);
615
616/**
617 * Returns the body as a readable stream resource.
618 *
619 * Note that the stream may not be rewindable, and therefore may only be
620 * read once.
621 *
622 * @return resource
623 */
624function getBodyAsStream();
625
626/**
627 * Returns the body as a string.
628 *
629 * Note that because the underlying data may be based on a stream, this
630 * method could only work correctly the first time.
631 *
632 * @return string
633 */
634function getBodyAsString();
635
636/**
637 * Returns the message body, as it's internal representation.
638 *
639 * This could be either a string or a stream.
640 *
641 * @return resource|string
642 */
643function getBody();
644
645
646/**
647 * Updates the body resource with a new stream.
648 *
649 * @param resource $body
650 * @return void
651 */
652function setBody($body);
653
654/**
655 * Returns all the HTTP headers as an array.
656 *
657 * @return array
658 */
659function getHeaders();
660
661/**
662 * Returns a specific HTTP header, based on it's name.
663 *
664 * The name must be treated as case-insensitive.
665 *
666 * If the header does not exist, this method must return null.
667 *
668 * @param string $name
669 * @return string|null
670 */
671function getHeader($name);
672
673/**
674 * Updates a HTTP header.
675 *
676 * The case-sensitity of the name value must be retained as-is.
677 *
678 * @param string $name
679 * @param string $value
680 * @return void
681 */
682function setHeader($name, $value);
683
684/**
685 * Resets HTTP headers
686 *
687 * This method overwrites all existing HTTP headers
688 *
689 * @param array $headers
690 * @return void
691 */
692function setHeaders(array $headers);
693
694/**
695 * Adds a new set of HTTP headers.
696 *
697 * Any header specified in the array that already exists will be
698 * overwritten, but any other existing headers will be retained.
699 *
700 * @param array $headers
701 * @return void
702 */
703function addHeaders(array $headers);
704
705/**
706 * Removes a HTTP header.
707 *
708 * The specified header name must be treated as case-insenstive.
709 * This method should return true if the header was successfully deleted,
710 * and false if the header did not exist.
711 *
712 * @return bool
713 */
714function removeHeader($name);
715
716/**
717 * Sets the HTTP version.
718 *
719 * Should be 1.0 or 1.1.
720 *
721 * @param string $version
722 * @return void
723 */
724function setHttpVersion($version);
725
726/**
727 * Returns the HTTP version.
728 *
729 * @return string
730 */
731function getHttpVersion();
732```
733
734Made at fruux
735-------------
736
737This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
738
739[1]: http://getcomposer.org/
740[2]: http://sabre.io/
741[3]: https://github.com/symfony/HttpFoundation
742[4]: http://php.net/curl
743[5]: https://github.com/fruux/sabre-event
744[6]: http://en.wikipedia.org/wiki/Decorator_pattern
745[7]: http://guzzlephp.org/
746[8]: http://php.net/curl_multi_init
747