PSR-15 middleware
Qubus\Http\RequestHandler is a compact PSR-15 middleware dispatcher. It accepts a PSR-17 response factory and an ordered list of middleware. When the pipeline reaches its end, it returns a new 200 response from the factory.
Building a pipeline
<?php
declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Qubus\Http\Factories\JsonResponseFactory;
use Qubus\Http\Factories\Psr17Factory;
use Qubus\Http\RequestHandler;
$requestId = new class implements MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$id = bin2hex(random_bytes(8));
$response = $handler->handle($request->withAttribute('request_id', $id));
return $response->withHeader('X-Request-Id', $id);
}
};
$endpoint = new class implements MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
return JsonResponseFactory::create([
'request_id' => $request->getAttribute('request_id'),
]);
}
};
$handler = new RequestHandler(new Psr17Factory(), [$requestId, $endpoint]);
$response = $handler->handle($request);
Middleware appears in request order and unwinds in reverse response order:
request -> middleware A -> middleware B -> endpoint
response <- middleware A <- middleware B <- endpoint
Note
The handler is reusable; handling one request does not consume the configured middleware list.
Delegating middleware
Delegating middleware performs work before or after calling the next handler:
<?php
final class SecurityHeaders implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
return $handler->handle($request)
->withHeader('X-Content-Type-Options', 'nosniff')
->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
}
}
Terminating middleware
A middleware may return a response without delegating:
<?php
final class RequireApiKey implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
if ($request->getHeaderLine('X-Api-Key') !== getenv('API_KEY')) {
return JsonResponseFactory::create(
['error' => 'Unauthorized'],
status: 401,
);
}
return $handler->handle($request);
}
}
Recommended ordering
A typical outer-to-inner order is:
- Error handling
- Request IDs and access logging
- Optional emitter middleware
- Encrypted-cookie middleware
- Session middleware
- Authentication and authorization
- Routing/controller dispatch
Ordering is application-specific. Cookie decryption must happen before code reads those cookies; session middleware must happen before code reads the session; response cookie encryption runs while the middleware stack unwinds.
If using EmitterMiddleware, keep it outside middleware that must still alter the response. Alternatively, leave emission out of the pipeline and emit the fully processed response in the front controller.
Available middleware
| Middleware | Purpose |
|---|---|
Qubus\Http\Cookies\Middleware\EncryptCookiesMiddleware | Transparently decrypt incoming cookies and encrypt outgoing cookies. |
Qubus\Http\Session\Middleware\SessionMiddleware | Load a cache-backed session, attach it to the request, and commit it to the response. |
Qubus\Http\Emitter\Middleware\EmitterMiddleware | Emit the downstream response in a SAPI environment. |
See Cookies, Sessions, and Responses for complete configuration examples.