REST, WCF (Windows) und PHP (Linux/Apache2) mit JSON Kodierung – Teil 1
Dezember 22, 2009 in Development, Tutorial von SeveQ
Ich habe einige Zeit gesucht, bis ich die richtigen Antworten hatte. Aber auf welche Frage? Nun, wie lasse ich eine in C#/.NET3.5 entwickelte Anwendung mit einem Webserver kommunizieren, ohne die ganze Kommunikationsschicht selber bauen zu müssen? Die kurze Antwort darauf lautet: WCF, REST und JSON. Die lange Antwort möchte ich hier nun auch in Form eines Tutorials geben.
Im ersten Teil dieses Tutorials werde ich die Erstellung eines REST Services unter PHP erklären. Der zweite Teil folgt später und wird die Erstellung eines Clients in C# beinhalten.
Kurzer Exkurs zu REST (vereinfacht)
Was ist denn REST überhaupt? REST ist die Abkürzung für Representational State Transfer. Es ist eine Spezifikation für die Kommunikation mit Diensten in sogenannten Hypermedia-Informationssystemen wie das World Wide Web. Die Kommunikation findet dabei über HTTP statt. Aufrufe von Funktionen REST implementierter Dienste werden dabei in der aufgerufenen URL kodiert. Beispielsweise sieht die URL für den Aufruf einer Funktion namens “foo” möglicherweise folgendermaßen aus: http://www.meinserver.de/rest/foo
Aufrufparameter, sofern erforderlich, werden dabei im Inhalt des HTTP Requests übermittelt. Aus diesem Grund sollten serverseitig Funktionen, die Argumente erfordern, so implementiert sein, dass sie über die HTTP Methode “POST” (statt standardmäßig “GET”) erreicht werden. Näheres dazu weiter unten. Man kann Aufrufparameter auch über die HTTP Methode “GET” übermitteln, sie landen dann in der URL in der Form
<url>?parameter1=wert1¶meter2=wert2
und so weiter. Ist im Endeffekt Geschmackssache, wirkt sich aber auf die Implementierung sowohl der Funktion serverseitig als auch des Interfaces clientseitig aus. Ich beschreibe in diesem Blogpost nur die Vorgehensweise bei der Implementierung mit HTTP Methode POST für Funktionen mit Parametern (ohne Parameter ist eigentlich immer GET).
Wer ist dieser JSON?
JSON (JavaScript Object Notation) ist eine Syntax, mit der Daten, Objekte, Variablen in Textform über beispielsweise das Web übermittelt werden können. Näheres dazu findet man bei Wikipedia. Das Format selber ist für dieses Tutorial nicht wichtig, da WCF und PHP so gut wie alles selber machen und hier auch mal sehr kompatibel erscheinen.
Den Apache vorbereiten
Damit wir überhaupt mit REST arbeiten können, müssen wir dem Apache noch sagen, dass er alle Anfragen, die auf nicht existierende Dokumente führen, auf eine PHP Datei umlenken soll. Standardmäßig wird das die index.php sein. Um ihm dies zu sagen, benötigen wir im Document Root der Webanwendung eine Datei namens “.htaccess”. Diese muss folgendes beinhalten:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-lRewriteRule .* index.php [L]
Außerdem muss das Modul mod_rewrite aktiviert sein. Näheres dazu kann man aber auch ergooglen. Was macht nun diese Datei? Sie sagt dem Apache, dass alles (“.*”), was nicht (“!”)
- eine existierende Date (“-f”)
- ein existierendes Verzeichnis (“-d”)
- ein existierender Symlink (“-l”)
ist, auf die index.php umgelenkt werden soll. Außerdem aktiviert es in der ersten Zeile die Rewrite Engine natürlich.
Der REST Server
Als REST Server verwende ich eine PHP Anwendung auf einem Apache/Linux System. Ein REST Server ist relativ schnell implementiert. Wir brauchen eine Weichen-Funktion, die bei jedem Aufruf des PHP Dokuments (ich verwende hier ausschließlich die index.php; andere Namen sind natürlich möglich, dann muss aber der Eintrag in der .htaccess Datei entsprechend angepasst werden) ausgeführt wird:
<?php function RESTServer() { $callback = NULL; if(preg_match('/rest\/([^\/]+)/', $_SERVER['REQUEST_URI'], $m)) { if(isset($GLOBALS['RESTmap'][$_SERVER['REQUEST_METHOD']][$m[1]])) { $callback = $GLOBALS['RESTmap'][$_SERVER['REQUEST_METHOD']][$m[1]]; } } else { return FALSE; } if(is_callable($callback)) { $data = NULL; if($_SERVER['REQUEST_METHOD'] == 'GET') { $data = $_GET; } else if ($tmp = file_get_contents('php://input')) { $data = json_decode($tmp); } header("{$_SERVER['SERVER_PROTOCOL']} 200 OK"); header('Content-Type: application/json'); print json_encode(call_user_func($callback, $data)); } else { header("{$_SERVER['SERVER_PROTOCOL']} 404 Not Found"); } return TRUE; } ?>
Diese Funktion wird bei jedem Aufruf des Dokuments ausgeführt. Sie holt sich die aufrufende URL (wie oben erwähnt: aufgrund der Umleitung in der .htaccess Datei landet jeder Aufruf, der nicht direkt ein existierendes Dokument anspricht, auf der index.php) und extrahiert daraus, sofern in URL und auch in der Function-Map (siehe weiter unten) vorhanden, die aufgerufene REST Funktion. Stimmt die URL nicht mit der Konvention “<url>/rest/function” überein, so kehrt die Weichen-Funktion mit einem FALSE zurück. Hat die URL jedoch das entsprechende Format, so schaut die Funktion in die Function-Map, ob sie zur HTTP Methode und zum Funktionsnamen einen Eintrag findet. Konnte in der Function-Map kein entsprechender Eintrag gefunden werden, so sendet die Weichen-Funktion ein HTTP 404 (nicht vorhanden) an den Client, der entsprechend reagiert.
Die Function-Map
Was aber ist nun diese ominöse Function-Map? Im Grunde ist es nichts anderes als ein mehrdimensionales Array, das folgendermaßen aussieht:
<?php /******************************************************************************** REST function mappings for access via HTTP GET method *********************************************************************************/ $GLOBALS['RESTmap'] = array(); $GLOBALS['RESTmap']['GET'] = array( ); $GLOBALS['RESTmap']['POST'] = array( ); $GLOBALS['RESTmap']['PUT'] = array( ); $GLOBALS['RESTmap']['DELETE'] = array( ); ?>
Wir legen also eine globale Variable mit dem Namen RESTmap als Array an. Sie erhält vier Einträge, jeweils einen für die entsprechende HTTP Aufrufmethode GET, POST, PUT und DELETE. PUT und DELETE lasse ich außen vor, beziehe mich in diesem Text nur auf GET und POST.
Jeder Eintrag des Arrays ist wiederum selbst ein assoziatives Array (d.h. mit Namen als Index, statt Nummern). Um nun eine PHP-Funktion beispielsweise mit dem Namen “get_Foo” über REST erreichbar zu machen, muss man sie folgendermaßen in diese Map eintragen:
<?php $GLOBALS['RESTmap']['GET'] = array( 'foo' => 'get_Foo', ); ?>
Ein Aufruf der URL “http://blahblubb/rest/foo” führt also nun dazu, dass eine PHP-Funktion namens get_Foo aufgerufen wird. Ihr Rückgabewert wird in JSON kodiert an den Aufrufer zurückgegeben. Man muss also dafür sorgen, dass die Daten, die man gerne clientseitig verarbeiten würde, in Form eines Objektes oder eines Arrays oder einer Variablen per return zurückgegeben werden. Die Kodierung übernimmt unsere oben beschriebene Weichen-Funktion. Eine beispielhafte Implementierung der foo-Funktion:
<?php function foo() { return "Hallo Welt!"; } ?>
PHP kodiert den Rückgabewert dann am Ende der Weichen-Funktion in einen JSON Term und sendet ihn an den Client zurück (ein Browser würde ihn also anzeigen oder eventuell, aufgrund des Content-Types, auch als Download anbieten; jedenfalls reagiert mein FireFox mit einem Downloadfenster auf solch eine Antwort).
Soweit zum serverseitigen Teil der Verbindung.
Die Implementierung des Clients folgt in Teil 2



Hallo,
wann kommt Teil 2?
gruß LLPeter
Tja, sollte eigentlich schon laaaange mal fertig sein. Aber irgendwie bin ich davon immer wieder abgekommen. Ich hab demnächst Semesterferien, da werd ich mich darum mal kümmern. Danke für den Tritt in den Allerwertesten. Sowas brauch ich ab und an mal.