@@ -11,7 +11,6 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht
1111 * [ Server] ( #server )
1212 * [ Request] ( #request )
1313 * [ Response] ( #response )
14- * [ writeHead()] ( #writehead )
1514* [ Install] ( #install )
1615* [ Tests] ( #tests )
1716* [ License] ( #license )
@@ -24,9 +23,12 @@ This is an HTTP server which responds with `Hello World` to every request.
2423$loop = React\EventLoop\Factory::create();
2524$socket = new React\Socket\Server(8080, $loop);
2625
27- $http = new Server($socket, function (RequestInterface $request, Response $response) {
28- $response->writeHead(200, array('Content-Type' => 'text/plain'));
29- $response->end("Hello World!\n");
26+ $http = new Server($socket, function (RequestInterface $request) {
27+ return new Response(
28+ 200,
29+ array('Content-Type' => 'text/plain'),
30+ "Hello World!\n"
31+ );
3032});
3133
3234$loop->run();
@@ -52,9 +54,12 @@ constructor with the respective [request](#request) and
5254``` php
5355$socket = new React\Socket\Server(8080, $loop);
5456
55- $http = new Server($socket, function (RequestInterface $request, Response $response) {
56- $response->writeHead(200, array('Content-Type' => 'text/plain'));
57- $response->end("Hello World!\n");
57+ $http = new Server($socket, function (RequestInterface $request) {
58+ return new Response(
59+ 200,
60+ array('Content-Type' => 'text/plain'),
61+ "Hello World!\n"
62+ );
5863});
5964```
6065
@@ -70,9 +75,12 @@ $socket = new React\Socket\SecureServer($socket, $loop, array(
7075 'local_cert' => __DIR__ . '/localhost.pem'
7176));
7277
73- $http = new Server($socket, function (RequestInterface $request, Response $response) {
74- $response->writeHead(200, array('Content-Type' => 'text/plain'));
75- $response->end("Hello World!\n");
78+ $http = new Server($socket, function (RequestInterface $request) {
79+ return new Response(
80+ 200,
81+ array('Content-Type' => 'text/plain'),
82+ "Hello World!\n"
83+ );
7684});
7785```
7886
@@ -102,6 +110,22 @@ $http->on('error', function (Exception $e) {
102110});
103111```
104112
113+ The server will also emit an ` error ` event if you return an invalid
114+ type in the callback function or have a unhandled ` Exception ` .
115+ If your callback function throws an exception,
116+ the ` Server ` will emit a ` RuntimeException ` and add the thrown exception
117+ as previous:
118+
119+ ``` php
120+ $http->on('error', function (Exception $e) {
121+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
122+ if ($e->getPrevious() !== null) {
123+ $previousException = $e->getPrevious();
124+ echo $previousException->getMessage() . PHP_EOL;
125+ }
126+ });
127+ ```
128+
105129Note that the request object can also emit an error.
106130Check out [ request] ( #request ) for more details.
107131
@@ -117,10 +141,15 @@ This request object implements the
117141and will be passed to the callback function like this.
118142
119143 ``` php
120- $http = new Server($socket, function (RequestInterface $request, Response $response) {
121- $response->writeHead(200, array('Content-Type' => 'text/plain'));
122- $response->write("The method of the request is: " . $request->getMethod());
123- $response->end("The requested path is: " . $request->getUri()->getPath());
144+ $http = new Server($socket, function (RequestInterface $request) {
145+ $body = "The method of the request is: " . $request->getMethod();
146+ $body .= "The requested path is: " . $request->getUri()->getPath();
147+
148+ return new Response(
149+ 200,
150+ array('Content-Type' => 'text/plain'),
151+ $body
152+ );
124153});
125154```
126155
@@ -155,22 +184,31 @@ Instead, you should use the `ReactPHP ReadableStreamInterface` which
155184gives you access to the incoming request body as the individual chunks arrive:
156185
157186``` php
158- $http = new Server($socket, function (RequestInterface $request, Response $response) {
159- $contentLength = 0;
160- $body = $request->getBody();
161- $body->on('data', function ($data) use (& $contentLength) {
162- $contentLength += strlen($data);
163- });
164-
165- $body->on('end', function () use ($response, & $contentLength){
166- $response->writeHead(200, array('Content-Type' => 'text/plain'));
167- $response->end("The length of the submitted request body is: " . $contentLength);
168- });
169-
170- // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
171- $body->on('error', function (\Exception $exception) use ($response, & $contentLength) {
172- $response->writeHead(400, array('Content-Type' => 'text/plain'));
173- $response->end("An error occured while reading at length: " . $contentLength);
187+ $http = new Server($socket, function (RequestInterface $request) {
188+ return new Promise(function ($resolve, $reject) use ($request) {
189+ $contentLength = 0;
190+ $request->getBody()->on('data', function ($data) use (& $contentLength) {
191+ $contentLength += strlen($data);
192+ });
193+
194+ $request->getBody()->on('end', function () use ($resolve, & $contentLength){
195+ $response = new Response(
196+ 200,
197+ array('Content-Type' => 'text/plain'),
198+ "The length of the submitted request body is: " . $contentLength
199+ );
200+ $resolve($response);
201+ });
202+
203+ // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
204+ $request->getBody()->on('error', function (\Exception $exception) use ($resolve, & $contentLength) {
205+ $response = new Response(
206+ 400,
207+ array('Content-Type' => 'text/plain'),
208+ "An error occured while reading at length: " . $contentLength
209+ );
210+ $resolve($response);
211+ });
174212 });
175213});
176214```
@@ -210,109 +248,176 @@ Note that this value may be `null` if the request body size is unknown in
210248advance because the request message uses chunked transfer encoding.
211249
212250``` php
213- $http = new Server($socket, function (RequestInterface $request, Response $response ) {
251+ $http = new Server($socket, function (RequestInterface $request) {
214252 $size = $request->getBody()->getSize();
215253 if ($size === null) {
216- $response->writeHead(411, array('Content-Type' => 'text/plain'));
217- $response->write('The request does not contain an explicit length.');
218- $response->write('This server does not accept chunked transfer encoding.');
219- $response->end();
220- return;
254+ $body = 'The request does not contain an explicit length.';
255+ $body .= 'This server does not accept chunked transfer encoding.';
256+
257+ return new Response(
258+ 411,
259+ array('Content-Type' => 'text/plain'),
260+ $body
261+ );
221262 }
222- $response->writeHead(200, array('Content-Type' => 'text/plain'));
223- $response->end("Request body size: " . $size . " bytes\n");
263+
264+ return new Response(
265+ 200,
266+ array('Content-Type' => 'text/plain'),
267+ "Request body size: " . $size . " bytes\n"
268+ );
224269});
225270```
226271
227272### Response
228273
229- The ` Response ` class is responsible for streaming the outgoing response body.
274+ The callback function passed to the constructor of the [ Server] ( #server )
275+ is responsible for processing the request and returning a response,
276+ which will be delivered to the client.
277+ This function MUST return an instance imlementing
278+ [ PSR-7 ResponseInterface] ( https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#33-psrhttpmessageresponseinterface )
279+ object or a
280+ [ ReactPHP Promise] ( https://github.com/reactphp/promise#reactpromise )
281+ which will resolve a ` PSR-7 ResponseInterface ` object.
282+
283+ You will find a ` Response ` class
284+ which implements the ` PSR-7 ResponseInterface ` in this project.
285+ We use instantiation of this class in our projects,
286+ but feel free to use any implemantation of the
287+ ` PSR-7 ResponseInterface ` you prefer.
230288
231- It implements the ` WritableStreamInterface ` .
232-
233- See also [ example #3 ] ( examples ) for more details.
289+ ``` php
290+ $http = new Server($socket, function (RequestInterface $request) {
291+ return new Response(
292+ 200,
293+ array('Content-Type' => 'text/plain'),
294+ "Hello World!\n"
295+ );
296+ });
297+ ```
234298
235- The constructor is internal, you SHOULD NOT call this yourself.
236- The ` Server ` is responsible for emitting ` Request ` and ` Response ` objects.
299+ The example above returns the response directly, because it needs
300+ no time to be processed.
301+ Using a database, the file system or long calculations(
302+ in fact every action that will take >=1ms) to create your
303+ response, will slow down the server.
304+ To prevent this you SHOULD use a
305+ [ ReactPHP Promise] ( https://github.com/reactphp/promise#reactpromise ) .
306+ This example shows how such a long-term action could look like:
237307
238- The ` Response ` will automatically use the same HTTP protocol version as the
239- corresponding ` Request ` .
308+ ``` php
309+ $server = new \React\Http\Server($socket, function (RequestInterface $request) use ($loop) {
310+ return new Promise(function ($resolve, $reject) use ($request, $loop) {
311+ $loop->addTimer(1, function() use ($loop, $resolve) {
312+ $response = new Response(
313+ 200,
314+ array('Content-Type' => 'text/plain'),
315+ "Hello world"
316+ );
317+ $resolve($response);
318+ });
319+ });
320+ });
321+ ```
240322
241- HTTP/1.1 responses will automatically apply chunked transfer encoding if
242- no ` Content-Length ` header has been set.
243- See [ ` writeHead() ` ] ( #writehead ) for more details.
323+ The above example will create a response after 1 second.
324+ This example shows that you need a promise,
325+ if your response needs time to created.
326+ The ` ReactPHP Promise ` will resolve in a ` Response ` object when the request
327+ body ends.
244328
245- See the above usage example and the class outline for details.
329+ The ` Response ` class in this project supports to add an instance which implements the
330+ [ ReactPHP ReadableStreamInterface] ( https://github.com/reactphp/stream#readablestreaminterface )
331+ for the response body.
332+ So you are able stream data directly into the response body.
333+ Note that other implementations of the ` PSR-7 ResponseInterface ` likely
334+ only support string.
246335
247- #### writeHead()
336+ ``` php
337+ $server = new Server($socket, function (RequestInterface $request) use ($loop) {
338+ $stream = new ReadableStream();
248339
249- The ` writeHead(int $status = 200, array $headers = array(): void ` method can be used to
250- write the given HTTP message header.
340+ $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) {
341+ $stream->emit('data', array(microtime(true) . PHP_EOL));
342+ });
251343
252- This method MUST be invoked once before calling ` write() ` or ` end() ` to send
253- the actual HTTP message body:
344+ $loop->addTimer(5, function() use ($loop, $timer, $stream) {
345+ $loop->cancelTimer($timer);
346+ $stream->emit('end');
347+ });
254348
255- ``` php
256- $response->writeHead(200, array(
257- 'Content-Type' => 'text/plain'
258- ));
259- $response->end('Hello World!');
349+ return new Response(200, array('Content-Type' => 'text/plain'), $stream);
350+ });
260351```
261352
262- Calling this method more than once will result in an ` Exception `
263- (unless the response has ended/closed already).
264- Calling this method after the response has ended/closed is a NOOP.
353+ The above example will emit every 0.5 seconds the current Unix timestamp
354+ with microseconds as float to the client and will end after 5 seconds.
355+ This is just a example you could use of the streaming,
356+ you could also send a big amount of data via little chunks
357+ or use it for body data that needs to calculated.
265358
266- Unless you specify a ` Content-Length ` header yourself, HTTP/1.1 responses
267- will automatically use chunked transfer encoding and send the respective header
359+ If the response body is a ` string ` a ` Content-Length ` header will be added automatically.
360+ Unless you specify a ` Content-Length ` header for a ReactPHP ` ReadableStreamInterface `
361+ response body yourself, HTTP/1.1 responses will automatically use chunked transfer encoding
362+ and send the respective header
268363(` Transfer-Encoding: chunked ` ) automatically. The server is responsible for handling
269364` Transfer-Encoding ` so you SHOULD NOT pass it yourself.
270- If you know the length of your body, you MAY specify it like this instead:
365+ If you know the length of your stream body, you MAY specify it like this instead:
271366
272367``` php
273- $data = 'Hello World!';
274-
275- $response->writeHead(200, array(
276- 'Content-Type' => 'text/plain',
277- 'Content-Length' => strlen($data)
278- ));
279- $response->end($data);
368+ $stream = new ReadableStream()
369+ $server = new Server($socket, function (RequestInterface $request) use ($loop, $stream) {
370+ return new Response(
371+ 200,
372+ array(
373+ 'Content-Length' => '5',
374+ 'Content-Type' => 'text/plain',
375+ ),
376+ $stream
377+ );
378+ });
280379```
380+ An invalid return value or an unhandled ` Exception ` in the code of the callback
381+ function, will result in an ` 500 Internal Server Error ` message.
382+ Make sure to catch ` Exceptions ` to create own response messages.
383+
384+ After the return in the callback function the response will be processed by the ` Server ` .
385+ The ` Server ` will add the protocol version of the request, so you don't have to.
281386
282387A ` Date ` header will be automatically added with the system date and time if none is given.
283388You can add a custom ` Date ` header yourself like this:
284389
285390``` php
286- $response->writeHead(200, array(
287- 'Date' => date('D, d M Y H:i:s T')
288- ) );
391+ $server = new Server($socket, function (RequestInterface $request) {
392+ return new Response(200, array( 'Date' => date('D, d M Y H:i:s T')));
393+ } );
289394```
290395
291396If you don't have a appropriate clock to rely on, you should
292- unset this header with an empty array :
397+ unset this header with an empty string :
293398
294399``` php
295- $response->writeHead(200, array(
296- 'Date' => array()
297- ) );
400+ $server = new Server($socket, function (RequestInterface $request) {
401+ return new Response(200, array( 'Date' => ''));
402+ } );
298403```
299404
300405Note that it will automatically assume a ` X-Powered-By: react/alpha ` header
301406unless your specify a custom ` X-Powered-By ` header yourself:
302407
303408``` php
304- $response->writeHead(200, array(
305- 'X-Powered-By' => 'PHP 3'
306- ) );
409+ $server = new Server($socket, function (RequestInterface $request) {
410+ return new Response(200, array( 'X-Powered-By' => 'PHP 3'));
411+ } );
307412```
308413
309- If you do not want to send this header at all, you can use an empty array as
414+ If you do not want to send this header at all, you can use an empty string as
310415value like this:
311416
312417``` php
313- $response->writeHead(200, array(
314- 'X-Powered-By' => array()
315- ) );
418+ $server = new Server($socket, function (RequestInterface $request) {
419+ return new Response(200, array( 'X-Powered-By' => ''));
420+ } );
316421```
317422
318423Note that persistent connections (` Connection: keep-alive ` ) are currently
0 commit comments