The purpose of this PSR is to define formal interfaces for HTTP server request handlers ("request handlers") and HTTP server request middleware ("middleware") that are compatible with HTTP messages as defined in PSR-7 or subsequent replacement PSRs.
Note: All references to "request handlers" and "middleware" are specific to server request processing.
The HTTP messages specification does not contain any reference to request handlers or middleware.
Request handlers are a fundamental part of any web application. The handler is the component that receives a request and produces a response. Nearly all code that works with HTTP messages will have some kind of request handler.
Middleware has existed for many years in the PHP ecosystem. The general concept of reusable middleware was popularized by StackPHP. Since the release of HTTP messages as a PSR, many frameworks have adopted middleware that use HTTP message interfaces.
Agreeing on formal request handler and middleware interfaces eliminates several problems and has a number of benefits:
There are many approaches to request handlers that use HTTP messages. However, the general process is the same in all of them:
Given an HTTP request, produce an HTTP response for that request.
The internal requirements of that process will vary from framework to framework and application to application. This proposal makes no effort to determine what that process should be.
There are currently two common approaches to middleware that use HTTP messages.
The signature used by most middleware implementations has been mostly the same and is based on Express middleware, which is defined as:
fn(request, response, next): response
Based on the middleware implementations already used by frameworks that have adopted this signature, the following commonalities are observed:
ServerRequestInterface
implementation.ResponseInterface
implementation.callable
that receives the request and response to delegate to the next middleware.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.
The primary downside of this interface is that the while the interface itself is a callable, there is currently no way to strictly type a closure.
The other approach to middleware is much closer to StackPHP style and is defined as:
fn(request, next): response
Middleware taking this approach generally has the following commonalities:
In this form, middleware has no access to a response until one is generated by the request handler. Middleware can then modify the response before returning it.
This approach is often referred to as "single pass" or "lambda" in reference to only the request being passed to the middleware.
There are fewer examples of this approach within projects using HTTP messages, with one notable exception.
Guzzle middleware is focused on outgoing (client) requests and uses this signature:
function (RequestInterface $request, array $options): ResponseInterface
There are also significant projects that predate HTTP messages using this approach.
StackPHP is based on Symfony HttpKernel and supports middleware with this signature:
function handle(Request $request, $type, $catch): Response
Note: While Stack has multiple arguments, a response object is not included.
Laravel middleware uses Symfony components and supports middleware with this signature:
function handle(Request $request, callable $next): Response
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 (PSR-7).
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 processing.
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 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.
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, a standard approach to handling dependency inversion is possible.
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 chosen for this proposal.
The RequestHandlerInterface
defines a single method that accepts a request and
MUST return a response. The request handler MAY delegate to another handler.
To make it clear that the request handler can only be used in a server side context. In an client side context, a promise would likely be returned instead of a response.
The term "handler" means something designated to manage or control. In terms of request processing, a request handler is the point where the request must be acted upon to create a response.
As opposed to the term "delegate", which was used in a previous version of this specification, the internal behavior of this interface is not specified. As long as the request handler ultimately produces a response, it is valid.
__invoke
?Using __invoke
is less transparent than using a named method. It also makes
it easier to call the request handler when it is assigned to a class variable,
without using call_user_func
or other less common syntax.
See PHP-FIG discussion of FrameInterface for additional information.
The MiddlewareInterface
defines a single method that accepts an HTTP request
and a request handler and must return a response. The middleware may:
When delegating from one middleware to another in a sequence, one approach for dispatching systems is to use an intermediary request handler composing the middleware sequence as a way to link middleware together. The final or innermost middleware will act as a gateway to application code and generate a response from its results; alternately, the middleware MAY delegate this responsibility to a dedicated request handler.
__invoke
?Doing so would conflict with existing middleware that implements the double-pass approach and may want to implement the middleware interface for purposes of forward compatibility with this specification.
process()
?We reviewed a number of existing monolithic and middleware frameworks to determine what method(s) each defined for processing incoming requests. We found the following were commonly used:
__invoke
(within middleware systems, such as Slim, Expressive, Relay, etc.)handle
(in particular, software derived from Symfony's HttpKernel)dispatch
(Zend Framework's DispatchableInterface)We chose to allow a forward-compatible approach for such classes to repurpose
themselves as middleware (or middleware compatible with this specification),
and thus needed to choose a name not in common usage. As such, we chose
process
, to indicate processing a request.
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, outbound requests are typically processed asynchronously and would typically return a promise 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 cycles.
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 PHP-FIG discussion about client vs server side middleware for additional information.
Middleware has the following roles:
Producing a response on its own. If specific request conditions are met, the middleware can produce and return a response.
Returning the result of the request handler. In cases where the middleware cannot produce its own response, it can delegate to the request handler to produce one; sometimes this may involve providing a transformed request (e.g., to inject a request attribute, or the results of parsing the request body).
Manipulating and returning the response produced by the request handler. In some cases, the middleware may be interested in manipulating the response the request handler returns (e.g., to gzip the response body, to add CORS headers, etc.). In such cases, the middleware will capture the response returned by the request handler, and return a transformed response on completion.
In these latter two cases, the middleware may have code such as the following:
// Straight delegation:
return $handler->handle($request);
// Capturing the response to manipulate:
$response = $handler->handle($request);
How the handler acts is entirely up to the developer, so long as it produces a response.
In one common scenario, the handler implements a queue or a stack of
middleware instances internally. In such cases, calling
$handler->handle($request)
will advance the internal pointer, pull the
middleware associated with that pointer, and call it using
$middleware->process($request, $this)
. If no more middleware exists, it will
generally either raise an exception or return a canned response.
Another possibility is for routing middleware that matches the incoming server request to a specific handler, and then returns the response generated by executing that handler. If unable to route to a handler, it would instead execute the handler provided to the middleware. (This sort of mechanism can even be used in conjunction with middleware queues and stacks.)
The two interfaces, RequestHandlerInterface
and MiddlewareInterface
, were
designed to work in conjunction with one another. Middleware gains flexibility
when de-coupled from any over-arching application layer, and instead only
relying on the provided request handler to produce a response.
Two approaches to middleware dispatch systems that the Working Group observed and/or implemented are demonstrated below. Additionally, examples of re-usable middleware are provided to demonstrate how to write middleware that is loosely-coupled.
Please note that these are not suggested as definitive or exclusive approaches to defining middleware dispatch systems.
In this approach, a request handler maintains a queue of middleware, and a fallback response to return if the queue is exhausted without returning a response. When executing the first middleware, the queue passes itself as a request handler to the middleware.
class QueueRequestHandler implements RequestHandlerInterface
{
private $middleware = [];
private $fallbackHandler;
public function __construct(RequestHandlerInterface $fallbackHandler)
{
$this->fallbackHandler = $fallbackHandler;
}
public function add(MiddlewareInterface $middleware)
{
$this->middleware[] = $middleware;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
// Last middleware in the queue has called on the request handler.
if (0 === count($this->middleware)) {
return $this->fallbackHandler->handle($request);
}
$middleware = array_shift($this->middleware);
return $middleware->process($request, $this);
}
}
An application bootstrap might then look like this:
// Fallback handler:
$fallbackHandler = new NotFoundHandler();
// Create request handler instance:
$app = new QueueRequestHandler($fallbackHandler);
// Add one or more middleware:
$app->add(new AuthorizationMiddleware());
$app->add(new RoutingMiddleware());
// execute it:
$response = $app->handle(ServerRequestFactory::fromGlobals());
This system has two request handlers: one that will produce a response if the
last middleware delegates to the request handler, and one for dispatching the
middleware layers. (In this example, the RoutingMiddleware
will likely execute
composed handlers on a successful route match; see more on that below.)
This approach has the following benefits:
QueueRequestHandler
is agnostic of the PSR-7 implementation in use.In this approach, a request handler implementation decorates both a middleware instance and a fallback request handler to pass to it. The application is built from the outside-in, passing each request handler "layer" to the next outer one.
class DecoratingRequestHandler implements RequestHandlerInterface
{
private $middleware;
private $nextHandler;
public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $nextHandler)
{
$this->middleware = $middleware;
$this->nextHandler = $nextHandler;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->middleware->process($request, $this->nextHandler);
}
}
// Create a response prototype to return if no middleware can produce a response
// on its own. This could be a 404, 500, or default page.
$responsePrototype = (new Response())->withStatus(404);
$innerHandler = new class ($responsePrototype) implements RequestHandlerInterface {
private $responsePrototype;
public function __construct(ResponseInterface $responsePrototype)
{
$this->responsePrototype = $responsePrototype;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->responsePrototype;
}
};
$layer1 = new DecoratingRequestHandler(new RoutingMiddleware(), $innerHandler);
$layer2 = new DecoratingRequestHandler(new AuthorizationMiddleware(), $layer1);
$response = $layer2->handle(ServerRequestFactory::fromGlobals());
Similar to the queue-based middleware, request handlers serve two purposes in this system:
In the examples above, we have two middleware composed in each. In order for these to work in either situation, we need to write them such that they interact appropriately.
Implementors of middleware striving for maximum interoperability may want to consider the following guidelines:
Test the request for a required condition. If it does not satisfy that condition, use a composed prototype response or a composed response factory to generate and return a response.
If pre-conditions are met, delegate creation of the response to the provided
request handler, optionally providing a "new" request by manipulating the
provided request (e.g., $handler->handle($request->withAttribute('foo', 'bar')
).
Either pass the response returned by the request handler unaltered, or provide
a new response by manipulating the one returned (e.g., return $response->withHeader('X-Foo-Bar', 'baz')
).
The AuthorizationMiddleware
is one that will exercise all three of these guidelines:
class AuthorizationMiddleware implements MiddlewareInterface
{
private $authorizationMap;
public function __construct(AuthorizationMap $authorizationMap)
{
$this->authorizationMap = $authorizationMap;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (! $this->authorizationMap->needsAuthorization($request)) {
return $handler->handle($request);
}
if (! $this->authorizationMap->isAuthorized($request)) {
return $this->authorizationMap->prepareUnauthorizedResponse();
}
$response = $handler->handle($request);
return $this->authorizationMap->signResponse($response, $request);
}
}
Note that the middleware is not concerned with how the request handler is implemented; it merely uses it to produce a response when pre-conditions have been met.
The RoutingMiddleware
implementation described below follows a similar
process: it analyzes the request to see if it matches known routes. In this
particular implementation, routes map to request handlers, and the middleware
essentially delegates to them in order to produce a response. However, in the
case that no route is matched, it will execute the handler passed to it to
produce the response to return.
class RoutingMiddleware implements MiddlewareInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$result = $this->router->match($request);
if ($result->isSuccess()) {
return $result->getHandler()->handle($request);
}
return $handler->handle($request);
}
}
This PSR was produced by a FIG Working Group with the following members:
The working group would also like to acknowledge the contributions of:
Note: Order descending chronologically.
...