Skip to content

Commit 08c50a4

Browse files
authored
Merge pull request #26 from http-interop/feature/import-meta-as-readme
Import PSR meta as README
2 parents 351d754 + 5043d1c commit 08c50a4

File tree

1 file changed

+327
-2
lines changed

1 file changed

+327
-2
lines changed

README.md

Lines changed: 327 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,328 @@
1-
# HTTP Middleware
1+
HTTP Server Middleware
2+
======================
23

3-
PSR-15 interfaces for HTTP middleware.
4+
1. Summary
5+
----------
6+
7+
The purpose of this PSR is to provide an interface that defines the formal
8+
method signature for HTTP Server Middleware that is compatible with HTTP
9+
Messages, as defined in [PSR-7][psr7].
10+
11+
[psr7]: http://www.php-fig.org/psr/psr-7/
12+
13+
2. Why Bother?
14+
--------------
15+
16+
The HTTP Messages specification does not contain any reference to HTTP Middleware.
17+
18+
The design pattern used by middleware has existed for many years as the
19+
[pipeline pattern][pipeline], or more specifically, "linear pipeline processing".
20+
The general concept of reusable middleware was popularized within PHP by
21+
[StackPHP][stackphp]. Since the release of the HTTP Messages standard, a number
22+
of frameworks have adopted middleware that use HTTP Message interfaces.
23+
24+
Agreeing on a formal server middleware interface eliminates several problems and
25+
provides a number of benefits:
26+
27+
* Provides a formal standard for middleware developers to commit to.
28+
* Eliminates duplication of similar interfaces defined by various frameworks.
29+
* Avoids minor discrepancies in method signatures.
30+
* Enables any middleware component to run in any compatible framework.
31+
32+
[pipeline]: https://en.wikipedia.org/wiki/Pipeline_(computing)
33+
[stackphp]: http://stackphp.com/
34+
35+
3. Scope
36+
--------
37+
38+
### 3.1 Goals
39+
40+
* Create a middleware interface that uses HTTP Messages.
41+
* Ensure that middleware will not be coupled to a specific implementation of HTTP Messages.
42+
* Implement a middleware signature that is based on best practices.
43+
44+
### 3.2 Non-Goals
45+
46+
* Attempting to define how middleware is dispatched.
47+
* Attempting to define interfaces for client/asynchronous middleware.
48+
* Attempting to define the mechanism by which HTTP responses are created.
49+
50+
4. Approaches
51+
-------------
52+
53+
There are currently two common approaches to server middleware that use HTTP Messages.
54+
55+
### 4.1 Double Pass
56+
57+
The signature used by most middleware implementations has been mostly the same
58+
and is based on [Express middleware][express], which is defined as:
59+
60+
```
61+
fn(request, response, next): response
62+
```
63+
64+
[express]: http://expressjs.com/en/guide/writing-middleware.html
65+
66+
Based on the middleware implementations already used by frameworks that have
67+
adopted this signature, the following commonalities are observed:
68+
69+
* The middleware is defined as a [callable][php-callable].
70+
* The middleware is passed 3 arguments during invocation:
71+
1. A `ServerRequestInterface` implementation.
72+
2. A `ResponseInterface` implementation.
73+
3. A `callable` that receives the request and response to delegate the next middleware.
74+
75+
[php-callable]: http://php.net/manual/language.types.callable.php
76+
77+
A significant number of projects provide and/or use exactly the same interface.
78+
This approach is often referred to as "double pass" in reference to both the
79+
request and response being passed to the middleware.
80+
81+
#### 4.1.1 Projects Using Double Pass
82+
83+
* [mindplay/middleman](https://github.com/mindplay-dk/middleman/blob/1.0.0/src/MiddlewareInterface.php#L24)
84+
* [relay/relay](https://github.com/relayphp/Relay.Relay/blob/1.0.0/src/MiddlewareInterface.php#L24)
85+
* [slim/slim](https://github.com/slimphp/Slim/blob/3.4.0/Slim/MiddlewareAwareTrait.php#L66-L75)
86+
* [zendframework/zend-stratigility](https://github.com/zendframework/zend-stratigility/blob/1.0.0/src/MiddlewarePipe.php#L69-L79)
87+
88+
#### 4.1.2 Middleware Implementing Double Pass
89+
90+
* [bitexpert/adroit](https://github.com/bitExpert/adroit)
91+
* [akrabat/rka-ip-address-middleware](https://github.com/akrabat/rka-ip-address-middleware)
92+
* [akrabat/rka-scheme-and-host-detection-middleware](https://github.com/akrabat/rka-scheme-and-host-detection-middleware)
93+
* [bear/middleware](https://github.com/bearsunday/BEAR.Middleware)
94+
* [hannesvdvreken/psr7-middlewares](https://github.com/hannesvdvreken/psr7-middlewares)
95+
* [los/api-problem](https://github.com/Lansoweb/api-problem)
96+
* [los/los-rate-limit](https://github.com/Lansoweb/LosRateLimit)
97+
* [monii/monii-action-handler-psr7-middleware](https://github.com/monii/monii-action-handler-psr7-middleware)
98+
* [monii/monii-nikic-fast-route-psr7-middleware](https://github.com/monii/monii-nikic-fast-route-psr7-middleware)
99+
* [monii/monii-response-assertion-psr7-middleware](https://github.com/monii/monii-response-assertion-psr7-middleware)
100+
* [mtymek/blast-base-url](https://github.com/mtymek/blast-base-url)
101+
* [ocramius/psr7-session](https://github.com/Ocramius/PSR7Session)
102+
* [oscarotero/psr7-middlewares](https://github.com/oscarotero/psr7-middlewares)
103+
* [php-middleware/block-robots](https://github.com/php-middleware/block-robots)
104+
* [php-middleware/http-authentication](https://github.com/php-middleware/http-authentication)
105+
* [php-middleware/log-http-messages](https://github.com/php-middleware/log-http-messages)
106+
* [php-middleware/maintenance](https://github.com/php-middleware/maintenance)
107+
* [php-middleware/phpdebugbar](https://github.com/php-middleware/phpdebugbar)
108+
* [php-middleware/request-id](https://github.com/php-middleware/request-id)
109+
* [relay/middleware](https://github.com/relayphp/Relay.Middleware)
110+
111+
The primary downside of this interface is that the while the interface itself is
112+
a callable, there is currently no way to type hint a closure in a similar way.
113+
This may be resolved in the future with [functional interfaces][php-functional].
114+
115+
[php-functional]: https://wiki.php.net/rfc/functional-interfaces
116+
117+
### 4.2 Single Pass (Lambda)
118+
119+
The other approach to middleware is much closer to [StackPHP][stackphp] style
120+
and is defined as:
121+
122+
```
123+
fn(request, frame): response
124+
```
125+
126+
Middleware taking this approach generally has the following commonalities:
127+
128+
* The middleware is defined with a specific interface with a method that takes
129+
the request for processing.
130+
* The middleware is passed 2 arguments during invocation:
131+
1. An object that represents an HTTP request.
132+
2. A delegate that receives the request to dispatch next middleware in the pipeline.
133+
134+
In this form, middleware has no access to a response until one is generated by
135+
innermost middleware. Middleware can then modify the response before returning
136+
back up the stack.
137+
138+
This approach is often referred to as "single pass" or "lambda" in reference to
139+
only the request being passed to the middleware.
140+
141+
[php-closure]: http://php.net/closure
142+
143+
#### 4.2.1 Projects Using Single Pass
144+
145+
There are fewer examples of this approach within projects using HTTP Messages,
146+
with one notable exception.
147+
148+
[Guzzle middleware][guzzle-middleware] is focused on outgoing (client) requests
149+
and uses this signature:
150+
151+
```
152+
function(RequestInterface $request, array $options): ResponseInterface
153+
```
154+
155+
#### 4.2.2 Additional Projects Using Single Pass
156+
157+
There are also significant projects that predate HTTP Messages using this approach.
158+
159+
[StackPHP][stackphp] is based on [Symfony HttpKernel][httpkernel] and supports
160+
middleware with this signature:
161+
162+
```
163+
handle(Request $request, $type, $catch): Response
164+
```
165+
166+
*__Note__: While Stack has multiple arguments, a response object is not included.*
167+
168+
[Laravel middleware][laravel-middleware] uses Symfony components and supports
169+
middleware with this signature:
170+
171+
```
172+
function handle(Request $request, callable $next): Response
173+
```
174+
175+
[guzzle-middleware]: http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html
176+
[httpkernel]: https://symfony.com/doc/2.0/components/http_kernel/introduction.html
177+
[laravel-middleware]: https://laravel.com/docs/master/middleware
178+
179+
### 4.3 Comparison of Approaches
180+
181+
The single pass approach to middleware has been well established in the PHP
182+
community for many years. This is most evident with the large number of packages
183+
that are based around StackPHP.
184+
185+
The double pass approach is much newer but has been almost universally used by
186+
early adopters of HTTP Messages.
187+
188+
### 4.4 Chosen Approach
189+
190+
Despite the nearly universal adoption of the double-pass approach there are
191+
significant issues regarding implementation.
192+
193+
The most severe is that passing an empty response has no guarantees that the
194+
response is in a usable state. This is further exacerbated by the fact that a
195+
middleware may modify the response before passing it for further dispatching.
196+
197+
Further compounding the problem is that there is no way to ensure that the
198+
response body has not been written to, which can lead to incomplete output or
199+
error responses being sent with cache headers attached. It is also possible
200+
to end up with [corrupted body content][rob-allen-filtering] when writing over
201+
existing body content if the new content is shorter than the original. The most
202+
effective way to resolve these issues is to always provide a fresh stream when
203+
modifying the body of a message.
204+
205+
[rob-allen-filtering]: https://akrabat.com/filtering-the-psr-7-body-in-middleware/
206+
207+
Some have argued that passing the response helps ensure dependency inversion.
208+
While it is true that it helps avoid depending on a specific implementation of
209+
HTTP messages, the problem can also be resolved by injecting factories into the
210+
middleware to create HTTP message objects, or by injecting empty message instances.
211+
With the creation of HTTP Factories in [PSR-17][psr17], a standard approach to
212+
handling dependency inversion is possible.
213+
214+
[psr17]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-factory/http-factory-meta.md
215+
216+
A more subjective, but also important, concern is that existing double-pass
217+
middleware typically uses the `callable` type hint to refer to middleware.
218+
This makes strict typing impossible, as there is no assurance that the `callable`
219+
being passed implements a middleware signature, which reduces runtime safety.
220+
221+
**Due to these significant issues the lambda approach has been choosen for this proposal.**
222+
223+
5. Design Decisions
224+
-------------------
225+
226+
### 5.1 Middleware Design
227+
228+
The `ServerMiddlewareInterface` defines a single method that accepts a server
229+
request and a delegate and must return a response. The middleware may:
230+
231+
- Evolve the request before passing it to the delegate to execute the next
232+
available middleware.
233+
- Evolve the response received from the delegate before returning it.
234+
- Create and return a response without passing it to the delegate, thereby
235+
preventing any further middleware from executing.
236+
237+
#### Why doesn't middleware use `__invoke`?
238+
239+
Doing so would conflict with existing middleware that implements the double-pass
240+
approach and may want to implement the middleware interface.
241+
242+
In addition, classes that define `__invoke` can be type hinted as `callable`,
243+
which results in less strict typing. This is generally undesirable, especially
244+
when the `__invoke` method uses strict typing.
245+
246+
#### Why is a server request required?
247+
248+
To make it clear that the middleware can only be used in a synchronous, server
249+
side context.
250+
251+
While not all middleware will need to use the additional methods defined by the
252+
server request interface, external requests are typically handled asynchronously
253+
and would need to return a [promise][promises] of a response. (This is primarily
254+
due to the fact that multiple requests can be made in parallel and processed as
255+
they are returned.) It is outside the scope of this proposal to address the needs
256+
of asynchronous request/response life cycle.
257+
258+
Attempting to define client middleware would be premature at this point. Any future
259+
proposal that is focused on client side request processing should have the opportunity
260+
to define a standard that is specific to the nature of asynchronous middleware.
261+
262+
_See "client vs server side middleware" in [relevant links](#relevant-links) for
263+
additional information._
264+
265+
[promises]: https://promisesaplus.com/
266+
267+
### 5.2 Delegate Design
268+
269+
The `DelegateInterface` defines a single method that accepts a request and
270+
returns a response. The delegate interface must be implemented by any middleware
271+
dispatcher that uses middleware implementing `ServerMiddlewareInterface`.
272+
273+
#### Why isn't the delegate a `callable`?
274+
275+
Using an interface type hint improves runtime safety and IDE support.
276+
277+
In addition, it allows compatibility with existing middleware that already defines
278+
an `__invoke` method.
279+
280+
_See "discussion of FrameInterface" in [relevant links](#relevant-links) for
281+
additional information._
282+
283+
#### Why does the delegate conflict with middleware?
284+
285+
Both the middleware and delegate interface define a `process` method to prevent
286+
misuse of middleware as delegates.
287+
288+
If a middleware was used as a delegate the entire middleware stack would end up
289+
recursive, instead of piped. The implementation of delegate should be defined
290+
within middleware dispatching systems.
291+
292+
6. People
293+
---------
294+
295+
### 6.1 Editor(s)
296+
297+
* Woody Gilk, <woody.gilk@gmail.com>
298+
299+
### 6.2 Sponsors
300+
301+
* Paul M Jones, <pmjones88@gmail.com> (Coordinator)
302+
* Jason Coward, <jason@opengeek.com> (Sponsor)
303+
304+
### 6.3 Contributors
305+
306+
* Rasmus Schultz, <rasmus@mindplay.dk>
307+
* Matthew Weier O'Phinney, <mweierophinney@gmail.com>
308+
309+
7. Votes
310+
--------
311+
312+
* [Entrance Vote](https://groups.google.com/d/msg/php-fig/v9AijALWJhI/04XCwqgIEAAJ)
313+
* **Acceptance Vote:** _(not yet taken)_
314+
315+
8. Relevant Links
316+
-----------------
317+
318+
_**Note:** Order descending chronologically._
319+
320+
* [PHP-FIG mailing list thread](https://groups.google.com/d/msg/php-fig/vTtGxdIuBX8/NXKieN9vDQAJ)
321+
* [The PHP League middleware proposal](https://groups.google.com/d/msg/thephpleague/jyztj-Nz_rw/I4lHVFigAAAJ)
322+
* [PHP-FIG discussion of FrameInterface](https://groups.google.com/d/msg/php-fig/V12AAcT_SxE/aRXmNnIVCwAJ)
323+
* [PHP-FIG discussion about client vs server side middleware](https://groups.google.com/d/msg/php-fig/vBk0BRgDe2s/GTaT0yKNBgAJ)
324+
325+
9. Errata
326+
---------
327+
328+
...

0 commit comments

Comments
 (0)