This repository has been archived by the owner on Jan 29, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 197
Allow HEAD and OPTIONS requests for any matched route #413
Merged
weierophinney
merged 3 commits into
zendframework:develop
from
weierophinney:feature/implicit-head-options
Dec 20, 2016
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
133 changes: 133 additions & 0 deletions
133
doc/book/features/middleware/implicit-methods-middleware.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# ImplicitHeadMiddleware and ImplicitOptionsMiddleware | ||
|
||
Starting with version 1.1, Expressive offers middleware for implicitly | ||
supporting `HEAD` and `OPTIONS` requests. The HTTP/1.1 specifications indicate | ||
that all server implementations _must_ support `HEAD` requests for any given | ||
URI, and that they _should_ support `OPTIONS` requests. To make this possible, | ||
we have added features to our routing layer, and middleware that can detect | ||
_implicit_ support for these methods (i.e., the route was not registered | ||
_explicitly_ with the method). | ||
|
||
## ImplicitHeadMiddleware | ||
|
||
`Zend\Expressive\Middleware\ImplicitHeadMiddleware` provides support for | ||
handling `HEAD` requests to routed middleware when the route does not expliclity | ||
allow for the method. It should be registered _between_ the routing and dispatch | ||
middleware. | ||
|
||
By default, it can be instantiated with no extra arguments. However, you _may_ | ||
provide a response instance to use by default to the constructor if you need to | ||
craft special headers, status code, etc. | ||
|
||
Register the dependency via `dependencies` configuration: | ||
|
||
```php | ||
use Zend\Expressive\Middleware\ImplicitHeadMiddleware; | ||
|
||
return [ | ||
'dependencies' => [ | ||
'invokables' => [ | ||
ImplicitHeadMiddleware::class => ImplicitHeadMiddleware::class, | ||
], | ||
|
||
// or, if you have defined a factory to inject a response: | ||
'factories' => [ | ||
ImplicitHeadMiddleware::class => \Your\ImplicitHeadMiddlewareFactory::class, | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
Within your application pipeline, add the middleware between the routing and | ||
dispatch middleware: | ||
|
||
```php | ||
$app->pipeRoutingMiddleware(); | ||
$app->pipe(ImplicitHeadMiddleware::class); | ||
// ... | ||
$app->pipeDispatchMiddleware(); | ||
``` | ||
|
||
(Note: if you used the `expressive-pipeline-from-config` tool to create your | ||
programmatic pipeline, or if you used the Expressive 1.1 skeleton or later, this | ||
middleware is likely already in your pipeline, as is a dependency entry.) | ||
|
||
When in place, it will do the following: | ||
|
||
- If the request method is `HEAD`, AND | ||
- the request composes a `RouteResult` attribute, AND | ||
- the route result composes a `Route` instance, AND | ||
- the route returns true for the `implicitHead()` method, THEN | ||
- the middleware will return a response. | ||
|
||
In all other cases, it returns the result of delegating to the next middleware | ||
layer. | ||
|
||
When `implicitHead()` is matched, one of two things may occur. First, if the | ||
route does not support the `GET` method, then the middleware returns the | ||
composed response (either the one injected at instantiation, or an empty | ||
instance). However, if `GET` is supported, it will dispatch the next layer, but | ||
with a `GET` request instead of `HEAD`; additionally, it will inject the | ||
returned response with an empty response body before returning it. | ||
|
||
## ImplicitOptionsMiddleware | ||
|
||
`Zend\Expressive\Middleware\ImplicitOptionsMiddleware` provides support for | ||
handling `OPTIONS` requests to routed middleware when the route does not | ||
expliclity allow for the method. Like the `ImplicitHeadMiddleware`, it should be | ||
registered _between_ the routing and dispatch middleware. | ||
|
||
By default, it can be instantiated with no extra arguments. However, you _may_ | ||
provide a response prototype instance to use by default to the constructor if | ||
you need to craft special headers, status code, etc. | ||
|
||
Register the dependency via `dependencies` configuration: | ||
|
||
```php | ||
use Zend\Expressive\Middleware\ImplicitOptionsMiddleware; | ||
|
||
return [ | ||
'dependencies' => [ | ||
'invokables' => [ | ||
ImplicitOptionsMiddleware::class => ImplicitOptionsMiddleware::class, | ||
], | ||
|
||
// or, if you have defined a factory to inject a response: | ||
'factories' => [ | ||
ImplicitOptionsMiddleware::class => \Your\ImplicitOptionsMiddlewareFactory::class, | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
Within your application pipeline, add the middleware between the routing and | ||
dispatch middleware: | ||
|
||
```php | ||
$app->pipeRoutingMiddleware(); | ||
$app->pipe(ImplicitOptionsMiddleware::class); | ||
// ... | ||
$app->pipeDispatchMiddleware(); | ||
``` | ||
|
||
(Note: if you used the `expressive-pipeline-from-config` tool to create your | ||
programmatic pipeline, or if you used the Expressive 1.1 skeleton or later, this | ||
middleware is likely already in your pipeline, as is a dependency entry.) | ||
|
||
When in place, it will do the following: | ||
|
||
- If the request method is `OPTIONS`, AND | ||
- the request composes a `RouteResult` attribute, AND | ||
- the route result composes a `Route` instance, AND | ||
- the route returns true for the `implicitOptions()` method, THEN | ||
- the middleware will return a response with an `Allow` header indicating | ||
methods the route allows. | ||
|
||
In all other cases, it returns the result of delegating to the next middleware | ||
layer. | ||
|
||
One thing to note: the allowed methods reported by the route and/or route | ||
result, and returned via the `Allow` header, may vary based on router | ||
implementation. In most cases, it should be an aggregate of all routes using the | ||
same path specification; however, it *could* be only the methods supported | ||
explicitly by the matched route. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive for the canonical source repository | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License | ||
*/ | ||
|
||
namespace Zend\Expressive\Middleware; | ||
|
||
use Fig\Http\Message\RequestMethodInterface as RequestMethod; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Zend\Diactoros\Response; | ||
use Zend\Diactoros\Stream; | ||
use Zend\Expressive\Router\RouteResult; | ||
|
||
/** | ||
* Handle implicit HEAD requests. | ||
* | ||
* Place this middleware after the routing middleware so that it can handle | ||
* implicit HEAD requests -- requests where HEAD is used, but the route does | ||
* not explicitly handle that request method. | ||
* | ||
* When invoked, it will create an empty response with status code 200. | ||
* | ||
* You may optionally pass a response prototype to the constructor; when | ||
* present, that instance will be returned instead. | ||
* | ||
* The middleware is only invoked in these specific conditions: | ||
* | ||
* - a HEAD request | ||
* - with a `RouteResult` present | ||
* - where the `RouteResult` contains a `Route` instance | ||
* - and the `Route` instance defines implicit HEAD. | ||
* | ||
* In all other circumstances, it will return the result of the delegate. | ||
* | ||
* If the route instance supports GET requests, the middleware dispatches | ||
* the next layer, but alters the request passed to use the GET method; | ||
* it then provides an empty response body to the returned response. | ||
*/ | ||
class ImplicitHeadMiddleware | ||
{ | ||
/** | ||
* @var null|ResponseInterface | ||
*/ | ||
private $response; | ||
|
||
/** | ||
* @param null|ResponseInterface $response Response prototype to return | ||
* for implicit HEAD requests; if none provided, an empty zend-diactoros | ||
* instance will be created. | ||
*/ | ||
public function __construct(ResponseInterface $response = null) | ||
{ | ||
$this->response = $response; | ||
} | ||
|
||
/** | ||
* Handle an implicit HEAD request. | ||
* | ||
* If the route allows GET requests, dispatches as a GET request and | ||
* resets the response body to be empty; otherwise, creates a new empty | ||
* response. | ||
* | ||
* @param ServerRequestInterface $request | ||
* @param ResponseInterface $response | ||
* @param callable $next | ||
* @return ResponseInterface | ||
*/ | ||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) | ||
{ | ||
if ($request->getMethod() !== RequestMethod::METHOD_HEAD) { | ||
return $next($request, $response); | ||
} | ||
|
||
if (false === ($result = $request->getAttribute(RouteResult::class, false))) { | ||
return $next($request, $response); | ||
} | ||
|
||
$route = $result->getMatchedRoute(); | ||
if (! $route || ! $route->implicitHead()) { | ||
return $next($request, $response); | ||
} | ||
|
||
if (! $route->allowsMethod(RequestMethod::METHOD_GET)) { | ||
return $this->getResponse(); | ||
} | ||
|
||
$response = $next( | ||
$request->withMethod(RequestMethod::METHOD_GET), | ||
$response | ||
); | ||
|
||
return $response->withBody(new Stream('php://temp/', 'wb+')); | ||
} | ||
|
||
/** | ||
* Return the response prototype to use for an implicit HEAD request. | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
private function getResponse() | ||
{ | ||
if ($this->response) { | ||
return $this->response; | ||
} | ||
|
||
return new Response(); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not check and assign it on
__contructor
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to create one if the middleware does not need to return a response in the first place.