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 | [](https://travis-ci.org/fruux/sabre-http) | 34| 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