321 lines
13 KiB
Markdown
321 lines
13 KiB
Markdown
HTTP Server Middleware
|
|
======================
|
|
|
|
1. Summary
|
|
----------
|
|
|
|
The purpose of this PSR is to provide an interface that defines the formal
|
|
method signature for HTTP Server Middleware that is compatible with HTTP
|
|
Messages, as defined in [PSR-7][psr7].
|
|
|
|
[psr7]: http://www.php-fig.org/psr/psr-7/
|
|
|
|
2. Why Bother?
|
|
--------------
|
|
|
|
The HTTP Messages specification does not contain any reference to HTTP Middleware.
|
|
|
|
The design pattern used by middleware has existed for many years as the
|
|
[pipeline pattern][pipeline], or more specifically, "linear pipeline processing".
|
|
The general concept of reusable middleware was popularized within PHP by
|
|
[StackPHP][stackphp]. Since the release of the HTTP Messages standard, a number
|
|
of frameworks have adopted middleware that use HTTP Message interfaces.
|
|
|
|
Agreeing on a formal server middleware interface eliminates several problems and
|
|
provides a number of benefits:
|
|
|
|
* Provides a formal standard for middleware developers to commit to.
|
|
* Eliminates duplication of similar interfaces defined by various frameworks.
|
|
* Avoids minor discrepancies in method signatures.
|
|
* Enables any middleware component to run in any compatible framework.
|
|
|
|
[pipeline]: https://en.wikipedia.org/wiki/Pipeline_(computing)
|
|
[stackphp]: http://stackphp.com/
|
|
|
|
3. Scope
|
|
--------
|
|
|
|
### 3.1 Goals
|
|
|
|
* Create a middleware interface that uses HTTP Messages.
|
|
* Ensure that middleware will not be coupled to a specific implementation of HTTP Messages.
|
|
* Implement a middleware signature that is based on best practices.
|
|
|
|
### 3.2 Non-Goals
|
|
|
|
* Attempting to define how middleware is dispatched.
|
|
* Attempting to define interfaces for client/asynchronous middleware.
|
|
* Attempting to define the mechanism by which HTTP responses are created.
|
|
|
|
4. Approaches
|
|
-------------
|
|
|
|
There are currently two common approaches to server middleware that use HTTP Messages.
|
|
|
|
### 4.1 Double Pass
|
|
|
|
The signature used by most middleware implementations has been mostly the same
|
|
and is based on [Express middleware][express], which is defined as:
|
|
|
|
```
|
|
fn(request, response, next): response
|
|
```
|
|
|
|
[express]: http://expressjs.com/en/guide/writing-middleware.html
|
|
|
|
Based on the middleware implementations already used by frameworks that have
|
|
adopted this signature, the following commonalities are observed:
|
|
|
|
* The middleware is defined as a [callable][php-callable].
|
|
* The middleware is passed 3 arguments during invocation:
|
|
1. A `ServerRequestInterface` implementation.
|
|
2. A `ResponseInterface` implementation.
|
|
3. A `callable` that receives the request and response to delegate the next middleware.
|
|
|
|
[php-callable]: http://php.net/manual/language.types.callable.php
|
|
|
|
A significant number of projects provide and/or use exactly the same interface.
|
|
This approach is often referred to as "double pass" in reference to both the
|
|
request and response being passed to the middleware.
|
|
|
|
#### 4.1.1 Projects Using Double Pass
|
|
|
|
* [mindplay/middleman](https://github.com/mindplay-dk/middleman/blob/1.0.0/src/MiddlewareInterface.php#L24)
|
|
* [relay/relay](https://github.com/relayphp/Relay.Relay/blob/1.0.0/src/MiddlewareInterface.php#L24)
|
|
* [slim/slim](https://github.com/slimphp/Slim/blob/3.4.0/Slim/MiddlewareAwareTrait.php#L66-L75)
|
|
* [zendframework/zend-stratigility](https://github.com/zendframework/zend-stratigility/blob/1.0.0/src/MiddlewarePipe.php#L69-L79)
|
|
|
|
#### 4.1.2 Middleware Implementing Double Pass
|
|
|
|
* [bitexpert/adroit](https://github.com/bitExpert/adroit)
|
|
* [akrabat/rka-ip-address-middleware](https://github.com/akrabat/rka-ip-address-middleware)
|
|
* [akrabat/rka-scheme-and-host-detection-middleware](https://github.com/akrabat/rka-scheme-and-host-detection-middleware)
|
|
* [bear/middleware](https://github.com/bearsunday/BEAR.Middleware)
|
|
* [hannesvdvreken/psr7-middlewares](https://github.com/hannesvdvreken/psr7-middlewares)
|
|
* [los/api-problem](https://github.com/Lansoweb/api-problem)
|
|
* [los/los-rate-limit](https://github.com/Lansoweb/LosRateLimit)
|
|
* [monii/monii-action-handler-psr7-middleware](https://github.com/monii/monii-action-handler-psr7-middleware)
|
|
* [monii/monii-nikic-fast-route-psr7-middleware](https://github.com/monii/monii-nikic-fast-route-psr7-middleware)
|
|
* [monii/monii-response-assertion-psr7-middleware](https://github.com/monii/monii-response-assertion-psr7-middleware)
|
|
* [mtymek/blast-base-url](https://github.com/mtymek/blast-base-url)
|
|
* [ocramius/psr7-session](https://github.com/Ocramius/PSR7Session)
|
|
* [oscarotero/psr7-middlewares](https://github.com/oscarotero/psr7-middlewares)
|
|
* [php-middleware/block-robots](https://github.com/php-middleware/block-robots)
|
|
* [php-middleware/http-authentication](https://github.com/php-middleware/http-authentication)
|
|
* [php-middleware/log-http-messages](https://github.com/php-middleware/log-http-messages)
|
|
* [php-middleware/maintenance](https://github.com/php-middleware/maintenance)
|
|
* [php-middleware/phpdebugbar](https://github.com/php-middleware/phpdebugbar)
|
|
* [php-middleware/request-id](https://github.com/php-middleware/request-id)
|
|
* [relay/middleware](https://github.com/relayphp/Relay.Middleware)
|
|
|
|
The primary downside of this interface is that the while the interface itself is
|
|
a callable, there is currently no way to type hint a closure in a similar way.
|
|
|
|
### 4.2 Single Pass (Lambda)
|
|
|
|
The other approach to middleware is much closer to [StackPHP][stackphp] style
|
|
and is defined as:
|
|
|
|
```
|
|
fn(request, frame): response
|
|
```
|
|
|
|
Middleware taking this approach generally has the following commonalities:
|
|
|
|
* The middleware is defined with a specific interface with a method that takes
|
|
the request for processing.
|
|
* The middleware is passed 2 arguments during invocation:
|
|
1. An object that represents an HTTP request.
|
|
2. A delegate that receives the request to dispatch next middleware in the pipeline.
|
|
|
|
In this form, middleware has no access to a response until one is generated by
|
|
innermost middleware. Middleware can then modify the response before returning
|
|
back up the stack.
|
|
|
|
This approach is often referred to as "single pass" or "lambda" in reference to
|
|
only the request being passed to the middleware.
|
|
|
|
[php-closure]: http://php.net/closure
|
|
|
|
#### 4.2.1 Projects Using Single Pass
|
|
|
|
There are fewer examples of this approach within projects using HTTP Messages,
|
|
with one notable exception.
|
|
|
|
[Guzzle middleware][guzzle-middleware] is focused on outgoing (client) requests
|
|
and uses this signature:
|
|
|
|
```
|
|
function(RequestInterface $request, array $options): ResponseInterface
|
|
```
|
|
|
|
#### 4.2.2 Additional Projects Using Single Pass
|
|
|
|
There are also significant projects that predate HTTP Messages using this approach.
|
|
|
|
[StackPHP][stackphp] is based on [Symfony HttpKernel][httpkernel] and supports
|
|
middleware with this signature:
|
|
|
|
```
|
|
handle(Request $request, $type, $catch): Response
|
|
```
|
|
|
|
*__Note__: While Stack has multiple arguments, a response object is not included.*
|
|
|
|
[Laravel middleware][laravel-middleware] uses Symfony components and supports
|
|
middleware with this signature:
|
|
|
|
```
|
|
function handle(Request $request, callable $next): Response
|
|
```
|
|
|
|
[guzzle-middleware]: http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html
|
|
[httpkernel]: https://symfony.com/doc/2.0/components/http_kernel/introduction.html
|
|
[laravel-middleware]: https://laravel.com/docs/master/middleware
|
|
|
|
### 4.3 Comparison of Approaches
|
|
|
|
The single pass approach to middleware has been well established in the PHP
|
|
community for many years. This is most evident with the large number of packages
|
|
that are based around StackPHP.
|
|
|
|
The double pass approach is much newer but has been almost universally used by
|
|
early adopters of HTTP Messages.
|
|
|
|
### 4.4 Chosen Approach
|
|
|
|
Despite the nearly universal adoption of the double-pass approach there are
|
|
significant issues regarding implementation.
|
|
|
|
The most severe is that passing an empty response has no guarantees that the
|
|
response is in a usable state. This is further exacerbated by the fact that a
|
|
middleware may modify the response before passing it for further dispatching.
|
|
|
|
Further compounding the problem is that there is no way to ensure that the
|
|
response body has not been written to, which can lead to incomplete output or
|
|
error responses being sent with cache headers attached. It is also possible
|
|
to end up with [corrupted body content][rob-allen-filtering] when writing over
|
|
existing body content if the new content is shorter than the original. The most
|
|
effective way to resolve these issues is to always provide a fresh stream when
|
|
modifying the body of a message.
|
|
|
|
[rob-allen-filtering]: https://akrabat.com/filtering-the-psr-7-body-in-middleware/
|
|
|
|
Some have argued that passing the response helps ensure dependency inversion.
|
|
While it is true that it helps avoid depending on a specific implementation of
|
|
HTTP messages, the problem can also be resolved by injecting factories into the
|
|
middleware to create HTTP message objects, or by injecting empty message instances.
|
|
With the creation of HTTP Factories in [PSR-17][psr17], a standard approach to
|
|
handling dependency inversion is possible.
|
|
|
|
[psr17]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-factory/http-factory-meta.md
|
|
|
|
A more subjective, but also important, concern is that existing double-pass
|
|
middleware typically uses the `callable` type hint to refer to middleware.
|
|
This makes strict typing impossible, as there is no assurance that the `callable`
|
|
being passed implements a middleware signature, which reduces runtime safety.
|
|
|
|
**Due to these significant issues the lambda approach has been choosen for this proposal.**
|
|
|
|
5. Design Decisions
|
|
-------------------
|
|
|
|
### 5.1 Middleware Design
|
|
|
|
The `MiddlewareInterface` defines a single method that accepts a server
|
|
request and a delegate and must return a response. The middleware may:
|
|
|
|
- Evolve the request before passing it to the delegate to execute the next
|
|
available middleware.
|
|
- Evolve the response received from the delegate before returning it.
|
|
- Create and return a response without passing it to the delegate, thereby
|
|
preventing any further middleware from executing.
|
|
|
|
#### Why doesn't middleware use `__invoke`?
|
|
|
|
Doing so would conflict with existing middleware that implements the double-pass
|
|
approach and may want to implement the middleware interface.
|
|
|
|
In addition, classes that define `__invoke` can be type hinted as `callable`,
|
|
which results in less strict typing. This is generally undesirable, especially
|
|
when the `__invoke` method uses strict typing.
|
|
|
|
#### Why is a server request required?
|
|
|
|
To make it clear that the middleware can only be used in a synchronous, server
|
|
side context.
|
|
|
|
While not all middleware will need to use the additional methods defined by the
|
|
server request interface, external requests are typically handled asynchronously
|
|
and would need to return a [promise][promises] of a response. (This is primarily
|
|
due to the fact that multiple requests can be made in parallel and processed as
|
|
they are returned.) It is outside the scope of this proposal to address the needs
|
|
of asynchronous request/response life cycle.
|
|
|
|
Attempting to define client middleware would be premature at this point. Any future
|
|
proposal that is focused on client side request processing should have the opportunity
|
|
to define a standard that is specific to the nature of asynchronous middleware.
|
|
|
|
_See "client vs server side middleware" in [relevant links](#8-relevant-links) for
|
|
additional information._
|
|
|
|
[promises]: https://promisesaplus.com/
|
|
|
|
### 5.2 Delegate Design
|
|
|
|
The `DelegateInterface` defines a single method that accepts a request and
|
|
returns a response. The delegate interface must be implemented by any middleware
|
|
dispatcher that uses middleware implementing `MiddlewareInterface`.
|
|
|
|
#### Why isn't the delegate a `callable`?
|
|
|
|
Using an interface type hint improves runtime safety and IDE support.
|
|
|
|
_See "discussion of FrameInterface" in [relevant links](#8-relevant-links) for
|
|
additional information._
|
|
|
|
#### Why does the delegate conflict with middleware?
|
|
|
|
Both the middleware and delegate interface define a `process` method to prevent
|
|
misuse of middleware as delegates.
|
|
|
|
The implementation of delegate should be defined within middleware dispatching systems.
|
|
|
|
6. People
|
|
---------
|
|
|
|
### 6.1 Editor(s)
|
|
|
|
* Woody Gilk, <woody.gilk@gmail.com>
|
|
|
|
### 6.2 Sponsors
|
|
|
|
* Paul M Jones, <pmjones88@gmail.com> (Coordinator)
|
|
* Jason Coward, <jason@opengeek.com> (Sponsor)
|
|
|
|
### 6.3 Contributors
|
|
|
|
* Rasmus Schultz, <rasmus@mindplay.dk>
|
|
* Matthew Weier O'Phinney, <mweierophinney@gmail.com>
|
|
|
|
7. Votes
|
|
--------
|
|
|
|
* [Entrance Vote](https://groups.google.com/d/msg/php-fig/v9AijALWJhI/04XCwqgIEAAJ)
|
|
* **Acceptance Vote:** _(not yet taken)_
|
|
|
|
8. Relevant Links
|
|
-----------------
|
|
|
|
_**Note:** Order descending chronologically._
|
|
|
|
* [PHP-FIG mailing list thread](https://groups.google.com/d/msg/php-fig/vTtGxdIuBX8/NXKieN9vDQAJ)
|
|
* [The PHP League middleware proposal](https://groups.google.com/d/msg/thephpleague/jyztj-Nz_rw/I4lHVFigAAAJ)
|
|
* [PHP-FIG discussion of FrameInterface](https://groups.google.com/d/msg/php-fig/V12AAcT_SxE/aRXmNnIVCwAJ)
|
|
* [PHP-FIG discussion about client vs server side middleware](https://groups.google.com/d/msg/php-fig/vBk0BRgDe2s/GTaT0yKNBgAJ)
|
|
|
|
9. Errata
|
|
---------
|
|
|
|
...
|