diff --git a/README.md b/README.md index 23010002..50797fe6 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ require __DIR__ . '/vendor/autoload.php'; $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -735,7 +735,7 @@ object and expects a [response](#server-response) object in return: ```php $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -953,7 +953,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $body .= "The requested path is: " . $request->getUri()->getPath(); return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -995,7 +995,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1027,7 +1027,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf } return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/html' ), @@ -1074,7 +1074,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $name = $request->getParsedBody()['name'] ?? 'anonymous'; return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array(), "Hello $name!\n" ); @@ -1099,7 +1099,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $name = $data->name ?? 'anonymous'; return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array('Content-Type' => 'application/json'), json_encode(['message' => "Hello $name!"]) ); @@ -1122,7 +1122,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $name = isset($files['avatar']) ? $files['avatar']->getClientFilename() : 'nothing'; return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array(), "Uploaded $name\n" ); @@ -1205,7 +1205,7 @@ $http = new React\Http\HttpServer( $body->on('end', function () use ($resolve, &$bytes){ $resolve(new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1216,7 +1216,7 @@ $http = new React\Http\HttpServer( // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event $body->on('error', function (Exception $e) use ($resolve, &$bytes) { $resolve(new React\Http\Message\Response( - 400, + React\Http\Message\Response::STATUS_BAD_REQUEST, array( 'Content-Type' => 'text/plain' ), @@ -1272,7 +1272,7 @@ $http = new React\Http\HttpServer( $body .= 'This example does not accept chunked transfer encoding.'; return new React\Http\Message\Response( - 411, + React\Http\Message\Response::STATUS_LENGTH_REQUIRED, array( 'Content-Type' => 'text/plain' ), @@ -1281,7 +1281,7 @@ $http = new React\Http\HttpServer( } return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1343,7 +1343,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf $body = "Your cookie value is: " . $request->getCookieParams()[$key]; return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1352,7 +1352,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf } return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain', 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') @@ -1410,7 +1410,7 @@ In its most simple form, you can use it like this: ```php $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1440,7 +1440,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf return new Promise(function ($resolve, $reject) { Loop::addTimer(1.5, function() use ($resolve) { $response = new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1487,7 +1487,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf }); return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1568,7 +1568,7 @@ a `string` response body like this: ```php $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -1593,7 +1593,7 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf }); return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Length' => '13', 'Content-Type' => 'text/plain', @@ -1663,7 +1663,7 @@ a custom `Server` response header like this: ```php $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Server' => 'PHP/3' ) @@ -1678,7 +1678,7 @@ string value like this: ```php $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Server' => '' ) @@ -1693,7 +1693,7 @@ like this: ```php $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Date' => gmdate('D, d M Y H:i:s \G\M\T') ) @@ -1708,7 +1708,7 @@ like this: ```php $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Date' => '' ) @@ -1786,7 +1786,7 @@ encourages [Third-Party Middleware](#third-party-middleware) implementations. In order to use middleware request handlers, simply pass a list of all callables as defined above to the [`HttpServer`](#httpserver). The following example adds a middleware request handler that adds the current time to the request as a -header (`Request-Time`) and a final request handler that always returns a 200 code without a body: +header (`Request-Time`) and a final request handler that always returns a `200 OK` status code without a body: ```php $http = new React\Http\HttpServer( @@ -1795,7 +1795,7 @@ $http = new React\Http\HttpServer( return $next($request); }, function (Psr\Http\Message\ServerRequestInterface $request) { - return new React\Http\Message\Response(200); + return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } ); ``` @@ -1821,7 +1821,7 @@ $http = new React\Http\HttpServer( }); }, function (Psr\Http\Message\ServerRequestInterface $request) { - return new React\Http\Message\Response(200); + return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } ); ``` @@ -1842,7 +1842,7 @@ $http = new React\Http\HttpServer( }); return $promise->then(null, function (Exception $e) { return new React\Http\Message\Response( - 500, + React\Http\Message\Response::STATUS_INTERNAL_SERVER_ERROR, array(), 'Internal error: ' . $e->getMessage() ); @@ -1852,7 +1852,7 @@ $http = new React\Http\HttpServer( if (mt_rand(0, 1) === 1) { throw new RuntimeException('Database error'); } - return new React\Http\Message\Response(200); + return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } ); ``` @@ -2439,7 +2439,7 @@ represent an outgoing server response message. ```php $response = new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/html' ), @@ -2452,6 +2452,13 @@ This class implements the which in turn extends the [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface). +On top of this, this class implements the +[PSR-7 Message Util `StatusCodeInterface`](https://github.com/php-fig/http-message-util/blob/master/src/StatusCodeInterface.php) +which means that most common HTTP status codes are available as class +constants with the `STATUS_*` prefix. For instance, the `200 OK` and +`404 Not Found` status codes can used as `Response::STATUS_OK` and +`Response::STATUS_NOT_FOUND` respectively. + > Internally, this implementation builds on top of an existing incoming response message and only adds required streaming support. This base class is considered an implementation detail that may change in the future. @@ -2516,7 +2523,7 @@ $http = new React\Http\HttpServer( }); $body->on('close', function () use (&$bytes, $resolve) { $resolve(new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, [], "Received $bytes bytes\n" )); @@ -2653,7 +2660,7 @@ $http = new React\Http\HttpServer( new React\Http\Middleware\RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB function (Psr\Http\Message\ServerRequestInterface $request) { // The body from $request->getBody() is now fully available without the need to stream it - return new React\Http\Message\Response(200); + return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); }, ); ``` @@ -2700,7 +2707,7 @@ $handler = function (Psr\Http\Message\ServerRequestInterface $request) { } return new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/composer.json b/composer.json index 25f96db5..8a5c7df6 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "require": { "php": ">=5.3.0", "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "fig/http-message-util": "^1.1", "psr/http-message": "^1.0", "react/event-loop": "^1.2", "react/promise": "^2.3 || ^1.2.1", diff --git a/examples/51-server-hello-world.php b/examples/51-server-hello-world.php index f549ece8..88831525 100644 --- a/examples/51-server-hello-world.php +++ b/examples/51-server-hello-world.php @@ -7,7 +7,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/52-server-count-visitors.php b/examples/52-server-count-visitors.php index d52285d0..bdd53af9 100644 --- a/examples/52-server-count-visitors.php +++ b/examples/52-server-count-visitors.php @@ -8,7 +8,7 @@ $counter = 0; $http = new React\Http\HttpServer(function (ServerRequestInterface $request) use (&$counter) { return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/53-server-whatsmyip.php b/examples/53-server-whatsmyip.php index 5df1050d..e0835a82 100644 --- a/examples/53-server-whatsmyip.php +++ b/examples/53-server-whatsmyip.php @@ -9,7 +9,7 @@ $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/54-server-query-parameter.php b/examples/54-server-query-parameter.php index 22be7566..18dd56b0 100644 --- a/examples/54-server-query-parameter.php +++ b/examples/54-server-query-parameter.php @@ -16,7 +16,7 @@ } return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/html' ), diff --git a/examples/55-server-cookie-handling.php b/examples/55-server-cookie-handling.php index a6858061..8260fc33 100644 --- a/examples/55-server-cookie-handling.php +++ b/examples/55-server-cookie-handling.php @@ -12,7 +12,7 @@ $body = "Your cookie value is: " . $request->getCookieParams()[$key]; return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -21,7 +21,7 @@ } return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain', 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') diff --git a/examples/56-server-sleep.php b/examples/56-server-sleep.php index caa22644..6bb6f82b 100644 --- a/examples/56-server-sleep.php +++ b/examples/56-server-sleep.php @@ -11,7 +11,7 @@ return new Promise(function ($resolve, $reject) { Loop::addTimer(1.5, function() use ($resolve) { $response = new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/57-server-error-handling.php b/examples/57-server-error-handling.php index 4a1b6757..71cbad15 100644 --- a/examples/57-server-error-handling.php +++ b/examples/57-server-error-handling.php @@ -16,7 +16,7 @@ } $response = new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/58-server-stream-response.php b/examples/58-server-stream-response.php index 2069b7a8..015ddd9a 100644 --- a/examples/58-server-stream-response.php +++ b/examples/58-server-stream-response.php @@ -9,7 +9,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { - return new Response(404); + return new Response(Response::STATUS_NOT_FOUND); } $stream = new ThroughStream(); @@ -30,7 +30,7 @@ }); return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/59-server-json-api.php b/examples/59-server-json-api.php index 7fa8cc66..0d50b52b 100644 --- a/examples/59-server-json-api.php +++ b/examples/59-server-json-api.php @@ -14,7 +14,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { if ($request->getHeaderLine('Content-Type') !== 'application/json') { return new Response( - 415, // Unsupported Media Type + Response::STATUS_UNSUPPORTED_MEDIA_TYPE, array( 'Content-Type' => 'application/json' ), @@ -25,7 +25,7 @@ $input = json_decode($request->getBody()->getContents()); if (json_last_error() !== JSON_ERROR_NONE) { return new Response( - 400, // Bad Request + Response::STATUS_BAD_REQUEST, array( 'Content-Type' => 'application/json' ), @@ -35,7 +35,7 @@ if (!isset($input->name) || !is_string($input->name)) { return new Response( - 422, // Unprocessable Entity + Response::STATUS_UNPROCESSABLE_ENTITY, array( 'Content-Type' => 'application/json' ), @@ -44,7 +44,7 @@ } return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'application/json' ), diff --git a/examples/61-server-hello-world-https.php b/examples/61-server-hello-world-https.php index 01182fdd..2fd6f9af 100644 --- a/examples/61-server-hello-world-https.php +++ b/examples/61-server-hello-world-https.php @@ -7,7 +7,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/62-server-form-upload.php b/examples/62-server-form-upload.php index 6984b4e3..899caa0a 100644 --- a/examples/62-server-form-upload.php +++ b/examples/62-server-form-upload.php @@ -110,7 +110,7 @@ HTML; return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/html; charset=UTF-8' ), diff --git a/examples/63-server-streaming-request.php b/examples/63-server-streaming-request.php index c1e6ac89..b20b8f08 100644 --- a/examples/63-server-streaming-request.php +++ b/examples/63-server-streaming-request.php @@ -20,7 +20,7 @@ function (Psr\Http\Message\ServerRequestInterface $request) { $body->on('end', function () use ($resolve, &$bytes){ $resolve(new React\Http\Message\Response( - 200, + React\Http\Message\Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -31,7 +31,7 @@ function (Psr\Http\Message\ServerRequestInterface $request) { // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event $body->on('error', function (Exception $e) use ($resolve, &$bytes) { $resolve(new React\Http\Message\Response( - 400, + React\Http\Message\Response::STATUS_BAD_REQUEST, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/71-server-http-proxy.php b/examples/71-server-http-proxy.php index c4fe244e..e0bf8404 100644 --- a/examples/71-server-http-proxy.php +++ b/examples/71-server-http-proxy.php @@ -16,7 +16,7 @@ $http = new React\Http\HttpServer(function (RequestInterface $request) { if (strpos($request->getRequestTarget(), '://') === false) { return new Response( - 400, + Response::STATUS_BAD_REQUEST, array( 'Content-Type' => 'text/plain' ), @@ -36,7 +36,7 @@ // left up as an exercise: use an HTTP client to send the outgoing request // and forward the incoming response to the original client request return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/72-server-http-connect-proxy.php b/examples/72-server-http-connect-proxy.php index ac033370..0500822a 100644 --- a/examples/72-server-http-connect-proxy.php +++ b/examples/72-server-http-connect-proxy.php @@ -19,7 +19,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) use ($connector) { if ($request->getMethod() !== 'CONNECT') { return new Response( - 405, + Response::STATUS_METHOD_NOT_ALLOWED, array( 'Content-Type' => 'text/plain', 'Allow' => 'CONNECT' @@ -33,14 +33,14 @@ function (ConnectionInterface $remote) { // connection established => forward data return new Response( - 200, + Response::STATUS_OK, array(), $remote ); }, function (Exception $e) { return new Response( - 502, + Response::STATUS_BAD_GATEWAY, array( 'Content-Type' => 'text/plain' ), diff --git a/examples/81-server-upgrade-echo.php b/examples/81-server-upgrade-echo.php index 2f77172f..cd3dc156 100644 --- a/examples/81-server-upgrade-echo.php +++ b/examples/81-server-upgrade-echo.php @@ -30,7 +30,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { return new Response( - 426, + Response::STATUS_UPGRADE_REQUIRED, array( 'Upgrade' => 'echo' ), @@ -48,7 +48,7 @@ }); return new Response( - 101, + Response::STATUS_SWITCHING_PROTOCOLS, array( 'Upgrade' => 'echo' ), diff --git a/examples/82-server-upgrade-chat.php b/examples/82-server-upgrade-chat.php index 42635e8c..bd791fb0 100644 --- a/examples/82-server-upgrade-chat.php +++ b/examples/82-server-upgrade-chat.php @@ -38,7 +38,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) use ($chat) { if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { return new Response( - 426, + Response::STATUS_UPGRADE_REQUIRED, array( 'Upgrade' => 'chat' ), @@ -76,7 +76,7 @@ }); return new Response( - 101, + Response::STATUS_SWITCHING_PROTOCOLS, array( 'Upgrade' => 'chat' ), diff --git a/examples/99-server-benchmark-download.php b/examples/99-server-benchmark-download.php index df0e69e7..6c737605 100644 --- a/examples/99-server-benchmark-download.php +++ b/examples/99-server-benchmark-download.php @@ -94,7 +94,7 @@ public function getSize() switch ($request->getUri()->getPath()) { case '/': return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'text/html' ), @@ -107,13 +107,13 @@ public function getSize() $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); break; default: - return new Response(404); + return new Response(Response::STATUS_NOT_FOUND); } React\EventLoop\Loop::addTimer(0, array($stream, 'resume')); return new Response( - 200, + Response::STATUS_OK, array( 'Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize() diff --git a/src/HttpServer.php b/src/HttpServer.php index 95ccc5ff..f2334733 100644 --- a/src/HttpServer.php +++ b/src/HttpServer.php @@ -26,7 +26,7 @@ * ```php * $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { * return new React\Http\Message\Response( - * 200, + * React\Http\Message\Response::STATUS_OK, * array( * 'Content-Type' => 'text/plain' * ), diff --git a/src/Io/RequestHeaderParser.php b/src/Io/RequestHeaderParser.php index 64b5dcdb..743c006c 100644 --- a/src/Io/RequestHeaderParser.php +++ b/src/Io/RequestHeaderParser.php @@ -4,6 +4,7 @@ use Evenement\EventEmitter; use Psr\Http\Message\ServerRequestInterface; +use React\Http\Message\Response; use React\Http\Message\ServerRequest; use React\Socket\ConnectionInterface; use Exception; @@ -39,7 +40,7 @@ public function handle(ConnectionInterface $conn) $fn = null; $that->emit('error', array( - new \OverflowException("Maximum header size of {$maxSize} exceeded.", 431), + new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE), $conn )); return; @@ -127,7 +128,7 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri) // only support HTTP/1.1 and HTTP/1.0 requests if ($start['version'] !== '1.1' && $start['version'] !== '1.0') { - throw new \InvalidArgumentException('Received request with invalid protocol version', 505); + throw new \InvalidArgumentException('Received request with invalid protocol version', Response::STATUS_VERSION_NOT_SUPPORTED); } // match all request header fields into array, thanks to @kelunik for checking the HTTP specs and coming up with this regex @@ -256,20 +257,20 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri) // ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers if ($request->hasHeader('Transfer-Encoding')) { if (\strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') { - throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', 501); + throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', Response::STATUS_NOT_IMPLEMENTED); } // Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time // as per https://tools.ietf.org/html/rfc7230#section-3.3.3 if ($request->hasHeader('Content-Length')) { - throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', 400); + throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', Response::STATUS_BAD_REQUEST); } } elseif ($request->hasHeader('Content-Length')) { $string = $request->getHeaderLine('Content-Length'); if ((string)(int)$string !== $string) { // Content-Length value is not an integer or not a single integer - throw new \InvalidArgumentException('The value of `Content-Length` is not valid', 400); + throw new \InvalidArgumentException('The value of `Content-Length` is not valid', Response::STATUS_BAD_REQUEST); } } diff --git a/src/Io/Sender.php b/src/Io/Sender.php index c1bbab42..2f04c797 100644 --- a/src/Io/Sender.php +++ b/src/Io/Sender.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ResponseInterface; use React\EventLoop\LoopInterface; use React\Http\Client\Client as HttpClient; +use React\Http\Message\Response; use React\Promise\PromiseInterface; use React\Promise\Deferred; use React\Socket\ConnectorInterface; @@ -110,7 +111,7 @@ public function send(RequestInterface $request) $requestStream->on('response', function (ResponseInterface $response, ReadableStreamInterface $body) use ($deferred, $request) { $length = null; $code = $response->getStatusCode(); - if ($request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == 204 || $code == 304) { + if ($request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == Response::STATUS_NO_CONTENT || $code == Response::STATUS_NOT_MODIFIED) { $length = 0; } elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') { $body = new ChunkedDecoder($body); diff --git a/src/Io/StreamingServer.php b/src/Io/StreamingServer.php index 5f9632e9..dd4c0584 100644 --- a/src/Io/StreamingServer.php +++ b/src/Io/StreamingServer.php @@ -31,7 +31,7 @@ * ```php * $server = new StreamingServer($loop, function (ServerRequestInterface $request) { * return new Response( - * 200, + * Response::STATUS_OK, * array( * 'Content-Type' => 'text/plain' * ), @@ -120,7 +120,7 @@ public function __construct(LoopInterface $loop, $requestHandler) // parsing failed => assume dummy request and send appropriate error $that->writeError( $conn, - $e->getCode() !== 0 ? $e->getCode() : 400, + $e->getCode() !== 0 ? $e->getCode() : Response::STATUS_BAD_REQUEST, new ServerRequest('GET', '/') ); }); @@ -182,7 +182,7 @@ function ($response) use ($that, $conn, $request) { $exception = new \RuntimeException($message); $that->emit('error', array($exception)); - return $that->writeError($conn, 500, $request); + return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request); } $that->handleResponse($conn, $request, $response); }, @@ -199,7 +199,7 @@ function ($error) use ($that, $conn, $request) { $exception = new \RuntimeException($message, null, $previous); $that->emit('error', array($exception)); - return $that->writeError($conn, 500, $request); + return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request); } ); } @@ -262,10 +262,10 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt // assign "Content-Length" header automatically $chunked = false; - if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === 204) { + if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === Response::STATUS_NO_CONTENT) { // 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header $response = $response->withoutHeader('Content-Length'); - } elseif ($code === 304 && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) { + } elseif ($code === Response::STATUS_NOT_MODIFIED && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) { // 304 Not Modified: preserve explicit Content-Length and preserve missing header if body is empty } elseif ($body->getSize() !== null) { // assign Content-Length header when using a "normal" buffered body string @@ -285,7 +285,7 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt // assign "Connection" header automatically $persist = false; - if ($code === 101) { + if ($code === Response::STATUS_SWITCHING_PROTOCOLS) { // 101 (Switching Protocols) response uses Connection: upgrade header // This implies that this stream now uses another protocol and we // may not persist this connection for additional requests. @@ -307,7 +307,7 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt // 101 (Switching Protocols) response (for Upgrade request) forwards upgraded data through duplex stream // 2xx (Successful) response to CONNECT forwards tunneled application data through duplex stream - if (($code === 101 || ($method === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) { + if (($code === Response::STATUS_SWITCHING_PROTOCOLS || ($method === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) { if ($request->getBody()->isReadable()) { // request is still streaming => wait for request close before forwarding following data from connection $request->getBody()->on('close', function () use ($connection, $body) { @@ -333,7 +333,7 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt // response to HEAD and 1xx, 204 and 304 responses MUST NOT include a body // exclude status 101 (Switching Protocols) here for Upgrade request handling above - if ($method === 'HEAD' || $code === 100 || ($code > 101 && $code < 200) || $code === 204 || $code === 304) { + if ($method === 'HEAD' || ($code >= 100 && $code < 200 && $code !== Response::STATUS_SWITCHING_PROTOCOLS) || $code === Response::STATUS_NO_CONTENT || $code === Response::STATUS_NOT_MODIFIED) { $body->close(); $body = ''; } diff --git a/src/Message/Response.php b/src/Message/Response.php index a9710170..0ce8bef0 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -2,6 +2,7 @@ namespace React\Http\Message; +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\StreamInterface; use React\Http\Io\BufferedBody; use React\Http\Io\HttpBodyStream; @@ -13,7 +14,7 @@ * * ```php * $response = new React\Http\Message\Response( - * 200, + * React\Http\Message\Response::STATUS_OK, * array( * 'Content-Type' => 'text/html' * ), @@ -26,16 +27,23 @@ * which in turn extends the * [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface). * + * On top of this, this class implements the + * [PSR-7 Message Util `StatusCodeInterface`](https://github.com/php-fig/http-message-util/blob/master/src/StatusCodeInterface.php) + * which means that most common HTTP status codes are available as class + * constants with the `STATUS_*` prefix. For instance, the `200 OK` and + * `404 Not Found` status codes can used as `Response::STATUS_OK` and + * `Response::STATUS_NOT_FOUND` respectively. + * * > Internally, this implementation builds on top of an existing incoming * response message and only adds required streaming support. This base class is * considered an implementation detail that may change in the future. * * @see \Psr\Http\Message\ResponseInterface */ -final class Response extends Psr7Response +final class Response extends Psr7Response implements StatusCodeInterface { /** - * @param int $status HTTP status code (e.g. 200/404) + * @param int $status HTTP status code (e.g. 200/404), see `self::STATUS_*` constants * @param array $headers additional response headers * @param string|ReadableStreamInterface|StreamInterface $body response body * @param string $version HTTP protocol version (e.g. 1.1/1.0) @@ -43,7 +51,7 @@ final class Response extends Psr7Response * @throws \InvalidArgumentException for an invalid body */ public function __construct( - $status = 200, + $status = self::STATUS_OK, array $headers = array(), $body = '', $version = '1.1',