Benutzer-Werkzeuge

Webseiten-Werkzeuge


schule:rest_in_15_minuten

REST in 15 Minuten

Bei REST basierten Anwendungen handelt es sich um einfache Versionen von Webservices. REST steht für REpresentational State Transfer. Dahinter verbergen sich verschiedene Regeln, die sich stark an etablierte Konzepte von HTTP anlehnen. Da es eine sehr lose und einfache Spezifikation ist, kann sie jeder einfach umsetzen und, weil sich die Spezifikation stark an HTTP anlehnt, ist die Umsetzung mit vielen unterschiedlichen Programmiersprachen möglich. Ich habe mich entschieden, das ganze einmal in Python zu testen. Dafür habe ich das Webframework Bottle verwendet. Eine weitere und einfachere Möglichkeit bietet das Paket hug.

Ein zentrales Element in REST und auch HTTP sind die Ressourcen, die über eine URI bzw. URL erreichbar sind. Das können Datenbankobjekte oder Dienste sein. Bei der Umsetzung von REST mit HTTP werden die unterschiedlichen Methoden von HTTP genutzt: GET zum Abrufen und POST zum Speichern und Erstellen von Daten. Wie das Ergebnis aussehen soll, kann zusätzlich in den Headerinformationen des HTTP requests eingetragen werden.

Quelltext des Webservices

Als Beispiel habe ich eine kleine Zitatesammlung gewählt, die aus einer Liste von Zitaten besteht. Es können neue Zitate hinzugefügt und die bestehenden Zitate angezeigt werden. Schauen wir uns den Quelltext etwas genauer an. Ich habe ihn in der Datei rest.py abgelegt.

rest.py
# coding=UTF8
# ^^^^^^^^^^^ 
# Mit der ersten Zeile legen wir die Kodierung der Quelltextdatei fest. Dies
# ist nötig, damit wir hier auch deutsche Umlaute benutzen können.
 
# Die Anwendung wurde mit dem Bottle-Framework (http://bottlepy.org/)
# erstellt. Dies ist ein einfaches Webframework für Python Anwendungen. Wir
# importieren zunächst die notwendigen Methoden.
from bottle import route, run, debug, request, response
 
# In dem Dictionary zitate sollen Zitate gesammelt werden. Jedes Zitat ist
# über eine ID erreichbar: {0: "Zitat 1", 1 : "Zitat 2", ...}
zitate = {}
 
# Mit route wird eine Ressource festgelegt. Dies ist der Teil, der hinter der
# Adresse des Hostnamens folgt. Eine Anfrage wird normalerweise über ein
# GET-request entgegen genommen. In diesem Fall wird eine Liste aller Zitate
# ausgegeben. Es ist guter Stil, den Namen des Ressource jeweils im Plural zu
# verwenden.
@route('/zitate', method='GET')
def zitate_liste():
    # Über den Header können wir prüfen, welche Form der Ausgabe der Client
    # erwartet. Da wir bei REST nur von Repräsentationen ausgehen, können die
    # Inhalte unterschiedlich dargestellt und damit auch angefordert
    # werden. Wir unterstützen HTML und Json als Formate.
    if request.get_header('Accept') == "application/json":
        return zitate
    else:
        # Wenn kein besonderes Format angefordert wurde, wird HTML
        # ausgegeben. Wir stecken die Zitate in eine Tabelle und geben diese
        # zurück.
        html = "<html><body><h1>Zitate</h1><table border='1'>"
        html += "<tr><td>Id</td><td>Zitat</td></tr>"
        for k  in zitate:
            html += "<tr>"            
            html += "<td>" + str(k) + "</td><td>" + zitate[k] + "</td>"
            html += "</tr>"
        html += "</table></body></html>"
        return html
 
# Ein bestimmtes Zitat kann über seine Nummer angesprochen und einzeln
# ausgegeben werden. Der Parameter <nr> in der URL wird an die Methode
# übergeben.
@route('/zitate/<nr>')
def zitate_get(nr):
    zid = int(nr)
    if zid in zitate:
        return zitate[zid]
    else:
        # Die Nummer und damit das Zitat wurden nicht gefunden.
        # Über den Statuscode melden wir den Misserfolg an den Client. 
        # Status 404: Not Found
        response.status = 404
        return "Zitatnummer nicht vorhanden: " + nr
 
# Damit wir etwas für die Anzeige haben, müssen auch neue Zitate eingetragen
# werden können. Diese geschieht über die HTTP Post-Methode. Die Daten, also
# der Text des Zitates, wird über den Body des requests übertragen. In der
# Antwort wird der Client über die Adresse der neuen Ressource
# informiert. Zusätzlich setzen wir die neue Adresse im Headerfeld Location.
# Schließlich wird der Status der Antwort auf 201 (Created) gesetzt. Damit die
# Methode nur auf POST reagiert, wird die route entsprechend konfiguriert.
@route('/zitate', method='POST')
def zitat_erstellen():
    # Die ID für das nächste Zitat wird bestimmt. Wenn es noch keine Zitate
    # gibt, ist es die 0, ansonsten wird die größte ID um 1 erhöht.
    if len(zitate) == 0:
        neue_id = 0
    else:
        neue_id = max(zitate.keys()) + 1
 
    zitate[neue_id] = request.body.readline()
    response.set_header("Location", request.url + "/" + str(neue_id))
    response.status = 201 # 201: Created
 
    # Als Ergebnis geben wir die komplette URI des neuen Zitates zurück
    return request.url + "/" + str(neue_id)
 
# Anmerkung: Komplexe Einträge wie z.B. ein Zitat mit Informationen über den
# Autor und das Jahr der Verwendung werden nicht als Text, sondern als Json-
# oder XML-Inhalt übermittelt. Es sind aber auch binäre Daten wie Bilder oder
# MP3-Dateien möglich.
 
# 
# START des Webservers auf Port 8000.
#
 
debug(True)
run(port=8000, reloader=True)  

Aufruf des Webservices

Nun können wir testweise auf den Web-Service zugreifen. Ich benutze hierfür das Kommandozeilentool cURL, da es viele Freiheiten bei der Erstellung des requests und dem Auslesen der Antwort bietet. Zunächst rufen wir die Adresse einfach nur auf. Mit der Option -i werden die Headerinformationen zusätzlich angezeigt.

$ curl localhost:8000/zitate;
<html><body><h1>Zitate</h1><table border='1'><tr><td>Id</td><td>Zitat</td></tr></table></body></html>

Ohne weitere Angaben wird HTML als Ausgabeformat produziert. Dies könnte ein Browser sogar anzeigen. Da wir noch keine Zitate hinzugefügt haben, zeigt die Ausgabe eine leere Tabelle.

Probieren wir es nun mit einer anderen Variante und fordern Json als Ausgabeformat an. Die URI ist die gleiche, denn es ist ja die gleiche Ressource, auf die ich zugreifen möchte. Lediglich die Darstellung soll eine andere sein. Daher müssen wir ein Headerfeld entsprechend konfigurieren. Mit der Option -H können wir dies tun.

$ curl -H "Accept:application/json" localhost:8000/zitate;
{}

Nun wird Json als Rückgabeformat verwendet. Im Moment ist unser Ergebnis noch eine leere Zitatesammlung.

Wir können aber nicht nur Daten ausgeben lassen, sondern auch setzen oder erstellen. Dazu muss eine HTTP POST Methode aufgerufen werden. Wir nehmen diesmal die gleiche Ressource - also URI - und generieren eine POST Anfrage mit -d. Dieser Parameter erwartet noch Daten, die wird senden wollen. Wir geben hier nur den Text eines Zitates an.

$ curl -d "The cake is a lie." localhost:8000/zitate;
http://localhost:8000/zitate/0

Als Ergebnis erhalten wir eine URL mit dem neu erstellten Zitat. Fügen wir noch ein paar Zitate hinzu.

$ curl -d "Go To Statement Considered Harmful." localhost:8000/zitate; 
http://localhost:8000/zitate/1
 
$ curl -d "foo bar" localhost:8000/zitate;
http://localhost:8000/zitate/2

Das Ergebnis können wir uns erneut ausgeben lassen.

$ curl -H "Accept:application/json" localhost:8000/zitate
 
{"0": "The cake is a lie.", "1": "Go To Statement Considered Harmful.", "2":
"foo bar"}

Wie geht's weiter?

Das obige Beispiel ist ein erster Einstieg in REST. Das Programm kann um verschiedene Aspekte erweitert werden. Greif dir doch einen Aspekt heraus und probiere es einmal selbst.

  • Ein Zitat kann mittels der HTTP-Methode DELETE gelöscht werden.
  • Informationen über Zitate werden angereichert um z.B. den Autor.
  • Für verändernde Operationen wie das Anlegen eines neuen Zitates muss man sich mit Nutzerdaten anmelden.

Server mit nodejs

Mit node.js kann ein HTTP-Server in JavaScript einfach mit Hilfe des http-Paketes erstellt werden. Der folgende Quelltext zeigt das Prinzip.

var http = require('http');
 
// Erstelle den Server. Der Parameter ist eine 
// Callback-Methode, die bei einem Request aufgerufen wird.
var server = http.createServer(
	function (req, res) {
		res.write('Request erhalten.\n');
		res.write("Method: " + req.method + "\n");
		res.write("URL: " + req.url + "\n");
		res.write('Hallo Client');
		res.end();
	});
 
 
server.listen(12345);

Bei einem Aufruf der URL http://localhost:12345/hallo wird im Browser anschließend folgende Ausgabe angezeigt.

Request erhalten.
Method: GET
URL: /hallo
Hallo Client
  • Kapitel 5 der Dissertation „Architectural Styles and the Design of Network-based Software Architectures“ von Roy Thomas Fielding (der „Erfinder“ von REST)
  • RESTful Web services: The basics - guter Übersichtsartikel
  • RFC zu HTTP/1.1 - vollständige Beschreibung der HTTP-Standards, inklusive Beschreibung aller HTTP Methoden und Haderinformationen.
  • REST API Tutorial liefert auf einer übersichtlichen Seite die wesentlichen Informationen.
  • CouchDB ist eine nicht-relationale Datenbank, die mit HTTP-Methoden (GET, POST, PUT, etc.) bedient werden kann. Die Inhalte werden in JSON-Dokumenten abgelegt und können über URLs abgerufen werden.
  • Microservices stellen eine Funktionalität bereit und lassen sich mit sprachunabhängigen Programmierschnittstellen miteinander verbinden.
  • ProgrammableWeb stellt Schnittstellen unterschiedlicher WebServices vor.
  • httpie ist eine Weiterentwicklung cURL.
  • hug ist eine Python-Bibliothek, mit deren Hilfe Methoden als WebService zur Verfügung gestellt werden können.
schule/rest_in_15_minuten.txt · Zuletzt geändert: 2017-04-19 10:27 von marco.bakera