1- HTTP Middleware
2- ===============
1+ HTTP Server Middleware
2+ ======================
33
441 . Summary
55----------
66
77The purpose of this PSR is to provide an interface that defines the formal
8- method signature for HTTP Middleware that is compatible with HTTP Messages,
9- as defined in PSR-7.
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/
1012
11132 . Why Bother?
1214--------------
1315
14- The general concept of reusable middleware was popularized by [ StackPHP] [ stackphp ] .
15- Since the release of the HTTP Messages standard, a number of frameworks have
16- adopted middleware that uses HTTP Message interfaces.
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.
1723
18- Agreeing on a formal middleware interface eliminates several problems and
24+ Agreeing on a formal server middleware interface eliminates several problems and
1925provides a number of benefits:
2026
2127* Provides a formal standard for middleware developers to commit to.
2228* Eliminates duplication of similar interfaces defined by various frameworks.
2329* Avoids minor discrepancies in method signatures.
2430* Enables any middleware component to run in any compatible framework.
2531
32+ [ pipeline ] : https://en.wikipedia.org/wiki/Pipeline_(computing)
2633[ stackphp ] : http://stackphp.com/
27- [ express ] : http://expressjs.com/en/guide/writing-middleware.html
2834
29353 . Scope
3036--------
3137
32- ## 3.1 Goals
38+ ### 3.1 Goals
3339
3440* Create a middleware interface that uses HTTP Messages.
35- * Provide a suggested interface for middleware stack containers.
36- * Ensure that middleware will not be tied to a specific implementation of HTTP Messages.
41+ * Ensure that middleware will not be coupled to a specific implementation of HTTP Messages.
3742* Implement a middleware signature that is based on best practices.
3843
39- ## 3.2 Non-Goals
44+ ### 3.2 Non-Goals
4045
46+ * Attempting to define how middleware is dispatched.
47+ * Attempting to define interfaces for client/asynchronous middleware.
4148* Attempting to define the mechanism by which HTTP responses are created.
4249
43504 . Approaches
4451-------------
4552
46- There are currently two common approaches to middleware that use HTTP Messages.
53+ There are currently two common approaches to server middleware that use HTTP Messages.
4754
4855### 4.1 Double Pass
4956
@@ -54,6 +61,8 @@ and is based on [Express middleware][express], which is defined as:
5461fn(request, response, next): response
5562```
5663
64+ [ express ] : http://expressjs.com/en/guide/writing-middleware.html
65+
5766Based on the middleware implementations already used by frameworks that have
5867adopted this signature, the following commonalities are observed:
5968
@@ -110,19 +119,17 @@ This may be resolved in the future with [functional interfaces][php-functional].
110119The other approach to middleware is much closer to [ StackPHP] [ stackphp ] style
111120and is defined as:
112121
113-
114122```
115- fn(request, next ): response
123+ fn(request, frame ): response
116124```
117125
118126Middleware taking this approach generally has the following commonalities:
119127
120- * The middleware is defined as a [ callable ] [ php-callable ] using a [ closure ] [ php-closure ]
121- or a class with an ` __invoke ` method .
128+ * The middleware is defined with a specific interface with a method that takes
129+ the request for processing .
122130* The middleware is passed 2 arguments during invocation:
123- 1 . A ` RequestInterface ` implementation.
124- 3 . A ` callable ` that receives the request to dispatch next middleware.
125-
131+ 1 . An object that represents an HTTP request.
132+ 2 . A delegate that receives the request to dispatch next middleware in the pipeline.
126133
127134In this form, middleware has no access to a response until one is generated by
128135innermost middleware. Middleware can then modify the response before returning
@@ -136,59 +143,142 @@ only the request being passed to the middleware.
136143#### 4.2.1 Projects Using Single Pass
137144
138145There are fewer examples of this approach within projects using HTTP Messages,
139- with a couple of notable exceptions .
146+ with one notable exception .
140147
141- [ Guzzle middleware] ( http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html )
142- uses a rather unique approach to middleware that is based around generators and
143- promises. Ignoring the generator portion, Guzzle middleware has the signature:
148+ [ Guzzle middleware] [ guzzle-middleware ] is focused on outgoing (client) requests
149+ and uses this signature:
144150
145151```
146152function(RequestInterface $request, array $options): ResponseInterface
147153```
148154
149- [ Laravel middleware] ( https://laravel.com/docs/master/middleware ) does make use
150- of HTTP Messages but supports middleware with the signature:
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:
151161
152162```
153- function handle(Request $request, callable $next ): Response
163+ handle(Request $request, $type, $catch ): Response
154164```
155165
156- [ StackPHP] [ stackphp ] is based specifically around [ Symfony HttpKernel] [ httpkernel ]
157- and not HTTP Messages but does use a single pass approach:
166+ * __ Note__ : While Stack has multiple arguments, a response object is not included.*
158167
159- ``` php
160- handle(Request $request, $type, $catch): Response
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
161173```
162174
175+ [ guzzle-middleware ] : http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html
163176[ httpkernel ] : https://symfony.com/doc/2.0/components/http_kernel/introduction.html
177+ [ laravel-middleware ] : https://laravel.com/docs/master/middleware
164178
165179### 4.3 Comparison of Approaches
166180
167181The single pass approach to middleware has been well established in the PHP
168182community for many years. This is most evident with the large number of packages
169183that are based around StackPHP.
170184
171- The double pass approach is much newer but has already been widely adopted by
185+ The double pass approach is much newer but has been almost universally used by
172186early adopters of HTTP Messages.
173187
174- 5 . ` DelegateInterface `
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
175224-------------------
176225
177- The ` $next ` argument is a callable in most existing middleware systems. However using
178- an interface allows to improve type safety and IDE support.
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 ` ?
179274
180- Using ` __invoke() ` as the method name was considered but was not kept. That solution
181- would have had the benefit of keeping the existing practice of invoking ` $next `
182- directly (` $response = $next($request) ` ). However many existing middleware systems
183- already use a class that implements ` __invoke() ` for the ` $next ` argument with
184- a different signature: ` $request ` * and* ` $response ` . By selecting a different method,
185- PSR-15 may be implemented within existing systems in parallel with existing features,
186- providing a migration path for these libraries and their consumers. In some cases,
187- existing ` __invoke() ` implementations could even proxy to the method defined by
188- PSR-15, or vice versa.
275+ Using an interface type hint improves runtime safety and IDE support.
189276
190- You can read [ the relevant discussion in the mailing list] ( https://groups.google.com/d/topic/php-fig/V12AAcT_SxE/discussion ) .
277+ In addition, it allows compatibility with existing middleware that already defines
278+ an ` __invoke ` method.
191279
280+ _ See "discussion of FrameInterface" in [ relevant links] ( #relevant-links ) for
281+ additional information._
192282
1932836 . People
194284---------
@@ -205,11 +295,12 @@ You can read [the relevant discussion in the mailing list](https://groups.google
205295### 6.3 Contributors
206296
207297* Rasmus Schultz, < rasmus@mindplay.dk >
298+ * Matthew Weier O'Phinney, < mweierophinney@gmail.com >
208299
2093007 . Votes
210301--------
211302
212- * [ Entrance Vote] ( https://groups.google.com/forum/#!topic /php-fig/v9AijALWJhI )
303+ * [ Entrance Vote] ( https://groups.google.com/d/msg /php-fig/v9AijALWJhI/04XCwqgIEAAJ )
213304* ** Acceptance Vote:** _ (not yet taken)_
214305
2153068 . Relevant Links
@@ -219,6 +310,8 @@ _**Note:** Order descending chronologically._
219310
220311* [ PHP-FIG mailing list thread] ( https://groups.google.com/d/msg/php-fig/vTtGxdIuBX8/NXKieN9vDQAJ )
221312* [ The PHP League middleware proposal] ( https://groups.google.com/d/msg/thephpleague/jyztj-Nz_rw/I4lHVFigAAAJ )
313+ * [ PHP-FIG discussion of FrameInterface] ( https://groups.google.com/d/msg/php-fig/V12AAcT_SxE/aRXmNnIVCwAJ )
314+ * [ PHP-FIG discussion about client vs server side middleware] ( https://groups.google.com/d/msg/php-fig/vBk0BRgDe2s/GTaT0yKNBgAJ )
222315
2233169 . Errata
224317---------
0 commit comments