1. Aufbau eines REST Web-Service – Teil 3

    Stefan · 06.12.08, 18:30 Uhr · Konzepte, Tutorials · Tags: , , , , ,

    Um einen REST Web-Service aufzubauen, eignet sich vor allem ein Framework, das nach dem MVC Pattern arbeitet. Wir haben uns als Default PHP Framework schon vor einiger Zeit für das Zend Framework entschieden, das vor allem durch die vielen mitgelieferten Klassensammlungen hervorsticht, einem dabei allerdings nicht wie bei anderen Frameworks üblich, eine MVC-Nutzung aufzwingt.

    Grundaufbau/Klassenstruktur
    Bei einem Webservice gilt es, sich sehr viele Gedanken im Vorfeld zu machen, wie er aufgebaut sein muss, was überhaupt benötigt und wie erweiterbar er sein muss. Wir haben uns zwar für einen RESTful Webservice entschieden, halten uns aber die Möglichkeit offen, später, falls wirklich notwendig, mit möglichst geringem Aufwand z.B eine xml-rpc API oder einen SOAP-basierten Dienst zu erstellen.

    Um diesen Aufwand so gering wie möglich zu halten, setzen wir neben den MVC Dateien vor allem auf eine generelle Library, die das eigentliche Handling übernimmt. So gibt es dort allgemeine Artikelobjekte, die sich um die Ein- und Ausgabe von Artikeln kümmert; Projektobjekte, die api-key basierend die Artikelspezifikationen bereitstellt, um z.B. nur Artikel mit spezifischen Kriterien in sein Projekt/Feed zu übernehmen.

    Auf diese Library Objekte setzen nun unsere Models aus dem MVC auf, nach angeforderter URL und Request-Method die Entscheidungen fällt, was zu tun ist. In unserem Fall findet also keine Datenbankbindung direkt in den Models statt. Auf der Serverseite unseres Webserivce geben alle Models ein Zend_Http_Response zurück, das durch die Models bereits korrekt gefüllt wurde – in einem Fall nur ein Http-Statuscode, in anderen fällen ein durch DOMDocument generiertes XML. Da wir nur mit XML Daten auf Serverseite arbeiten, entfallen bei uns die Views. Die XML werden direkt in den Models generiert.

    Basisfunktionalität/HTTP-Responses
    Dadurch dass wir richtig RESTful arbeiten, sind auch unsere Controller sehr übersichtlich. Jeder Controller erweitert eine globale Controller-Klasse, die sich erst einmal selbständig im Constructor darum kümmert, ob der Request überhaupt authorisiert ist, sprich, ob ein API-Key vorhanden ist, wenn ja, dann binde das Userobjekt des authentifizierten Benutzers direkt mit an den Controller, da wir diese Userdaten öffter benötigen werden. Hier loggen wir auch das erste Mal den Zugriff des Users, um ein besseres Nutzungsverhalten unseres Services zu erhalten.

    Wenn der User nun nicht authorisiert ist, den Service zu nutzen, geben wir z.B. nur noch den HTTP Statuscode “401 Not Authorized” als Response zurück. Somit sparen wir uns einige KB Contentlength, die durch eine individuelle Fehlermeldung angefallen wären. Das ist dort nicht nötig.

    Je nach angeforderter URL wird jetzt im Controller zusätzlich noch das Projektobjekt hinzugeladen, das selbständig wieder überprüft, ob ein existentes Projekt des Users angefragt wurde (Wenn nicht HTTP-Status Code 404) und ob es nicht etwa noch auf einen weiteren Server Task wartet, also nicht verfügbar ist (HTTP-Status Code 503). Wenn alles passt, wir auch hier wieder der Zugriff geloggt. Daneben erhöhen wir einen projektspezifischen Counter, der die Anzahl der Requests pro Projekt in der Stunde mitzählt. Das ist notwendig, da wir die Zugriffe auf den Server limitieren, um eine ausreichend stabile Verfügbarkeit und Responsezeiten zu garantieren. Wir als Admins können die maximale Anzahl an erlaubten Requests individuell pro Projekt steuern, um z.B. Sonderfällen auch mehr Request zur Verfügung zu stellen. Ist das Maximum der Requests erreicht, geben wir den HTTP-Status Code 403 Forbidden zurück, dieser wird allerdings von einem XML Statement mit näherer Erläuterung begleitet.

    Da jetzt die Umgebung korrekt geladen ist, arbeiten wir jetzt die normalen Controller-Actions ab. Bei einer nicht unterstützten oder zugewieenen Request-Method geben wir den HTTP-Status Code “405 Method not allowed” zurück. Ab jetzt sprechen wir nur noch die einzelnen Models an und geben danach das darin generierte Response Objekt direkt zurück.

    Das ist eigentlich ein ganz einfaches Prinzip, was ohne großen Aufwand umzusetzen ist.

    Soviel loggen wie möglich
    Ein ganz wichtiges Detail bei dem Arbeiten mit Webservices, also keinen von uns selbst definierten Applikationen, ist das Mitloggen von Fehlern oder generelles beobachten der Useraktivitäten. Da so ein detailiertes Loggen durchaus auch viel Zeit kostet, bzw einfach unmengen an Daten sammelt, können wir dieses Loggen einmal Userspezifisch (also für alle Aktivitäten des Users) und einmal Projektspezifisch (de-)aktivieren. Wenn man also bei einem Projekt Supporten muss, kann man diese Logs einschalten und man enthält einen detailierten Überblick was gemacht wird. Große Fehler werden auch ohne das explizite Aktivieren der Logfunktion aufgezeichnet und noch zusätzlich per Mail an einen Admin geschickt.

    Ein typischer Logeintrag enthält neben den normalen Werten wie IP und Zeitpunkt noch eine eindeutige RequestId, die allen Einträgen des einen Requests zugeordnet werden, die aufgerufene Methode und entsprechend den Dateinamen + Zeilennummer, verbrauchter Speicher, ausgeführte Zeit und ein Customfeld, was da überhaupt gemacht wird und dann der Inhalt des Logs. Wir loggen z.B. sehr detailliert, so dass wir serialisiert ganze Objekte und Umgebungen in das Logfile schreiben, um später wirklich genau nachvollziehen zu können, was für Ergebnisse bei uns Produziert werden.

    Performance: Serverseitiges Caching
    Ein weiterer sehr wichtiger Punkt auf Serverseite ist ein mehrstufiges Cachingverfahren, um die Datenbankrequests möglich gering zu halten. Ganz ohne DB-Queries kommt man nicht aus, aber man kann sie minimieren. Gerade das Aufbereiten von umfangreichen XML Daten mittels DomDocument kostet viel Zeit und Speicher, den man sehr schön weg cachen kann. Am Anfang wird ein einzelner, schneller DB-Query getätigt, um das Änderungsdatum des Artikels zu erhalten. Ist dieses Datum älter als unser gecachtes XML-File, wird das gecachte XML-File zurückgegeben – wenn nicht, wird ein neues generiert. Je nach Komplexität der Datenbank spart uns das schon mal gerne 5 DB-Queries. Daneben cachen wir viele Sachen, wie Ergebnis-Ids von Suchanfragen, oder andere häufig benötigten Daten (z.B. Userobjekte, Projektobjekte etc) im MemCache – so können wir sehr schnell über viele Informationen verfügen.

    Performance: Verwendung von Job-Queues
    Ein weiterer Punkt, der vor allem die Performance betrifft ist, dass man möglichst viele Rechen-/Zeitintensive Aufgaben in Jobqueues setzt, so dass man nicht sofort parallel alles abarbeitet, sondern nur so viele Jobs parallel bearbeitet, dass die Auslastung des Servers nicht zu hoch wird. Das sollte man vor allem bei Bild-/Videoverarbeitung machen oder aber beim Sammeln von externen Informationen über Fremd-XML-Feeds. Asynchrones Abarbeiten von Tasks ist ein immens wichtiger Teil, um eine Website auch auf Dauer performant zu halten.

    ältere Artikel:
    Aufbau eines REST Web-Service – Teil 2
    Grundsätzliche Überlegungen für einen Web-Service – Teil 1

  2. Kommentar schreiben

    XHTML: Du kannst diese Tags nutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Gehe zur polyCODER Startseite