William’s words sum up the concept of middleware. The middleware is a structure to work directly on the HTTP protocol, having as input the HTTP request received by the server and as output the generated HTTP response. This response may or may not be generated by middleware. In the case of authentication, for example, middleware responsible will verify the credentials present in the request; if null or invalid, the itself middleware will generate the access response denied; but otherwise, it allows the request to propagate by the application.
Too Long, Read Anyway
The answer will be long, but worth reading (worth to me at least).
Middleware and the HTTP protocol
The middleware works directly on the HTTP protocol - in fact, any WEB application that uses such a protocol will work directly on it, but is not always as frank as cosstuma is with the middleware. In PHP, this is usually very clear because the middleware work on the recommendation set out in PSR-7: HTTP Message Interfaces. This recommendation defines an interface for HTTP requests:
Psr\Http\Message\RequestInterface
Or, precisely to represent the request received by the server, the interface (this interface extends to the previous one):
Psr\Http\Message\ServerRequestInterface
To work with HTTP responses, the interface has been defined:
Psr\Http\Message\ResponseInterface
These three can be considered the trivial ones of the PSR-7. The implementations of these interfaces can be found in the repository PSR official and installed in the project through Composer:
composer install psr/http-message
In particular, the framework Slim uses these interfaces. To confirm, just check the implementations of the classes Request
and Response
:
Request.php:
use Psr\Http\Message\ServerRequestInterface;
class Request extends Message implements ServerRequestInterface {}
Response.php:
use Psr\Http\Message\ResponseInterface;
class Response extends Message implements ResponseInterface {}
So from this moment on, when referring to an object Request
is understood by an object that implements the interface Psr\Http\Message\RequestInterface
(or equivalent), whether Slim or not, and when referring to an object Response
is understood by an object that implements the interface Psr\Http\Message\ResponseInterface
(or equivalent), whether Slim or not.
Implementation of a middleware
This way, then, it becomes clearer which are the inputs and outputs of a middleware: an object Request
incoming ($request
) and an object Response
outgoing ($response
). There is a new PSR that deals only on middleware: to PSR-10, but this recommendation is still at the draft stage and may change, although it already has a well consolidated and applied structure in some frameworks.
Simply put, there are 3 different ways to implement a middleware:
1. Double Pass
This approach is currently the most widely used and was based on framework Express:
function(request, response, next): response
In this approach, we can define that the middleware is defined as an object callable
(commonly as Closure), which takes three parameters:
request
: an object Request
;
response
: an object Response
;
next
: an object callable
representing the next middleware;
This approach makes more sense when the middleware are put in the form of pipeline, that is, aligned, in which the output of one will serve as the input of the other, explaining the reason of the object Response
also be propagated as a parameter.
Figure 1: Block diagram representing the organization of middleware aligned.
An example of this implementation would be:
function auth ($request, $response, $next)
{
if (! Auth::validate()) {
$response = new Response(401, "Unauthorized");
}
return $next($request, $response);
}
Note that in the middleware authentication logic is executed to verify credentials and, if it fails, the answer is changed to a 401 response. The problem with this structure is that all middleware will be executed and those who need authentication to be executed will have to make the verification of which is the answer that arrived as parameter:
function anything ($request, $response, $next)
{
if ($response->getCode() == 200) {
// Faça algo apenas se a resposta está OK.
}
return $next($request, $response);
}
To get around this problem, some applications prefer the battery structure to aligned, as the stack structure allows what we call short-circuiting in the HTTP response. For example, if the middleware Authentication already generate a 401 response, there is no need to run the rest of the application, so this response is "short-circuited" for the output of the application. To do this, just slightly modify the code:
function auth ($request, $response, $next)
{
if (! Auth::validate()) {
return new Response(401, "Unauthorized");
}
return $next($request, $response);
}
Note that instead of just modifying the object $response
, the new answer is returned, without having to execute the next middleware, through $next
. In this way, the graphic representation stays this way:
Figure 2: Block diagram representing the organization of middleware piled.
Realize that the object Response
that is sent out is the return of the first middleware, no more than the latter, which is what allows the short-circuit of the response.
Projects using this approach:
2. Single Pass
The approach single pass is very similar to the double pass, differing only that the object Response
is not passed as parameter:
function(request, next): response
In this approach, we can define that the middleware is defined as an object callable
(commonly as Closure), which takes two parameters:
request
: an object Request
;
next
: an object callable
representing the next middleware;
In this way, each middleware does not have access to the HTTP response until one is generated by the application, at middleware more internal. That is, in this approach, only the cell structure is possible (Figure 2). The short-circuit response technique can also be applied in this approach.
function auth ($request, $next)
{
if(! Auth::validate())
{
return new Response(401, "Unauthorized");
}
return $next($request);
}
Projects using single pass:
Personal note: I personally think it makes much more sense to approach single pass than the double pass, when using the middleware together with an MVC application, the controller is expected to generate the final response and being the middleware executed before the controller, there will be no input response.
3. Middleware Interface
PSR-10 defined a third approach based on an interface to middleware. This interface defines only one method, called process
, with two parameters: the object Request
and an object Delegate
, that represents the next middleware. As such PSR is still in draft stage, there is no implementation officer, then I’ll set mine as an example:
interface Middleware
{
/**
* Processa uma requisição HTTP de entrada e retorna uma resposta.
*
* O middleware é responsável por fazer o tratamento da requisição HTTP de
* entrada e retornar uma resposta. A resposta pode ser gerada pelo
* middleware ou o mesmo pode delegar o retorno ao próximo middleware da
* lista, através do objeto `$delegate`. Para o primeiro caso, quando o
* middleware gera a resposta retornada, todos os middlewares restantes na
* lista serão ignorados pela aplicação.
*
* @param Request $request Requisição a ser tratada pelo middleware.
* @param Delegate $delegate Instância de Delegate que controla os middlewares.
* @return Response Resposta gerada pelo middleware.
*/
public function process($request, $delegate);
}
Some comments on this approach:
Why not use the __invoke method?
In the previous approaches, double pass and single pass, how the middleware are defined as closures, they can be implemented through classes using the method __invoke
, then the approach defined in the PSR-10 chose not to use it to not generate conflicts and maintain the forwards Compatibility.
Why use the name process
for the method?
Some other existing structures, mainly related to the MVC architecture, already have similar names, such as: __invoke
, as mentioned above, handle
, as used in HttpKernel
of Symfony, and dispatch
, as used in DispatchableInterface
of Zend. In order not to create conflict or confusion with these structures, the name was chosen process
.
More details can be found directly on the PSR-10, but for simplicity, the object Delegate
must have at least the process
, which is analogous to the functioning of $next
in the previous approaches. An example of middleware in this approach would be:
class AuthMiddleware implements MiddlewareInterface
{
public function process($request, $delegate)
{
if (! Auth::validate()) {
return new Response(401, "Unauthorized");
}
return $delegate->process($request);
}
}
The graphical representation of this approach is the same as the single pass, illustrated in Figure 2, therefore, the short circuit technique of the response is also allowed, as shown in the above code.
Middleware and MVC applications
The vast majority of frameworks using the concept of middleware have used MVC architecture before, so today they unite the two concepts, defining the middleware as plug-in components to the application - that is, you can add or remove without having to modify the logic of your application itself. Slim is an example of this: it uses the approach double pass stack-shaped and centralize the MVC application within it, as in the figure below:
Figure 3: Illustration of how the interaction between middleware and MVC applications in the vast majority of cases.
Superficially, we can understand the MVC application as a great middleware, because the entry of it is an HTTP request and it generates an appropriate response. The concept of middleware, in fact, it serves to delegate distinct tasks to distinct structures. For example, the MVC architecture can create a context to work with very diverse routes, running certain controllers for each existing route, but if implementing authentication rules, cache control, etc, controller logic begins to complicate in exponential form. With the concept of middleware you can delegate the MVC application to do what it does best and delegate the authentication and cache control logics to frameworks adapted to this: the middleware.