Skip to content

Commit 57cc76c

Browse files
committed
Update README
1 parent a0f2fdd commit 57cc76c

File tree

2 files changed

+195
-95
lines changed

2 files changed

+195
-95
lines changed

README.md

Lines changed: 173 additions & 83 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

@@ -117,10 +125,15 @@ This request object implements the
117125
and will be passed to the callback function like this.
118126

119127
```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());
128+
$http = new Server($socket, function (RequestInterface $request) {
129+
$body = "The method of the request is: " . $request->getMethod();
130+
$body .= "The requested path is: " . $request->getUri()->getPath();
131+
132+
return new Response(
133+
200,
134+
array('Content-Type' => 'text/plain'),
135+
$body
136+
);
124137
});
125138
```
126139

@@ -155,22 +168,31 @@ Instead, you should use the `ReactPHP ReadableStreamInterface` which
155168
gives you access to the incoming request body as the individual chunks arrive:
156169

157170
```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);
171+
$http = new Server($socket, function (RequestInterface $request) {
172+
return new Promise(function ($resolve, $reject) use ($request) {
173+
$contentLength = 0;
174+
$request->getBody()->on('data', function ($data) use (&$contentLength) {
175+
$contentLength += strlen($data);
176+
});
177+
178+
$request->getBody()->on('end', function () use ($resolve, &$contentLength){
179+
$response = new Response(
180+
200,
181+
array('Content-Type' => 'text/plain'),
182+
"The length of the submitted request body is: " . $contentLength
183+
);
184+
$resolve($response);
185+
});
186+
187+
// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
188+
$request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) {
189+
$response = new Response(
190+
400,
191+
array('Content-Type' => 'text/plain'),
192+
"An error occured while reading at length: " . $contentLength
193+
);
194+
$resolve($response);
195+
});
174196
});
175197
});
176198
```
@@ -210,58 +232,114 @@ Note that this value may be `null` if the request body size is unknown in
210232
advance because the request message uses chunked transfer encoding.
211233

212234
```php
213-
$http = new Server($socket, function (RequestInterface $request, Response $response) {
235+
$http = new Server($socket, function (RequestInterface $request) {
214236
$size = $request->getBody()->getSize();
215237
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;
238+
$body = 'The request does not contain an explicit length.';
239+
$body .= 'This server does not accept chunked transfer encoding.';
240+
241+
return new Response(
242+
411,
243+
array('Content-Type' => 'text/plain'),
244+
$body
245+
);
221246
}
222-
$response->writeHead(200, array('Content-Type' => 'text/plain'));
223-
$response->end("Request body size: " . $size . " bytes\n");
247+
248+
return new Response(
249+
200,
250+
array('Content-Type' => 'text/plain'),
251+
"Request body size: " . $size . " bytes\n"
252+
);
224253
});
225254
```
226255

227256
### Response
228257

229-
The `Response` class is responsible for streaming the outgoing response body.
258+
The callback function passed to the constructor of the [Server](#server)
259+
is responsible to return process a response, which will be delivered to the client.
260+
This function MUST return either a implementation of the
261+
[PSR-7 ResponseInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#33-psrhttpmessageresponseinterface)
262+
object or a
263+
[ReactPHP Promise](https://github.com/reactphp/promise#reactpromise)
264+
which will resolve a `PSR-7 ResponseInterface` object.
230265

231-
It implements the `WritableStreamInterface`.
232-
233-
See also [example #3](examples) for more details.
266+
```php
267+
$http = new Server($socket, function (RequestInterface $request) {
268+
return new Response(
269+
200,
270+
array('Content-Type' => 'text/plain'),
271+
"Hello World!\n"
272+
);
273+
});
274+
```
234275

235-
The constructor is internal, you SHOULD NOT call this yourself.
236-
The `Server` is responsible for emitting `Request` and `Response` objects.
276+
You will find a `Response` class
277+
which implements the `PSR-7 RequestInterface` in this project.
278+
We use instantiation of this class in our projects,
279+
but feel free to use any implemantation of the
280+
`PSR-7 RequestInterface` you prefer.
237281

238-
The `Response` will automatically use the same HTTP protocol version as the
239-
corresponding `Request`.
282+
If your response takes time to be processed you SHOULD use a `ReactPHP Promise`.
240283

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.
284+
```php
285+
$server = new Server($socket, function (RequestInterface $request) {
286+
return new Promise(function ($resolve, $reject) use ($request) {
287+
$contentLength = 0;
288+
$body = $request->getBody();
289+
290+
$body->on('data', function ($data) use (&$contentLength) {
291+
$contentLength += strlen($data);
292+
});
293+
294+
$body->on('end', function () use ($resolve, &$contentLength){
295+
$resolve(
296+
new Response(
297+
200,
298+
array('Content-Type' => 'text/plain'),
299+
"The length of the submitted request body is: " . $contentLength
300+
)
301+
);
302+
});
303+
});
304+
});
305+
```
244306

245-
See the above usage example and the class outline for details.
307+
The above example simply counts the number of bytes received in the request body.
308+
This needs to be a promise, because the request body may needs time to be completed.
309+
The `ReactPHP Promise` will resolve in a `Response` object when the request
310+
body ends.
311+
Always use the `ReactPHP Promise` when your
312+
Check out the documentation of [ReactPHP Promise](https://github.com/reactphp/promise#reactpromise)
313+
for more information to the `Response` object.
314+
315+
This library is able to stream the response body data directly to the client,
316+
so you don't have to buffer the data or block your application.
317+
Add any stream that implements the
318+
[ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface)
246319

247-
#### writeHead()
320+
```php
321+
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
322+
$stream = new ReadableStream();
248323

249-
The `writeHead(int $status = 200, array $headers = array(): void` method can be used to
250-
write the given HTTP message header.
324+
$timer = $loop->addPeriodicTimer(0.5, function () use ($stream) {
325+
$stream->emit('data', array(microtime(true) . PHP_EOL));
326+
});
251327

252-
This method MUST be invoked once before calling `write()` or `end()` to send
253-
the actual HTTP message body:
328+
$loop->addTimer(5, function() use ($loop, $timer, $stream) {
329+
$loop->cancelTimer($timer);
330+
$stream->emit('end');
331+
});
254332

255-
```php
256-
$response->writeHead(200, array(
257-
'Content-Type' => 'text/plain'
258-
));
259-
$response->end('Hello World!');
333+
return new Response(200, array('Content-Type' => 'text/plain'), $stream);
334+
});
260335
```
261336

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.
337+
The above example will emit every 0.5 seconds the current Unix timestamp
338+
with microseconds as float to the client and will end after 5 seconds.
339+
This is just a example you could use of the streaming,
340+
you could also send a big amount of data via little chunks
341+
or use it for body data that needs to calculated.
342+
Use the oppertunties of the `ReactPHP Streams`
265343

266344
Unless you specify a `Content-Length` header yourself, HTTP/1.1 responses
267345
will automatically use chunked transfer encoding and send the respective header
@@ -270,49 +348,61 @@ will automatically use chunked transfer encoding and send the respective header
270348
If you know the length of your body, you MAY specify it like this instead:
271349

272350
```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);
351+
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
352+
$data = "Hello world!\n";
353+
354+
return new Response(
355+
200,
356+
array(
357+
'Content-Type' => 'text/plain',
358+
'Content-Length' => strlen($data)
359+
),
360+
$data
361+
);
362+
});
280363
```
281364

365+
An unhandled Exception in the code of the callback function will result
366+
in an `500 Internal Server Error` message.
367+
Make sure to catch these `Exceptions` to create own response messages.
368+
369+
After the return in the callback function the response will be processed by the `Server`.
370+
The `Server` will add the protocol version of the reequest, so you don't have to.
371+
282372
A `Date` header will be automatically added with the system date and time if none is given.
283373
You can add a custom `Date` header yourself like this:
284374

285375
```php
286-
$response->writeHead(200, array(
287-
'Date' => date('D, d M Y H:i:s T')
288-
));
376+
$server = new Server($socket, function (RequestInterface $request) {
377+
return new \RingCentral\Psr7\Response(200, array('Date' => date('D, d M Y H:i:s T')));
378+
});
289379
```
290380

291381
If you don't have a appropriate clock to rely on, you should
292-
unset this header with an empty array:
382+
unset this header with an empty string:
293383

294384
```php
295-
$response->writeHead(200, array(
296-
'Date' => array()
297-
));
385+
$server = new Server($socket, function (RequestInterface $request) {
386+
return new Response(200, array('Date' => ''));
387+
});
298388
```
299389

300390
Note that it will automatically assume a `X-Powered-By: react/alpha` header
301391
unless your specify a custom `X-Powered-By` header yourself:
302392

303393
```php
304-
$response->writeHead(200, array(
305-
'X-Powered-By' => 'PHP 3'
306-
));
394+
$server = new Server($socket, function (RequestInterface $request) {
395+
return new Response(200, array('X-Powered-By' => 'PHP 3'));
396+
});
307397
```
308398

309399
If you do not want to send this header at all, you can use an empty array as
310400
value like this:
311401

312402
```php
313-
$response->writeHead(200, array(
314-
'X-Powered-By' => array()
315-
));
403+
$server = new Server($socket, function (RequestInterface $request) {
404+
return new Response(200, array('X-Powered-By' => ''));
405+
});
316406
```
317407

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

0 commit comments

Comments
 (0)