Wenn man heutzutage einen Webservice bauen will, kommt man um REST nicht mehr herum. Als quasi Standard erfüllt es die Bedürfnisse an eine moderne Schnittstelle am besten, im Gegensatz zu den Alternativen aus der grauen Vergangenheit: RPC, DCOM, CORBA, RMI und SOAP.
Was sind die Vorteile von REST?
1. Lose Kopplung
REST Schnittstellen können theoretisch gegeneinander ausgetauscht werden.
2. Interoperabilität
REST über HTTP ist in jeder Umgebung verfügbar und sehr einfach anzusprechen im Gegensatz zu den komplizierteren Ansätzen wie SOAP und CORBA.
3. Performance und Skalierbarkeit
Durch die Zustandslosigkeit können viele Anfragen aus dem Cache beantwortet werden und aufeinander folgende Anfragen müssen nicht von demselben System beantwortet werden (Skalierbarkeit)
Grundprinzipien von REST
eindeutige Ressourcen Identifikation
REST benutzt die URI (Uniform Resource Identifier) des HTTP Standards als eindeutige Kennzeichnung von Ressourcen.
Beispiel:
http://api.example.com/cart/1
Verknüpfung von Ressourcen (Linking)
Durch die Verknüpfung von Ressourcen mittels Links kann man Ressourcen verschieben und auch auf andere Server und Schnittstellen verteilen und erhält somit eine große Flexibilität. Dies setzt natürlich voraus, dass die Konsumenten die Links auch benutzten und nicht statisch darauf zugreifen.
Beispiel: GET http://api.example.com/post/21
{ "name" => "post 1 heading here", "content" => "I write poems" "_links": { "related": [ { "href": "http://api.example.com/comment/42", }, { "href": "http://api.example.com/comment/78", }, ], "author": [ { "href": "http://crm.example.com/user/42", } ], "_self": { "href": "http://api.example.com/post/1", } } }
Mit Hilfe der Links ist es auch möglich Statusübergänge zu modellieren, indem man z.B. bei einer Bestellungs-Ressource einen Link für die Stornierung anbietet und „related“ und nachdem die Bestellung storniert worden ist, diesen wieder entfernt. Dies sollte mit dem Caching abgewogen werden.
Die 6 HTTP Standardmethoden
REST Schnittstellen werden so aufgebaut, dass man theoretisch ohne Dokumentation mit Hilfe der 5 HTTP Methoden die Ressourcen beliebig manipulieren kann
- GET – lesen
- POST – ändern, im Zweifelsfall immer richtig
- PUT – erstellen, ändern
- DELETE – löschen
- HEAD – existiert die Ressource, letzte Änderung der Ressource
- OPTIONS – welche HTTP Methoden erlaubt die Ressource
Content Negotiation
Ein Client kann eine HTTP Ressource in einem bestimmten Format anfordern über den Accept Header.
Request:
GET http://api.example.com/post/21 Host: api.example.com Accept: application/json
Antwort:
Als Antwort bekommt man die Informationen im JSON-Format zurück.
Statuslose Kommunikation
Die Kommunikation von Client und Server muss statuslos geschehen. Der REST Standard schreibt vor, dass der Zustand entweder vom Client gehalten oder vom Server in einen Ressourcenstatus abgelegt werden sollte
URI Ressourcen-Architektur bei REST
Im folgenden Fallbeispiel aus der Praxis, sollen Bestellungen für einen Online-Shop mittels einer REST API modelliert werden.
Als Ressourcen Namen dürfen keine Verben in REST benutzt werden, sondern nur Substantive.
1. Alle Bestellungen
a) sollen ausgegeben werden:
URI: /orders Methode: GET
b) sollen gefiltert nach den neuen Bestellungen ausgegeben werden
URI: /orders?state=created Methode: GET
c) sollen gefiltert nach den stornierten Bestellungen ausgegeben werden
URI: /orders?state=cancelled Methode: GET
d) sollen gefiltert nach den versandten Bestellungen ausgegeben werden
URI: /orders?state=shipped Methode: GET
2. Eine einzelne Bestellung soll:
a) angezeigt werden
URI: /order/{id} Methode: GET
b) erzeugt werden
URI: /order/{id}Methode: PUT
Woher soll der CLient die ID kennen? Erzeugt werden wird über /orders und POST. Der Antwort Code sollte HTTP 201- Created sein bei erfolgreichem Anlegen
URI: /orders Methode: POST
c) gelöscht werden
URI: /order/{id} Methode: DELETE
d) geändert werden
URI: /order/{id} Methode: PUT
e) die Lieferadresse angezeigt werden (Sub Ressource)
URI: /order/{id}/shipping-address Methode: GET
Sub-Ressourcen
Sind Teil von Ressourcen und können nicht ohne diese existieren. Diese Abhängigkeit wird in der URL manifestiert (/order/{id}/shipping-address).
Listen
Für alle Primärressourcen (order) gibt es meist Listenressourcen (orders). Sie bekommen eine eigene URI (/orders) zugeteilt.
Filter
Kategorien von Primärressourcen können Zustände sein wie im Beispiel (shipped, canceled) aber auch alle Bestellungen einer Region oder eines Monats. Für Filter wird an die URI ein GET Parameter angehängt, es gibt keinen eigenen Endpunkt (/orders/?state=canclled), es sei denn der Zustand hat spezielle Funktionen, dann kann auch eine Ressource für alle Stornierungen angelegt werden: /cancelations.
Paginierung
Eine Paginierung ist immer notwendig, weil die Menge an übertragenden Daten den Speicher des Servers oder Client ansonten irgendwann sprengen kann. Die Paginierungsmenge kann vom Server festgelegt werden, z.B. 10 Bestellungen und der Client blättert nur durch das Ergbenis (/orders?page=2) oder es kann auch ein dynamisches System gebaut werden mit der der Client entscheidet, wie viele Daten er benötigt (/orders?offset=40&limit=20).
Projektionen
Um die Datenmenge sinnvoll zu begrenzen, werden nicht alle verfügbaren Informationen einer Ressource ausgegeben.
Aggregationen
Im Gegensatz zur Projektion werden Attribute von verschiedenen Primär- und Listenressourcen zusammengefasst, um die Anzahl der Client Requests zu begrenzen.
HTTP Verben
GET
Mit der HTTP GET-Methode können Informationen von Ressourcen abgeholt werden. Es werden keine Seiteneffekte durch GET erzeugt werden, d.h. serverseitig darf sich kein Zustand ändern durch diese Operation. GET ist safe und idempotent und damit cachefähig. Der meiste Traffic einer API wird über GET Requests generiert.
Safe
Safe bedeutet: Die Aufrufe könnne gecacht und vorberechnet werden ohne Auswirkung auf die Ressource.
Idempotent
Bedeutet: Mehrmaliges Ausführen führt immer zum selben Ergebnis.
HEAD
Ist GET sehr ähnlich, nur ohne den Body der Response, es werden nur die Metadaten zurückgegeben. Damit kann der Client auf ressourcenschonende Weise feststellen, ob eine Ressource existiert und wann eine Ressource geändert worden ist. HEAD ist safe und idempotent und damit cachefähig.
PUT
Mit PUT werden Ressourcen aktualisiert oder erzeugt, falls nicht vorhanden. Auch PUT ist idempotent, ein mehrmaliges Aufrufen führt also zu demselben Ergebnis ohne Seiteneffekte. Dieses verhalten ist notwendig, da der Client eine PUT Anfrage an den Server senden kann ohne eine Antwort zu bekommen. Wenn er nochmals versucht den Request zu senden, sollte entweder die Ressource angelegt oder die schon im vorherigen Request erfolgreich angelegte Ressource geupdatet werden.
POST
Im engeren Sinne bedeutet POST das Anlegen von Ressourcen, aber es kann im weiteren Sinne auch für alle Zwecke eingesetzt werden, in dem keine andere Methode passt. POST muss weder idempotent und auch nicht save sein und ist niemals cachefähig.
DELETE
Eine Ressource kann hiermit gelöscht werden. Mehrmaliges löschen einer Ressource führt zum selben Ergbenis.
Options
Liefert die HTTP Methoden, die eine Ressource unterstützt.
Der Unterschied zwischen POST und PUT
Folgendes Szenario soll die unterschiedliche Nutzung von POST und PUT verdeutlichen:
Eine Bestellung soll den Zustand ändern von created auf committed und es gibt noch mehr Zustände einer Bestellung.
Mit einem PUT Request auf die Ressource müsste man den gesamten Inhalt der Bestellung übermittelt werden: Wann wurde sie aufgenommen, wieviel Geld kostet die Bestellung inkl. des neuen Status.
Bei einem POST Request würde man einen eigenen Endpunkt für die Ressource bauen /orders/1234/commit und mit einem leeren POST Request den Zustandsübergang herbeiführen.
Die POST Variante ist hier zu bevorzugen, weil nicht die ganze Ressource übertragen werden soll.
Außerdem gibt es dann die Möglichkeit die möglichen Zustandsänderungen mittels HAL (siehe nächstes Kapitel) zu verlinken und so für den Client die Businesslogik der Bestellungszustände zu verdeutlichen.
HTTP Status Codes
Eine Übersicht über alle Codes gibt es hier.
Der Server sollte mit einem passenden Status Code antworten. Wichtig ist die grobe Einteilung im Auge zu behalten:
2xx – Erfolgreiche Operation
3xx – Umleitung
4xx – Client-Fehler
5xx – Server-Fehler
REST Ressourcen Struktur: HAL
Eine Ressource kann in REST in einem beliebigen Format abgebildet werden. Das populärste Fomat ist HAL, welches vor allem die Verlinkungen der Ressourcen untereinander standartisiert.
{
....
"_links": {
"self": { "href": "/orders" },
"curies": [{ "name": "ea", "href": "http://example.com/docs/rels/{rel}", "templated": true }],
"next": { "href": "/orders?page=2" },
"ea:find": {
"href": "/orders{?id}",
"templated": true
},
"ea:admin": [{
"href": "/admins/2",
"title": "Fred"
}, {
"href": "/admins/5",
"title": "Kate"
}]
},
}
REST und PHP
Mit PHP gibt es folgende Frameworks, die einem dabei unterstütze gute REST Webservices zu erstellen:
Caching
Expirations-Caching
Beispiel:
Request:
GET /orders
Response:
HTTP/1.1 200 OK Cache-Control: max-age=60 #oder Expires: Sun, 22 Jan 2017 11:11:11 GMT
Der Server teilt mit Hilfe des Cache-Control Headers mit, das die Antwort 60 Sekunden lang gültig ist / gecacht wird und der Client solange nicht anfragen braucht. Alternativ kann er auch ein Expires Zeitpunkt setzen, bis zu dem die Informationen aktuell bleiben. Dies setz voraus, dass es sinnvoll ist die Daten zu cachen bzw. vorzuberechnen. Möglich ist dies z.B. bei allen Bestellungen, die 2016 gemacht worden sind. Bei den aktuellen Bestellungen ist dies nicht zwangsläufig sinnvoll.
Validierungs-Caching
Request:
GET /orders If-Modified-Since: Sun, 22 Jan 2017 11:11:11 GM
Response:
HTTP/1.1 304 NOT MODIFIED
In diesem Fall entscheidet der Client, ob er eine Antwort benötig auf die Anfrage, wenn keine Änderungen vorliegen, seit einem bestimmten Datum. Der Server antwortet im Fall von nicht akutellen Daten mit einem Statuscode 304 ohne überflüssige Daten zu übertragen.
Der Cache-Control Header
Der Server kann mit Hilfe des Cache-Controls-Header dem Client mitteilen, wie clientseitig gecacht werden darf.
Dafür gibt es verschiedene Parameter:
max-age: die Dauer des Cachings in Sekunden
private: die Antwort darf nur im Client-Cache gespeichert, ist also User spezifisch
public: die Antwort darf auch in einem Shared-Cache gespeichert werden, ist nicht User spezifisch
s-max-age: die Dauer des Cachings in Sekunden für den Shared-Cache
no-store: bitte nicht cachen, bei Vertraulichen Daten
Caching-Systeme
Zum Cachen von HTTP Requests sollte man Varnish benutzen.
Security
Es sollte immer SSL und HTTPS verwendet werden.
Authentifizierung für private APIs
Basic Auth kann für private APIs schnell und einfach benutzt werden, dabei werden Username und Passwort in jedem Request mitgesendet. Es können auch sehr einfach LDAP oder NTLM Identity-Management Lösungen angeschlossen werden über den Webserver. Beispiel Apache Webserver mit LDAP.
Sichere Authentifizierung für öffentliche APIs
Mit Hilfe von Bearer Web Tokens Implementationen wie zum Beispiel JWT, kann man sehr einfach mit Clients kommunizieren, ohne das diese bei jedem Request Username und Passwort mitsenden müssen. Hierbei wird beim ersten Authentifizierungs-Request ein JWT-Token zurückgegeben, der bei jedem weiteren Reqeust als Authentifizierung benutzt werden kann. Der Hashing-Algorithmus kann geändert werden und den aktuellen und immer steigenden Sicherheitsansprüchen angepasst werden.
Dokumentations Tools
Swagger
Swagger ist ein geniales Tool um sehr schnell einfache API Dokumentationen zu erstellen. Man schreibt damit in einem swagger File die YAML oder JSON die Dokumentation und kann mit der Swagger Library diese dann in eine schicke Dokumentation verwandeln. Live Demo
Apigility von Zend unterstützt Swagger via Adapter
genauso wie Symfony 2 und Yii 2
Es gibt natürlich auch ein PHPStorm Plugin.
RAML
Raml ist ähnlich wie Swagger, bietet aber noch mehr, z.B. die Funktionalität eines Mock-Servers für das Testen von Applikationen. Es gibt auch ein PHPStorm Plugin.
Asynchrone Verarbeitung von Anfragen
Um Reqeusts mit einer längeren Verarbeitungsdauer zu handeln, kann man diese Jobs asynchron verarbeiten, d.h. sie werden entgegen genommen und sofort beantwortet. Die eigentliche Verarbeitung findet nachgelagert statt. Für diese Funktion gibt es verschiedene Möglichkeiten:
Notifikation mittels HTTP-Callback
Bei der Variante übermittel der Client dem Server eine URL im Body, bei der der Client benachrichtigt wird im Erfolgsfall. Dies sett voraus, dass keine Firewall dazwischen geschaltet ist.
Beispiel:
POST /orders { "notification-url" : "http:///client-ip/notify-me.php" }
Response:
HTTP/1.1 202 Accepted Job Accepted, will notify URL http:///client-ip/notify-me.php
Polling
Beim Polling fragt der Client eine bestimmte URL nach dem Ergebnis periodisch ab, bis das Ergebnis vorhanden ist.
Beispiel:
POST /orders
Response:
HTTP/1.1 202 Accepted Job Accepted, check URL http:///example.com/your-result
Diese URL kann dann angefragt werden.