4. Einen Webserver programmieren

4.3 Auf Benutzer-Input reagieren

Im letzten Abschnitt haben wir gelernt, wie der Server, in Abhängigkeit von dem angefragten URL, HTML-Seiten (oder was auch immer) generiert und zurückschickt. Dabei muss der URL nicht immer genau einer Datei entsprechen: zu dem Link <a href="unit/1">Rund ums Fahrrad</a> existiert keine Datei names 1 im Verzeichnis unit (obwohl das nicht verboten wäre; 1 ist ein legitimer Dateiname). Nein, der Server soll verstehen, dass er, wenn /unit/1 angefordert wird, dynamisch eine HTML-Seite als Antwort erstellen muss.

Beispiel In folgendem Beispiel habe ich dieses Prinzip ins Extrem getrieben. Wir können mit den bisher gelernten Prinzipien sogar ein einfaches Browser-basiertes Spiel spielen, in diesem Falle den Klassiker Minesweeper.

Laden Sie sich den Minesweeper-Server herunter: minesweeper-scheder.zip, entpacken ihn, gehen rein und starten ihn:

npm run dev                            
...
Now listening on port 3001

und gehen dann in Ihrem Browser auf localhost:3001.

Wie erreiche ich hier Interaktivität? Jede "Kachel" ist ein Link, also ein a-Tag. Was passieren soll, ist im href-Attribut codiert. Hier sehen Sie den Html-Code für die Kachel in Spalte 7 und Zeile 2:

<a href="click?x=7&amp;y=2">
	<img src="img/covered.svg">
</a>

Wenn ich draufklicke, lädt der Browser die URL localhost:3001/click?x=7&amp;y=2. Diese entspricht nicht einer Datei auf meinem Rechner. Der Server nimmt den String auseinander, erkennt am click, dass ich hier die Kachel aufdecken will, und am String x=7&amp;y=2, um welche Kachel es sich handelt. Der Server führt daraufhin die nötige Spielelogik aus (schauen, ob da eine Bombe lag; falls nicht, dann wieviele in der Nachbarschaft liegen; falls 0, dann rekursiv alle Nachbarn aufdecken, etc.), und generiert eine neue Html-Seite, die den neuen Spielzustand beschreibt. Diese neue Seite enthält natürlich wieder für jede Kachel einen Link, genau wie oben beschrieben.

Die Interaktivität ist hier leider fundamental beschränkt. Der Nutzer kann nicht wirklich Daten eingeben, sondern nur zwischen endlich vielen, bereits vorgefertigten Möglichkeiten auswählen.

Übungsaufgabe Könnten Sie mit den bisherigen Werkzeugen es dem Nutzer ermöglichen, einen Namen einzugeben, beispielsweise für eine Minesweeper-Bestenliste? Oder Zahlen einzugeben, um Breite und Höhe des Minesweeper-Felder selbst zu wählen?

Meine Lösung

Laden Sie sich 05-z-artificial-keyboard.zip herunter, entpacken es, gehen mit der Konsole in das Verzeichnis und starten den Server mit npm run dev. Dann gehen Sie im Browser auf localhost:3037.

Der Server ist recht primitiv. Er unterhält einen globalen Zustand, nämlich den String, den der Nutzer eingegeben hat. Jede "Taste" ist ein Link. Die Taste C beispielsweise ist der a-Tag <a href="click?key=C">C</a>. Klicken wir drauf, so wird der URL /click?key=C aufgerufen. Der Server parst den URL und erkennt, dass C geklickt wurde, und kann den Buchstaben der globalen String-Variable hinten anhängen. Als weiteren Zustand unterhält der Sever den Modus, also ob gerade Kleinbuchstaben / Großbuchstaben / Zahlen etc. angezeigt werden sollen.

Der Server legt nicht für jeden Nutzer einen neuen String an. Wenn mehrere Nutzer also auf localhost:3037 gehen, dann können sie einen gemeinsamen String bearbeiten.

Übungsaufgabe Erweitern Sie (zumindest konzeptionell, aber gerne auch, indem Sie es programmieren) meinen "Tastaturserver" so, dass verschiedene User jeweils verschiedene Texte eingeben können.

Meine Lösung

Sie können sich meine Lösung ansehen, indem Sie in Ordner 05-z-artificial-keyboard.zip den Server keyboard-state-in-url.js aufrufen. Das erreichen Sie entweder direkt oder indem Sie die Datei package.json anpassen, also da drin jedes keyboard.js durch ein keyboard-state-in-url.js ersetzen.

Ganz allgemein: in meiner Lösung unterhält der Server keinerlei inneren Zustand. Der Textinhalt sowie der Status werden immer per URL übergeben. Also muss auch jeder Link in der Seite, also alle "Tasten", den gesamten Inhalt in seinem href-Attribut enthalten. Die ausgetauschte Datenmenge wird dadurch also deutlich größer. Ein Vorteil ist dafür, dass sie Lesezeichen setzen können oder sich den URL anderweitig speichern können.

Übungsaufgabe Laden Sie sich den Minesweeper von Herrn Schellenberger herunter, starten den Server, spielen Sie, und versuchen Sie, zu verstehen, wie sich die Implementierung von meiner unterscheidet.

Hinweis: Wenn der Server gestartet ist, müssen Sie auf localhost:3001/minesweeper klicken.

Richtige Nutzereingaben

Die obigen Übungen und Beispiele waren nur "zum Spaß", um zu demonstrieren, dass wir bereits jetzt Nutzereingaben lesen können, obwohl der ganze Mechanismus sehr grobschlächtig war. Natürlich stellt uns Html von sich aus bereits Werkzeuge zur Verfügung, mit Nutzerinput umzugehen. Als konkretes Beispiel wollen wir eine HTML-Seite mit zwei Eingabefeldern:

Klicken Sie auf index.html, geben zwei Namen ein und klicken Submit.

Das schaut auf den ersten Blick nicht gut aus. Werfen Sie aber einen Blick auf den URL in der Adresszeile:

Nachdem ich meinen Vor- und Nachnamen eingegeben und auf Submit geklickt habe, fordert mein Browser den URL mit dem Pfad

/enter-name?firstname=Dominik&lastname=Scheder

an. Man bezeichnet firstname=Dominik&lastname=Scheder auch als query string. Der Browser hat also aus den Eingabefeldern den Inhaltstext "rausgefischt" und in den URL eingebaut. Welchem Schema folgt er dabei? Die folgende Abbildung zeigt mit Farben, wie Elemente im HTML-Code und der Benutzer-Input mit dem Query-String zusammenhängen:

Alle farbig markierten Bezeichner können vom Entwickler, also von uns, frei gewählt werden. Sie müssen nur übereinstimmen. Sie dürfen also gerne alle Orange unterlegten Strings im Code durch "Nachname" ersetzen; der entsprechende Teil des Query-Strings würde sich dann auch zu Nachname ändern.

Wie kommt der Server an die Anfrageparameter?

Im Code unsers Servers (sagen wir, in Javascript geschrieben), könnten wir jetzt gezeilt nach den Zeichen ?, = und & im Query-String suchen und ihn dementsprechend zerlegen. Dann hielten wir die Werte, die der Benutzer eingegeben hat, in der Hand, und könnten beliebig darauf reagieren.
Mit einem Korn Salz könnte man jetzt sagen, Sie haben jetzt den Web-Engineering-Teil von Google Search verstanden. Der Benutzer schickt über ein Eingabefeld eine Suchanfrage, und der Server erhält die als Query-String. Was jetzt auf der Seite des Google-Servers passiert, fällt in den Bereich der Datenbanken, Künstlichen Intelligenz etc., aber ist eben nicht mehr Web-Engineering.

Fehlt nur noch, auf der Server-Seite den Code zu schreiben, der den Query-Strings in Parameter-Wert-Paare zerlegt. Natürlich müssen wir das nicht selbst tun. Dafür gibt es das Modul url. Hier sehen Sie, wie ich im REPL-Modus von Node einen URL zerlege:

node                            
var url = require('url')
var urlString = 'http://localhost:8080/enter-name?firstname=Dominik&lastname=Scheder'
var parsedUrl = url.parse(urlString, true);
parsedUrl.query
[Object: null prototype] { firstname: 'Dominik', lastname: 'Scheder' }
parsedUrl.query.firstname
'Dominik'
Die Funktion url.parse(urlString, true) zerlegt den urlString in seine Bestandteile. Spielen Sie mit obigen Code und lassen Sie sich das Objekt parsedUrl anzeigen:
parsedUrl           
[Probieren Sie's selbst aus!]

Insbesondere erzeugt es mit parsedUrl.query ein Objekt, dass bereits unsere Query-String-Parameter firstname und lastname als Schlüssel besitzt! Wir können jetzt also den Server-Code schreiben.

Übung Schreiben Sie in Node.js einen Server, der auf Anfrage die Datei index.html schickt und dann aber, wenn der Benutzer auf Submit klickt, dynamisch eine HTML-Seite erzeugt, die die eingegebenen Namen beinhalten, zum Beispiel auf dem Query-String 'http://enter-name?firstname=Veronika&lastname=Schneider' mit der Meldung Herzlich Willkommen, Veronika Schneider macht.
Übung Machen Sie so etwas wie in der letzten Aufgabe, nun soll es aber ein Eingabefeld geben, in das der User eine natürliche Zahl \(n\) eingeben kann, worauf der Server eine HTML-Seite mit der Meldung F_n = ... reagiert und die \(n\)-te Fibonacci-Zahl anzeigt.
Übung Erweitern Sie Ihre Vokabel-App aus der letzten Übung um eine Suchfunktion. Die zugrundeliegende "Datenbank", im Moment bestehend aus zwei Lerneinheiten, finden Sie in der Datei vocabulary.json. Ihre Hauptseite sollte nun nicht lediglich eine Übersicht über die Lerneinheiten geben, sondern auch ein Such-Feld. Wenn der Benutzer einen Begriff xxx eingibt und auf den Search-Knopf drückt, sollten Sie vocabulary.json nach xxx durchsuchen und eine Ergebnisseite erzeugen. Wie die genau aussieht, bleibt Ihnen überlassen. Beispiele sind:
  • Sie liefern das erste Wortpaar a,b züruck, bei dem eines der Wörter a,b mit dem Suchbegriff xxx übereinstimmt
  • Sie erzeugen eine Tabelle, die alle solche Wortpaare enthält.
  • Sie liefern noch mehr zurück: xxx muss nicht notwendigerweise identisch mit a oder b sein, es reicht, wenn es als Wort darin vorkommt. Falls es also in vocabulary.json das Wortpaar {"source": "Vorderrad", "target": "front wheel"} gibt, dann sollte eine Suche mit dem Suchbegriff wheel diesen auch zurückliefern.
Hinweis. Für die letzte Aufgabe müssen Sie herausfinden, wie Sie einen String in durch Leerzeichen begrenzte Teilstrings zerlegen, also zum Beispiel aus "looking forward to" eine Liste ["looking", "forward", "to"] erzeugt.