├── .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 | [](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 | }
--------------------------------------------------------------------------------