4. Einen Webserver programmieren

4.5 Http GET und Http POST

Im letzten Abschnitt haben wir gesehen, wie der Browser dynamisch generierte Daten zum Server schicken kann. Dynamisch bedeutet hier, dass die Daten noch nicht im Code der HTML-Seite selbst vorhanden sind. Dynamisch sind zum Beispiel die Daten, die der Nutzer auf 06-enter-name/index.html in die Eingabefelder eingibt.

Die vom Benutzer eingegebenen Daten (im Beispiel Vor- und Nachname) wurden einfach in den URL des GET-Requests geschrieben. Schauen wir uns einmal an, wie dieser GET-Request aussieht (erinnern Sie sich: Browser und Server kommunizieren, indem Sie sich über eine TCP-Verbindung mehr oder weniger menschenlesbare HTTP-Nachrichten schicken).

GET /enter-name?firstname=Dominik&lastname=Scheder HTTP/1.1
Host: localhost:2002
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:2002/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,de;q=0.6,da;q=0.5                            
                        

Für uns ist nur die erste Zeile interessant. Sie enthält bereits alle Daten, die der Nutzer eingegeben hat. Schreiben wir jetzt eine weitere kleine Anwendung, in der ein Nutzer einen Beitrag schreiben und abschicken kann. Um mehr Text schreiben und auch scrollen zu können, verwenden wir als Eingabe nicht das <input>-Tag, sondern den <textarea>-Tag. Der Rest sieht aber gleich aus.

Starten Sie den Server und gehen auf localhost:4011 und geben Sie eine Nachricht ein. Achten Sie auf das, was in der Adresszeile erscheint. Hier sind zwei Screenshots:

Ich habe ein ungutes Gefühl. Was ist mit sehr langen Beiträgen? Gibt es denn eine Obergrenze, wie lange ein URL sein darf? Probieren Sie es aus! Schreiben Sie einen richtig langen Beitrag und sehen, wie lange es gutgeht. Bei mir waren 16384 Zeichen bereits zuviel. Das ist nicht besonders lange! Der Quelltext dieser HTML-Seite ist bereits um ein vielfaches länger. Auf Sistrix finden Sie einen Beitrag darüber, wie lange URLs sein dürfen. Anscheinend schreibt das HTTP-Protokol keine Obergrenze vor, empfiehlt aber

Various ad hoc limitations on request-line length are found in practice. It is RECOMMENDED that all HTTP senders and recipients support, at a minimum, request-line lengths of 8000 octets.

Jedenfalls können wir in Theorie und Praxis größere Datenmengen nicht als Query-String eines URLs verschicken. Hierfür gibt es einen weiteren HTTP-Request, nämlich POST.

Verschicken von Daten per POST

Für Sie als Entwickler, die die HTML-Seite oder den Server schreiben, ändert sich gar nicht so viel. Hier ist der Quelltext für einen express.js-Server, der GET benutzt und einen, der POST benutzt.

submit-via-GET.js
const express = require('express');
const url = require('url');
const bodyParser = require("body-parser");
const portnumber = 4011;
const server = express();

server.use(express.static("."));
server.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }));

server.get ('/submit-message', submitMessageGet);
server.listen(portnumber, function ()  {
    console.log('listening at port ' + portnumber);
});

function submitMessageGet (req, res) {
    data = url.parse(req.url, true).query;
    var text = `<html>n<body>n`;
    for (let key in data) {
        text += `<p><strong>${key}:</strong> ${data[key]}</p>n`;
    }
    text += `</body>n</html>n`;
    res.send(text);
}
Ausschnitt aus index.html
    <form action="submit-message" method="GET">
      <div class="form-group">
        <label for="username">Benutzername:</label>
        <input name="username"><br>
        <textarea name="user-message"></textarea>
      </div>
      <button type="submit" class="btn btn-default">Submit via GET</button>
    </form>
submit-via-POST.js
const express = require('express');
const url = require('url');
const bodyParser = require("body-parser");
const portnumber = 4012;
const server = express();

server.use(express.static("."));
server.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }));

server.post('/submit-message', submitMessagePost);
server.listen(portnumber, function ()  {
    console.log('listening at port ' + portnumber);
});

function submitMessagePost(req, res) {
    data = req.body;  
    var text = `<html>n<body>n`;
    for (let key in data) {
        text += `<p><strong>${key}:</strong> ${data[key]}</p>n`;
    }
    text += `</body>n</html>n`;
    res.send(text);
}
Ausschnitt aus index.html
    <form action="submit-message" method="POST">
      <div class="form-group">
        <label for="username">Benutzername:</label>
        <input name="username"><br>
        <textarea name="user-message"></textarea>
      </div>
      <button type="submit" class="btn btn-default">Submit via POST</button>
    </form>

Der Unterschied im Node.js-Code ist, dass wir im ersten Fall dem Server mit server.get (...) mitteilen, wie er auf ein bestimmtes GET reagieren soll, im zweiten Fall mit server.post (...), wie er auf einen bestimmten POST-Befehl reagieren soll (Zeile 10). Der zweite Unterschied ist, dass im GET-Fall die Daten, also in unserem Fall unsername, user-message, im URL selbst kodiert sind und wir ihn mit data = url.parse(req.url, true).query; rausziehen müssen, während im POST-Fall diese Daten in req.body liegen (Zeile 16). Zum Abschluss dieses Abschnitts schauen wir uns auch noch einmal im HTTP-Urtext an, wie genau denn dieser POST-Request aussieht, den der Browser an den Server schickt.

POST /submit-message HTTP/1.1
Host: localhost:2005
Connection: keep-alive
Content-Length: 179
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:2005
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:2005/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,de;q=0.6,da;q=0.5

username=Dominik&user-message=Dies+ist+ein+Beispieltext%2C+der+demonstrieren+soll%2C+wie+ein+POST-Request+im+Original+aussieht.%0D%0A%0D%0A-+G%C3%B6rlitz%2C+den+22.+September+2022ˇ
Übung Schreiben Sie einen kleinen Dateien-Server. Der Server soll ein Verzeichnis public_html oder wie auch immer bedienen. Hier ist ein Beispiel:
Der Server soll bei Anfrage auf / oder /index.html dynamisch eine HTML-Datei generieren, die alle Dateien im Ordner public_html als Liste von Links darstellt:
Darunter soll es eine Input-Maske geben, in der der User eine neue Datei hochladen kann. Auf Knopfdruck "upload file" soll der Server eine neue Datei mit den angegebenen Dateinamen anlegen und den Inhalt der Text-Area in die Datei schreiben. Er soll also im Order public_html eine neue Datei anlegen.
Übung

Schreiben Sie mit express.js eine Anwendung, auf der mehrere Benutzer Nachrichten schreiben können, die dann einfach der Reihe nach allen angezeigt werden. Ihr Ergebnis sollte in etwa so aussehen: