Skip to content

Commit

Permalink
Update README
Browse files Browse the repository at this point in the history
  • Loading branch information
legionth committed Mar 28, 2017
1 parent bd05314 commit b71e67b
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 95 deletions.
258 changes: 175 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht
* [Server](#server)
* [Request](#request)
* [Response](#response)
* [writeHead()](#writehead)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
Expand All @@ -24,9 +23,12 @@ This is an HTTP server which responds with `Hello World` to every request.
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server(8080, $loop);

$http = new Server($socket, function (RequestInterface $request, Response $response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Hello World!\n");
$http = new Server($socket, function (RequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});

$loop->run();
Expand All @@ -52,9 +54,12 @@ constructor with the respective [request](#request) and
```php
$socket = new React\Socket\Server(8080, $loop);

$http = new Server($socket, function (RequestInterface $request, Response $response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Hello World!\n");
$http = new Server($socket, function (RequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
```

Expand All @@ -70,9 +75,12 @@ $socket = new React\Socket\SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/localhost.pem'
));

$http = new Server($socket, function (RequestInterface $request, Response $response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Hello World!\n");
$http = new Server($socket, function (RequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
```

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

```php
$http = new Server($socket, function (RequestInterface $request, Response $response) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->write("The method of the request is: " . $request->getMethod());
$response->end("The requested path is: " . $request->getUri()->getPath());
$http = new Server($socket, function (RequestInterface $request) {
$body = "The method of the request is: " . $request->getMethod();
$body .= "The requested path is: " . $request->getUri()->getPath();

return new Response(
200,
array('Content-Type' => 'text/plain'),
$body
);
});
```

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

```php
$http = new Server($socket, function (RequestInterface $request, Response $response) {
$contentLength = 0;
$body = $request->getBody();
$body->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});

$body->on('end', function () use ($response, &$contentLength){
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("The length of the submitted request body is: " . $contentLength);
});

// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
$body->on('error', function (\Exception $exception) use ($response, &$contentLength) {
$response->writeHead(400, array('Content-Type' => 'text/plain'));
$response->end("An error occured while reading at length: " . $contentLength);
$http = new Server($socket, function (RequestInterface $request) {
return new Promise(function ($resolve, $reject) use ($request) {
$contentLength = 0;
$request->getBody()->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});

$request->getBody()->on('end', function () use ($resolve, &$contentLength){
$response = new Response(
200,
array('Content-Type' => 'text/plain'),
"The length of the submitted request body is: " . $contentLength
);
$resolve($response);
});

// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event
$request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) {
$response = new Response(
400,
array('Content-Type' => 'text/plain'),
"An error occured while reading at length: " . $contentLength
);
$resolve($response);
});
});
});
```
Expand Down Expand Up @@ -210,58 +232,119 @@ Note that this value may be `null` if the request body size is unknown in
advance because the request message uses chunked transfer encoding.

```php
$http = new Server($socket, function (RequestInterface $request, Response $response) {
$http = new Server($socket, function (RequestInterface $request) {
$size = $request->getBody()->getSize();
if ($size === null) {
$response->writeHead(411, array('Content-Type' => 'text/plain'));
$response->write('The request does not contain an explicit length.');
$response->write('This server does not accept chunked transfer encoding.');
$response->end();
return;
$body = 'The request does not contain an explicit length.';
$body .= 'This server does not accept chunked transfer encoding.';

return new Response(
411,
array('Content-Type' => 'text/plain'),
$body
);
}
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Request body size: " . $size . " bytes\n");

return new Response(
200,
array('Content-Type' => 'text/plain'),
"Request body size: " . $size . " bytes\n"
);
});
```

### Response

The `Response` class is responsible for streaming the outgoing response body.
The callback function passed to the constructor of the [Server](#server)
is responsible for processing the request and returning a response,
which will be delivered to the client.
This function MUST return an instance imlementing
[PSR-7 ResponseInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#33-psrhttpmessageresponseinterface)
object or a
[ReactPHP Promise](https://github.com/reactphp/promise#reactpromise)
which will resolve a `PSR-7 ResponseInterface` object.

You will find a `Response` class
which implements the `PSR-7 ResponseInterface` in this project.
We use instantiation of this class in our projects,
but feel free to use any implemantation of the
`PSR-7 ResponseInterface` you prefer.

It implements the `WritableStreamInterface`.

See also [example #3](examples) for more details.
```php
$http = new Server($socket, function (RequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
```

The constructor is internal, you SHOULD NOT call this yourself.
The `Server` is responsible for emitting `Request` and `Response` objects.
The example above returns the response directly, because it needs
no time to be processed.
Using a database, the file system or long calculations(
in fact every action that will take >=1ms) to create your
response, will slow down the server.
To prevent this you SHOULD use a
[ReactPHP Promise](https://github.com/reactphp/promise#reactpromise).
This example shows how suche a long-term action could look like:

The `Response` will automatically use the same HTTP protocol version as the
corresponding `Request`.
```php
$server = new Server($socket, function (RequestInterface $request) {
return new Promise(function ($resolve, $reject) use ($request) {
$contentLength = 0;
$body = $request->getBody();

$body->on('data', function ($data) use (&$contentLength) {
$contentLength += strlen($data);
});

$body->on('end', function () use ($resolve, &$contentLength){
$response = new Response(
200,
array('Content-Type' => 'text/plain'),
"The length of the submitted request body is: " . $contentLength
);
$resolve($response);
});
});
});
```

HTTP/1.1 responses will automatically apply chunked transfer encoding if
no `Content-Length` header has been set.
See [`writeHead()`](#writehead) for more details.
The above example simply counts the number of bytes received in the request body.
This needs to be a promise, because the request body may needs time to be completed.
The `ReactPHP Promise` will resolve in a `Response` object when the request
body ends.

See the above usage example and the class outline for details.
The `Response` class in this project supports to add an instance which implements the
[ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface)
for the response body.
So you are able stream data directly into the response body.
Note that most implementation of the `PSR-7 ResponseInterface` likely
only support string.

#### writeHead()
```php
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
$stream = new ReadableStream();

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

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

```php
$response->writeHead(200, array(
'Content-Type' => 'text/plain'
));
$response->end('Hello World!');
return new Response(200, array('Content-Type' => 'text/plain'), $stream);
});
```

Calling this method more than once will result in an `Exception`
(unless the response has ended/closed already).
Calling this method after the response has ended/closed is a NOOP.
The above example will emit every 0.5 seconds the current Unix timestamp
with microseconds as float to the client and will end after 5 seconds.
This is just a example you could use of the streaming,
you could also send a big amount of data via little chunks
or use it for body data that needs to calculated.

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

```php
$data = 'Hello World!';

$response->writeHead(200, array(
'Content-Type' => 'text/plain',
'Content-Length' => strlen($data)
));
$response->end($data);
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
return new Response(
200,
array(
'Content-Type' => 'text/plain',
),
"Hello world!\n"
);
});
```

An invalid return value or an unhandled `Exceptions` in the code of the callback
function, will result in an `500 Internal Server Error` message.
Make sure to catch `Exceptions` to create own response messages.

After the return in the callback function the response will be processed by the `Server`.
The `Server` will add the protocol version of the request, so you don't have to.

A `Date` header will be automatically added with the system date and time if none is given.
You can add a custom `Date` header yourself like this:

```php
$response->writeHead(200, array(
'Date' => date('D, d M Y H:i:s T')
));
$server = new Server($socket, function (RequestInterface $request) {
return new Response(200, array('Date' => date('D, d M Y H:i:s T')));
});
```

If you don't have a appropriate clock to rely on, you should
unset this header with an empty array:
unset this header with an empty string:

```php
$response->writeHead(200, array(
'Date' => array()
));
$server = new Server($socket, function (RequestInterface $request) {
return new Response(200, array('Date' => ''));
});
```

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

```php
$response->writeHead(200, array(
'X-Powered-By' => 'PHP 3'
));
$server = new Server($socket, function (RequestInterface $request) {
return new Response(200, array('X-Powered-By' => 'PHP 3'));
});
```

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

```php
$response->writeHead(200, array(
'X-Powered-By' => array()
));
$server = new Server($socket, function (RequestInterface $request) {
return new Response(200, array('X-Powered-By' => ''));
});
```

Note that persistent connections (`Connection: keep-alive`) are currently
Expand Down
Loading

0 comments on commit b71e67b

Please sign in to comment.