Skip to content

Commit 89793e5

Browse files
committed
Documentation for concurrency and streaming requests
1 parent 227306f commit 89793e5

3 files changed

Lines changed: 289 additions & 95 deletions

File tree

README.md

Lines changed: 161 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht
1010
* [Usage](#usage)
1111
* [Server](#server)
1212
* [StreamingServer](#streamingserver)
13+
* [listen()](#listen)
14+
* [error event](#error-event)
1315
* [Request](#request)
1416
* [Request parameters](#request-parameters)
1517
* [Query parameters](#query-parameters)
@@ -65,11 +67,10 @@ The `Server` class is responsible for handling incoming connections and then
6567
processing each incoming HTTP request.
6668

6769
It buffers and parses the complete incoming HTTP request in memory. Once the
68-
complete request has been received, it will invoke the request handler.
69-
70-
For each request, it executes the callback function passed to the
71-
constructor with the respective [request](#request) object and expects
72-
a respective [response](#response) object in return.
70+
complete request has been received, it will invoke the request handler function.
71+
This request handler function needs to be passed to the constructor and will be
72+
invoked with the respective [request](#request) object and expects a
73+
[response](#response) object in return:
7374

7475
```php
7576
$server = new Server(function (ServerRequestInterface $request) {
@@ -83,18 +84,79 @@ $server = new Server(function (ServerRequestInterface $request) {
8384
});
8485
```
8586

86-
For most users a server that buffers and parses a requests before handling it over as a
87-
PSR-7 request is what they want. The `Server` facade takes care of that, and takes the more
88-
advanced configuration out of hand. Under the hood it uses [StreamingServer](#streamingserver)
89-
with the the three stock middleware using default settings from `php.ini`.
87+
Each incoming HTTP request message is always represented by the
88+
[PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
89+
see also following [request](#request) chapter for more details.
90+
Each outgoing HTTP response message is always represented by the
91+
[PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
92+
see also following [response](#response) chapter for more details.
9093

91-
The [LimitConcurrentRequestsMiddleware](#limitconcurrentrequestsmiddleware) requires a limit,
92-
as such the `Server` facade uses the `memory_limit` and `post_max_size` ini settings to
93-
calculate a sensible limit. It assumes a maximum of a quarter of the `memory_limit` for
94-
buffering and the other three quarter for parsing and handling the requests. The limit is
95-
division of half of `memory_limit` by `memory_limit` rounded up.
94+
In order to process any connections, the server needs to be attached to an
95+
instance of `React\Socket\ServerInterface` through the [`listen()`](#listen) method
96+
as described in the following chapter. In its most simple form, you can attach
97+
this to a [`React\Socket\Server`](https://github.com/reactphp/socket#server)
98+
in order to start a plaintext HTTP server like this:
9699

97-
> Note that any errors emitted by the wrapped `StreamingServer` are forwarded by `Server`.
100+
```php
101+
$server = new Server($handler);
102+
103+
$socket = new React\Socket\Server('0.0.0.0:8080', $loop);
104+
$server->listen($socket);
105+
```
106+
107+
See also the [`listen()`](#listen) method and the [first example](examples) for more details.
108+
109+
The `Server` class is built as a facade around the underlying
110+
[`StreamingServer`](#streamingserver) to provide sane defaults for 80% of the
111+
use cases and is the recommended way to use this library unless you're sure
112+
you know what you're doing.
113+
114+
Unlike the underlying [`StreamingServer`](#streamingserver), this class
115+
buffers and parses the complete incoming HTTP request in memory. Once the
116+
complete request has been received, it will invoke the request handler
117+
function. This means the [request](#request) passed to your request handler
118+
function will be fully compatible with PSR-7.
119+
120+
On the other hand, buffering complete HTTP requests in memory until they can
121+
be processed by your request handler function means that this class has to
122+
employ a number of limits to avoid consuming too much memory. In order to
123+
take the more advanced configuration out your hand, it respects setting from
124+
your [`php.ini`](https://www.php.net/manual/en/ini.core.php) to apply its
125+
default settings. This is a list of PHP settings this class respects with
126+
their respective default values:
127+
128+
```
129+
memory_limit 128M
130+
post_max_size 8M
131+
enable_post_data_reading 1
132+
max_input_nesting_level 64
133+
max_input_vars 1000
134+
135+
file_uploads 1
136+
upload_max_filesize 2M
137+
max_file_uploads 20
138+
```
139+
140+
In particular, the `post_max_size` setting limits how much memory a single HTTP
141+
request is allowed to consume while buffering its request body. On top of
142+
this, this class will try to avoid consuming more than 1/4 of your
143+
`memory_limit` for buffering multiple concurrent HTTP requests. As such, with
144+
the above default settings of `128M` max, it will try to consume no more than
145+
`32M` for buffering multiple concurrent HTTP requests. As a consequence, it
146+
will limit the concurrency to 4 HTTP requests with the above defaults.
147+
148+
It is imperative that you assign reasonable values to your PHP ini settings.
149+
It is usually recommended to either reduce the memory a single request is
150+
allowed to take (set `post_max_size 1M` or less) or to increase the total memory
151+
limit to allow for more concurrent requests (set `memory_limit 512M` or more).
152+
Failure to do so means that this class may have to disable concurrency and
153+
only handle one request at a time.
154+
155+
Internally, this class automatically assigns these limits to the
156+
[middleware](#middleware) request handlers as described below. For more
157+
advanced use cases, you may also use the advanced
158+
[`StreamingServer`](#streamingserver) and assign these middleware request
159+
handlers yourself as described in the following chapters.
98160

99161
### StreamingServer
100162

@@ -103,11 +165,11 @@ processing each incoming HTTP request.
103165

104166
Unlike the [`Server`](#server) class, it does not buffer and parse the incoming
105167
HTTP request body by default. This means that the request handler will be
106-
invoked with a streaming request body.
107-
108-
For each request, it executes the callback function passed to the
109-
constructor with the respective [request](#request) object and expects
110-
a respective [response](#response) object in return.
168+
invoked with a streaming request body. Once the request headers have been
169+
received, it will invoke the request handler function. This request handler
170+
function needs to be passed to the constructor and will be invoked with the
171+
respective [request](#request) object and expects a [response](#response)
172+
object in return:
111173

112174
```php
113175
$server = new StreamingServer(function (ServerRequestInterface $request) {
@@ -121,51 +183,90 @@ $server = new StreamingServer(function (ServerRequestInterface $request) {
121183
});
122184
```
123185

186+
Each incoming HTTP request message is always represented by the
187+
[PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
188+
see also following [request](#request) chapter for more details.
189+
Each outgoing HTTP response message is always represented by the
190+
[PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
191+
see also following [response](#response) chapter for more details.
192+
124193
In order to process any connections, the server needs to be attached to an
125-
instance of `React\Socket\ServerInterface` which emits underlying streaming
126-
connections in order to then parse incoming data as HTTP.
194+
instance of `React\Socket\ServerInterface` through the [`listen()`](#listen) method
195+
as described in the following chapter. In its most simple form, you can attach
196+
this to a [`React\Socket\Server`](https://github.com/reactphp/socket#server)
197+
in order to start a plaintext HTTP server like this:
198+
199+
```php
200+
$server = new StreamingServer($handler);
201+
202+
$socket = new React\Socket\Server('0.0.0.0:8080', $loop);
203+
$server->listen($socket);
204+
```
127205

128-
You can attach this to a
206+
See also the [`listen()`](#listen) method and the [first example](examples) for more details.
207+
208+
The `StreamingServer` class is considered advanced usage and unless you know
209+
what you're doing, you're recommended to use the [`Server`](#server) class
210+
instead. The `StreamingServer` class is specifically designed to help with
211+
more advanced use cases where you want to have full control over consuming
212+
the incoming HTTP request body and concurrency settings.
213+
214+
In particular, this class does not buffer and parse the incoming HTTP request
215+
in memory. It will invoke the request handler function once the HTTP request
216+
headers have been received, i.e. before receiving the potentially much larger
217+
HTTP request body. This means the [request](#request) passed to your request
218+
handler function may not be fully compatible with PSR-7. See also
219+
[streaming request](#streaming-request) below for more details.
220+
221+
### listen()
222+
223+
The `listen(React\Socket\ServerInterface $socket): void` method can be used to
224+
start processing connections from the given socket server.
225+
The given [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
226+
is responsible for emitting the underlying streaming connections.
227+
This HTTP server needs to be attached to it in order to process any connections
228+
and pase incoming streaming data as incoming HTTP request messages.
229+
In its most common form, you can attach this to a
129230
[`React\Socket\Server`](https://github.com/reactphp/socket#server)
130231
in order to start a plaintext HTTP server like this:
131232

132233
```php
234+
$server = new Server($handler);
235+
// or
133236
$server = new StreamingServer($handler);
134237

135-
$socket = new React\Socket\Server(8080, $loop);
238+
$socket = new React\Socket\Server('0.0.0.0:8080', $loop);
136239
$server->listen($socket);
137240
```
138241

139-
See also the `listen()` method and the [first example](examples) for more details.
242+
This example will start listening for HTTP requests on the alternative HTTP port
243+
`8080` on all interfaces (publicly). As an alternative, it is very common to use
244+
a reverse proxy and let this HTTP server listen on the localhost (loopback)
245+
interface only by using the listen address `127.0.0.1:8080` instead. This way, you
246+
host your application(s) on the default HTTP port `80` and only route specific
247+
requests to this HTTP server.
140248

141-
Similarly, you can also attach this to a
142-
[`React\Socket\SecureServer`](https://github.com/reactphp/socket#secureserver)
143-
in order to start a secure HTTPS server like this:
249+
Likewise, it's usually recommended to use a reverse proxy setup to accept
250+
secure HTTPS requests on default HTTPS port `443` (TLS termination) and only
251+
route plaintext requests to this HTTP server. As an alternative, you can also
252+
accept secure HTTPS requests with this HTTP server by attaching this to a
253+
[`React\Socket\Server`](https://github.com/reactphp/socket#server) using a
254+
secure TLS listen address, a certificate file and optional `passphrase` like this:
144255

145256
```php
257+
$server = new Server($handler);
258+
// or
146259
$server = new StreamingServer($handler);
147260

148-
$socket = new React\Socket\Server(8080, $loop);
149-
$socket = new React\Socket\SecureServer($socket, $loop, array(
261+
$socket = new React\Socket\Server('tls://0.0.0.0:8443', $loop, array(
150262
'local_cert' => __DIR__ . '/localhost.pem'
151263
));
152-
153264
$server->listen($socket);
154265
```
155266

156267
See also [example #11](examples) for more details.
157268

158-
When HTTP/1.1 clients want to send a bigger request body, they MAY send only
159-
the request headers with an additional `Expect: 100-continue` header and
160-
wait before sending the actual (large) message body.
161-
In this case the server will automatically send an intermediary
162-
`HTTP/1.1 100 Continue` response to the client.
163-
This ensures you will receive the request body without a delay as expected.
164-
The [Response](#response) still needs to be created as described in the
165-
examples above.
166-
167-
See also [request](#request) and [response](#response)
168-
for more details (e.g. the request data body).
269+
### error event
169270

170271
The `StreamingServer` supports both HTTP/1.1 and HTTP/1.0 request messages.
171272
If a client sends an invalid request message, uses an invalid HTTP protocol
@@ -198,6 +299,8 @@ $server->on('error', function (Exception $e) {
198299
Note that the request object can also emit an error.
199300
Check out [request](#request) for more details.
200301

302+
> Note that any errors emitted by the wrapped `StreamingServer` are forwarded by `Server`.
303+
201304
### Request
202305

203306
As seen above, the [`Server`](#server) and [`StreamingServer`](#streamingserver)
@@ -405,6 +508,14 @@ This method operates on the buffered request body, i.e. the request body size
405508
is always known, even when the request does not specify a `Content-Length` request
406509
header or when using `Transfer-Encoding: chunked` for HTTP/1.1 requests.
407510

511+
> Note: The `Server` automatically takes care of handling requests with the
512+
additional `Expect: 100-continue` request header. When HTTP/1.1 clients want to
513+
send a bigger request body, they MAY send only the request headers with an
514+
additional `Expect: 100-continue` request header and wait before sending the actual
515+
(large) message body. In this case the server will automatically send an
516+
intermediary `HTTP/1.1 100 Continue` response to the client. This ensures you
517+
will receive the request body without a delay as expected.
518+
408519
#### Streaming request
409520

410521
If you're using the advanced [`StreamingServer`](#streamingserver), the
@@ -536,6 +647,14 @@ $server = new StreamingServer(function (ServerRequestInterface $request) {
536647
});
537648
```
538649

650+
> Note: The `StreamingServer` automatically takes care of handling requests with the
651+
additional `Expect: 100-continue` request header. When HTTP/1.1 clients want to
652+
send a bigger request body, they MAY send only the request headers with an
653+
additional `Expect: 100-continue` request header and wait before sending the actual
654+
(large) message body. In this case the server will automatically send an
655+
intermediary `HTTP/1.1 100 Continue` response to the client. This ensures you
656+
will receive the streaming request body without a delay as expected.
657+
539658
#### Request method
540659

541660
Note that the server supports *any* request method (including custom and non-

0 commit comments

Comments
 (0)