4. Einen Webserver programmieren
4.4 Einfacher Webserver mit express.js
Im letzten Abschnitt haben wir mehreres "von Hand" gemacht:- Angeforderte Dateien haben wir direkt mit
data = fs.readFileSync(filename, { encoding: 'utf8', flag: 'r' });
aus dem Dateisystem des Servers eingelesen und dann perresponse.write(data);
den Response geschrieben. - Wenn wir HTML-Seiten dynamisch erstellt haben, z.B. aus Daten aus einer .json-Datei eine HTML-Tabelle erstellt haben, haben wir im Server-Quelltext dargestellt, wie unser HTML-Text zusammengeschnippelt werden soll.
Punkt 2 ist mühsam und schlechte Praxis: wir haben Inhalt (HTML-Schnipsel und Formatierung) nicht von der Server-Logik (wie und wann unser Server die Daten einliest) getrennt. In der Praxis hieße das wohl auch, dass verschiedene Entwickler nicht unabhängig und parallel an Server und Webseitendesign arbeiten könnten. Auch müssen wir immer, wenn wir das Format der Webseite ändern
Punkt 1 hat uns direkt in eine potentiell gravierende Sicherheitslücke geführt. Hätten wir nicht darauf geachtet, Pfade, die den Substring .. enthalten, explizit zu verbieten, dann hätte ein Angreifer von außen das gesamte Dateisystem unseres Rechners ausspionieren können.
Deshalb: Verwenden Sie, wo es nur geht, Bibliotheken. Gerade im Web-Engineering gibt die zu Hauf, gerne auch mal Frameworks genannt. Da dies eine Lehrveranstaltung ist, die Ihnen die Grundprinzipien beibringen soll, und weil sich die Bibliotheken und Frameworks auch alle paar Jahre ändern, habe ich beschlossen, anfangs aber gerne so weit wie möglich auf ausgefeilte Frameworks zu verzichten. Nun aber, da wir ein paar Webserver geschrieben haben und sie per Hand Requests haben beantworten lassen, können wir eine Abstraktionsstufe nach oben steigen und alles nochmal mit dem Framework express.js machen.
express.js installieren
Erschaffen Sie ein Verzeichnis simple-express-server, gehen in das Verzeichnis, legen eine (gerne noch leere) Datei express-server.js an und geben dann ein:
npm init
npm führt Sie dann durch eine Folge von Konfigurationsschritten. Sie dürfen
gerne bei allem einfac ENTER drücken, dann vergibt npm init einfach
die
Werte,
die es für sinnvoll hält. Wenn es fertig ist, hat npm init eine
Konfigurationsdatei package.json erstellt. Rufen Sie jetzt auf
npm install express
Öffnen Sie die Datei express-server.js und kopieren folgenden Code hinein:
Kopieren Sie ein paar andere Dateien in den gleichen Order, eine index.html und gerne auch Bilder oder pdfs, einfach um zu testen, ob der Server funktioniert. Starten Sie den Server:const express = require('express');
const path = require("path");
const portnumber = 4009;
const server = express();
server.use(express.static("."));
server.listen(portnumber, function () {
console.log('listening at port ' + portnumber);
});
node express-server.js
listening at port 4009
Testen Sie jetzt den Server, in dem Sie in Ihrem Browser die Adresse
http://localhost:4009
eingeben.
Der Server express-server.js bedient das Verzeichnis, in dem sein eigener
Quelltext liegt, daher ist er auch bereit, angefragte Dateien in diesem Ordner an Ihrem Browser zu
senden. Das Verzeichnis, dass der Server bedient, legen wir in Zeile 6 fest:
server.use(express.static("."));
Sie können "." durch anderes ersetzen. Üblicherweise würde man
nicht das Verzeichnis bedienen lassen, in dem der eigene Quelltext liegt,
sondern
ein Unterverzeichnis public_html oder public oder www
anlegen, und Zeile 6 in
server.use(express.static("public_html"));
oder dementsprechend ändern.
Auch die Konvention, dass bei einem Request des URLs /
die Datei /index.html
geschickt wird, versteht express.js. Wir
müssen dieses Verhalten also nicht explizit programmieren.
nc localhost 2000
GET /express-server.js HTTP/1.1
Der Server sollte jetzt mit HTTP/1.1 200 OK, dem Header und dem Inhalt von
express-server.js antworten. Stellen Sie jetzt sicher, dass ein es im
Verzeichnis
oberhalb
von express-simple-server eine Datei warning.txt mit einem kurzen
Text
als
Inhalt gibt.
Versuchen Sie, express-server.js dazu zu bringen, Ihnen
warning.txt zu
liefern:
nc localhost 2000
GET /../warning.txt HTTP/1.1
Sehen Sie, express-server.js fällt nicht drauf rein und liefert
HTTP/1.1 404 Not Found zurück. Dies ist ein wichtiger Grund, lieber
Bibliotheken zu
verwenden,
als selbst das Rad neu erfinden zu wollen: die Entwickler einer Bibliothek haben
wahrscheinlich
an mehr Sicherheitslücken gedacht als wir. Wenn Sie also das Package
express
verwenden,
um einen Server zu schaffen, dann sind Sie auf der sicheren Seite. Sehen Sie nur zu,
dass
Sie
auf keinen Fall
server.use(exrpess.static("/"));
in Ihren Code schreiben. Denn dann gibt der Server alles frei her, was auf
Ihrem
Rechner
liegt (oder streng genommen alles, worauf der Benutzer, der den Prozess node
express-server.js
gestartet hat, Zugriff hat).
const express = require('express');
const path = require("path");
const portnumber = 4010;
const server = express();
server.use(express.static("."));
server.get('/date', sendDate);
function sendDate(req, res) {
let answer = 'This is express-server.js ';
answer += 'This is a dynamic page that has been created on ' + (new Date()).toString() + '. ';
answer += 'You have just requested the URL ' + req.url;
res.send(answer);
}
server.listen(portnumber, function () {
console.log('listening at port ' + portnumber);
});
In Zeile 8 legen wir fest, was der Server tun soll, falls er einen
HTTP-GET-Requeste auf /date bekommt. Das zweite Argument von
get
ist
eine Funktion, hier createAndSendIndexHtml
, die beschreibt, wie mit
Request und Response Sie
umzugehen ist. Wir sehen hier wieder das funktionale Programmierparadigma. Kopieren den obigen
Code
in express-server.js, starten Sie den Server per
node express-server.js und geben Sie im Browser in die Adresszeile
server.get('/index.html', yourFunction)
ruft yourFunction
auf, falls der Pfad der angefragten URL
/index.html
ist;
ein angehängter Query-String stört nicht weiter und kann natürlich in der
Implementierung
von
yourFunction
sinnvoll weiterverwendet werden.
Tip. Sobald Sie den Query-String in ein Javascript-Objekt
query
umgewandelt
haben, können Sie im Code darauf zugreifen, z.B. mit query.type
.
Allerdings
wissen
Sie ja nicht,
ob query
überhaupt den Schlüssel "type"
besitzt
(erinnern
Sie
sich:
Javascript-Objekte
sind Lookup-Tables, keine structs wie in C). Mit dem folgenden Code können Sie
über
alle
Schlüssel iterieren:
for (let key in query) {
console.log("Schlüssel = " + key + ", Wert = " + query[key]);
}
Tip. Wenn Sie größere Blöcke von HTML-Schnipseln in Ihrem Javascript-Quelltext verwenden wollen, dann wird es Ihnen entgegenkommen, dass Javascript multiline strings unterstützt. Diese markieren Sie mit je einem `backtick` vorne und hinten:
var htmlTop = `
<!DOCTYPE html>
<html>
<head>
<title>Wandelt Query-String in Tabelle um</title>
<link rel="stylesheet" href="styles-dominik.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Parameter Ihrer URL-Anfrage auf `
Für diese Aufgabe müssen Sie kein HTML-Formular mit Texteingabefeldern schreiben. Geben Sie den URL wie im obigen Screenshot einfach per Hand in die Adresszeile ein und denken Sie sich einen schönen Query-String aus. Beachten Sie, dass URLs im Browser keine Leerzeichen enthalten dürfen und durch percent encoding mit %20 dargestellt werden (Ihr express-Server ersetzt die %20 dann wieder automatisch in Leerzeichen, wie oben beim flying unicorn; Sie müssen sich server-seitig also nicht drum kümmern.)