Neos Flow Middleware Tutorial: PSR-15 verstehen und anwenden

Zusammenfassung
Was ist eine Middleware?
Im Kontext von PSR (PHP Standards Recommendation) und Web-Applikationen ist eine Middleware eine eigenständige Komponente, die einen HTTP Request entgegennimmt, ihn optional verändert oder verarbeitet und dann entweder selbst eine HTTP Response zurückgibt oder den Request an die nächste Middleware weiterleitet.
Warum ist es für Neos Flow wichtig?
Middlewares sind in Neos Flow wichtig, weil sie ein zentraler Bestandteil der PSR-basierten Architektur von Web-Applikationen sind. Mit ihnen kann man zum Beispiel prüfen, ob ein Benutzer authentifiziert und/oder autorisiert ist, bevor er eine sicherheitskritische Aktion wie das Ändern seines Passworts ausführt (SecurityEntryPointMiddleware). Ebenso lassen sich mit Middlewares Flashmessages (z. B. im Neos CMS Dashboard: „Datei Dummy.pdf wurde hinzugefügt.“) im Cookie speichern, um diese im Browser ausgeben lassen zu können (FlashMessageMiddleware).
Möglichkeiten, die sich durch Middlewares bieten:
Authentifizierung & Autorisierung
Prüfen, ob ein Benutzer eingeloggt ist oder die nötigen Rechte hat
Security & Schutzmaßnahmen
Zusätzliche Header setzen (Content-Security-Policy, X-Frame-Options, …)
Logging & Monitoring
Jede Anfrage oder bestimmte Events ins Log schreiben
Performance messen (z. B. Zeit vom Request bis zur Response)
Request- und Response-Manipulation
Query-Parameter oder Header ergänzen/ändern
Cookies setzen oder auswerten
Response-Body anpassen, bevor er zurückgeht
Wartungsmodus oder Feature-Flags
Statt der normalen Seite eine Wartungsseite zurückgeben
Bestimmte Features nur für Test-User aktivieren
So funktioniert eine Middleware in Neos Flow
Hier ist ein Sequenzdiagramm, wie Middlewares in Neos Flow einzuordnen sind:
Quelle: Flow Framework > HTTP Foundation
Stark vereinfacht gesagt lässt sich das Diagramm so zusammenfassen: Der Browser sendet eine Anfrage an den Server. Dort übernimmt ein passender RequestHandler, der aus der Anfrage ein PSR-7-Request-Objekt erstellt. Dieses Objekt enthält alle relevanten Informationen der ursprünglichen HTTP-Anfrage und wird anschließend durch die nach PSR-15 implementierte Middleware-Kette geleitet.
PSR-7 definiert standardisierte PHP-Interfaces für HTTP-Nachrichten, also für Requests, Responses, Streams, URIs und hochgeladene Dateien. Dadurch gibt es eine gemeinsame, frameworkunabhängige Grundlage für die Arbeit mit HTTP-Objekten. Die Objekte sind unveränderlich aufgebaut, wodurch jede Veränderung eine neue Instanz erzeugt und somit ein konsistentes Verhalten garantiert wird.
PSR-15 baut auf diesen PSR-7-Interfaces auf und legt fest, wie Middlewares und Request-Handler im Server-Kontext aussehen. Eine Middleware erhält dabei ein PSR-7-Request-Objekt und entscheidet, ob sie selbst eine Response zurückgibt oder den Request an den nächsten Handler weiterleitet. Dadurch entsteht eine klar definierte und kompatible Middleware-Kette, die unabhängig vom verwendeten Framework funktioniert.
Wie implementiert man Middlewares in Neos Flow?
Wenn man eine eigene Middleware erstellen möchte, muss man das Psr\Http\Server\MiddlewareInterface implementieren. Dieses Interface definiert die process-Methode:
<?php
namespace Vendor\Package\Http;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
// A sample HTTP middleware that adds a custom header to the response
final class SomeMiddleware implements MiddlewareInterface
{
public function process (ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface {
$response = $next->handle($request);
return $response->withAddedHeader ('X-MyHeader', '123');
}
}
Um die neue Middleware zu aktivieren, muss es in der Settings.yaml konfiguriert werden:
Neos:
Flow:
http:
middlewares:
'custom':
position: 'before dispatch'
middleware: 'Vendor\Package\Http\SomeMiddleware'
In der position-Option kann man angeben, an welcher Stelle der Middleware-Kette die neue Middleware verarbeitet werden soll. Mit ./flow middleware:list findet man alle aktive Middlewares:
$ ./flow middleware:list
Currently configured middlewares:
+----+---------------------+-----------------------------------------------------------------+
| # | Name | Class name |
+----+---------------------+-----------------------------------------------------------------+
| 1 | standardsCompliance | Neos\Flow\Http\Middleware\StandardsComplianceMiddleware |
| 2 | trustedProxies | Neos\Flow\Http\Middleware\TrustedProxiesMiddleware |
| 3 | methodOverride | Neos\Flow\Http\Middleware\MethodOverrideMiddleware |
| 4 | session | Neos\Flow\Http\Middleware\SessionMiddleware |
| 5 | ajaxWidget | Neos\FluidAdaptor\Core\Widget\AjaxWidgetMiddleware |
| 6 | detectSite | Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionMiddleware |
| 7 | routing | Neos\Flow\Mvc\Routing\RoutingMiddleware |
| 8 | poweredByHeader | Neos\Flow\Http\Middleware\PoweredByMiddleware |
| 9 | flashMessages | Neos\Flow\Mvc\FlashMessage\FlashMessageMiddleware |
| 10 | parseBody | Neos\Flow\Http\Middleware\RequestBodyParsingMiddleware |
| 11 | securityEntryPoint | Neos\Flow\Http\Middleware\SecurityEntryPointMiddleware |
| 12 | dispatch | Neos\Flow\Mvc\DispatchMiddleware |
+----+---------------------+-----------------------------------------------------------------+
Die neue Middleware würde zwischen securityEntryPoint und dispatch aktiviert werden.
Eine Middleware-Implementierung in Neos Flow bietet verschiedene Möglichkeiten: Man kann die Middleware-Kette unterbrechen, Daten zwischen Middlewares austauschen, individuelle Optionen definieren und vieles mehr. Weitere Details findest du hier: https://flowframework.readthedocs.io/en/8.3/TheDefinitiveGuide/PartIII/Http.html#interrupting-the-chain
Praktische Beispiele
Logging Middleware für bestimmten Flow Kontext
<?php
namespace Vendor\Package\Http;
use Neos\Flow\Annotations as Flow;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
// A middleware that logs all requests if they are in development context
class LoggingMiddleware implements MiddlewareInterface
{
#[Flow\Inject] protected LoggerInterface $logger;
#[Flow\InjectConfiguration(path: "core. context", package: "Neos.Flow", type: "Settings")]
protected string $context = "Development";
public function process (ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->context== "Development/Docker") {
$this->logger->info("Incoming request: Method -> " . $request->getMethod() . "; Query -> " . $request->getUri()->getQuery());
}
return $response = $handler->handle($request);
}
}
Wartungsmodus Middleware
<?php
namespace Vendor\Package\Http;
use Neos\Flow\Annotations as Flow;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Vendor\Package\Http\MaintenanceResponse;
// A middleware that creates a maintenance response if in maintenance mode and not permitted user
class MaintenanceMiddleware implements MiddlewareInterface
{
#[Flow\InjectConfiguration(path: "core.maintenanceActive", package: "Vendor.Package", type: "Settings")]
protected bool $isMaintenanceActive = false;
public function process (ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$clientIp = $request->getServerParams()['REMOTE_ADDR'] ?? '';
if (!$this->isMaintenanceActive || $clientIp === "123.123.123.123") {
return $handler->handle($request);
}
return new MaintenanceResponse();
}
}