Kategorien
Diverses PHP

Die REST Webservice Architur – ein Überblick mit PHP

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.