From fc1cf25b7187bbcb64509fee24888ab602b23c18 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 12 Apr 2021 23:20:36 +0200 Subject: [PATCH] Introduce using PSR-7 HTTP Status code constants Introducing using these constants makes it easier to identify a specific HTTP status used in our code. And for our users to use readable status codes in their own code. --- composer.json | 3 ++- examples/51-server-hello-world.php | 3 ++- examples/52-server-count-visitors.php | 3 ++- examples/53-server-whatsmyip.php | 3 ++- examples/54-server-query-parameter.php | 3 ++- examples/55-server-cookie-handling.php | 5 +++-- examples/56-server-sleep.php | 3 ++- examples/57-server-error-handling.php | 3 ++- examples/58-server-stream-response.php | 5 +++-- examples/59-server-json-api.php | 9 +++++---- examples/61-server-hello-world-https.php | 3 ++- examples/62-server-form-upload.php | 3 ++- examples/63-server-streaming-request.php | 6 ++++-- examples/71-server-http-proxy.php | 5 +++-- examples/72-server-http-connect-proxy.php | 7 ++++--- examples/81-server-upgrade-echo.php | 5 +++-- examples/82-server-upgrade-chat.php | 5 +++-- examples/99-server-benchmark-download.php | 7 ++++--- src/Io/RequestHeaderParser.php | 11 ++++++----- src/Io/Sender.php | 3 ++- src/Io/StreamingServer.php | 13 +++++++------ src/Message/Response.php | 3 ++- 22 files changed, 67 insertions(+), 44 deletions(-) diff --git a/composer.json b/composer.json index 25f96db5..1939accf 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "react/promise-stream": "^1.1", "react/socket": "^1.9", "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "ringcentral/psr7": "^1.2", + "fig/http-message-util": "^1.1" }, "require-dev": { "clue/block-react": "^1.1", diff --git a/examples/51-server-hello-world.php b/examples/51-server-hello-world.php index f549ece8..2d9dc766 100644 --- a/examples/51-server-hello-world.php +++ b/examples/51-server-hello-world.php @@ -1,5 +1,6 @@ 'text/plain' ), diff --git a/examples/52-server-count-visitors.php b/examples/52-server-count-visitors.php index d52285d0..8e219ad0 100644 --- a/examples/52-server-count-visitors.php +++ b/examples/52-server-count-visitors.php @@ -1,5 +1,6 @@ 'text/plain' ), diff --git a/examples/53-server-whatsmyip.php b/examples/53-server-whatsmyip.php index 5df1050d..14ad2da8 100644 --- a/examples/53-server-whatsmyip.php +++ b/examples/53-server-whatsmyip.php @@ -1,5 +1,6 @@ getServerParams()['REMOTE_ADDR']; return new Response( - 200, + StatusCodeInterface::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..e3854626 100644 --- a/examples/54-server-query-parameter.php +++ b/examples/54-server-query-parameter.php @@ -1,5 +1,6 @@ 'text/html' ), diff --git a/examples/55-server-cookie-handling.php b/examples/55-server-cookie-handling.php index a6858061..7a093d24 100644 --- a/examples/55-server-cookie-handling.php +++ b/examples/55-server-cookie-handling.php @@ -1,5 +1,6 @@ getCookieParams()[$key]; return new Response( - 200, + StatusCodeInterface::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -21,7 +22,7 @@ } return new Response( - 200, + StatusCodeInterface::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..cfc805d3 100644 --- a/examples/56-server-sleep.php +++ b/examples/56-server-sleep.php @@ -1,5 +1,6 @@ 'text/plain' ), diff --git a/examples/57-server-error-handling.php b/examples/57-server-error-handling.php index 4a1b6757..72b9c02b 100644 --- a/examples/57-server-error-handling.php +++ b/examples/57-server-error-handling.php @@ -1,5 +1,6 @@ 'text/plain' ), diff --git a/examples/58-server-stream-response.php b/examples/58-server-stream-response.php index 2069b7a8..596ca6fc 100644 --- a/examples/58-server-stream-response.php +++ b/examples/58-server-stream-response.php @@ -1,5 +1,6 @@ getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { - return new Response(404); + return new Response(StatusCodeInterface::STATUS_NOT_FOUND); } $stream = new ThroughStream(); @@ -30,7 +31,7 @@ }); return new Response( - 200, + StatusCodeInterface::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..8a1ba358 100644 --- a/examples/59-server-json-api.php +++ b/examples/59-server-json-api.php @@ -6,6 +6,7 @@ // $ php examples/59-server-json-api.php 8080 // $ curl -v http://localhost:8080/ -H 'Content-Type: application/json' -d '{"name":"Alice"}' +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\Response; @@ -14,7 +15,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { if ($request->getHeaderLine('Content-Type') !== 'application/json') { return new Response( - 415, // Unsupported Media Type + StatusCodeInterface::STATUS_UNSUPPORTED_MEDIA_TYPE, array( 'Content-Type' => 'application/json' ), @@ -25,7 +26,7 @@ $input = json_decode($request->getBody()->getContents()); if (json_last_error() !== JSON_ERROR_NONE) { return new Response( - 400, // Bad Request + StatusCodeInterface::STATUS_BAD_REQUEST, array( 'Content-Type' => 'application/json' ), @@ -35,7 +36,7 @@ if (!isset($input->name) || !is_string($input->name)) { return new Response( - 422, // Unprocessable Entity + StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY, array( 'Content-Type' => 'application/json' ), @@ -44,7 +45,7 @@ } return new Response( - 200, + StatusCodeInterface::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..f563282e 100644 --- a/examples/61-server-hello-world-https.php +++ b/examples/61-server-hello-world-https.php @@ -1,5 +1,6 @@ 'text/plain' ), diff --git a/examples/62-server-form-upload.php b/examples/62-server-form-upload.php index 6984b4e3..b09f178d 100644 --- a/examples/62-server-form-upload.php +++ b/examples/62-server-form-upload.php @@ -7,6 +7,7 @@ // $ curl --form name=test --form age=30 http://localhost:8080/ // $ curl --form name=hi --form avatar=@avatar.png http://localhost:8080/ +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use React\Http\Message\Response; @@ -110,7 +111,7 @@ HTML; return new Response( - 200, + StatusCodeInterface::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..073d0e0e 100644 --- a/examples/63-server-streaming-request.php +++ b/examples/63-server-streaming-request.php @@ -1,5 +1,7 @@ on('end', function () use ($resolve, &$bytes){ $resolve(new React\Http\Message\Response( - 200, + StatusCodeInterface::STATUS_OK, array( 'Content-Type' => 'text/plain' ), @@ -31,7 +33,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, + StatusCodeInterface::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..3029dd7f 100644 --- a/examples/71-server-http-proxy.php +++ b/examples/71-server-http-proxy.php @@ -3,6 +3,7 @@ // $ php examples/71-server-http-proxy.php 8080 // $ curl -v --proxy http://localhost:8080 http://reactphp.org/ +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\RequestInterface; use React\Http\Message\Response; use RingCentral\Psr7; @@ -16,7 +17,7 @@ $http = new React\Http\HttpServer(function (RequestInterface $request) { if (strpos($request->getRequestTarget(), '://') === false) { return new Response( - 400, + StatusCodeInterface::STATUS_BAD_REQUEST, array( 'Content-Type' => 'text/plain' ), @@ -36,7 +37,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, + StatusCodeInterface::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..55b03a84 100644 --- a/examples/72-server-http-connect-proxy.php +++ b/examples/72-server-http-connect-proxy.php @@ -3,6 +3,7 @@ // $ php examples/72-server-http-connect-proxy.php 8080 // $ curl -v --proxy http://localhost:8080 https://reactphp.org/ +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\Response; use React\Socket\Connector; @@ -19,7 +20,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) use ($connector) { if ($request->getMethod() !== 'CONNECT') { return new Response( - 405, + StatusCodeInterface::STATUS_METHOD_NOT_ALLOWED, array( 'Content-Type' => 'text/plain', 'Allow' => 'CONNECT' @@ -33,14 +34,14 @@ function (ConnectionInterface $remote) { // connection established => forward data return new Response( - 200, + StatusCodeInterface::STATUS_OK, array(), $remote ); }, function (Exception $e) { return new Response( - 502, + StatusCodeInterface::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..4fa54def 100644 --- a/examples/81-server-upgrade-echo.php +++ b/examples/81-server-upgrade-echo.php @@ -17,6 +17,7 @@ < world */ +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Loop; use React\Http\Message\Response; @@ -30,7 +31,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) { if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { return new Response( - 426, + StatusCodeInterface::STATUS_UPGRADE_REQUIRED, array( 'Upgrade' => 'echo' ), @@ -48,7 +49,7 @@ }); return new Response( - 101, + StatusCodeInterface::STATUS_SWITCHING_PROTOCOLS, array( 'Upgrade' => 'echo' ), diff --git a/examples/82-server-upgrade-chat.php b/examples/82-server-upgrade-chat.php index 42635e8c..84117203 100644 --- a/examples/82-server-upgrade-chat.php +++ b/examples/82-server-upgrade-chat.php @@ -19,6 +19,7 @@ Hint: try this with multiple connections :) */ +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Loop; use React\Http\Message\Response; @@ -38,7 +39,7 @@ $http = new React\Http\HttpServer(function (ServerRequestInterface $request) use ($chat) { if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { return new Response( - 426, + StatusCodeInterface::STATUS_UPGRADE_REQUIRED, array( 'Upgrade' => 'chat' ), @@ -76,7 +77,7 @@ }); return new Response( - 101, + StatusCodeInterface::STATUS_SWITCHING_PROTOCOLS, array( 'Upgrade' => 'chat' ), diff --git a/examples/99-server-benchmark-download.php b/examples/99-server-benchmark-download.php index df0e69e7..f7117aa5 100644 --- a/examples/99-server-benchmark-download.php +++ b/examples/99-server-benchmark-download.php @@ -15,6 +15,7 @@ // $ docker run -it --rm --net=host skandyla/wrk -t8 -c10 -d20 http://localhost:8080/ use Evenement\EventEmitter; +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\Response; use React\Stream\ReadableStreamInterface; @@ -94,7 +95,7 @@ public function getSize() switch ($request->getUri()->getPath()) { case '/': return new Response( - 200, + StatusCodeInterface::STATUS_OK, array( 'Content-Type' => 'text/html' ), @@ -107,13 +108,13 @@ public function getSize() $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); break; default: - return new Response(404); + return new Response(StatusCodeInterface::STATUS_NOT_FOUND); } React\EventLoop\Loop::addTimer(0, array($stream, 'resume')); return new Response( - 200, + StatusCodeInterface::STATUS_OK, array( 'Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize() diff --git a/src/Io/RequestHeaderParser.php b/src/Io/RequestHeaderParser.php index 64b5dcdb..a6136e2f 100644 --- a/src/Io/RequestHeaderParser.php +++ b/src/Io/RequestHeaderParser.php @@ -3,6 +3,7 @@ namespace React\Http\Io; use Evenement\EventEmitter; +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\ServerRequest; use React\Socket\ConnectionInterface; @@ -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.", StatusCodeInterface::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', StatusCodeInterface::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', StatusCodeInterface::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', StatusCodeInterface::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', StatusCodeInterface::STATUS_BAD_REQUEST); } } diff --git a/src/Io/Sender.php b/src/Io/Sender.php index c1bbab42..6e2d661e 100644 --- a/src/Io/Sender.php +++ b/src/Io/Sender.php @@ -2,6 +2,7 @@ namespace React\Http\Io; +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use React\EventLoop\LoopInterface; @@ -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 == StatusCodeInterface::STATUS_NO_CONTENT || $code == StatusCodeInterface::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..c7039f50 100644 --- a/src/Io/StreamingServer.php +++ b/src/Io/StreamingServer.php @@ -3,6 +3,7 @@ namespace React\Http\Io; use Evenement\EventEmitter; +use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\LoopInterface; @@ -120,7 +121,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() : StatusCodeInterface::STATUS_BAD_REQUEST, new ServerRequest('GET', '/') ); }); @@ -199,7 +200,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, StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, $request); } ); } @@ -262,7 +263,7 @@ 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 === StatusCodeInterface::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)) { @@ -285,7 +286,7 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt // assign "Connection" header automatically $persist = false; - if ($code === 101) { + if ($code === StatusCodeInterface::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 +308,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 === StatusCodeInterface::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 +334,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 > StatusCodeInterface::STATUS_SWITCHING_PROTOCOLS && $code < 200) || $code === StatusCodeInterface::STATUS_NO_CONTENT || $code === StatusCodeInterface::STATUS_NOT_MODIFIED) { $body->close(); $body = ''; } diff --git a/src/Message/Response.php b/src/Message/Response.php index a9710170..46c91501 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; @@ -43,7 +44,7 @@ final class Response extends Psr7Response * @throws \InvalidArgumentException for an invalid body */ public function __construct( - $status = 200, + $status = StatusCodeInterface::STATUS_OK, array $headers = array(), $body = '', $version = '1.1',