Verbinden über HTTP
(Auszug aus "Python & XML" von Christopher A. Jones & Fred L. Drake, Jr.)
Obwohl urllib vielleicht genau das richtige für Internetdateien ist, könnten Sie möglicherweise dennoch einen Bedarf für eine kompliziertere Kommunikation mit einem HTTP-Server haben. Wenn Sie z. B. ein Python-Programm schreiben, um zwischen zwei Websites zu kommunizieren, müssen Sie vielleicht die Nachrichten-Header anpassen, um Cookies aufzunehmen, die die Site vielleicht benötigt. Oder Sie müssen eventuell eine spezielle Browser-Art emulieren (indem Sie dessen Namen in Ihren User-Agent-Header einsetzen), falls die Site nach der neuesten Version von Internet Explorer verlangt. Wenn Sie in solchen Fällen mit httplib statt mit urllib arbeiten, gibt Ihnen das mehr Freiheiten bei der Feineinstellung.
Kommunikation über HTTP
Zu einer HTTP-Konversation zwischen Browser und Server gehören Header und Daten. Die Interaktion zwischen einem Webbrowser und einem Webserver läßt eine Menge Informationen über beide Parteien erkennen. Die HTTP-Header, die den Inhalten des Servers sowie den Anfragen des Browsers vorausgehen, enthalten eine Menge Metadaten über Client und Server. Wenn Sie z. B. eine URL in Ihren Browser eingeben und die Eingabetaste drücken, wird eine komplette HTTP-Abfrage an den entfernten Server geschickt, die so oder ähnlich aussehen könnte:
Die Header sagen dem Webserver eine ganze Menge über die Fähigkeiten des Client-Browsers. Aus der ersten Zeile des Headers (GET /c7/favquote.cgi HTTP/1.1) geht hervor, daß es sich um eine Abfrage vom Typ GET handelt, daß die gesuchte Datei /c7/favquote.cgi und die verwendete HTTP-Version 1.1 ist. Über diese essentielle Information hinaus gibt es Daten, die dem Server sagen, welche Dateiarten der Browser akzeptiert, um welchen Browser es sich handelt und welche Art von HTTP-Verbindung benutzt werden soll. Die Accept-Zeilen sagen dem Server, daß Ihr Browser mit Dateien vom Typ .gif und .jpeg und anderen zurechtkommt. Beachten Sie, daß es drei Zeilen im HTTP-Header gibt, die mit Accept beginnen. Sie zeigen, daß der Browser en-us als Sprache und sowohl gzip als auch deflate als Codierung akzeptiert.
Der User-Agent informiert den Server darüber, welchen Browser oder HTTP-Client Sie benutzen. Jeder Browser (und jede HTTP-Bibliothek) füllt dieses Feld auf die eine oder andere Weise aus und läßt Websites damit wissen, wie ihre Benutzer sie besuchen. Einige Websites sind so entworfen, daß sie speziell die Eigenschaften entweder von Netscape Navigator oder Internet Explorer benutzen, und können einen Browser auf die eine oder andere Gruppe von Seiten umleiten, je nachdem, was im User-Agent-String steht:
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90)
Hier ist der User-Agent Internet Explorer 5.5, der auf Microsoft Windows läuft. Der letzte Eintrag im Header des vorangegangenen Beispiels sagt dem Server, welche Art von Verbindung zu benutzen ist. In diesem Fall ist es Keep-Alive:
Connection: Keep-Alive
Der Keep-Alive-Verbindungstyp weist den Server an, den Socket zwischen Client und Server für weitere Ressourcen offenzuhalten. Normalerweise wird beim Laden einer Webseite zuerst eine Anfrage gestellt, um die eigentliche HTML-Seite selbst zu empfangen, dann wird eine Serie weiterer Anfragen abgeschickt, um Bilder zu laden, die mit dem img-Tag referenziert werden, ebenso wie dazugehörige Stylesheets und Rahmen-Sets. Für jede dieser Ressourcen eine neue Verbindung aufzubauen wäre sehr zeitraubend, besonders bei den heutigen Seiten, in denen sehr viele Grafiken vorkommen. Mit der Option Keep-Alive kann der Browser den Kanal weiterhin benutzen, den er bereits aufgebaut hat, um alle weiteren Ressourcen herunterzuladen.
Anfragetypen
Zusätzlich zur GET-Anfrage existieren drei weitere Anfragetypen. Im Prinzip kann ein Browser mit einem Webserver vier verschiedene Dinge tun. Er kann mit GET eine Datei laden. Er kann mit POST auch Daten an eine Datei schicken, z. B. ein Formular an ein CGI-Skript. Die beiden anderen, weniger bekannten Typen sind HEAD und PUT. Mit HEAD werden nur Header des Webservers geladen, und mit PUT wird eine Datei zum Server geschickt.
- GET
Fragt nach einer Datei und enthält optional einen Abfrage-String, der von dieser Datei verwendet wird (falls es eine CGI- oder sonst eine ausführbare Datei ist), um dynamische Daten zu erzeugen.
- POST
Sendet URL-codierte Daten in einem großen Paket an den Server. Wird oft verwendet, um Formularfelder an den Server zu senden. Alles, was gePOSTet wird, kann ebenfalls mit GET gesendet werden, aber der Unterschied ist, daß der Abfrage-String bei einem GET groß und unübersichtlich oder im Browser unhandlich wird. Ein POST wird nicht über den Abfrage-String transportiert und ist damit für den Endbenutzer unsichtbar. Einige Server lassen eventuell weniger Daten im Abfrage-String zu, akzeptieren aber größere Datenmengen in einer POST-Anfrage.
- HEAD
Ähnlich zu GET, gibt aber nur die Header-Einträge zurück.
- PUT
Eine selten benutzte HTTP-Methode zum Aufspielen von Dateien auf den Server. Bei dieser Methode wird der Dateiname, der normalerweise nach einer GET- oder POST-Anfrage kommt, als Dateiname des zum Server gesendeten Inhalts benutzt. Mit anderen Worten: Statt dem Server zu sagen, Sie wollen mit GET eine Seite namens /index.html laden, sagen Sie, daß Sie mit PUT eine Datei namens /index.html auf dem Server speichern wollen. Während einer PUT-Operation wird der Inhalt der Datei direkt nach dem Header gesendet, genau wie bei einer POST-Operation.
Mit Python ein Dokument laden
Um HTTP in Python manuell zu benutzen, verwendet man httplib. Das httplib ist ein Standardmodul und Teil von Python 2.x. Die Klasse HTTP in httplib bietet mehrere wichtige Methoden, um sich mit dem Server zu verbinden:
Req = HTTP(address, [port])
Liefert eine Instanz der Klasse HTTP zurück, die als Anfrageobjekt in dieser Verbindung benutzt wird. Die Verbindung wird zur angegebenen Adresse und mit dem optionalen Port hergestellt. Die meisten Webserver laufen auf Port 80, und diesen Argumentwert müssen Sie nicht angeben. Einige Websites laufen jedoch auf anderen Ports, und diese Option gibt Ihnen dann die Möglichkeit, einen speziellen Port zu wählen.
Req.putrequest(method, file)
Führt die erste HTTP-Anfrage mit dem entsprechenden Dateinamen sowie mit einer Angabe der HTTP-Version durch. Dies ist die erste Zeile des Headers wie in:
GET /c7/favquote.cgi HTTP/1.1
Req.putheader(header-type, value)
Fügt der Anfrage einen neuen Header hinzu. Mit dieser Methode würden Sie Ihren speziellen User-Agent-, Accept-Types-String oder Cookies hinzufügen.
Req.endheaders( )
Verlangt vom Modul, den Header zu beenden. Bei HTTP wird der Header durch eine Leerzeile von den Daten getrennt, d. h., wenn der Server eine HTML-Seite zurücksendet, bekommt der Browser erst seine Header, danach eine Leerzeile und dann das HTML-Dokument. Wenn umgekehrt der Browser mit POST ein Formular an einen Server überträgt, tut er genau das gleiche und trennt seinen Anfrage-Header durch eine Leerzeile von den zu sendenden Daten.
Req.send(data)
Sendet Daten nach Ihrer Anfrage. Die send-Methode muß nach endheaders aufgerufen werden, damit das HTTP-Protokoll eingehalten wird. Sie verwenden diese Methode im Zusammenhang mit POST oder PUT.
ErrorCode, ErrorMessage, Headers = Req.getreply( )
Gibt Ihnen Header und Antwortcode des Servers in einem Schwung zurück. ErrorCode bzw. ErrorMessage sollten 200 bzw. OK sein, wenn alles korrekt vor sich geht. Das Headers-Objekt ist eigentlich eine Instanz von mimetools.Message.
fp. = Req.getfile( )
Liefert ein dateiähnliches Objekt zurück, mit dem Sie auf das eigentliche HTML- (oder andere) Dokument zugreifen können.
Die Verwendung der HTTP-Klasse aus httplib ist einfach. Das folgende Beispiel zeigt, wie man eine Verbindung zu einem Server aufbaut und von dort die Seite index.html lädt.
Beispiel: Durchführen einer HTTP-Anfrage
>>> from httplib import HTTP
>>> req = HTTP("www.example.com")
>>> req.putrequest("GET", "/index.html")
>>> req.putheader("Accept", "text/html")
>>> req.putheader("User-Agent", "MeinPythonSkript")
>>> req.endheaders( )
>>> ec, em, h = req.getreply( )
>>> print ec, em
200 OK
>>> fd = req.getfile( )
>>> textlines = fd.read( )
>>> fd.close( )
Die einzelnen Schritte in diesem Beispiel sind ganz simpel. Die HTTP-Klasse wird mit einem Argument aufgerufen, das den Server angibt, "www.example.com". Anschließend werden Header hinzugefügt, die dem Server eine Mindestmenge an Informationen über die Art der erwarteten Daten und den Namen des User-Agents, hier MeinPythonSkript, geben. Beim Aufruf von getreply werden Fehlercode und Fehlermeldung abgerufen, und das Ergebnis wird auf die Konsole ausgegeben:
>>> print ec, em
200 OK
Danach wird getfile benutzt, um das dateiähnliche Objekt zu holen, in dem sich der Dokumentinhalt befindet. Die getfile-Methode gibt einen Dateideskriptor zurück, von dem Sie Daten lesen können. Mit dem Aufruf von read wird der Rückgabewert an die Variable textlines zugewiesen, die nun das eigentliche Dokument enthält. Der Aufruf von close beendet die Anfrage. Sie können textlines ausgeben, um zu sehen, was Sie erhalten haben:
>>> print textlines
<html>
<head>
<link rel="stylesheet" type="text/css" href="/zpath.css">
</head>
<body BGCOLOR="#CDFF00">
<p>
<table WIDTH=100% height=100%>
<tr>
<td VALIGN="top" ALIGN="left"><a HREF="/cgi-bin/start.cgi?page=top"><img SRC="images/zplogo.gif" WIDTH=457 HEIGHT=144 BORDER=0></a></td>
</tr>
</table>
</p>
</body>
</html>
Besondere Beachtung bei dieser Abfrage verdient der User-Agent-String. Die meisten Administratoren von Websites erstellen Zugriffsstatistiken mit Details über die verwendeten Browser-Typen. Indem Sie Ihre eigenen Python-Internetprogramme schreiben, können Sie zu diesen Statistiken beitragen. Im obigen Beispiel Durchführen einer HTTP-Anfrage setzen wir den User-Agent-String folgendermaßen auf MeinPythonSkript:
req.putheader("User-Agent", "MeinPythonSkript")
Dies wird in den Server-Logdateien festgehalten und taucht sehr wahrscheinlich in der Kategorie Unter-ein-Prozent in der Browser-Statistik des Website-Administrators auf.
Einen Abfrage-String mit httplib bilden
Das Beispiel Durchführen einer HTTP-Anfrage zeigt, wie man eine spezielle Datei abfragt. Nehmen wir an, Sie wollten zu Ihrer GET-Anfrage auch einen Abfrage-String hinzufügen. Das zweite Argument von HTTP.putrequest ist der Dateiname, nach dem Sie suchen. Um einen Abfrage-String zur HTTP-Anfrage anzufügen, könnten Sie den Dateinamen mit Ihren Daten koppeln, wie es hier gezeigt wird, vermutlich URL-codiert:
req.putrequest("GET", "/handler.cgi?id=12345")
Wenn Sie Ihre Daten codieren müssen, weil sie Sonderzeichen enthalten, könnten Sie die Funktion urllib.quote benutzen, die bereits im Abschnitt Codieren von URLs beschrieben worden ist.
req.putrequest("GET", "/handler.cgi?" + quote("numbers=1/2/3/4/5"))
Kekse für den Server backen
Jeder hungrige Server-Administrator wird enttäuscht sein, wenn er erkennt, daß die Cookies, die Ihr Browser an seine Website schickt, rein elektronischer Natur sind. Cookies werden oft von Browsern an Webserver übergeben, um Ihren Browser speziell zu identifizieren. Ihr Browser behält den Cookie und gibt ihn wieder heraus, wann immer die gleiche Website oder das gleiche Dokument verlangt wird. Damit kann der Webserver Site-Inhalte für Sie persönlich maßschneidern oder Sie mit speziellen Daten verbinden, die eventuell in einer Datenbank enthalten sind, wie etwa Ihre Profil-Information oder Ihren virtuellen Einkaufswagen. Wenn Sie Python-Skripten für verschiedene Websites schreiben, müssen Sie eventuell Cookies in Ihren Headern verschicken. Sie verwenden dazu die putheader-Methode der HTTP-Klasse, wie hier gezeigt:
req.putheader("Cookie", "key=value")
Umgekehrt, wenn der Server Cookies an Ihren Browser schickt, kommt ein set-cookie-Header zu den anderen Headern hinzu und wird von Ihrem Browser verdaut.
Durchführen einer POST-Operation
Wenn Sie in Python HTTP manuell benutzen, sind die Chancen hoch, daß Sie Dokumente hin- und herschieben. Sie verwenden vielleicht eine URL, um Informationen von einer Datenbank zu erhalten, ein Formular zu erzeugen und diese Daten an eine andere Website mit der POST-Operation abzuschicken. Ein POST mit httplib ist relativ einfach, aber etwas komplizierter als die bisher in diesem Abschnitt gezeigten Beispiele. Diese Methode wird in den folgenden Abschnitten detailliert besprochen.
Erzeugen eines POST-Empfängers
Ein Beispiel, das ein POST illustriert, ist wertlos ohne ein Ziel für diese Operation. Für dieses Beispiel können Sie also ein einfaches CGI-Skript erzeugen, das Ihre gesendeten Daten wie ein Echo wieder zurückgibt. Um es zu benutzen, kopieren Sie diese 10-Zeilen-Datei unter dem Namen favquote.cgi in ein CGI-fähiges Verzeichnis Ihres Webservers, wie im folgenden Beispiel zu sehen.
Beispiel: favquote.cgi
#!/usr/bin/python
import cgi
form = cgi.FieldStorage( )
favquote = form["favquote"].value
print "Content-type: text/html"
print ""
print "<html><body>"
print "Ihr Zitat lautet: "
print favquote
print "</body></html>"
Dieses einfache CGI-Programm benutzt das cgi-Modul, um die mit POST gesendeten Daten zu erhalten. Im nächsten Abschnitt führen wir ein POST zu dieser Datei durch.
Garantieren der korrekten URL-Codierung
Einer der interessanteren Punkte bei der Durchführung einer POST-Operation ist zu garantieren, daß Ihre Daten korrekt URL-codiert sind. Das heißt zu garantieren, daß der Schlüssel favquote in den Daten nicht codiert ist, wenn Ihr CGI-Skript nach einer Variable namens favquote sucht. Ein korrektes Schlüssel/Wert-Paar ist z. B.:
favquote=Mein%20Zitat%20lautet%3A%20%22Ich%20denke%2C%20also%20bin%20ich.%22
Wenn Sie in Ihrem Übermut jedoch den gesamten String und nicht nur den Werteanteil codieren, erhalten Sie folgendes:
favquote%3dMein%20Zitat%20lautet%3A%20%22Ich%20denke...
Leider wird der Server im zweiten, fehlerhaften Fall nicht wissen, was er tun soll, da es keinen Schlüssel gibt, mit dem der Wert assoziiert werden kann, weil favquote= transformiert wurde in favquote%3d.
Ausführen von POST mit httplib
Im Beispiel Durchführen einer HTTP-Anfrage hatten wir eine GET-Anfrage mit Methoden von httplib erstellt. Für eine POST-Anfrage braucht es einige spezielle Methodenaufrufe und eine sehr präzise Reihenfolge der Ereignisse. Die Reihenfolge der HTTP-Aufrufe ist wichtig, und für POST werden zusätzliche Header benötigt. Der Anfang einer POST-Anfrage ist ähnlich zu GET, wie an diesem Beispiel deutlich wird:
req = HTTP("127.0.0.1")
req.putrequest("POST", "/c7/favquote.cgi")
req.putheader("Accept", "text/html")
req.putheader("User-Agent", "MeinPythonSkript")
Man beachte, daß das erste Argument von putrequest jetzt POST lautet. Abgesehen vom Wechsel von GET zu POST, sieht der Aufruf von putrequest gleich aus. Wenn Sie Daten an den Server senden, ist es wichtig, daß der Server genau weiß, wie viele Daten-Bytes der HTTP-Client sendet. Während sich die HTTP-Header auf Zeilenenden und eine Leerzeile als Feldbegrenzungen verlassen, können gesendete Daten alle möglichen Sonderzeichen, Binärdaten oder sonstige nicht-druckbare Zeichen enthalten. Anstatt sich auf Zeilenenden zu verlassen, verlangt der Server, daß Sie angeben, wie viele Bytes Sie senden werden, um anschließend diese Anzahl von Bytes aus Ihrer Anfrage zu lesen. Geben Sie die Länge des Inhalts mit putheader an (und beachten Sie, daß Sie die Anzahl der Bytes kennen müssen):
myquote = 'Mein Lieblingszitat lautet: "Ich denke, also bin ich."'
postdata = "favquote=" + quote(myquote)
req.putheader("Content-Length", str(len(postdata)))
In diesen Aufrufen fügen Sie die Post-Daten zusammen, indem Sie den Schlüsselteil (favquote=) und den codierten Wert miteinander verbinden. Benutzen Sie die Funktion len, um die Länge Ihres URL-codierten Strings namens postdata zu bestimmen. Da putheader außerdem einen String und keine Zahl als zweites Argument erwartet, müssen Sie die Länge noch mit der Funktion str konvertieren.
Mit der Methode HTTP.send schicken Sie die Daten nach Beendigung des Headers ab:
req.endheaders( )
req.send(postdata)
Nun erhalten Sie die Antwort und können das Ergebnis genauso auslesen, wie Sie es bereits zuvor bei der GET-Anfrage im Beispiel favquote.cgi weiter oben getan haben. Das Ergebnis von POST könnten dynamisch erzeugte Daten, z. B. Suchergebnisse, oder eine HTML-Seite sein, die ein Problem mit der POST-Anfrage schildert.
Betrachtung einer kompletten POST-Operation
Wie Sie sehen, sind für eine POST-Operation einige zusätzliche Schritte notwendig, verglichen mit GET. Die im folgenden Beispiel gezeigte Datei post.py trägt diese Ideen zusammen und zeigt eine komplette POST-Operation. Wenn Sie favquote.cgi in ein CGI-fähiges Verzeichnis Ihres Webservers kopieren, sollten Sie in der Lage sein, post.py von der Kommandozeile aus zu starten. Vergewissern Sie sich, die passende IP-Adresse oder localhost beim Aufruf des HTTP-Konstruktors zu verwenden!
Beispiel: post.py
"""
post.py
"""
from httplib import HTTP
from urllib import quote
# Erstelle POST-Daten
myquote = 'Mein Lieblingszitat lautet: "Ich denke, also bin ich."'
# Sicherstellen, daß key= ausgenommen bleibt...
postdata = "favquote=" + quote(myquote)
print "Werde ", len(postdata), "Bytes POSTen:"
print postdata
# Beginne HTTP-Anfrage
req = HTTP("127.0.0.1") # nehmen Sie eine andere IP, falls nötig
req.putrequest("POST", "/c7/favquote.cgi")
req.putheader("Accept", "text/html")
req.putheader("User-Agent", "MeinPythonSkript")
# Setze Content-length auf Länge von postdata
req.putheader("Content-Length", str(len(postdata)))
req.endheaders( )
# Sende postdata nach Beenden des Headers,
# CGI-Skript wird es auf STDIN empfangen
req.send(postdata)
ec, em, h = req.getreply( )
print "HTTP RESPONSE: ", ec, em
# Hole dateiähnliches Objekt aus HTTP-Antwort
# und gib empfangenes HTML auf Bildschirm aus
fd = req.getfile( )
textlines = fd.read( )
fd.close( )
print "\nFolgendes HTML erhalten:\n"
print textlines
Der rohe HTTP-Header und die Post-Daten, die post.py produziert, sind hier angegeben:
POST /c7/favquote.cgi HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg
Accept-Language: en-us
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90)
Host: 192.168.1.45
Content-Length: 70
Connection: Keep-Alive
favquote=Mein+Lieblingszitat+lautet%3A+%22Ich+denke%2C+also+bin+ich.%22
Wie Sie sehen können, ist die Länge des Inhalts angegeben, und die Daten folgen exakt hinter der einen Leerzeile nach dem Header.
Bisher haben wir in diesem Abschnitt urllib und httplib kennengelernt und haben damit generische URLs geladen sowie spezifische HTTP-Anfragen erzeugt. Nun werden wir sehen, welche Unterstützung Python für die serverseitige Implementierung einer Verbindung anbietet.
<< zurück | vor >> |
Tipp der data2type-Redaktion: Zum Thema Python & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an: |
Copyright © 2002 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Python & XML" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.
O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de