From a160314abc2cce0115714326fd92350e9a950677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 4 Mar 2022 10:51:04 +0100 Subject: [PATCH] Improve documentation for routing HTTP `HEAD` requests --- docs/api/app.md | 5 +++++ examples/index.php | 25 +++++++++++++++++++++++++ tests/acceptance.sh | 5 +++++ 3 files changed, 35 insertions(+) diff --git a/docs/api/app.md b/docs/api/app.md index a7fd6ad..df5a00b 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -70,6 +70,11 @@ you can use this additional shortcut: $app->any('/user/{id}', $controller); ``` +Any registered `GET` routes will also match HTTP `HEAD` requests by default, +unless a more explicit `HEAD` route can also be matched. Responses to HTTP `HEAD` +requests can never have a response body, so X will automatically discard any +HTTP response body in this case. + ## Redirects The `App` also offers a convenient helper method to redirect a matching route to diff --git a/examples/index.php b/examples/index.php index b16a3b8..f199bd1 100644 --- a/examples/index.php +++ b/examples/index.php @@ -109,6 +109,31 @@ $request->getMethod() . "\n" ); }); +$app->get('/method/get', function (ServerRequestInterface $request) { + return React\Http\Message\Response::plaintext( + "GET\n" + )->withHeader('X-Is-Head', $request->getMethod() === 'HEAD' ? 'true' : 'false'); +}); +$app->head('/method/head', function (ServerRequestInterface $request) { + return new React\Http\Message\Response( + React\Http\Message\Response::STATUS_OK, + [ + 'Content-Length' => 5, + 'Content-Type' => 'text/plain; charset=utf-8', + 'X-Is-Head' => 'true' + ] + ); +}); +$app->get('/method/head', function (ServerRequestInterface $request) { + return new React\Http\Message\Response( + React\Http\Message\Response::STATUS_OK, + [ + 'Content-Type' => 'text/plain; charset=utf-8', + 'X-Is-Head' => 'false' + ], + "HEAD\n" + ); +}); $app->get('/etag/', function (ServerRequestInterface $request) { $etag = '"_"'; diff --git a/tests/acceptance.sh b/tests/acceptance.sh index dec09b7..74eb476 100755 --- a/tests/acceptance.sh +++ b/tests/acceptance.sh @@ -102,6 +102,11 @@ out=$(curl -v $base/method -X DELETE 2>&1); match "HTTP/.* 200" && match "DE out=$(curl -v $base/method -X OPTIONS 2>&1); match "HTTP/.* 200" && match "OPTIONS" out=$(curl -v $base -X OPTIONS --request-target "*" 2>&1); skipif "Server: nginx" && match "HTTP/.* 200" # skip nginx (400) +out=$(curl -v $base/method/get 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 4[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: false[\r\n]" && match -P "GET$" +out=$(curl -v $base/method/get -I 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 4[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: true[\r\n]" +out=$(curl -v $base/method/head 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 5[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: false[\r\n]" && match -P "HEAD$" +out=$(curl -v $base/method/head -I 2>&1); skipif "Server: ReactPHP" && match "HTTP/.* 200" && match -iP "Content-Length: 5[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: true[\r\n]" # skip built-in webserver (always includes Content-Length : 0) + out=$(curl -v $base/etag/ 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 0[\r\n]" && match -iP "Etag: \"_\"" out=$(curl -v $base/etag/ -H 'If-None-Match: "_"' 2>&1); match "HTTP/.* 304" && notmatch -i "Content-Length" && match -iP "Etag: \"_\"" out=$(curl -v $base/etag/a 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 2[\r\n]" && match -iP "Etag: \"a\""