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&y=2"> <img src="img/covered.svg"> </a>
Wenn ich draufklicke, lädt der Browser die URL
localhost:3001/click?x=7&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&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
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.
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:
Die Funktionnode
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'
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.
- 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.
"looking forward to"
eine Liste
["looking", "forward", "to"]
erzeugt.