6.2 CLIENT-SERVER-MODELL, HTTP

 

 

EINFÜHRUNG

 

Du weisst bereits, dass eine Webseite, die  in deinem Web-Browser angezeigt wird, durch eine gewöhnliche HTML-Textdatei beschrieben ist, die sich in der Regel auf einem Internet-Server (auch Host genannt) befindet. Um die Datei zu lokalisieren, verwendet der Browser die URL in der Form http://servername/dateipfad.

http steht hier für Hypertext Transfer Protocol und bezeichnet das Verfahren, wie die Datei vom Server zu deinem Browser-PC, auch Client genannt, transferiert wird. Der Servername, auch IP-Adresse (IP: Internet Protocol) genannt, ist entweder in der "gepunkteten" Form, z.B. 192.41.150.141, oder als Alias,  z.B. www.tigerjython.com. Der Dateipfad der HTML-Datei beginnt mit einem Bruchstrich, ist aber auf dem Server relativ zu einem bestimmten Dokumentenpfad.

Bei der Kommunikation zwischen dem Client und dem Server wir das Request-Respond-Verfahren eingesetzt, eines der wichtigsten Prinzipien der Rechnerkommunikation. Dabei wird davon ausgegangen, dass auf dem Server ein Serverprogramm gestartet ist, das auf einem bestimmten TCP-Port (für das Web Port 80) auf  einen Client-Request wartet.


Der Datenaustausch besteht aus folgenden 4 Phasen:

Phase 1:

Der Client erstellt ein Socket-Objekt und verbindet sich mit dem Server. Der Server akzeptiert die Verbindung und merkt sich die Client-Adresse


Phase 2:

Der Client sendet einen Request an den Server und übergibt ihm dabei den Pfad auf die gewünschte Datei


Phase 3:

Der Server verarbeitet den Request und sendet dem Client im Response die Datei


Phase 4:

Der Client empfängt den Response und verarbeitet ihn (Anzeige im Browser-Fenster)



PROGRAMMIERKONZEPTE: Host, Client, IP-Adresse, Request-Respond-Modell, HTTP, Parsen

 

 

MIT HTTP EINE WEBSEITE ANFORDERN

 

Dein Client-Programm führt die Phasen 1, 2, und 4 durch und holt dabei die Datei welcomex.html, die sich im Unterverzeichnis py des Server-Dokumentenpfads befindet.

Die Methode socket() der socket-Klasse, liefert in der Variablen s ein Socket-Objekt. Dabei müssen zwei Konstanten übergeben werden, die den richtigen Socket-Typ festlegen.

import socket
 
host = "www.tigerjython.ch"
port = 80

# Phase 1
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))

# Phase 2
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n"
s.sendall(request)

# Phase 4
reply = s.recv(4096)
print "\nReply:\n"
print reply
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Der Request, um eine Webseite von einem Webserver anzufordern, verwendet das HTTP-Protokoll. Dies ist eine Vereinbarung zwischen Client und Server und legt in allen Einzelheiten den Ablauf des Datentransfers fest. Der GET-Befehl ist im HTTP wie folgt dokumentiert [mehr... ]:

1. Zeile

GET /py/welcome.html HTTP/1.1\r\n          

Befehl zum Holen der Datei im Verzeichnispfad des Servers, Version des Protokolls <carriage return><line feed> (Zeilenumbruch)

 
2. Zeile

Host: hostname\r\n     

Name des Hosts <carriage return><line feed>

3. Zeile \r\n    

Leerzeile als Markierung für das Ende des Befehls

 

 

HTTPS VERWENDEN

 

Immer mehr Websites verwenden an Stelle von HTTP das HTTPS-Protokoll, bei dem die Daten mittels SSL (Secure Socket Layer) verschlüsselt werden. Bei einem solchen Server kann ein HTTP-Request auf Port 80 zu einer Fehlermeldung führen, wie beim folgenden Programm für den Server github.com:

import socket 
 
host = "www.github.com"
port = 80

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n" 
s.sendall(request)
reply = s.recv(4096)
print "\nReply:\n"
print reply
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Die Antwort ist:

HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://www.github.com/py/welcome.html

Es ist aber sehr einfach, den Request in HTTPS zu ändern. Dazu muss der Port auf 443 gesetzt werden und eine Zeile mit ssl.wrap_socket() eingefügt werden:

import socket 
import ssl
 
host = "github.com"
port = 443

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
s = ssl.wrap_socket(s)
request = "GET / HTTP/1.1\r\nHost: " + host + "\r\n\r\n" 
s.sendall(request)
reply = s.recv(4096)
print "\nReply:\n"
print reply
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

HTTP-HEADER UND CONNECT

 

Der Response besteht aus einem Kopf (Header) mit Statusangaben und dem Inhalt (Content) mit der angeforderten Datei. Um die Webseite darzustellen, schneidest du den Kopf weg und übergibst den Inhalt einer HtmlPane.

import socket
from ch.aplu.util import HtmlPane

host = "www.tigerjython.ch"
port = 80

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n"
s.sendall(request)
reply = s.recv(4096)

index = reply.find("<html")
html = reply[index:]

pane = HtmlPane()
pane.insertText(html)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Die Funktion recv(4096) liefert maximal 4096 Zeichen aus einem Datenbuffer, in den die empfangenen Zeichen kopiert werden.

Um den Kopf wegzuschneiden, verwendest du die String-Methode find(str), welche im String nach dem übergebenen Teilstring str sucht und den Index des ersten Vorkommens zurückgibt. (Falls der Teilstring nicht vorkommt, wird -1 zurückgegeben.) Nachher kannst du mit einer Slice-Operation elegant den Teilstring, der beim Index beginnt und bis zum Ende geht, herausfiltern.

 

 

LESEN DER WETTERPROGNOSEN

 

Du wirst dich fragen, warum man ein so kompliziertes Verfahren anwenden soll, um eine Webseite anzuzeigen, wenn man dasselbe doch mit einer einzigen Zeile insertUrl() von HtmlPane erledigen kann. Was du eben gelernt hast, macht aber durchaus Sinn, wenn du beispielsweise den Inhalt der Webseite gar nicht in einem Browserfenster darstellen möchtest, sondern du dich lediglich für gewisse darin eingebettete Informationen interessierst. Als sinnvolle Anwendung holt dein Programm aus der Webseite von Meteo-Schweiz die aktuelle Wetterprognose und schreibt sie als Text aus.

Du kannst dir das Programmierer-Leben doch etwas einfacher machen, wenn du statt der socket-Klasse die Bibliothek urllib2 heranziehst, um die Datei vom Webserver zu holen. [mehr... ].

Um herauszufinden, wo sich die gewünschte Information befindet, stellst du in einem Analyseprogramm die Seite als Text im Konsole-Fenster und  gleichzeitig als Webseite in deinem Standard-Browser dar.

import urllib2
from ch.aplu.util import HtmlPane

url = "http://www.meteonews.ch/de"
HtmlPane.browse(url)
html = urllib2.urlopen(url).read()
print html
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Die Verwendung von Software-Bibliotheken wie urllib2 vereinfacht den Programmcode, verdeckt aber grundlegende Mechanismen.

 

 

PARSEN EINES TEXTS

 

Du hast jetzt die interessante und anspruchsvolle Aufgabe, die relevante Information aus einem langen Textstring herauszuholen, man spricht auch vom Parsen eines Texts.

Als erstens stellst du dir die Teilaufgabe, mit der Funktion remove_html_tags() alle HTML-Tags aus dem String zu entfernen. Das Vorgehen ist typisch und der dabei angewendete Algorithmus kann wie folgt beschrieben werden:

Du  durchläufst den Text Zeichen-um-Zeichen. Dabei stellst du dir vor, dass du dir zwei Zustände merkst: Du bist ausserhalb oder innerhalb eines HTML-Tags. Nur wenn du ausserhalb eines HTML-Tags bist, kopierst du das Zeichen ans Ende eines Ausgabestrings. Der Zustandswechsel erfolgt beim Lesen der Tag-Spitzklammern < bzw. > [mehr... ].

Um die Information zu extrahieren, solltest du den ganzen Text nach der Entfernung der HTML-Tags in einen Texteditor kopieren und ihn untersuchen. Dabei suchst du nach einem Token, welches den Beginn der Information kennzeichnet. Mit der String-Methode find() kannst du den Index start dieses Tokens bestimmen. Nun suchst du beginnend bei start nach einem Token für das Ende der Information und bestimmst seinen Index end. Für diese Website handelt es sich für start um das Token "Allgemeine Lage" und für end um das Token "AutorIn". Die dazwischen liegende Information extrahierst du mit einer Slice-Operation [start:end].

import urllib2

def remove_html_tags(s):
    inTag = False
    out = ""

    for c in s:
        if c == '<':
            inTag = True
        elif c == '>':
          inTag = False
        elif not inTag:
            out = out + c
    return out

def replace_umlaute(s):
    di = {"\xC3\xA4" : "ä", "\xC3\xB6" : "ö", "\xC3\xBC" : "ü",
          "\xC3\x84" : "Ä", "\xC3\x96" : "Ö", "\xC3\x9C" : "Ü",
          "xC3\x9F" : "ss"}
    for key in di:
        s = s.replace(key, di[key])      
    return s

url = "http://meteonews.ch/de/"
html = urllib2.urlopen(url).read()
html = remove_html_tags(html)
html = replace_umlaute(html) 
start = html.find("Allgemeine Lage")
end = html.find("AutorIn")
html = html[start:end].strip()
print html
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Beim Parsen von Texten wird dieser gewöhnlich zeichenweise durchlaufen. In vielen Fällen helfen aber auch Methoden der String-Klasse [mehr... ].

Spezialzeichen (Umlaute, usw.) werden leider unterschiedlich codiert. In HTML folgender Code verwendet.

Immer häufiger wird aber die Codierung UTF-8 verwendet, bei der Spezialzeichen mit 2 oder 3 Bytes gemäss folgender Tabelle codiert sind:

 Zeichen  Code
 ä  0xC3 0xA4
 ö  0xC3 0xB6
 ü  0xC3 0xBC
 Ä  0xC3 0x84
 Ö  0xC3 0x96
 Ü  0xC3 0x9C
 ß  0xC3 0x9F

In der Umwandlungsfunktion replace_umlaute() wird von dieser Codierung ausgegangen

 

 

SPRACHSYNTHESE DER WETTERPROGNOSEN

 

Mit deinen Kenntnissen aus dem Kapitel Sound, kannst du den Text der Wetterprognose mit ein paar zusätzlichen Zeilen durch eine synthetische Stimme vorlesen lassen. Du fügst einfach im Programm die folgenden Zeilen an:

from soundsystem import *

initTTS()
selectVoice("german-man")
sound = generateVoice(html)
openSoundPlayer(sound)
play() 
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

AUFGABEN

 

1.


Unter der URL http://www.timeanddate.com werden verschiedene interessante  Informationen angeboten, die man mit einem selbstgeschriebenen Programm extrahieren und weiterverwenden kann. Du kannst beispielsweise Wetterinformationen von einer Stadt irgendwo auf der Welt erhalten. Schau dir mit einem Webbrowser beispielsweise die Website http://www.timeanddate.com/weather/canada/halifax an.

Dein Programm soll die aktuelle Temperatur in einer frei wählbaren Stadt ausschreiben.

Vorgehen: Du schreibst zuerst ohne Veränderung den ganzen Text in der Konsole aus und suche, wo du die Temperaturangabe findest. Extrahiere diese in deinem Programm mit geeigneten Stringmethoden. Verwende entweder inputString() oder EntryDialog, um Land und Ort interaktiv aus zu wählen und schreibe die Temperatur in der Konsole oder im EntryDialog aus.

2.

Die Methode urllib2.urlopen(url) wirft eine Exception, falls die url nicht gefunden wird. Setzt man den Aufruf in einen try-except-Block

try:
   urllib2.urlopen(url)
except:
   print "Fehler"

so verzweigt das Programm beim Fehler in den except-Block. Ergänze das Programm in Aufgabe 1 mit einer sinnvollen Fehlerausgabe, wenn die Stadt nicht gefunden wird.



3.

Es ist sehr instruktiv, die Daten anzusehen, die ein Browser bei einem GET-Request an den Server sendet und die er als Reply zurück erhält. In Firefox öffnet man dazu mit Ctrl-Shift-I die Entwickler-Werkzeuge und klickt auf den Reiter Netzwerkanalyse. Nachfolgend führt man mit dem Browser eine Anfrage durch und erkennt, dass meist mehrere GET-Requests zu einer Anfrage gehören, da ja nicht nur HTML-Text, sondern auch andere Ressourcen angefordert werden, beispielsweise Bilder. Klickt man auf ein GET, so werden unter dem Register Kopfzeilen die Informationen sichtbar, die der Browser tatsächlich beim GET-Request zusätzlich an den Server sendet.

https://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol
host: de.wikipedia.org
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; 
            rv:43.0) Gecko/20100101 Firefox/43.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: https://www.google.ch
Cookie: WMF-Last-Access=02-Jan-2016
Connection: keep-alive

Die Daten von Zeile 3 enthalten optionale Information über das verwendete Betriebssystem, den Browsertyp, Cookies, usw.

Versuche mit Hilfe von Wikipedia herauszufinden, welche Informationen die einzelnen Zeilen enthalten und welche problematisch bezüglich von gelegten persönlichen Datenspuren sein können.