Event-driven, streaming plaintext HTTP and secure HTTPS server for ReactPHP
Table of Contents
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) {
return new Response(
200,
array('Content-Type' => 'text/plain')
"Hello World!\n"
);
});
$loop->run();
See also the examples.
The Server
class is responsible for handling incoming connections and then
processing each incoming HTTP request.
It attaches itself to an instance of React\Socket\ServerInterface
which
emits underlying streaming connections in order to then parse incoming data
as HTTP.
For each request, it executes the callback function passed to the constructor with the respective request and response objects:
$socket = new React\Socket\Server(8080, $loop);
$http = new Server($socket, function (RequestInterface $request, Response $response) {
return new Response(
200,
array('Content-Type' => 'text/plain')
"Hello World!\n"
);
});
See also the first example for more details.
Similarly, you can also attach this to a
React\Socket\SecureServer
in order to start a secure HTTPS server like this:
$socket = new React\Socket\Server(8080, $loop);
$socket = new React\Socket\SecureServer($socket, $loop, array(
'local_cert' => __DIR__ . '/localhost.pem'
));
$http = new Server($socket, function (RequestInterface $request, Response $response) {
return new Response(
200,
array('Content-Type' => 'text/plain')
"Hello World!\n"
);
});
See also example #11 for more details.
When HTTP/1.1 clients want to send a bigger request body, they MAY send only
the request headers with an additional Expect: 100-continue
header and
wait before sending the actual (large) message body.
In this case the server will automatically send an intermediary
HTTP/1.1 100 Continue
response to the client.
This ensures you will receive the request body without a delay as expected.
The Response still needs to be created as described in the
examples above.
See also request and response for more details (e.g. the request data body).
The Server
supports both HTTP/1.1 and HTTP/1.0 request messages.
If a client sends an invalid request message, uses an invalid HTTP protocol
version or sends an invalid Transfer-Encoding
in the request header, it will
emit an error
event, send an HTTP error response to the client and
close the connection:
$http->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
Note that the request object can also emit an error. Check out request for more details.
An seen above, the Server
class is responsible for handling incoming
connections and then processing each incoming HTTP request.
The request object will be processed once the request headers have been received by the client. This request object implements the PSR-7 RequestInterface and will be passed to the callback function like this.
$http = new Server($socket, function (RequestInterface $request, Response $response) {
$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
);
});
For more details about the request object, check out the documentation of PSR-7 RequestInterface.
Note that the request object will be processed once the request headers have been received. This means that this happens irrespective of (i.e. before) receiving the (potentially much larger) request body. While this may be uncommon in the PHP ecosystem, this is actually a very powerful approach that gives you several advantages not otherwise possible:
- React to requests before receiving a large request body, such as rejecting an unauthenticated request or one that exceeds allowed message lengths (file uploads).
- Start processing parts of the request body before the remainder of the request body arrives or if the sender is slowly streaming data.
- Process a large request body without having to buffer anything in memory, such as accepting a huge file upload or possibly unlimited request body stream.
The getBody()
method can be used to access the request body stream.
This method returns a stream instance that implements both the
PSR-7 StreamInterface
and the ReactPHP ReadableStreamInterface.
However, most of the PSR-7 StreamInterface
methods have been
designed under the assumption of being in control of the request body.
Given that this does not apply to this server, the following
PSR-7 StreamInterface
methods are not used and SHOULD NOT be called:
tell()
, eof()
, seek()
, rewind()
, write()
and read()
.
Instead, you should use the ReactPHP ReadableStreamInterface
which
gives you access to the incoming request body as the individual chunks arrive:
$http = new Server($socket, function (RequestInterface $request, Response $response) {
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) {
$resolve(
new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"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 ($resolve, &$contentLength) {
$resolve(
new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"An error occured while reading at length: " . $contentLength
)
);
});
}
});
The above example simply counts the number of bytes received in the request body. This can be used as a skeleton for buffering or processing the request body.
See also example #4 for more details.
The data
event will be emitted whenever new data is available on the request
body stream.
The server automatically takes care of decoding chunked transfer encoding
and will only emit the actual payload as data.
In this case, the Transfer-Encoding
header will be removed.
The end
event will be emitted when the request body stream terminates
successfully, i.e. it was read until its expected end.
The error
event will be emitted in case the request stream contains invalid
chunked data or the connection closes before the complete request stream has
been received.
The server will automatically pause()
the connection instead of closing it.
A response message can still be sent (unless the connection is already closed).
A close
event will be emitted after an error
or end
event.
For more details about the request body stream, check out the documentation of ReactPHP ReadableStreamInterface.
The getSize(): ?int
method can be used if you only want to know the request
body size.
This method returns the complete size of the request body as defined by the
message boundaries.
This value may be 0
if the request message does not contain a request body
(such as a simple GET
request).
Note that this value may be null
if the request body size is unknown in
advance because the request message uses chunked transfer encoding.
$http = new Server($socket, function (RequestInterface $request) {
$size = $request->getBody()->getSize();
if ($size === null) {
$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
);
}
return new Response(
200,
array('Content-Type' => 'text/plain'),
"Request body size: " . $size . " bytes\n"
);
});
The callback function passed to the constructor of the Server
is responsible to return process a response, which will be delivered to the client.
This function MUST return either the
PSR-7 ResponseInterface
object or a
ReactPHP Promise
which will resolve a PSR-7 ResponseInterface
object.
$http = new Server($socket, function (RequestInterface $request, Response $response) {
$body = "The method of the request is: " . $request->getMethod();
$body .= "The requested path is: " . $request->getUri()->getPath();
See also [example #3](examples) for more details.
The constructor is internal, you SHOULD NOT call this yourself.
The `Server` is responsible for emitting `Request` and `Response` objects.
return new Response(
200,
array('Content-Type' => 'text/plain'),
$body
);
});
You will find a Response
class
which implements the PSR-7 RequestInterface
in this project.
We use instantiation of this class in our projects,
but feel free to use any implemantation of the
PSR-7 RequestInterface
you prefer.
If your response takes time to be processed you SHOULD use a ReactPHP Promise
.
$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){
$resolve(
new Response(
200,
array(
'Content-Type' => 'text/plain'
),
"The length of the submitted request body is: " . $contentLength
)
);
});
});
});
The above example simply counts the number of bytes received in the request body.
The ReactPHP Promise
will resolve in a Response
object when the request
body ends.
Always use the ReactPHP Promise
when your
Check out the documentation of ReactPHP Promise
for more information to the Response
object.
This library is able to stream the response body data directly to the client, so you don't have to buffer the data or block your application. Add any stream that implements the ReactPHP ReadableStreamInterface
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
$stream = new ReadableStream();
$timer = $loop->addPeriodicTimer(0.5, function () use ($stream) {
$stream->emit('data', array(microtime(true) . PHP_EOL));
});
$loop->addTimer(5, function() use ($loop, $timer, $stream) {
$loop->cancelTimer($timer);
$stream->emit('end');
});
return new Response(200, array('Content-Type' => 'text/plain'), $stream);
});
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.
Use the oppertunties of the ReactPHP Streams
Unless you specify a Content-Length
header yourself, HTTP/1.1 responses
will automatically use chunked transfer encoding and send the respective header
(Transfer-Encoding: chunked
) automatically. The server is responsible for handling
Transfer-Encoding
so you SHOULD NOT pass it yourself.
If you know the length of your body, you MAY specify it like this instead:
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
$data = "Hello world!\n";
return new Response(
200,
array(
'Content-Type' => 'text/plain',
'Content-Length' => strlen($data)
),
$data
);
});
After the return in the callback function the response will be processed by the Server
.
The Server
will add the protocol version of the reequest, 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:
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
return new \RingCentral\Psr7\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 string:
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
return new \RingCentral\Psr7\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:
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
return new \RingCentral\Psr7\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:
$server = new Server($socket, function (RequestInterface $request) use ($loop) {
return new \RingCentral\Psr7\Response(200, array('X-Powered-By' => ''));
});
Note that persistent connections (Connection: keep-alive
) are currently
not supported.
As such, HTTP/1.1 response messages will automatically include a
Connection: close
header, irrespective of what header values are
passed explicitly.
The recommended way to install this library is through Composer. New to Composer?
This will install the latest supported version:
$ composer require react/http:^0.6
More details about version upgrades can be found in the CHANGELOG.
To run the test suite, you first need to clone this repo and then install all dependencies through Composer:
$ composer install
To run the test suite, go to the project root and run:
$ php vendor/bin/phpunit
MIT, see LICENSE file.