Das HTTP Protokoll ist ein synchrones Client-Server Protokoll mit sieben Methoden. Jede Methode impliziert eine bestimmte Semantik die von der WWW-Infrastruktur insbesondere für Optimierung durch Caching und Client-Recovery genutzt wird:
| Methode | Bedeutung | idempotent | safe |
|---|---|---|---|
| GET | Fordert den Inhalt einer Ressource an. Der Aufruf darf keine Zustandsänderung am Server hinterlassen. | ja | ja |
| POST | Ressourcen werden im Server geändert. | nein | nein |
| HEAD | Sendet nur die Headerinformationen einer Ressource. | ja | ja |
| PUT | Dem Server werden Ressourcen hinzugefügt, dabei bleiben alle vorher existierenden Ressourcen unverändert. | ja | nein |
| DELETE | Vom Server werden Ressourcen genommen, dabei bleiben alle anderen Ressourcen unverändert. | ja | nein |
| OPTIONS | Der Server sendet dem Client alle Optionen einer Ressource. | ja | ja |
| TRACE | Trace Info: alle Infrastrukturkomponente hinterlassen im Request-Response Pfad eine Spur. | ja | ja |
Das POST-Redirect-GET (PRG) Pattern regelt die semantisch korrekte Verwendung von POST und GET Anfragen:
PRG Pattern und temporärer Zustand
In der Praxis ist die sauber Umsetzung des PRG Patterns eine Herausforderung. Zweck einer POST Anfrage ist immer, den Zustand der Anwendung zu verändern. Ein einfaches Beispiel ist das gelungene Update in einer Datenbanktabelle, eine persistente Zustandsänderung. Endet die POST Anfragen mit einem Fehler ist die Sache weniger klar. Der Client soll natürlich eine Fehlernachricht bekommen - ohne PRG Pattern hätte man den Fehler einfach als Response der POST Anfrage gerendert. Beim PRG Pattern ist eine Fehlermeldung eine View mit Parametern, die erst nach einem Redirect über eine GET Anfrage zum Client gesendet wird.
Das Problem besteht offensichtlich darin, dass eine solche View nichtpersistente Daten darstellen soll, im beschriebenen Beispiel die Fehlermeldung zu einem misslungenen Update. Für diese nichtpersistenten Daten - sie werden im folgenden als UI Zustand bezeichnet - muss ein Ablageort gefunden werden, damit sie einen Redirect überdauern. Diese Strategien bieten sich an:
Man kann aus der Not eine Tugend machen und den UI Zustand in eine Datenbank schreiben - nichtpersistente Zustände werden damit persistent gemacht! Das hat mindestens zwei Vorteile. Zum einen belastet eine solche Lösung nicht den RAM des Servers oder zwingt zum Einsatz eines clusterfähigen Caches. Zum anderen braucht eine produktive Anwendung oftmals für Monitoring und Logging ohnehin einen persistenten Ort, an dem solche Informationen zu hinterlegen sind. Viele Datenbanken erlauben das Einrichten sogenannter Archiv-Tabellen, in denen sind nur Inserts und Selects gestattet, sie sind aber dafür performanter. Der Nachteil dieses Vorgehens: der UI Zustand muss einen leichtgewichtigen Mechanismus zur Serialisierung und Deserialisierung in ein Textformat bereitstellen (JSON, YAML, XML etc.). Ein Textformat ist notwendig, da aus fachlicher Sicht Revisionssicherheit und Technologieneutralität der Logging-Informationen gegeben sein muss.
Vielleicht kombiniert man diese Variante mit den Vorteilen der Speicher basierten Lösung, indem regelmäßig die ältesten Einträge eines LRU Caches nicht verworfen, sondern in eine Datenbank geschrieben werden.
PRG Token
Für alle Lösungen, bei denen der UI Zustand nicht zum Client geschickt werden kann, muss der UI Zustand nach einem Redirect dem folgenden GET Request zugeordnet werden können. Dafür wird eine UID, ein Token benötigt. Aus Gründen, die noch beleuchtet werden, wird diese UID als hidden Field schon in der HTML-Form hinterlegt, die den POST Request auslöst. Dieses Token wird als Parameter an die URL des Redirects angehängt und erlaubt dann die Zuweisung des UI Zustands.
Double Submit bezeichnet in Webanwendungen das ungewollte, mehrfache Versenden eines POST-Requests. Ursachen sind:
Wie bereits erwähnt verhindert das PRG Pattern ein Double Submit für die Ursachen 1 und 2. Für 3 muss jedoch eine Lösung entwickelt werden, die am besten gleich Bestandteil der PRG Pattern Implementierung wird. Eine Lösung besteht darin, ein eindeutiges Token schon in der HTML-Form anzulegen: damit lassen sich Double Submits identifizieren und verhindern. Die Logik für den Ablauf beim PRG Pattern und die Buchhaltung der UI Zustände muss an zentraler Stelle implementiert sein.
Wird für die Umsetzung der Anwendungsfälle ohnehin eine Session benötigt, dann ist hier der richtige Ort für die Buchhaltung der UIStates. Zwar kann auf synchronisierten Code nicht verzichtet werden, die Synchronisierung erfolgt aber jeweils an Session-spezifischen Objektinstanzen und ist deshalb unkritisch. Eine funktionierenden Implementierung stelle ich gerne zur Verfügung. Wenn keine Session zur Verfügung steht, kann man auch die Buchhaltung der UIStates an eine Filterimplementierung delegieren.
Clientseitig kann unterstützend mit JavaScript der Submit-Button ausgeschaltet werden, das ist gleichzeitig auch gutes User Interface Design, da der Nutzer seine Anfrage als "abgeschickt" identifizieren kann. Im Beispiel wird der Submit Button vor dem Submit disabled:
<input type='submit' name='Los!' onclick='this.disabled=true;this.form.submit();'>Allerdings kann man sich niemals darauf verlassen, dass clientseitig JavaScript erlaubt ist!
PRG-Pattern und das Model-2 Architekturpattern
Aus dem Bedürfnis heraus, in einer Webanwendung Code für die Darstellung von Seiten, die Manipulation von Inhalten und die steuernde Logik dafür zu separieren, entstand das Model-2 Architekturparadigma. Hier wird jeder Request von einem Controller-Servlet empfangen. Dieses Servlet leitet den Aufruf an die dafür vorgesehene Verarbeitungslogik weiter und, je nach Ergebnis der Verarbeitung, verweist an ein View, der das Ergebnis der Abarbeitung repräsentiert:
GET, POST, READ,
DELETE, PUT WRITE
Request --- 1 ---> Controller --- 2 ---> Verarbeitung
(Servlet) | UPDATE,DELETE,
| V SELECT,INSERT
4 Forward Model --- 3 ---> Backend
| A
V |
Response <--- 5 --- View (JSP) ------------+
Diese Architektur kann mit dem PRG-Pattern sogar wesentlich differenzierter umgesetzt werden. Dabei wird zunächst die Tatsache ausgenutzt, dass jeder View mit einem GET Request geholt wird:
GET READ SELECT
Request -------> View ----> Model ------> Backend
(JSP)
|
Response <--------+
Alternativ dazu kann auch ein GET Request mit einem Redirect antworten. Das ist zum Beispiel der Fall, wenn die URL-Parameter des Requests nicht valide sind. POST/PUT/DELETE Requests werden grundsätzlich mit einem Redirect erwidert:
POST WRITE UPDATE
Request ----> Verarbeitung ----> Model -----> Backend
(Servlet)
|
Redirect <----------+
PUT WRITE INSERT
Request ---> Verarbeitung ----> Model -----> Backend
(Servlet)
|
Redirect <---------+
DELETE WRITE DELETE
Request ------> Verarbeitung ----> Model -----> Backend
(Servlet)
|
Redirect <-----------+
Im Vergleich zum klassischen Model-2 Ansatz erfolgt der Forward demnach nicht intern sondern wird als Redirect über den Client abgewickelt. Die Abarbeitung von GET-Requests auf Views und von POST/PUT/DELETE Requests ist damit komplett separiert!
Enthält eine View Parameter die validiert werden müssen, so kann das PRG-Pattern auch hier zu einer Entkopplung von Logik (zur Validierung der Parameter) und der eigentlichen View beitragen. Dabei erfolgt zunächst ein GET Request einer HTML Form zur Validierung der Parameter. Die Verarbeitungslogik hinterlegt die validierten Parameter als UI State und endet mit einem Redirect auf die eigentliche View.
GET
Request ------> Validierung ----> Model
(Servlet)
|
Redirect <-----------+
Durch das PRG-Pattern steigt dann der Grad der Wiederverwendung der Views enorm. Denn Views können nun ohne jede Fachlogik gestaltet werden, sie müssen lediglich mit einem assoziierten UI State designed werden. Im Rahmen der JSP Technologie wird man das zum Beispiel in Form von Beans machen, die mittels jsp:useBean in der JSP leicht verfügbar gemacht werden können.
Fazit
Das PRG Pattern lässt sich verkürzt formulieren als:
Pfiffigerweise bringt man dieses Token schon in der HTML-Form als hidden field unter und verwendet es zur Behandlung des Double Submit Problems verursacht durch mehrfaches Drücken des Submit Knopfes der Form. Werden diese Token und die damit assoziierten UI-States vorgehalten, so kann der Benutzer problemlos auch über Seiten mit HTML-Form navigieren. Denn jeder View ist nun dank seines ergänzenden UI-States in sich konsistent und abgeschlossen.
Literatur
Hypertext Transfer Protocol -- HTTP/1.1
Ausführlicher Artikel auf theserverside: Redirect After Post
PRG Pattern in JSF1.x basierend auf PhaseListener
PRG Pattern mit JSF2