|
1 | 1 | HTTP Server Middleware
|
2 | 2 | ======================
|
3 | 3 |
|
4 |
| -1. Summary |
5 |
| ----------- |
| 4 | +This is an implementation of the proposed [PSR-15][psr-15]. Please refer to the |
| 5 | +proposal for a description. |
6 | 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 |
| - |
114 |
| -### 4.2 Single Pass (Lambda) |
115 |
| - |
116 |
| -The other approach to middleware is much closer to [StackPHP][stackphp] style |
117 |
| -and is defined as: |
118 |
| - |
119 |
| -``` |
120 |
| -fn(request, frame): response |
121 |
| -``` |
122 |
| - |
123 |
| -Middleware taking this approach generally has the following commonalities: |
124 |
| - |
125 |
| -* The middleware is defined with a specific interface with a method that takes |
126 |
| - the request for processing. |
127 |
| -* The middleware is passed 2 arguments during invocation: |
128 |
| - 1. An object that represents an HTTP request. |
129 |
| - 2. A delegate that receives the request to dispatch next middleware in the pipeline. |
130 |
| - |
131 |
| -In this form, middleware has no access to a response until one is generated by |
132 |
| -innermost middleware. Middleware can then modify the response before returning |
133 |
| -back up the stack. |
134 |
| - |
135 |
| -This approach is often referred to as "single pass" or "lambda" in reference to |
136 |
| -only the request being passed to the middleware. |
137 |
| - |
138 |
| -[php-closure]: http://php.net/closure |
139 |
| - |
140 |
| -#### 4.2.1 Projects Using Single Pass |
141 |
| - |
142 |
| -There are fewer examples of this approach within projects using HTTP Messages, |
143 |
| -with one notable exception. |
144 |
| - |
145 |
| -[Guzzle middleware][guzzle-middleware] is focused on outgoing (client) requests |
146 |
| -and uses this signature: |
147 |
| - |
148 |
| -``` |
149 |
| -function(RequestInterface $request, array $options): ResponseInterface |
150 |
| -``` |
151 |
| - |
152 |
| -#### 4.2.2 Additional Projects Using Single Pass |
153 |
| - |
154 |
| -There are also significant projects that predate HTTP Messages using this approach. |
155 |
| - |
156 |
| -[StackPHP][stackphp] is based on [Symfony HttpKernel][httpkernel] and supports |
157 |
| -middleware with this signature: |
158 |
| - |
159 |
| -``` |
160 |
| -handle(Request $request, $type, $catch): Response |
161 |
| -``` |
162 |
| - |
163 |
| -*__Note__: While Stack has multiple arguments, a response object is not included.* |
164 |
| - |
165 |
| -[Laravel middleware][laravel-middleware] uses Symfony components and supports |
166 |
| -middleware with this signature: |
167 |
| - |
168 |
| -``` |
169 |
| -function handle(Request $request, callable $next): Response |
170 |
| -``` |
171 |
| - |
172 |
| -[guzzle-middleware]: http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html |
173 |
| -[httpkernel]: https://symfony.com/doc/2.0/components/http_kernel/introduction.html |
174 |
| -[laravel-middleware]: https://laravel.com/docs/master/middleware |
175 |
| - |
176 |
| -### 4.3 Comparison of Approaches |
177 |
| - |
178 |
| -The single pass approach to middleware has been well established in the PHP |
179 |
| -community for many years. This is most evident with the large number of packages |
180 |
| -that are based around StackPHP. |
181 |
| - |
182 |
| -The double pass approach is much newer but has been almost universally used by |
183 |
| -early adopters of HTTP Messages. |
184 |
| - |
185 |
| -### 4.4 Chosen Approach |
186 |
| - |
187 |
| -Despite the nearly universal adoption of the double-pass approach there are |
188 |
| -significant issues regarding implementation. |
189 |
| - |
190 |
| -The most severe is that passing an empty response has no guarantees that the |
191 |
| -response is in a usable state. This is further exacerbated by the fact that a |
192 |
| -middleware may modify the response before passing it for further dispatching. |
193 |
| - |
194 |
| -Further compounding the problem is that there is no way to ensure that the |
195 |
| -response body has not been written to, which can lead to incomplete output or |
196 |
| -error responses being sent with cache headers attached. It is also possible |
197 |
| -to end up with [corrupted body content][rob-allen-filtering] when writing over |
198 |
| -existing body content if the new content is shorter than the original. The most |
199 |
| -effective way to resolve these issues is to always provide a fresh stream when |
200 |
| -modifying the body of a message. |
201 |
| - |
202 |
| -[rob-allen-filtering]: https://akrabat.com/filtering-the-psr-7-body-in-middleware/ |
203 |
| - |
204 |
| -Some have argued that passing the response helps ensure dependency inversion. |
205 |
| -While it is true that it helps avoid depending on a specific implementation of |
206 |
| -HTTP messages, the problem can also be resolved by injecting factories into the |
207 |
| -middleware to create HTTP message objects, or by injecting empty message instances. |
208 |
| -With the creation of HTTP Factories in [PSR-17][psr17], a standard approach to |
209 |
| -handling dependency inversion is possible. |
210 |
| - |
211 |
| -[psr17]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-factory/http-factory-meta.md |
212 |
| - |
213 |
| -A more subjective, but also important, concern is that existing double-pass |
214 |
| -middleware typically uses the `callable` type hint to refer to middleware. |
215 |
| -This makes strict typing impossible, as there is no assurance that the `callable` |
216 |
| -being passed implements a middleware signature, which reduces runtime safety. |
217 |
| - |
218 |
| -**Due to these significant issues the lambda approach has been choosen for this proposal.** |
219 |
| - |
220 |
| -5. Design Decisions |
221 |
| -------------------- |
222 |
| - |
223 |
| -### 5.1 Middleware Design |
224 |
| - |
225 |
| -The `MiddlewareInterface` defines a single method that accepts a server |
226 |
| -request and a delegate and must return a response. The middleware may: |
227 |
| - |
228 |
| -- Evolve the request before passing it to the delegate to execute the next |
229 |
| - available middleware. |
230 |
| -- Evolve the response received from the delegate before returning it. |
231 |
| -- Create and return a response without passing it to the delegate, thereby |
232 |
| - preventing any further middleware from executing. |
233 |
| - |
234 |
| -#### Why doesn't middleware use `__invoke`? |
235 |
| - |
236 |
| -Doing so would conflict with existing middleware that implements the double-pass |
237 |
| -approach and may want to implement the middleware interface. |
238 |
| - |
239 |
| -In addition, classes that define `__invoke` can be type hinted as `callable`, |
240 |
| -which results in less strict typing. This is generally undesirable, especially |
241 |
| -when the `__invoke` method uses strict typing. |
242 |
| - |
243 |
| -#### Why is a server request required? |
244 |
| - |
245 |
| -To make it clear that the middleware can only be used in a synchronous, server |
246 |
| -side context. |
247 |
| - |
248 |
| -While not all middleware will need to use the additional methods defined by the |
249 |
| -server request interface, external requests are typically handled asynchronously |
250 |
| -and would need to return a [promise][promises] of a response. (This is primarily |
251 |
| -due to the fact that multiple requests can be made in parallel and processed as |
252 |
| -they are returned.) It is outside the scope of this proposal to address the needs |
253 |
| -of asynchronous request/response life cycle. |
254 |
| - |
255 |
| -Attempting to define client middleware would be premature at this point. Any future |
256 |
| -proposal that is focused on client side request processing should have the opportunity |
257 |
| -to define a standard that is specific to the nature of asynchronous middleware. |
258 |
| - |
259 |
| -_See "client vs server side middleware" in [relevant links](#8-relevant-links) for |
260 |
| -additional information._ |
261 |
| - |
262 |
| -[promises]: https://promisesaplus.com/ |
263 |
| - |
264 |
| -### 5.2 Delegate Design |
265 |
| - |
266 |
| -The `DelegateInterface` defines a single method that accepts a request and |
267 |
| -returns a response. The delegate interface must be implemented by any middleware |
268 |
| -dispatcher that uses middleware implementing `MiddlewareInterface`. |
269 |
| - |
270 |
| -#### Why isn't the delegate a `callable`? |
271 |
| - |
272 |
| -Using an interface type hint improves runtime safety and IDE support. |
273 |
| - |
274 |
| -_See "discussion of FrameInterface" in [relevant links](#8-relevant-links) for |
275 |
| -additional information._ |
276 |
| - |
277 |
| -#### Why does the delegate conflict with middleware? |
278 |
| - |
279 |
| -Both the middleware and delegate interface define a `process` method to prevent |
280 |
| -misuse of middleware as delegates. |
281 |
| - |
282 |
| -The implementation of delegate should be defined within middleware dispatching systems. |
283 |
| - |
284 |
| -6. People |
285 |
| ---------- |
286 |
| - |
287 |
| -### 6.1 Editor(s) |
288 |
| - |
289 |
| - |
290 |
| - |
291 |
| -### 6.2 Sponsors |
292 |
| - |
293 |
| -* Paul M Jones, <[email protected]> (Coordinator) |
294 |
| -* Jason Coward, <[email protected]> (Sponsor) |
295 |
| - |
296 |
| -### 6.3 Contributors |
297 |
| - |
298 |
| -* Rasmus Schultz, <[email protected]> |
299 |
| -* Matthew Weier O'Phinney, <[email protected]> |
300 |
| - |
301 |
| -7. Votes |
302 |
| --------- |
303 |
| - |
304 |
| -* [Entrance Vote](https://groups.google.com/d/msg/php-fig/v9AijALWJhI/04XCwqgIEAAJ) |
305 |
| -* **Acceptance Vote:** _(not yet taken)_ |
306 |
| - |
307 |
| -8. Relevant Links |
308 |
| ------------------ |
309 |
| - |
310 |
| -_**Note:** Order descending chronologically._ |
311 |
| - |
312 |
| -* [PHP-FIG mailing list thread](https://groups.google.com/d/msg/php-fig/vTtGxdIuBX8/NXKieN9vDQAJ) |
313 |
| -* [The PHP League middleware proposal](https://groups.google.com/d/msg/thephpleague/jyztj-Nz_rw/I4lHVFigAAAJ) |
314 |
| -* [PHP-FIG discussion of FrameInterface](https://groups.google.com/d/msg/php-fig/V12AAcT_SxE/aRXmNnIVCwAJ) |
315 |
| -* [PHP-FIG discussion about client vs server side middleware](https://groups.google.com/d/msg/php-fig/vBk0BRgDe2s/GTaT0yKNBgAJ) |
316 |
| - |
317 |
| -9. Errata |
318 |
| ---------- |
319 |
| - |
320 |
| -... |
| 7 | +[psr-15]: https://github.com/php-fig/fig-standards/tree/master/proposed/http-middleware |
0 commit comments