Representational State Transfer (REST)

From
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

In den frühen 1990ern war das Web von einer einfachen Logik geprägt, Dokumente in einem bestimmten Format die sich eindeutig identifizieren konnten und sich über diese Identifikation referenzierten, sowie ein einfaches Protokoll zum übertragen der Dokumente. Das reichte nicht mehr aus als das Internet dynamischer wurde und man Datenbankinhalte oder Inhalte, die auf mehr oder weniger komplizierten Berechnungen beruhten, abrufen konnte. Dadurch wurde unklar ob eine bestimmte URL eine Datei oder einen Dienst, der viele Dateien oder Informationen liefern kann, anspricht. Seit 1994 beschäftigte sich dann Roy Fieldings mit dieser Problemstellung und entwarf in seiner Dissertation<ref>Roy Thomas Fielding: Architectural Styles and the Design of Network-based Software Architectures. Abgerufen am 6. Mai 2018(englisch)</ref> ein Architekturstil der sich bis heute zum Industriestandard entwickelt hat. In seiner Architektur nennt Fieldings 6 Prinzipien (Constraints) die es einzuhalten gilt um einen REST oder RESTful Service zu erstellen.

Rest-Constraints

Den Kern des von Roy Fieldling in seiner Dissertation vorgestellten REST-Paradigmas bilden eine Sammlung von 6 Constraints (Einschränkungen), die die Kommunikation zwischen REST-konformen Systemen beschreiben. Die systeminterne Art der Implementierung stellt hierbei keinen Teil von REST dar, vielmehr geht es darum zu spezifizieren, wie ein System nach außen kommuniziert.

Client-Server

Die einheitliche Schnittstelle trennt die Daten-Schicht von der Benutzer-Schnittstelle. Diese Trennung bedeutet, dass beispielsweise Clients sich nicht mit Datenspeicherung befassen, die intern auf dem Server verbleibt, so dass die Portabilität des Client-Codes verbessert wird. Server befassen sich nicht mit der Benutzerschnittstelle oder dem Benutzerstatus, sodass Server einfacher und besser skalierbar sind. Server und Clients können auch unabhängig ersetzt und entwickelt werden, solange die Schnittstelle nicht geändert wird.

Stateless

Die Kommunikation zwischen Client und Server hat Zustandslos zu erfolgen. Hierzu muss jede Anfrage eines Clients alle nötigen Informationen zur Beantwortung beinhalten und ohne Vorwissen über vorangegangene Anfragen vom Server ausgewertet und beantwortet werden können. Das bedeutet auch, dass der Client alle für die Kommunikation nötigen Zustände selbst verwaltet. Bei Wiederholung einer Anfrage wird somit immer die gleiche Antwort vom Server erwartet. Dieses erhöht die Skalierbarkeit, da Anfragen auf mehrere Server verteilt werden können ohne dass ein Server den Zustand des anderen Servers kennen muss. Bei verteilten Servern ist natürlich besondere Vorsicht geboten wenn zum Beispiel nach einer Änderungsoperationen lesend auf den inkonsistente Zustand zugegriffen wird.

Cache

Daten in einer Server-Antwort sollen explizit oder implizit als Cache-fähig oder -unfähig markiert werden. Die Verwendung eines Zwischenspeichers (Cache) auf Client-Seite steigert die Systemkapazität und die vom Benutzer empfundene Geschwindigkeit, da gleiche Anfragen mit Cache-fähigen Antworten nicht mehrfach an den oder die Server gestellt werden. Beim Cachen ist natürlich sehr genau abzuwägen welche Daten sich zum Cachen eignen. Es besteht die Gefahr sich ungewollte Effekte einzuhandeln, wie zum Beispiel durch die Verwendung veralteter Messwerte in einem sehr dynamischen Regelkreislauf. Cachen wird im Allgemeinen auch immer einen höheren Bedarf an Speicherplatz auf der Client-Seite erfordern.

Layered System

Diese Richtlinie besagt, dass für Clients nicht ersichtlich sein soll, ob diese direkt zum eigentlichen Server verbunden sind, oder lediglich zu einer Schnittstelle, die den Zugriff auf die REST-API bietet. Dies ist beispielsweise bei CDNs (Content Delivery Networks) der Fall, die von vielen verteilten Systemen unter anderem zum Zweck der Lastverteilung eingesetzt werden. Anfragen von Clients werden hier jeweils von dem geeigneten Knoten des CDNs bearbeitet, um eine hohe Effizienz und Ausfalltoleranz zu gewährleisten. Für den Client selbst ist diese Mehrschichtigkeit nicht direkt ersichtlich, womit dieses REST-Constraint erfüllt ist.

Ein weiterer Vorteil von mehrschichtigen Systemen ist es, dass die Struktur des Servers intern verändert werden kann, ohne dass bestehende REST-Anfragen angepasst werden müssen: Solange die obere Schicht unverändert bleibt, bleiben auch die darauf basierenden REST-Anfragen syntaktisch und semantisch korrekt -- auch, wenn sich beispielsweise die interne Speicherform der Ressourcen geändert hat.

Uniform Interface

Dieses Constraint fasst vier Eigenschaften zusammen, deren gemeinsames Ziel es ist, eine einfache Architektur der Schnittstelle zu gewährleisten. In seiner Dissertation bezeichnete Fieldling insbesondere diese Einschränkung als fundamental für ein System, das dem REST-Paradigma folgt.

Resource identification in requests:

Individuelle Ressourcen einer REST-Schnittstelle sollen durch Anfragen eindeutig identifizierbar sein, im Fall von Web-Anwendungen beispielsweise durch URIs (Uniform Resource Identifier). Die Darstellungsform, in der die gewünschte Ressource an den Klienten gesendet wird sei dabei unabhängig von der tatsächlichen Form, in der die Ressource auf dem Server gespeichert ist. So kann die Antwort auf eine Datenbankabfrage beispielsweise im CSV- oder JSON-Format erfolgen, auch wenn die Daten intern in einer ganz anderen Form verwaltet werden (siehe Abschnitt “Beispiele”).

Resource manipulation through representations

Wie in 5.1 beschrieben können Daten dem REST-Paradigma nach in unterschiedlichen, jedoch wohldefinierten Repräsentationen vorliegen, je nach Anwendungsfall und API beispielsweise als JSON oder XML. Die gleiche Ressource kann also durchaus in verschiedenen Formen dargestellt werden, und REST-Schnittstellen ermöglichen es den Clients, eine für sie geeignete Repräsentation auszuwählen. Diese Einschränkung besagt nun, dass Ressourcen nicht nur in verschiedenen Formaten gelesen werden, sondern auch in solchen modifiziert oder gelöscht werden können. Liegt dem Klienten eine Repräsentation einer Ressource vor, soll diese auch geeignet sein, durch weitere Anfragen an die REST-ful API die entsprechende Ressource zu manipulieren. Diese Entkupplung der Darstellungsformen einer Ressource von ihrer URI stellt ein wichtiges Merkmal von REST-Schnittstellen dar.

Self-descriptive messages

Diese Eigenschaft greift das Statelessness-Constraint auf und besagt, dass der Inhalt der Nachrichten so wie er ist ("as is"), also ohne weitere Informationen von außen, weiterverarbeitet werden kann. Eine Nachricht an eine REST-ful API enthält also alle Informationen, die für das entsprechende Ziel (beispielsweise das Ändern einer Ressource) benötigt werden. Solche Nachrichten werden auch als kontextfrei bezeichnet.

Hypermedia As The Engine Of Application State (HATEOAS)

Laut Roy Fielding, dem Autoren der ursprünglichen REST-Spezifikation, beschreibt dies die wichtigste aller REST-Eigenschaften. Dennoch wird sie von vielen Entwicklern missachtet. Die Idee hinter HATEOAS ist, dass Repräsentationen von Ressourcen jeweils auch immer Links mitliefern, die die weitere, dynamische Bearbeitung der Ressource ermöglichen. Wenn HATEOAS korrekt implementiert wird müssen die genauen URLS der Rest-Anfragen nicht mehr fest in der Client-Application kodiert sein, sondern können direkt aus der vorhergehenden Antwort übernommen werden. Durch REST-Anfragen ändert sich somit der Zustand (Application State) des Systems: Beispielsweise ändert ein erfolgreicher POST-Request das System in der Art, dass sich die entsprechende Tabelle in einer Datenbank um einen neuen Eintrag vergrößert. Dieser neue Systemzustand kann dann wiederum durch weitere Anfragen an die Schnittstelle abgefragt werden.

Code-on-Demand

Dieses optionale Constraint besagt, dass REST-Schnittstellen auch ermöglichen sollten, als Antwort auf API-Anfragen Code zurückzuliefern, der dann von der Client-Anwendung weiterverarbeitet werden kann. Anwendung findet dies beispielsweise bei dynamischen Java-Applets, die dann auf Webseiten eingebettet und auf dem Client-System ausgeführt werden können. Damit erweitert sich der Funktionsumfang der Klienten, ohne dass deren Benutzer neue Software installieren muss.

Umsetzung

Roy Fielding legt sich in seiner Dissertation nicht auf eine Technologie fest und beschreibt weder, welche Aktionen noch welche Protokolle für die Umsetzung verwendet werden sollen. Da sich das REST Paradigma jedoch vor allem im WWW durchgesetzt hat, werden heute zur Umsetzung hauptsächlich HTTP und HTTPS verwendet. Das Protokoll ist etabliert, vergleichsweise einfach aufgebaut und in fast jeder Firewall offen. Zudem bietet es eine Reihe von Funktionen zur Realisierung der Prinzipien an. So kann beispielsweise für die Caching constraints auf den Caching Mechanismus von HTTP zurückgegriffen werden. Bei der Verwendung von HTTP, gibt die beim Zugriff verwendete HTTP-Methode an welche Operation des Dienstes gewünscht ist.

HTTP-Methode  Beschreibung
GET Fordert die angegebene Ressource vom Server an. GET arbeitet ausschließlich lesend und wird daher als sicher bezeichnet.
POST Erstellt eine Ressource
PUT Erstellt oder bearbeitet eine Ressource falls diese bereits existiert
DELETE Löscht eine Ressource
PATCH Ändert einen Teil einer Ressource
OPTIONS Gibt verfügbare Methoden auf einer Ressource zurück

Beispiele

Anhand von zwei Beispielen soll hier demonstriert werden, wie die Einhaltung der REST-Constraints bei einer Schnittstelle einen effizienten und leicht verständlichen Datenaustausch ermöglicht.

REST-Schnittstelle für IP-Informationen

Um Informationen über eine bestimmte IP-Adresse oder Domain zu ermitteln, bietet der Online-Dienst http://ip-api.com eine RESTful-API an. Um diese zu benutzen stellt der Client zunächst eine reguläre GET-Anfrage an den Server, die Implementierung dabei hängt von der Art und Programmiersprache der Client-Anwendung ab. In python beispielsweise bietet das Modul urllib eine einfache Bibliothek für Internetkommunikation. Da es sich um einen regulären GET-Request handelt, ist es genauso möglich, einfach die Anfrage in die Adresszeile eines regulären Internetbrowsers zu kopieren, um dann die unformatierte Antwort im Browserfenster begutachten zu können. Wir verwenden nun die REST-Schnittstelle des Dienstes http://ip-api.com, um Informationen über den Server der Humboldt-Universität zu erhalten:

http://ip-api.com/json/141.20.5.218


In diesem REST-Request ist das Objekt unserer Anfrage (141.20.5.218) als Teil der URL kodiert. Gerade bei Anfragen mit mehrerern Parametern (insbesondere dann, wenn beispielsweise auch ein API-Key erforderlich ist) werden diese Parameter häufig stattdessen als GET-Parameter übermittelt. Diese sind dann mit einem '?' von dem Rest der URL und mit '&' voneinander getrennt sind.

Wird dieser Request gesendet, könnte die Antwort der Schnittstelle folgendermaßen aussehen:

{"as":"AS680 Verein zur Foerderung eines Deutschen Forschungsnetzes e.V.","city":"Berlin","country":"Germany","countryCode":"DE","isp":"Humboldt-Universitaet zu Berlin","lat":52.5167,"lon":13.4,"org":"Humboldt-Universitaet zu Berlin","query":"141.20.5.218","region":"BE","regionName":"Land Berlin","status":"success","timezone":"Europe/Berlin","zip":"12529"}

Hierbei sind sämtliche Informationen im JSON-Format kodiert, was eine leichte Weiterverarbeitung der Ergebnisse ermöglicht. Das gewünschte Format, in dem wir die Repräsentation der Ressource erhalten wollten, war ebenso wie die IP-Adresse in der URL kodiert. Alternativ wäre es beispielsweise möglich, die gleiche Ressource im csv-Format zu erhalten:

http://ip-api.com/csv/141.20.5.218}

success,Germany,DE,BE,Land Berlin,Berlin,10117,52.5185,13.3936,Europe/Berlin,Verein zur Foerderung eines Deutschen Forschungsnetzes e.V.,Humboldt-Universitaet zu Berlin,AS680 Verein zur Foerderung eines Deutschen Forschungsnetzes e.V.,141.20.5.218

Oder auch als XML:

http://ip-api.com/xml/141.20.5.218}

<query> <status>success</status> <country>Germany</country> <countryCode>DE</countryCode> <region>BE</region> <regionName>Land Berlin</regionName> <city>Berlin</city> <zip>10117</zip> <lat>52.5185</lat> <lon>13.3936</lon> <timezone>Europe/Berlin</timezone> <isp> Verein zur Foerderung eines Deutschen Forschungsnetzes e.V. </isp> <org>Humboldt-Universitaet zu Berlin</org> <as> AS680 Verein zur Foerderung eines Deutschen Forschungsnetzes e.V. </as> <query>141.20.5.218</query> </query>

Je nach Anwendungsfall kann sich der Entwickler also eine gewünschte Repräsentation aussuchen. Der tatsächliche Inhalt der Nachricht bleibt aber unabhängig von seiner Darstellung der gleiche, was ein wichtiges Merkmal von REST-Schnittstellen ist.


Wetter API

ür aktuelle Wetterdaten, stellt zum Beispiel openweathermap.com eine umfangreiche RestfulAPI zur Verfügung. Genau wie im ersten Beispiel muss der Client an den Server eine GET-Anfrage mit den vereinbarten Parametern übersenden. In diesem Fall erhält die GET-Anfrage die URL sowie die Parameter Stadt, Ländercode und den API Key.

https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22

Mit dieser Anfrage werden die Wetterdaten von London abgefragt und ebenfalls als Teil der URL kodiert. Anders als im ersten Beispiel werden die Parameter mit einem q vom Rest der URL getrennt: Die Trennung der unterschiedlichen Parameter erfolgt wieder mit einem &.

Als Antwort liefert der Server folgende Daten standardmäßig im JSON Format zurück:

  {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main": 
  {"temp":280.32,"pressure":1012,"humidity":81,"temp_min":279.15,"temp_max":281.15},"visibility":10000,"wind":{"speed":4.1,"deg":80},"clouds":{"all":90},"dt":1485789600,"sys": 
  {"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200}

Quellen