Start - Publikationen - Wissen - TOGAF - Impressum -

HTTP Methoden


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

Idempotent bedeutet: die Methode kann Änderungen am Zustand des Servers verursachen, jedoch der mehrmalige Aufruf der gleichen Methode ändert das Ergebnis nicht. Bei einer idempotenten Methode müssen Client und Server keine Vorkehrungen treffen, um einen versehentlichen Mehrfachaufruf zu vermeiden. Eine Methode ist safe, wenn sie keine Veränderung im Zustand des Servers hinterlässt. Hier hat der Client die Möglichkeit, das Ergebnis einer Anfrage nach bestimmten, zwischen Client und Server ausgehandelten Regeln, zu cachen. Es ist Aufgabe des Designs, die Anwendungsfälle einer Webanwendung auf die richtigen Methoden des HTTP Protokolls zu verteilen. Auffällig: GET ist die einzige Methode bei der die Repräsentation einer Ressource geliefert wird. Hier kommt das PRG Pattern ins Spiel.

Das POST-Redirect-GET Pattern


Das POST-Redirect-GET (PRG) Pattern regelt die semantisch korrekte Verwendung von POST und GET Anfragen:

  • Views auf fachliche Inhalte werden ausschließlich über GET Anfragen geholt. Solche Anfragen sind 'safe' in dem Sinne, dass sie auf dem Server keine Änderungen verusachen und damit auch idempotent.
  • Änderungen der fachlichen Inhalte erfolgen über POST Anfragen. Solche Anfragen verändern den Datenbestand, eine Mehrfachverarbeitung solcher POST Anfragen ist gewöhnlich ein fachlicher Fehler.
  • Die View nach einem POST wird über eine Redirect-Anweisung an den Client geliefert.
Eine strikte Umsetzung des PRG Pattern hat enorme Vorteile:
  • Die HTTP Caching Spezifikation ist für GET Anfragen ausgelegt. Werden Inhalte als Response eines POST an den Client geschickt, gibt es keine einheitliche Vorgaben und konzeptionelle Probleme für das Cachen dieser Seiten.
  • Ein versehentliches doppeltes Verschicken eines POST Request durch Reload oder Back-Forward Funktionen des Browsers ist ausgeschlossen. Manche Browser zeigen dem Nutzer sogar eine Warnung, bevor eine solche Anfrage nochmal verschickt wird. Diese Warnung wird es bei der Verwendung des PRG Pattern nicht mehr geben.
Netzwerktechnisch ist der zusätzliche Roundtrip beim PRG-Pattern übrigens völlig unproblematisch: seit HTTP1.1 wird standardmäßig die TCP Verbindung zwischen Client und Server aufrecht erhalten. Auch bei PUT und DELETE Requests sollte das PRG-Pattern zum Einsatz kommen.

Welcher HTTP Statuscode ist der richtige?


Anscheinend ist die Antwort schnell gefunden: HTTP 303 See Other beschreibt genau das Verhalten, das für das PRG-Pattern benötigt wird. Diesen Status gibt es aber erst seit HTTP1.1 und vorher, mit HTTP1.0, war HTTP 302 Moved Temporarily die einzige Wahl. HTTP Clients haben auf diesen Status im Sinne des PRG Patterns reagiert. Aus dem HTTP 302 Moved Temporarily wurde dann mit HTTP1.1 ein HTTP 302 Found und ein HTTP 303 See Other. Laut Spezifikation ist eigentlich HTTP 302 Found nicht für das PRG Pattern geeignet, die Änderung der Methode und eine automatische Weiterleitung sind nicht gestattet! Dennoch wurden HTTP Clients weiter so implementiert, dass sie auf den Statuscode HTTP 302 Found im Sinne des PRG Patterns reagieren. Die Antwort auf die gestellte Frage lautet also:

  • seit HTTP1.1 ist der Statuscode HTTP 303 See Other der korrekte
  • für abwärtskompatibile Implementierungen nutzt man Statuscode HTTP 302 Found oder HTTP 302 Moved Temporarily, denn HTTP Clients reagieren auf diesen Status konform zum PRG Pattern und dem alten HTTP 302 Moved Temporarily aus HTTP1.0
Die Komfortmethode HTTPServletResponse.sendRedirect sendet jedenfalls den alten Status HTTP 302 Moved Temporarily und alle mir bekannten Browser reagieren darauf im Sinne des PRG Pattern.

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:

  • Der UI Zustand verbleibt als Java Objekt in der Session des Nutzers und damit im Speicher des Servers. Wenn für die Umsetzung der Anwendungsfälle ohnehin eine Session nötig ist, dann ist das auch die Methode der Wahl. Die Menge der vorgehaltenen UI-States sollte nicht endlos wachsen, sondern ist auf ein sinnvolles Maß zu begrenzen. Die Klassen, die den UI-State repräsentieren müssen passivierbar/aktivierbar im Sinne der Servlet-Spezifikation sein. Die UI Zustände werden niemals direkt in der Session abgelegt, sondern in einer Map oder einem LRU Cache. Auf den UI State wird dann mit einer schwer zu erratenden ID oder einem Hash referenziert, der in der Redirect URL als Parameter angegeben wird. Diese Map repräsentiert dann eine Historie von UI States auf deren Basis die Pagehistory-Navigation funktioniert.
  • 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.

  • Die Daten des UI Zustands werden als Parameter an die Redirect URL angefügt. Der UI Zustand wird damit einfach zum Client geschickt, aus Sicht des Servers eine enorme Entlastung wenn er sich nicht um die Buchhaltung der UI Zustände seiner Clients kümmern muss. Auch hier ist ein Serialisierungsmechanismus nötig, der zudem nur Zeichen erzeugen darf, die in einer URL übetragen werden können. Dieses Vorgehen hat eine Einschränkung: die erlaubte Länge einer URL ist weder für Clients noch für Server spezifiziert (!) und man muss sich auf Erfahrungswerte verlassen. In einer aufschlussreichen Analyse zum Thema, kann man zu dem Schluss kommen, dass man 2083 Zeichen zur Verfügung hat - der IE steckt hier den engen Rahmen. Webanwendungen bei denen auf Sessions verzichtet wird, zeigen bestes Skalierverhalten und können problemlos geclustert werden.
  • Cookies sind ein anderer möglicher clientseitiger Ablageort. Tatsächlich ließen sich c.a. 80kB (verteilt auf 20 Cookies der Größe 4kb) im Client ablegen. Allerdings haben viele Nutzer Cookies abgeschalten und noch schlimmer: Cookies haben eine ungeklärte Wirkung auf das Cachingverhalten der Clients. Besonders vorsichtige Browser cachen garnicht, wenn mit der Seite Cookies assoziiert sind! Cookies sind hier deshalb nur der Vollständigkeit halber erwähnt und meines Erachtens für produktive Zwecke nicht geeignet.

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.

Das Double Submit Problem


Double Submit bezeichnet in Webanwendungen das ungewollte, mehrfache Versenden eines POST-Requests. Ursachen sind:

  1. Der Reload-Button des Clients führt eine POST Request mehrfach aus - der Nutzer wollte eigentlich nur seine View aktualisieren.
  2. Die Back-Forward Buttons führen einen POST Request mehrfach aus - der Nutzer wollte eigentlich nur zu einer anderen View navigieren.
  3. Der Submit Button eines Formulars wird mehrfach betätigt - der Nutzer ist zu ungeduldig und macht etwas vollkommen Menschliches. Und er wird es immer wieder tun.
Das gleiche gilt für GET Requests, die (problematischerweise) semantisch wie POST Anfragen verarbeitet werden. Das Verhalten der Browser beim Double Submit ist nicht geregelt und dementsprechend unterschiedlich. Der FireFox in der aktuellen Version verhindert vorbildlich ein Double Submit, es werden dabei Formulare einfach nicht zweimal veschickt solange noch ein Request aktiv ist. Beim IE sieht die Sache nicht so rosig aus - jeder Click auf den Submit Button sendet brav einen Request. Dabei ist unklar, welcher Response dann am Client angezeigt wird.

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. 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:

  • Die View auf ein Model ist immer eine GET Anfrage und ist safe im Sinne der HTTP Spezifikation.
  • Änderungen am Model werden über POST Anfragen initiiert, die Antwort ist immer ein Redirect auf eine View.
Das PRG Pattern
  • ist nicht mehr und nicht weniger als die semantisch korrekte Verwendung des HTTP Protokolls
  • unterstützt clientseitiges Cachen, da alle Views per GET zum Client gelangen
  • vermeidet das Double Submit Problem einer HTML-Form bei Reload oder Navigations
  • vermeidet die hässliche 'Post nochmal senden?'-Warnung des Browsers
  • führt zwanglos zu einer differenzierten Model-2 Architektur
Die Umsetzung des PRG Pattern ist nicht trivial, da nun auch nichtpersistente Zustände (temporär, den Arbeitsstand des Clients betreffend) einen Redirect überdauern müssen. Ein anschauliches Beispiel für einen solchen temporären Zustand ist die Fehlermeldung einer Validierung, die in der nächsten View angezeigt werden soll. Eine Lösung des Problems ist es, temporäre Zustände in Form von Loggingeinträgen persistent zu machen. Eine clusterfähiger Cache oder ein Cache in der HttpSession ist ein anderer möglicher Ablageort für temporäre Zustände. So oder so muss der Zustand referenziert werden können, dazu nutzt man üblicherweise eine ID (Ticket oder Token genannt) im Redirect.

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

copyright © 2002-2018 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved