├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── example.php ├── httpkernel_example.php ├── phpunit.xml.dist ├── src ├── HttpKernelRequestHandler.php ├── HttpServer.php ├── RequestHandler.php └── RequestHandlerInterface.php └── tests ├── HttpKernelRequestHandlerTest.php └── HttpServerTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | phpunit.xml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - 7.1 8 | - hhvm 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --no-interaction 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017 Victor Puertas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple HTTP server for PHP 2 | ---------------------------- 3 | 4 | HttpServer is a simple HTTP server powerd [REACT](http://reactphp.org/). 5 | 6 | [![Build Status](https://travis-ci.org/yosymfony/HttpServer.svg?branch=master)](https://travis-ci.org/yosymfony/HttpServer) 7 | 8 | ## Installation 9 | 10 | Use [Composer](http://getcomposer.org/) to install Yosyfmony HttpServer package: 11 | 12 | Add the following to your `composer.json` and run `composer update`. 13 | 14 | "require": { 15 | "yosymfony/httpserver": "1.3.x" 16 | } 17 | 18 | More information about the package on [Packagist](https://packagist.org/packages/yosymfony/httpserver). 19 | 20 | ## How to use? 21 | 22 | It's simple. The RequestHandler need a function for managing each connection: 23 | 24 | ``` 25 | $requestHandler = new RequestHandler(function($request) { 26 | return 'Hi Yo! Symfony'; 27 | }); 28 | 29 | $server = new HttpServer($requestHandler); 30 | $server->start(); 31 | 32 | // go to http://localhost:8080 33 | ``` 34 | 35 | ## How to configure the RequestHandler? 36 | 37 | **You can configure port and host for listening requests**: 38 | 39 | ``` 40 | $requestHandler = new RequestHandler( function($request) { 41 | return 'Hi Yo! Symfony'; 42 | }); 43 | 44 | $requestHandler->listen(8081, '127.0.0.1'); 45 | ``` 46 | 47 | The defatult values: 48 | * port: 8080 49 | * host: 0.0.0.0 50 | 51 | ### The handler function 52 | 53 | The handler function receives a unique parameter to describe the resquest. By default, this argument 54 | is a object type [React\Http\Request](https://github.com/reactphp/http/blob/master/src/Request.php). 55 | If you want to receive a [Symfony HttpFoundation Request](http://symfony.com/doc/current/components/http_foundation/introduction.html#request) 56 | you need active this mode: 57 | 58 | ``` 59 | $requestHandler = new RequestHandler( function($request) { 60 | return 'Hi Yo! Symfony'; 61 | }); 62 | 63 | $requestHandler 64 | ->listen(8081, '127.0.0.1') 65 | ->enableHttpFoundationRequest(); // $requestHandler uses fluent interface 66 | ``` 67 | 68 | In case you want to use a HttpKernelInterface like Symfony, Silex or Laravel, simple use the `HttpKernelRequestHandler` handler like this: 69 | ``` 70 | // Create our kernel. 71 | $httpKernel = new ExampleHttpKernel(); 72 | $options = array( 73 | 'host' => '127.0.0.1', 74 | 'port' => 8081, 75 | ); 76 | 77 | // Wrap it with the RequestHandler. 78 | $handler = new \Yosymfony\HttpServer\HttpKernelRequestHandler($httpKernel, $options); 79 | 80 | // Start the server using the RequestHandler. 81 | $server = new \Yosymfony\HttpServer\HttpServer($handler); 82 | $server->start(); 83 | ``` 84 | 85 | ## The response 86 | 87 | The most simple use-case is return a string. By default the `Content-Type` value is `text/plain` at the response header: 88 | 89 | ``` 90 | $requestHandler = new RequestHandler( function($request) { 91 | return 'Hi Yo! Symfony'; 92 | }); 93 | ``` 94 | 95 | If you want customize the status code and the response header you can return a array like this: 96 | 97 | ``` 98 | requestHandler = new RequestHandler( function($request) { 99 | return [ 100 | 'content' => 'Hi Yo! Symfony', 101 | 'headers' => ['Content-Type' => 'text/xml'], 102 | 'status_code' => 200 103 | ]; 104 | }); 105 | ``` 106 | 107 | The best way to make a response is using [Response from Symfony HttpFoundation](http://symfony.com/doc/current/components/http_foundation/introduction.html#response): 108 | 109 | ``` 110 | use Symfony\Component\HttpFoundation\Response; 111 | 112 | requestHandler = new RequestHandler( function($request) { 113 | return new Response( 114 | 'Hi Yo! Symfony', 115 | Response::HTTP_OK, 116 | array('content-type' => 'text/html') 117 | ); 118 | }); 119 | ``` 120 | 121 | ## Unit tests 122 | 123 | You can run the unit tests with the following command: 124 | 125 | $ cd your-path/vendor/yosymfony/httpserver 126 | $ composer.phar install --dev 127 | $ phpunit 128 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"yosymfony/httpserver", 3 | "description": "A simple HTTP server", 4 | "keywords": ["http", "server", "request", "react"], 5 | "license": "MIT", 6 | "homepage": "http://yosymfony.com", 7 | "authors": [ 8 | { 9 | "name": "Victor Puertas", 10 | "email": "vpgugr@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.5.5", 15 | "react/http": "0.4.*", 16 | "symfony/http-foundation": "^2.7|^3.0", 17 | "symfony/http-kernel": "^2.7|^3.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Yosymfony\\HttpServer\\": "src/" 22 | } 23 | }, 24 | "extra": { 25 | "branch-alias": { 26 | "dev-master": "1.3-dev" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | get('name', 'Yo! Symfony'), 8 | 200, 9 | array('content-type' => 'text/html') 10 | ); 11 | }); 12 | $requestHandler->enableHttpFoundationRequest(); 13 | 14 | $server = new \Yosymfony\HttpServer\HttpServer($requestHandler); 15 | $server->start(); 16 | 17 | // go to http://localhost:8080 -------------------------------------------------------------------------------- /httpkernel_example.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ExampleHttpKernel implements \Symfony\Component\HttpKernel\HttpKernelInterface 11 | { 12 | public function handle(\Symfony\Component\HttpFoundation\Request $request, $type = self::MASTER_REQUEST, $catch = true) 13 | { 14 | $name = $request->get('name', 'World'); 15 | 16 | return new \Symfony\Component\HttpFoundation\Response('Hello '.$name); 17 | } 18 | } 19 | 20 | // Create our kernel. 21 | $httpKernel = new ExampleHttpKernel(); 22 | $options = array( 23 | 'host' => '127.0.0.1', 24 | 'port' => 8081, 25 | ); 26 | 27 | // Wrap it with the RequestHandler. 28 | $handler = new \Yosymfony\HttpServer\HttpKernelRequestHandler($httpKernel, $options); 29 | 30 | // Start the server using the RequestHandler. 31 | $server = new \Yosymfony\HttpServer\HttpServer($handler); 32 | $server->start(); 33 | 34 | // Check the response of the server with for example: 35 | // $ curl -s 'http://127.0.0.1:8081/?name=Julius' 36 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | ./ 23 | 24 | ./vendor 25 | ./tests 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/HttpKernelRequestHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer; 13 | 14 | use React\Http\Request as ReactRequest; 15 | use React\Http\Response as ReactResponse; 16 | use Symfony\Component\HttpFoundation\Request as SymfonyRequest; 17 | use Symfony\Component\HttpKernel\HttpKernelInterface; 18 | use Symfony\Component\HttpKernel\TerminableInterface; 19 | 20 | /** 21 | * Request handler for HttpKernel. 22 | * 23 | * @author Julius Beckmann 24 | */ 25 | class HttpKernelRequestHandler implements RequestHandlerInterface 26 | { 27 | protected $httpKernel; 28 | protected $handlerFunction; 29 | protected $options; 30 | 31 | function __construct(HttpKernelInterface $httpKernel, array $options = array()) 32 | { 33 | $this->options = array_merge($this->getDefaultOptions(), $options); 34 | 35 | $this->httpKernel = $httpKernel; 36 | $this->prepareHandlerFunction(); 37 | } 38 | 39 | public function getDefaultOptions() 40 | { 41 | return array( 42 | 'port' => 8080, 43 | 'host' => '0.0.0.0', 44 | ); 45 | } 46 | 47 | /** 48 | * @{inheritdoc} 49 | */ 50 | public function getHandlerFunction() 51 | { 52 | return $this->handlerFunction; 53 | } 54 | 55 | /** 56 | * @{inheritdoc} 57 | */ 58 | public function getPort() 59 | { 60 | return $this->options['port']; 61 | } 62 | 63 | /** 64 | * @{inheritdoc} 65 | */ 66 | public function getHost() 67 | { 68 | return $this->options['host']; 69 | } 70 | 71 | protected function prepareHandlerFunction() 72 | { 73 | $this->handlerFunction = function(ReactRequest $reactRequest, ReactResponse $reactResponse) 74 | { 75 | // Create symfony request and response. 76 | $symfonyRequest = SymfonyRequest::create( 77 | $reactRequest->getPath(), 78 | $reactRequest->getMethod(), 79 | $reactRequest->getQuery() 80 | ); 81 | $symfonyResponse = $this->httpKernel->handle($symfonyRequest); 82 | 83 | // Give response to React. 84 | $reactResponse->writeHead($symfonyResponse->getStatusCode(), $symfonyResponse->headers->all()); 85 | $reactResponse->end($symfonyResponse->getContent()); 86 | 87 | // Trigger HttpKernel terminate event. 88 | if($this->httpKernel instanceof TerminableInterface) { 89 | $this->httpKernel->terminate($symfonyRequest, $symfonyResponse); 90 | } 91 | }; 92 | } 93 | } -------------------------------------------------------------------------------- /src/HttpServer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer; 13 | 14 | use React; 15 | 16 | /** 17 | * HTTP Server powered by REACT 18 | * 19 | * @author Victor Puertas 20 | */ 21 | class HttpServer 22 | { 23 | private $requestHandler; 24 | private $loop; 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param RequestHandlerInterface $requestHandler 30 | */ 31 | public function __construct(RequestHandlerInterface $requestHandler) 32 | { 33 | $this->requestHandler = $requestHandler; 34 | } 35 | 36 | /** 37 | * start the loop 38 | */ 39 | public function start() 40 | { 41 | $this->loop = React\EventLoop\Factory::create(); 42 | $socket = new React\Socket\Server($this->loop); 43 | 44 | $http = new React\Http\Server($socket); 45 | $http->on('request', $function = $this->requestHandler->getHandlerFunction()); 46 | 47 | $socket->listen($this->requestHandler->getPort(), $this->requestHandler->getHost()); 48 | $this->loop->run(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/RequestHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer; 13 | 14 | use React; 15 | use Symfony\Component\HttpFoundation\Request; 16 | use Symfony\Component\HttpFoundation\Response; 17 | 18 | /** 19 | * HTTP requests handler 20 | * 21 | * @author Victor Puertas 22 | */ 23 | class RequestHandler implements RequestHandlerInterface 24 | { 25 | protected $port = 8080; 26 | protected $host = '0.0.0.0'; 27 | protected $statusCode = 200; 28 | protected $content = ''; 29 | protected $headers = []; 30 | protected $enableHttpFoundation = false; 31 | protected $handler; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param callable $handler Function to handle each request 37 | */ 38 | public function __construct(callable $handler) 39 | { 40 | $this->prepareHandlerFunction($handler); 41 | $this->headers = $this->getDefaultHeaders(); 42 | } 43 | 44 | /** 45 | * Setup for listen requests 46 | * 47 | * @param int $port 48 | * @param string $host 49 | * 50 | * @return RequestHandler 51 | */ 52 | public function listen($port, $host = '0.0.0.0') 53 | { 54 | $this->port = $port; 55 | $this->host = $host; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Enable HttpFoundation Request from Symfony. The function handler 62 | * receives a Symfony\Component\HttpFoundation\Request as argument 63 | * 64 | * @return RequestHandler; 65 | */ 66 | public function enableHttpFoundationRequest() 67 | { 68 | $this->enableHttpFoundation = true; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * @{inheritdoc} 75 | */ 76 | public function getPort() 77 | { 78 | return $this->port; 79 | } 80 | 81 | /** 82 | * @{inheritdoc} 83 | */ 84 | public function getHost() 85 | { 86 | return $this->host; 87 | } 88 | 89 | /** 90 | * @{inheritdoc} 91 | */ 92 | public function getHandlerFunction() 93 | { 94 | return $this->handler; 95 | } 96 | 97 | protected function prepareHandlerFunction(callable $handler) 98 | { 99 | $this->handler = function(React\Http\Request $request, React\Http\Response $response) use ($handler) 100 | { 101 | $requestObj = $request; 102 | 103 | if($this->enableHttpFoundation) 104 | { 105 | $requestObj = Request::create($request->getPath(), $request->getMethod(), $request->getQuery()); 106 | } 107 | 108 | $result = call_user_func_array($handler, [$requestObj]); 109 | $this->prepareResult($result); 110 | 111 | $response->writeHead($this->statusCode, $this->headers); 112 | $response->end($this->content); 113 | }; 114 | } 115 | 116 | protected function prepareResult($result) 117 | { 118 | if($result instanceof Response) 119 | { 120 | $this->statusCode = $result->getStatusCode(); 121 | $this->headers = $result->headers->all(); 122 | $this->content = $result->getContent(); 123 | 124 | return; 125 | } 126 | 127 | if(is_array($result)) 128 | { 129 | if(isset($result['content'])) 130 | { 131 | $this->content = $result['content']; 132 | } 133 | 134 | if(isset($result['status_code'])) 135 | { 136 | $this->statusCode = $result['status_code']; 137 | } 138 | 139 | if(isset($result['headers']) && is_array($result['headers'])) 140 | { 141 | $this->headers = $result['headers']; 142 | } 143 | 144 | return; 145 | } 146 | 147 | $this->content = $result; 148 | } 149 | 150 | protected function getDefaultHeaders() 151 | { 152 | return ['Content-Type' => 'text/plain']; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/RequestHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer; 13 | 14 | /** 15 | * Inteface for Requests handler 16 | * 17 | * @author Victor Puertas 18 | */ 19 | interface RequestHandlerInterface 20 | { 21 | /** 22 | * Get the function for handling each request 23 | * 24 | * @return callable 25 | */ 26 | public function getHandlerFunction(); 27 | 28 | /** 29 | * Get the port 30 | * 31 | * @return int 32 | */ 33 | public function getPort(); 34 | 35 | /** 36 | * Get the host. '0.0.0.0' for allowing access from everywhere 37 | * 38 | * @return string 39 | */ 40 | public function getHost(); 41 | } -------------------------------------------------------------------------------- /tests/HttpKernelRequestHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer\Tests; 13 | 14 | use React\Http\Request; 15 | use Symfony\Component\HttpFoundation\Response as SymfonyResponse; 16 | use Yosymfony\HttpServer\HttpKernelRequestHandler; 17 | 18 | /** 19 | * Class HttpKernelRequestHandlerTest. 20 | * 21 | * @author Julius Beckmann 22 | * @covers Yosymfony\HttpServer\HttpKernelRequestHandler 23 | */ 24 | class HttpKernelRequestHandlerTest extends \PHPUnit_Framework_TestCase 25 | { 26 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 27 | private $httpKernelMock; 28 | 29 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 30 | private $requestHandler; 31 | 32 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 33 | private $handlerFunction; 34 | 35 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 36 | private $reactResponseMock; 37 | 38 | public function setUp() 39 | { 40 | $this->httpKernelMock = $this->getMockBuilder('\Symfony\Component\HttpKernel\HttpKernelInterface') 41 | ->setMethods(array('handle')) 42 | ->getMockForAbstractClass(); 43 | 44 | $this->requestHandler = new HttpKernelRequestHandler($this->httpKernelMock, array( 45 | 'port' => 1337, 46 | 'host' => 'foo.bar', 47 | )); 48 | 49 | $this->handlerFunction = $this->requestHandler->getHandlerFunction(); 50 | 51 | $this->reactResponseMock = $this->getMockBuilder('\React\Http\Response') 52 | ->disableOriginalConstructor() 53 | ->setMethods(['writeHead', 'end']) 54 | ->getMock(); 55 | } 56 | 57 | public function testSimpleRequest() 58 | { 59 | $response = new SymfonyResponse('some_content'); 60 | $this->httpKernelMock->expects($this->once())->method('handle') 61 | ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Request')) 62 | ->will($this->returnValue($response)); 63 | 64 | $this->reactResponseMock->expects($this->once())->method('writeHead') 65 | ->with($response->getStatusCode(), $response->headers->all()); 66 | $this->reactResponseMock->expects($this->once())->method('end') 67 | ->with($response->getContent()); 68 | 69 | $request = new Request('GET', '/', ['foo' => 'bar']); 70 | 71 | call_user_func_array($this->handlerFunction, [$request, $this->reactResponseMock]); 72 | } 73 | 74 | public function testDefaultOptions() 75 | { 76 | $this->assertEquals( 77 | [ 78 | 'port' => 8080, 79 | 'host' => '0.0.0.0', 80 | ], 81 | $this->requestHandler->getDefaultOptions() 82 | ); 83 | } 84 | 85 | public function testHostAndPort() 86 | { 87 | $this->assertEquals( 88 | [ 89 | 1337, 90 | 'foo.bar', 91 | ], 92 | [ 93 | $this->requestHandler->getPort(), 94 | $this->requestHandler->getHost(), 95 | ] 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/HttpServerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Yosymfony\HttpServer\Tests; 13 | 14 | use React\Http\Request; 15 | use React\Http\Response; 16 | use Yosymfony\HttpServer\HttpServer; 17 | use Yosymfony\HttpServer\RequestHandler; 18 | 19 | class HttpServerTest extends \PHPUnit_Framework_TestCase 20 | { 21 | protected $request; 22 | protected $response; 23 | 24 | public function setUp() 25 | { 26 | $headers = []; 27 | $this->request = new Request('GET', '/', array(), '1.1', $headers); 28 | 29 | $conn = $this->getMock('React\Socket\ConnectionInterface'); 30 | $this->response = new Response($conn); 31 | } 32 | 33 | public function testHttpServer() 34 | { 35 | $requestHandler = new RequestHandler(function($request) { 36 | 37 | $this->assertInstanceOf('React\Http\Request', $request); 38 | 39 | return 'Hi Yo! Symfony'; 40 | }); 41 | 42 | $handler = $requestHandler->getHandlerFunction(); 43 | call_user_func_array($handler, [$this->request, $this->response]); 44 | } 45 | 46 | public function testHttpServerWithHttpFoundationRequest() 47 | { 48 | $requestHandler = new RequestHandler(function($request) { 49 | 50 | $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $request); 51 | 52 | return 'Hi Yo! Symfony'; 53 | }); 54 | 55 | $requestHandler->enableHttpFoundationRequest(); 56 | 57 | $handler = $requestHandler->getHandlerFunction(); 58 | call_user_func_array($handler, [$this->request, $this->response]); 59 | } 60 | 61 | public function testConfigureHost() 62 | { 63 | $requestHandler = new RequestHandler(function($request) { 64 | return 'Hi Yo! Symfony'; 65 | }); 66 | $requestHandler->listen(8080, '127.0.0.1'); 67 | 68 | $this->assertEquals(8080, $requestHandler->getPort()); 69 | $this->assertEquals('127.0.0.1', $requestHandler->getHost()); 70 | } 71 | } --------------------------------------------------------------------------------