Mais conteúdo relacionado Semelhante a Web Services leicht gemacht (20) Mais de Daniel Haller (6) Web Services leicht gemacht1. Web Services leicht gemacht
REST-basierte Schnittstellen mit dem Symfony-Framework implementieren
Im Web 2.0 verfügen praktisch alle großen Anbieter über eine API. Application Programming Interfaces
ermöglichen anderen Applikationen den kontrollierten Zugang zu den eigenen Daten und bilden so die
Grundlage für Mash-Ups oder Third-Party-Applikationen. Das PHP-basierte MVC-Framework Symfony
macht es einfach, derartige Schnittstellen effizient und sauber zu entwickeln. Dieser Artikel beleuchtet
den Aufbau von solchen Application Programming Interfaces und demonstriert, wie sie sich mit Hilfe des
Symfony-Frameworks implementieren lassen. ↘ Daniel Haller
REST-basierte APIs spielen in den letzten Jahren eine immer wich-
tigere Rolle im Web 2.0. Mit Hilfe einer derartigen Schnittstelle
können Anbieter von Webdiensten anderen Anwendungen Daten
in kontrolliertem Umfang bereit stellen und sich auf diese Weise
Zielgruppen jenseits der eigenen Website erschließen. APIs sind so
zu einem sehr wichtigen Erfolgsfaktor geworden – man denke nur
an Twitter oder Facebook und deren Ökosystem von Third-Party-
Applikationen.
Mit Hilfe des Symfony-Frameworks [1] lassen sich derartige APIs
auf Basis von REST [2] sehr effektiv umsetzen. Dies lässt sich am
besten anhand eines beispielhaften Anwendungsfalls verdeutli-
chen: Ein fiktiver Buchhändler stellt eine Schnittstelle bereit, über
den sich Informationen zum Angebot abfragen lassen und mit der
Verleger mit Hilfe eines dedizierten Clients neue Bücher direkt in
der Datenbank des Händlers speichern können. Symfony ist modular aufgebaut, einzelne Komponenten lassen sich auch ei-
genständig verwenden.
Die Grundlagen von REST
Die API in Symfony
Eine REST-basierte Schnittstelle besteht aus Ressourcen, die durch Die Bestandteile einer REST-API lassen sich gut in Symfony abbil-
URIs adressierbar sind und in verschiedenen Repräsentationen dar- den: Module beziehungsweise deren Actioncontroller stellen die
gestellt werden können. Ressourcen sind Informationen, die eigen- Ressourcen dar, die sich mit Hilfe des Symfony-Routings beliebig
ständig referenziert werden sollen, beispielsweise Texte, ein adressieren lassen. Dank des MVC-Patterns kann für jede ge-
Benutzerprofil oder eine Suchergebnisseite. Deren Repräsentation wünschte Repräsentation ein eigenes Template definiert werden.
erfolgt meistens im XML-Format, da dieses Format die gewünschte Das Auslesen des Bücherbestands ist der wohl einfachste An-
Interoperabilität zwischen verschiedenen Systemen oder Plattfor- wendungsfall: Der Request an einen URI liefert eine Liste mit Bü-
men noch am besten sicherstellen kann. Andere Formate wie JSON chern. Im Grunde unterscheidet sich dieser Anwendungsfall vom
sind aber ebenfalls denkbar. Ressourcen lassen sich, wie auch im herkömmlichen Abruf im Browser nur durch die Repräsentationen
Web, miteinander verlinken. der Response, die im XML- anstatt im HTML-Format dargestellt wird.
Der Zugriff auf die API erfolgt ganz einfach durch das HTTP- Dem Routing-System von Symfony kommt bei der Umsetzung
Protokoll. Die verschiedenen Methoden des Protokolls codieren die einer REST-API eine wichtige Rolle zu. Durch die Routing-Regeln
Operationen auf der Anwendungsebene. Wird beispielsweise per wird in erster Linie festgelegt, welchen URI eine Ressource be-
GET auf eine Ressource zugegriffen, weiß der Server, dass ein Lese- kommt, mit welcher HTTP-Methode darauf zugegriffen werden darf
vorgang durchgeführt werden soll – derselbe Request mit der und welcher Actioncontroller den Request verarbeitet.
DELETE-Methode würde die Ressource hingegen löschen (mehr
dazu unter [3] ). Eine einfache Routing-Regel
booklist: # Darstellung einer Liste mit dem Buchbestand
url: /booklist.:sf_format
Tools zum Testen von REST-APIs class: sfRequestRoute
REST-basierte Schnittstellen lassen sich nicht direkt im Browser ent- param: { module: book, action: list, , sf_format: html }
wickeln oder testen, da sich die HTTP-Methoden für den Request nicht requirements: { sf_method: GET, sf_format: (?:xml|json|html) }
festlegen lassen und auch kein direkter Zugriff auf weitere Parameter
Listing 1
des HTTP-Requests besteht. Abhilfe schafft das Programm
„RESTClient“ (http://bit.ly/RESTClient) oder die Firefox-Erweiterung
„RestTest“ (http://bit.ly/RESTTest). Im Zusammenhang mit REST-APIs spielt insbesondere der sf_for-
mat-Parameter eine wichtige Rolle. Durch ihn wird das Format
1 © yeebase media 2009. Veröffentlichung und Vervielfältigung nur mit Genehmigung der yeebase media GbR. http://t3n.yeebase.com
2. bestimmt, in dem die Ressource an den Client ausgeliefert wird. Der bere Trennnung zu den Front- und Backend-Applikationen zu ha-
Standardwert ist HTML, erlaubt sind in diesem Beispiel aber auch ben, die in fast jedem Symfony-Projekt vorhanden sind.
XML oder JSON. Alternativ lässt sich das Request-Format auch direkt Eine neue API-Applikation samt Book-Modul ist dank der Kom-
in der Action festlegen (Listing 2), allerdings ist es eleganter, das mandozeilentools schnell angelegt:
direkt über den URI zu tun.
API-Applikation genieren
Request-Format im Controller setzen $ symfony generate:app api
$ symfony generate:module api book
$request->setRequestFormat('xml');
Listing 3
Listing 2
Für das Book-Modul das Scaffolding zu verwenden, ist durchaus
Durch den sf_method-Parameter wird festgelegt, mit welchen Me-
möglich, aber nicht unbedingt sinnvoll: Es werden dann zwar alle
thoden eine Ressource aufgerufen werden kann. Dies kann beson-
nötigen Methoden im Actioncontroller angelegt, diese gehen aber
ders bei komplexeren APIs praktisch sein – zum Beispiel lässt sich
davon aus, dass die Daten zum Anlegen oder Ändern von Daten-
so der Request für eine Ressource per DELETE-Methode unterbin-
sätzen von den ebenfalls generierten (HTML-)Formularen in den
den.
Templates stammen. Für die API allerdings müssen diese stattdes-
Für komplexere Requests mit mehr Parametern ist es darüber
sen aus den HTTP-Bodys extrahiert werden, wo sie im XML-Format
hinaus sogar möglich, bereits im Routing eine Validierung der GET-
an die Schnittstelle übertragen werden.
Parameter per regulärem Ausdruck vorzunehmen.
Bei einer dedizierten Applikation für die API kann auch die View-
Konfiguration geändert werden (Listing 4): Ein Layout wird jetzt
nicht mehr benötigt, da die Templates aus reinem XML bestehen.
Außerdem wird auch der standardmäßige Content-Type ange-
passt.
Anpassungen an der view.yml für die API
default:
http_metas:
content-type: text/xml
has_layout: off
Listing 4
Symfony und das Doctrine-ORM stellen für das Routing einen nütz-
lichen Shortcut bereit:
Der Aufbau von Symfony gliedert sich in eine Plattform und das darauf aufset-
zende Framework. Shortcut in der routing.yml
book:
Die Bücherliste kann außer im HTML-Format auch als XML- oder class: sfDoctrineRouteCollection
options: { model: Book }
JSON-Repräsentation angefordert werden. Dazu müssen für jedes
Format eigene Templates angelegt werden und Symfony wählt auf Listing 5
Basis des gesetzten sf_request-Parameters das Passende aus. Aus-
schlaggebend ist hierbei die Dateiendung: listSuccess.php für eine Diese Regel wird zu einer Reihe REST-konformer Routen aufgelöst,
standardmäßige HTML-Repräsentation, listSuccess.xml.php für ähnlich wie jener in Listing 1, die man sich in der Zusammenfassung
XML und listSuccess.json.php für JSON. auch durch die Kommandozeile anzeigen lassen kann:
Symfony liefert standardmäßig nur die HTML-Repräsentation
mit einem Layout aus, das bei JSON oder XML natürlich entfällt. Routen in der API
Außerdem kümmert sich das Framework selbstständig um den kor- $ symfony app:routes
rekten Content-Type (text/xml bzw. text/x-json) im HTTP-Header. >> app Current routes for application
Name Method Pattern
book GET /book.:sf_format
Komplexer: Datenmanipulation book_new GET
book_create POST
/book/new.:sf_format
/book.:sf_format
book_edit GET /book/:id/edit.:sf_for
Mit den beschriebenen Funktionen hat man im Prinzip bereits alles book_update PUT /book/:id.:sf_format
zusammen, was man braucht, um eine sehr einfache API zum Aus- book_delete DELETE /book/:id.:sf_format
lesen und Bereitstellen von Daten zu implementieren. Anspruchs- book_show GET /book/:id.:sf_format
voller wird es jedoch, wenn die API nicht nur Daten auslesen, Listing 6
sondern auch ändern, löschen oder erstellen soll. Ein weitaus kom-
plexeres Anwendungsszenario ist daher die Implementierung einer Diese Routen verweisen auf die jeweiligen Methoden (new, create,
Schnittstellenfunktion, mit der ein Verleger neue Bücher direkt aus edit, update usw.) im Actioncontroller des Book-Moduls, die später
dem eigenen Warenwirtschaftssystem in das Shop-System über- implementiert werden müssen.
tragen kann. Aus einer „lesenden“ wird so eine „schreibende“ API. Für den Einsatz innerhalb einer API sollten die „Edit“- und
Je umfangreicher eine API wird, desto sinnvoller kann es sein, „New“-Methoden nicht implementiert werden beziehungsweise
diese in eine eigenständige Applikation auszulagern um eine sau- eine Fehlermeldung werfen, da eine GET-Methode niemals in der
© yeebase media 2009. Veröffentlichung und Vervielfältigung nur mit Genehmigung der yeebase media GbR. http://t3n.yeebase.com 2
3. Lage sein sollte, Daten auf einem Server in irgendeiner Weise zu koppelt dabei ein Datenbankobjekt direkt mit dem ID-Parameter in
ändern (oder zu erstellen). Das ist ausschließlich den idempotenten der URL, sodass man im Controller darauf zugreifen und sich eigene
Methoden PUT, POST und DELETE vorbehalten. Queries weitgehend sparen kann. Wird kein Datensatz mit der ge-
wünschten ID gefunden, wirft Symfony einen 404-Fehler. Die Im-
Authentifizierung durch API-Keys plementierung der Controller-Methoden geht daher schnell von
der Hand. Um die Dateilansicht eines Buchs anzuzeigen, reicht eine
Natürlich sollen nur autorisierte Benutzer Datensätze erstellen oder
Zeile im Controller:
ändern dürfen. Bevor also eine Aktion ausgeführt wird, muss sich
der Benutzer zunächst gegenüber der API authentifizieren. Eine
Detailansicht eines Buchs
Möglichkeit dazu ist die Verwendung von API-Keys. Der API-Key
kann im HTTP-Header (zum Beispiel als „X-ApiKey“) mitgeschickt // HTTP-Request: GET /book/[id] HTTP/1.1
public function executeShow(sfWebRequest $request) {
werden und dient als Ersatz für die übliche Benutzernamen/Pass-
$this->book = $this->getRoute()->getObject(); // $this->book is now available
wort-Kombination (sinnvollerweise sollten Requests an die API in in the template
diesem Fall per HTTP/S gesichert werden). Die Authentifizierung }
kann in Symfony als Filter (Listing 7) implementiert werden, der vor Listing 9
dem Abarbeiten des eigentlichen Requests im Controller durch-
laufen wird und in der filter.yml aktiviert werden muss. Ein Request für denselben URI mit der DELETE-Methode wird REST-
konform zur Delete-Methode geroutet:
API-Filter Klasse
class apiFilter extends sfFilter { Ein Buch löschen
public function execute($filterChain) {
if ($this->isFirstCall()) { // HTTP-Request: DELETE /book/[id] HTTP/1.1
$request = $this->getContext()->getRequest(); public function executeDelete(sfWebRequest $request) {
$session = $this->getContext()->getUser(); $book = $this->getRoute()->getObject();
// Get API-Key from HTTP-Header // User allowed to delete this Book? If not, stop execution and send HTTP-
$apikey = $request->getHttpHeader('X-ApiKey'); Statuscode 403
// Select user from database by given API-Key $this->checkPermission($book);
$userprofile = Doctrine::getTable('User')->findOneByApikey($apikey); // ...User has Permission, let's delete this Book
// Throw an error if user cannot be found... $book->delete();
jeHTTPError::forwardUnless($userprofile, 401, 'Invalid or missing API-Key'); }
// ...or save the user in the session
$session->setAttribute('user',$userprofile); Listing 10
}
// Execute next filter in filter symfonys filter chain
$filterChain->execute();
Mit den POST- und PUT-Methoden wird ein neues Buch angelegt
// Code to execute AFTER the action execution, before the rendering beziehungsweise ein bestehendes Buch geändert. Die dazu erfor-
// Delete User-Session after every API-Request derlichen Daten werden als XML im HTTP-Body übertragen und
session_unset();
session_destroy(); müssen daher ausgelesen und geparst werden. Hierzu kann man
} im lib-Verzeichnis in einer Klasse eine einfache Methode (Listing 11)
}
implementieren, die bei Gebrauch durch den Autoload-Mechanis-
Listing 7 mus von Symfony inkludiert wird.
Dieser Filter lädt zum einen das Benutzerobjekt anhand des API- Auslesen der XML-Nutzlast
Keys im Header aus der Datenbank und speichert es in der Symfony-
class Tools {
Benutzersession, sodass es überall im Code zur Verfügung steht. Ist public static function getRequestBody () {
der API-Key nicht korrekt, wird stattdessen eine Fehlermeldung mit $stream = fopen("php://input", "r");
$data = stream_get_contents($stream);
entsprechendem HTTP-Statuscode gesendet. Zum anderen muss fclose($stream);
die Session wieder gelöscht werden, nachdem der Request abge- return simplexml_load_string($data);
arbeitet wurde. REST ist per Definition zustandslos, das heisst eine }
}
„klassische“ Session wird nicht gespeichert.
Für das Error-Handling kann wie in Listing 7 das jeHTTPError- Listing 11
Exception-Plugin genutzt werden, welches das Symfony-Error
Handling um die Fähigkeit erweitert, beliebige HTTP-Statuscodes Ein neues Buch lässt sich ebenfalls sehr einfach erzeugen:
und Fehlermeldungen in der Response zurückzugeben. Gerade im
Zusammenhang mit REST ist das eine praktische Sache. Das Plugin Ein neues Buch anlegen
lässt sich mit Hilfe des CLI schnell installieren: // HTTP-Request: POST /book HTTP/1.1
public function executeCreate(sfWebRequest $request) {
Plugin installieren $data = Tools::getRequestBody();
$book = new Book();
$book->saveBook($data); // Save Book in the Model
$ symfony plugin:install jeHTTPErrorExceptionPlugin
$this->getResponse()->setStatusCode(201);
$this->getResponse()->setHttpHeader('Location',
Listing 8
$request->getHost().'/book/'.$book->get('id'));
}
Der Controller des Book-Moduls Listing 12
In Listing 5 werden Routen auf die Index-, Show-, Create-, Update-,
und Delete-Methoden des Book-Controllers erzeugt, die imple- In dieser Methode werden die Daten zunächst aus dem Request-
mentiert werden müssen. Die sfDoctrineRouteCollection-Klasse Body extrahiert und an das Model übergeben, wo das neue Buch
3 © yeebase media 2009. Veröffentlichung und Vervielfältigung nur mit Genehmigung der yeebase media GbR. http://t3n.yeebase.com
4. dann angelegt wird. Wichtig ist, dem Client im Response per HTTP-
Statuscode mitzuteilen, dass die Ressource erzeugt wurde („201 –
Created“) und deren (neue) URI im Location-Header anzugeben.
Analog dazu funktioniert das Editieren eines Buchs:
Ein bestehendes Buch editieren
// HTTP-Request: PUT /book/[id] HTTP/1.1
public function executeUpdate(sfWebRequest $request) {
$data = Tools::getRequestBody();
$book = $this->getRoute()->getObject();
// User allowed to edit Book? If not, stop execution, send HTTP-Statuscode 403
$this->checkPermission($book);
$book->saveBook($data);
}
Listing 13
Für alle genannten Methoden müssen natürlich auch die jeweiligen
(XML-)Templates angelegt werden, um einem Client das ge-
wünschte Feedback – zum Beispiel in Form einer Statusmeldung –
geben zu können. Für die Validierung der Benutzerdaten bringt
Symfony eine Reihe von Standard-Validatoren mit, die allerdings
primär zur Verwendung mit dem Formular-Framework gedacht
sind. Noch einfacher lassen sich alternativ die Validatoren des Doc-
trine-ORMs [4] verwenden, die man bereits in der Schema-Datei
konfigurieren kann und die die Daten noch auf der Anwendungs-
ebene unmittelbar vor dem Speichern in der Datenbank validieren.
Zusammenfassung
Mit Hilfe des Symfony-Frameworks lassen sich REST-basierte
Schnittstellen sehr sauber und vergleichsweise einfach implemen-
tieren. Dieser Artikel zeigt nur einen von vielen Ansätzen, der sich
aber grundsätzlich auch für umfangreiche APIs eignet – selbst wenn
für den produktiven Einsatz noch einige Aspekte tiefer ausgearbei-
tet werden müssten. ↔
Links und Literatur ↗ Softlink 2426
[1] Offizielle Website des Symfony-Projekts: http://www.symfony-
project.org/
[2] Wikipedia-Eintrag zu REST: http://de.wikipedia.org/wiki/
Representational_State_Transfer
[3] OIO: REST Web Services: http://www.oio.de/public/xml/rest-
webservices.pdf
[4] Doctrine Data Validation: http://www.doctrine-project.org/
documentation/manual/1_1/en/data-validation
Der Autor
Daniel Haller ist bei der Frankfurter Agen-
tur BlueMars als Technischer Projektma-
nager und Web Developer tätig. Mit REST-
basierten APIs hat er sich im Rahmen seiner
Diplomarbeit zum Media System Designer
in Darmstadt beschäftigt. Er ist begeister-
ter Anwender des Symfony-Frameworks.
© yeebase media 2009. Veröffentlichung und Vervielfältigung nur mit Genehmigung der yeebase media GbR. http://t3n.yeebase.com 4