3. Interaktion mit dem Server

3.3 HTTP: Mit einem Webserver kommunizieren

Versuchen wir nun, mit einem Webserver zu kommunizieren. Stark reduziert funktioniert die Kommunikation zwischen dem Web-Client (meistens ein Browser) und dem Server so: der Client sagt "schick mir bitte Datei X", und der Web-Server schickt X. Arbeiten wir uns langsam vor. Bauen wir eine Verbindung mit dem Webserver web1.hszg.de auf.

    telnet web1.hszg.de 1234   # auf windows
    nc web1.hszg.de 1234   # auf OSX
    java MyTelnet web1.hszg.de 1234   # alternativ, auf beiden Platformen

Ich werde jetzt mein selbst geschriebenes MyTelnet.java verwenden, der deutlicheren Fehlermeldungen wegen:

    java MyTelnet web1.hszg.de 1234  
    Exception in thread "main" java.net.ConnectException: Connection refused

Der Fehler kommt daher, da auf web1.hszg.de einfach kein Prozess läuft, der auf Port 1234 Verbindungen entgegennimmt ("lauscht"). Kein Wunder. Um einen Server zu finden, brauchen wir den Namen / die IP-Adresse des Rechners (hier also web1.hszg.de) und die Portnummer. Typischerweise haben bestimmte Protokolle feste Portnummern. HTTP verwendet Port 80. Also:

    java MyTelnet web1.hszg.de 80  
    |

Jetzt kommt keine Fehlermeldung mehr, allerdings auch keine Nachricht. Wir haben nun auf Port 80 einen Server erwischt. Schicken wir ihm doch ein paar Bytes!

    java MyTelnet web1.hszg.de 80  
    Hallo! Ist da wer?
    HTTP/1.1 400 Bad Request
    Date: Fri, 28 Oct 2022 12:39:34 GMT
    Server: Apache
    Content-Length: 347
    Connection: close
    Content-Type: text/html; charset=iso-8859-1
    
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    <html><head>
    <title>400 Bad Request</title>
    </head><body>
    <h1>Bad Request</h1>
    <p>Your browser sent a request that this server could not understand.<br />
    </p>
    <p>Additionally, a 400 Bad Request
    error was encountered while trying to use an ErrorDocument to handle the request.</p>
    </body></html>

Aha. Auf Port 80 lauscht tatsächlich ein Server, der auch tatsächlich antwortet, wenn auch nur "Ich verstehe nicht, was Du sagst". Aber sehen Sie sich die Antwort an: nach den ersten sechs Zeilen folgt eine Leerzeile, und dann sehen wir HTML-Code! Der Server schickt uns offensichtlich eine HTML-Datei.

Bitte beachten Sie den Unterschied: der Aufruf java MyTelnet web1.hszg.de 1234 erzeugte eine Exception in unserem Java-Code; bereits der Aufbau der TCP-Verbindung ist gescheitert. Der zweite Versuch, java MyTelnet web1.hszg.de 80, ist aus Sicht von TCP erfolgreich: es wurde eine Verbindung aufgebaut und Daten ausgetauscht. Die Antwort des Servers, ... 400 Bad Request ist zwar auch eine Fehlermeldung, allerdings kein TCP-Fehler, sondern ein Was-auch-immer-der-Server-erwartet-Fehler.

Akt 1: Browser mit http-server

Es ist für Erste einfacher, wenn wir mit einem Webserver kommunizieren, der direkt auf unserem Rechner läuft. Start wir also einen. Dies haben Sie in den obigen Übungen bereits getan. Öffnen Sie ein Terminal, hehen Sie in das Verzeichnis, in welchem Ihre Version von example.html liegt, und starten

    http-server . -p 8080

Jetzt öffnen Sie einen Browser und geben in der Adresszeile localhost:8080/example.html ein. Bei mir sieht das Ergebnis so aus:

Wir hätten genausogut die Datei direkt mit dem Browser öffnen können. Das Ergebnis wäre das gleiche. Allerdings passiert hier konzeptuell viel mehr: der Browser (in meinem Screenshot: Safari) versucht, eine TCP-Verbindung zum Rechner localhost auf Port 8080 aufzubauen. Dort lauscht gerade tatsächlich ein Server (hier: http-server). Der Browser schickt nun über die jetzt etablierte TCP-Verbindung etwas an den Server, das diesen offensichtlich dazu veranlasst, ihm etwas zu schicken, das mit example.html zu tun hat.

Akt 2: Browser mit Terminal-Server OnePersonServer

Um rauszufinden, was der Browser an den Serer geschickt hat, beenden wir den http-server (gehen auf das Terminal und drücken Ctrl+C). Dann starten wir OnePersonServer.java und spielen jetzt "Server von Hand" auf Port 8080. Geben Sie also auf einem Terminal java OnePersonServer 8080 ein. Gehen Sie dann in den Browser, öffen ein neues Tab und geben dort noch einmal localhost:8080/example.html ein. Drücken Sie ENTER. Der Browser sollte nun nichts anzeigen, sondern "hängen". Eventuell liegt example.html noch im Browser-Cache und wird korrekt angezeigt; das hängt vom Browser ab. Versuchen Sie, die Seite neu zu laden, sie in einem neuen Tab zu öffnen oder, falls sie daan immer noch korrekt angezeigt wird, Ihren Browser-Cache zu löschen. Wenn der Browser die Seite nicht mehr anzeigt, sondern hängt, hat es geklappt. Gehen Sie jetzt auf das Terminal, wo OnePersonServer.java läuft. Sie sollten in etwa so etwas sehen:

    java OnePersonServer 8080 
    GET /example.html HTTP/1.1
    Host: localhost:8080
    Upgrade-Insecure-Requests: 1
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15
    Accept-Language: en-us
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
                        
                    

Hier sehen Sie also, wie der Browser zum Webserver spricht (in diesem Falle: zu sprechen glaubt; er spricht ja mit uns, bzw. mit OnePersonServer.java. Was Sie hier sehen, ist ein GET-Request. Das Schlüsselwort GET ist, wie bei unserem MessageServer.java, im Protokoll definiert.

Akt 3: Terminal-Client nc / telnet mit echtem Server http-server und handgeschriebenem GET-Request

In Akt 2 konnten wir lesen, welche Daten den Browser an den Server schickt, wenn er versucht, eine Seite zu laden. In Akt 1 konnten wir das Ergebnis sehen: der Browser zeigt die Webseite an. Nun, im dritten Akt, werden wir herausfinden, was denn der Server antwortet. Das tun wir, in dem wir nun vom Terminal aus per nc (telnet, MyTelnet.java) Browser spielen. Beenden Sie OnePersonServer.java und starten wieder http-server . -p 8080 (natürlich in dem Verzeichnis, wo example.html liegt). Dann verbinden wir uns mit telnet (oder nc oder MyTelnet.java): nc localhost 8080 und copy-pasten den GET-Request aus Akt 2 (blauer Text) in das Terminal. Achten Sie, dass der Text von mindestens zwei Leerzeilen gefolgt wird. Diese zeigen nämlich dem Webserver, dass Ihr Request nun zu Ende ist.

    java MySslTelnet web1.hszg.de 443
    GET /scheder-lehre/WE-I/sessions/03/example.html HTTP/1.1
    host: web1.hszg.de 
    HTTP/1.1 200 OK
    Date: Mon, 07 Nov 2022 13:11:59 GMT
    Server: Apache
    Last-Modified: Mon, 07 Nov 2022 13:10:31 GMT
    ETag: "ee-5ece125edecf9"
    Accept-Ranges: bytes
    Content-Length: 238
    Content-Type: text/html
    
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
    <body>
        Dies ist nur eine <em>Beispielseite</em> für die Kommunikation 
        zwischen Browser und Webserver.
    </body>
    </html>
    

Sehr schön. Sie sehen hier in Blau einen HTTP-Response. Lesen Sie sich in Ruhe alles durch. Nicht alles ist für uns wichtig. Ich werde nun ein paar Zeilen näher besprechen:

  • Die ersten beiden Zeilen im GET-Request, also
    GET /scheder-lehre/WE-I/sessions/03/example.html HTTP/1.1
    host: web1.hszg.de 
    sind obligatorisch. GET zeigt an, dass der Browser eine "Resource" bekommen wollen (meist eine Datei); das /example.html ist dann der Name der gewünschten Resource; HTTP/1.1 zeigt die Version des HTTP-Protokols an, die der Browser "spricht". Die zweite Zeile wiederholt nochmal Hostname und Portnummer.
  • Im Response sind für uns erst einmal zwei Zeilen wichtig. HTTP/1.1 200 OK zeigt uns wieder die HTTP-Version an und dann den "Code" des Responses, zusammen mit dem Englischen Namen des Codes. 200 bedeutet also "Erfolg". Weiter oben haben Sie auch den Code 400 Bad Request gesehen. Wahrscheinlich ist Ihnen auch 404 Not Found schon einmal begegnet.

Akt 4: Browser mit Terminal-Server OnePersonServer mit handgeschriebenem Response

Wir wiederholen nun Akt 2. Wir starten OnePersonServer.java, rufen im Browser den URL localhost:8080/example-2.html auf (die Datei gibt es nicht, es kann uns also nicht der Cache dazwischenfunken) und copy-pasten in dem Terminal, wo OnePersonServer läuft, den obigen HTTP-Response rein. Ich "bereinige" ihn ein wenig, damit es übersichtlicher wird.

java OnePersonServer 8080                    
# geben Sie nun localhost:8080/example-2.html in die Adresszeile Ihres Browsers ein 
GET /example-2.html HTTP/1.1
Host: localhost:8080
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive


HTTP/1.1 200 OK
content-type: text/html; charset=UTF-8

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
    Dies ist ein HTTP-Response, den wir <em>per Hand</em> auf dem Terminal
    in unseren <code>OnePersonServer</code> eingegeben haben.
</body>
</html>

Achten Sie darauf, dass Sie nach <html> eine Leerzeile einfügen. Je nach Browser wird bereits jetzt die Webseite angezeigt (bei mir zum Beispiel mit Chrome); eventuell wartet Ihr Browser darauf, dass der Server die Verbindung beendet (das macht Safari bei mir); drücken Sie Ctrl+D auf der Konsole von OnePersonServer, dann ist die Verbindung zu, der Browser merkt, dass nichts mehr kommt und zeigt die Seite an.

Übung Wiederholen Sie Akt 4, also das Server-Spielen von der Konsole aus, lassen aber bitte die zweite Zeile des Responses, also content-type: text/html; charset=UTF-8 weg. Wie verändert das die Darstellung im Browser?

Akt 5: Kommunikation mit einem echten Server

In Akt 3 haben wir vom Terminal aus Browser gespielt und einem Webserver tatsächlich den Quellcode einer HTML-Seite "entlocken" können. In Akt 4 haben wir auf dem Terminal Webserver gespielt und einen Browser dazu gebracht, den von uns per Hand eingetippten HTML-Text auch anzuzeigen. Versuchen wir nun noch alles einmal mit einem echten Server, also einem, den wir nicht extra auf unserem Rechner gestartet haben. Zum Beispiel mit web1.hszg.de

Übung Schreiben Sie, analog zu Akt 3, per Hand einen HTTP-Request, der von web1.hszg.de die Resource (d.h.: Datei) die Datei web1.hszg.de/scheder-lehre/WE-I/WE-I.css anfordert. Schreiben Sie Ihren Request in eine Textdatei. Verbinden Sie sich per telnet web1.hszg.de 80 und verschicken Sie den Request. Funktioniert das oder geht es schief? Was würden Sie als Erfolg werten?

Je nachdem, ob die Windows oder Unix/OSX verwenden, werden Sie auf mehrere Problem stoßen (wenn Sie MAC-User sind, kontaktieren Sie mich). Wenn Sie die gelöst haben, bekommen Sie zwar vom Server eine Antwort. Die sagt aber nur, dass das gesuchte Dokument "umgezogen" ist (The document has moved ...). Der Grund dafür ist einfach: die allermeisten Server verwenden nicht HTTP sondern HTTPS. Dies ist im Prinzip das gleiche wie HTTP, nur dass es "weiter unten" im System die Daten verschlüsselt verschickt. Für uns als Anwender ändert nur wenig: als Port verwenden es 443 und nicht mehr 80; leider können wir, soweit ich das überblicke, telnet und nc nicht mehr verwenden. Ich habe dafür das Java-Programm MySslTelnet.java geschrieben.

Übung Wiederholen Sie die vorherige Aufgabe, nun aber nicht mit nc / telnet, sondern mit MySslTelnet.java mit dem Befehl java MySslTelnet web1.hszg.de 443. Sie sollten schaffen, dem Server die tatsächliche Datei zu entlocken.