diff --git a/README.md b/README.md index 5c8528fd..3751c332 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,6 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht * [Usage](#usage) * [Server](#server) * [Request](#request) - * [getMethod()](#getmethod) - * [getQueryParams()](#getqueryparams) - * [getProtocolVersion()](#getprotocolversion) - * [getHeaders()](#getheaders) - * [getHeader()](#getheader) - * [getHeaderLine()](#getheaderline) - * [hasHeader()](#hasheader) * [Response](#response) * [writeHead()](#writehead) * [Install](#install) @@ -31,7 +24,7 @@ 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 (Request $request, Response $response) { +$http = new Server($socket, function (RequestInterface $request, Response $response) { $response->writeHead(200, array('Content-Type' => 'text/plain')); $response->end("Hello World!\n"); }); @@ -59,7 +52,7 @@ constructor with the respective [`Request`](#request) and ```php $socket = new React\Socket\Server(8080, $loop); -$http = new Server($socket, function (Request $request, Response $response) { +$http = new Server($socket, function (RequestInterface $request, Response $response) { $response->writeHead(200, array('Content-Type' => 'text/plain')); $response->end("Hello World!\n"); }); @@ -75,7 +68,7 @@ $socket = new React\Socket\SecureServer($socket, $loop, array( 'local_cert' => __DIR__ . '/localhost.pem' )); -$http = new Server($socket, function (Request $request, Response $response) { +$http = new Server($socket, function (RequestInterface $request, Response $response) { $response->writeHead(200, array('Content-Type' => 'text/plain')); $response->end("Hello World!\n"); }); @@ -110,93 +103,80 @@ for more details. ### Request -The `Request` class is responsible for streaming the incoming request body -and contains meta data which was parsed from the request headers. -If the request body is chunked-encoded, the data will be decoded and emitted on the data event. -The `Transfer-Encoding` header will be removed. - -It implements the `ReadableStreamInterface`. - -Listen on the `data` event and the `end` event of the [Request](#request) -to evaluate the data of the request body: +A HTTP request will be sent by a client to the [Server](#server). +The `Server` will receive this request on the `data` event and +is responsible to create a request object from this data. +This object will be passed to the callback function. + +The request is an instance of [PSR-7 RequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#32-psrhttpmessagerequestinterface). + +As defined in PSR-7, the `getBody()` method returns a stream instance +which implements the [PSR-7 StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface). +Note that the incoming request will be processed once the request headers have +been received, which implies that the (potentially much larger) request body +may still be outstanding (in a streaming state). +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, the returned stream instance *also* implements the +[ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface) +which gives you access to the incoming request body as the individual chunks +arrive: ```php -$http = new Server($socket, function (Request $request, Response $response) { +$http = new Server($socket, function (RequestInterface $request, Response $response) { $contentLength = 0; - $request->on('data', function ($data) use (&$contentLength) { + $body = $request->getBody(); + $body->on('data', function ($data) use (&$contentLength) { $contentLength += strlen($data); }); - $request->on('end', function () use ($response, &$contentLength){ + $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 - $request->on('error', function (\Exception $exception) use ($response, &$contentLength) { + $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); }); }); ``` -An error will just `pause` the connection instead of closing it. A response message -can still be sent. - -A `close` event will be emitted after an `error` or `end` event. - -The constructor is internal, you SHOULD NOT call this yourself. -The `Server` is responsible for emitting `Request` and `Response` objects. - -See the above usage example and the class outline for details. - -#### getMethod() - -The `getMethod(): string` method can be used to -return the request method. - -#### getPath() - -The `getPath(): string` method can be used to -return the request path. - -#### getQueryParams() +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. -The `getQueryParams(): array` method can be used to -return an array with all query parameters ($_GET). +If you only want to know the request body size, you can use its `getSize()` +method. +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. -#### getProtocolVersion() - -The `getProtocolVersion(): string` method can be used to -return the HTTP protocol version (such as "1.0" or "1.1"). - -#### getHeaders() - -The `getHeaders(): array` method can be used to -return an array with ALL headers. - -The keys represent the header name in the exact case in which they were -originally specified. The values will be an array of strings for each -value for the respective header name. - -#### getHeader() - -The `getHeader(string $name): string[]` method can be used to -retrieve a message header value by the given case-insensitive name. - -Returns a list of all values for this header name or an empty array if header was not found - -#### getHeaderLine() - -The `getHeaderLine(string $name): string` method can be used to -retrieve a comma-separated string of the values for a single header. +```php +$http = new Server($socket, function (RequestInterface $request, Response $response) { + $response->writeHead(200, array('Content-Type' => 'text/plain')); + $response->write("Request method: " . $request->getMethod() . "\n"); + if ($request->getBody()->getSize() !== null) { + $response->write("Request body size: " . $request->getBody()->getSize() . "\n"); + } + $response->end("The requested path is: " . $request->getUri()->getPath()); +}); +``` -Returns a comma-separated list of all values for this header name or an empty string if header was not found +The server automatically takes care of decoding chunked transfer encoding +and will only emit the actual payload as data. +The `Transfer-Encoding` header will be removed. -#### hasHeader() +An error will just `pause` the connection instead of closing it. A response message +can still be sent. -The `hasHeader(string $name): bool` method can be used to -check if a header exists by the given case-insensitive name. +A `close` event will be emitted after an `error` or `end` event. ### Response diff --git a/src/Server.php b/src/Server.php index bd3faebf..7387b8b6 100644 --- a/src/Server.php +++ b/src/Server.php @@ -22,7 +22,7 @@ * ```php * $socket = new React\Socket\Server(8080, $loop); * - * $http = new Server($socket, function (Request $request, Response $response) { + * $http = new Server($socket, function (RequestInterface $request, Response $response) { * $response->writeHead(200, array('Content-Type' => 'text/plain')); * $response->end("Hello World!\n"); * }); @@ -38,7 +38,7 @@ * 'local_cert' => __DIR__ . '/localhost.pem' * )); * - * $http = new Server($socket, function (Request $request, Response $response) { + * $http = new Server($socket, function (RequestInterface $request, Response $response) { * $response->writeHead(200, array('Content-Type' => 'text/plain')); * $response->end("Hello World!\n"); * });