Skip to content

Commit 64839a4

Browse files
committed
Update README
1 parent 0c229d3 commit 64839a4

File tree

2 files changed

+214
-99
lines changed

2 files changed

+214
-99
lines changed

README.md

Lines changed: 192 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
105129
Note that the request object can also emit an error.
106130
Check out [request](#request) for more details.
107131

@@ -117,10 +141,15 @@ This request object implements the
117141
and 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
155184
gives 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
210248
advance 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

282387
A `Date` header will be automatically added with the system date and time if none is given.
283388
You 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

291396
If 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

300405
Note that it will automatically assume a `X-Powered-By: react/alpha` header
301406
unless 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
310415
value 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

318423
Note that persistent connections (`Connection: keep-alive`) are currently

0 commit comments

Comments
 (0)