Skip to content

Commit 5e6349c

Browse files
authored
Merge pull request #25 from http-interop/fix/import-meta-as-readme
Update README
2 parents 4ea26fa + d33ce2d commit 5e6349c

File tree

1 file changed

+140
-47
lines changed

1 file changed

+140
-47
lines changed

README.md

Lines changed: 140 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,56 @@
1-
HTTP Middleware
2-
===============
1+
HTTP Server Middleware
2+
======================
33

44
1. Summary
55
----------
66

77
The 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

1113
2. 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
1925
provides 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

2935
3. 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

4350
4. 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:
5461
fn(request, response, next): response
5562
```
5663

64+
[express]: http://expressjs.com/en/guide/writing-middleware.html
65+
5766
Based on the middleware implementations already used by frameworks that have
5867
adopted this signature, the following commonalities are observed:
5968

@@ -110,19 +119,17 @@ This may be resolved in the future with [functional interfaces][php-functional].
110119
The other approach to middleware is much closer to [StackPHP][stackphp] style
111120
and is defined as:
112121

113-
114122
```
115-
fn(request, next): response
123+
fn(request, frame): response
116124
```
117125

118126
Middleware 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

127134
In this form, middleware has no access to a response until one is generated by
128135
innermost 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

138145
There 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
```
146152
function(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

167181
The single pass approach to middleware has been well established in the PHP
168182
community for many years. This is most evident with the large number of packages
169183
that 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
172186
early 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

193283
6. 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

209300
7. 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

215306
8. 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

223316
9. Errata
224317
---------

0 commit comments

Comments
 (0)