Skip to content

12. Webdienste in Python

  

Python-Skripte können von einem Server WEB ausgeführt werden. Dieser Server nimmt die Client-Anfragen entgegen. Aus Sicht des Clients entspricht der Aufruf eines Dienstes WEB der Abfrage des URL dieses Dienstes. Der Client kann in einer beliebigen Sprache geschrieben werden, insbesondere in Python. Wir müssen wissen, wie man mit einem WEB-Dienst „kommuniziert“, d. h. das Kommunikationsprotokoll Http zwischen einem Webserver und seinen Clients verstehen. Das ist das Ziel der folgenden Programme.

Die Webdienst-Skripte werden vom Apache-Webserver von WampServer ausgeführt. Sie müssen in einem bestimmten Verzeichnis abgelegt werden: <WampServer>\bin\apache\apachex.y.z\cgi-bin, wobei <WampServer> das Installationsverzeichnis von WampServer ist und x.y.z die Version des Apache-Webservers angibt.

 

Es reicht nicht aus, die Python-Skripte im Ordner <cgi-bin> abzulegen. Das Skript muss in der ersten Zeile den Pfad zum zu verwendenden Python-Interpreter angeben. Dieser Pfad wird als Kommentar angegeben:

#!D:\Programme\ActivePython\Python2.7.2\python.exe

Der Leser muss diesen Pfad an seine eigene Umgebung anpassen.

12.1. Client-Server-Anwendung für Datum und Uhrzeit

Unser erster Webdienst wird ein Datums- und Zeitdienst sein: Der Client erhält das aktuelle Datum und die aktuelle Uhrzeit.

12.1.1. Der Server


Das Programm (web_02)


#!D:\Programme\ActivePython\Python2.7.2\python.exe

import time

# Kopfzeilen
print "Content-Type: text/plain\n"

# Zeitangabe an den Client
  # lokale Zeit: Anzahl der Millisekunden seit dem 01.01.1970
  # „Anzeigeformat für Datum und Uhrzeit“
  # d: zweistelliger Tag
  # m: zweistelliger Monat
  # y: zweistelliges Jahr
  # H: Stunde 0,23
  # M: Minuten
  # S: Sekunden

print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())

Hinweise:

  • Zeile 6: Das Skript muss einige der HTTP-Header der Antwort an den Client selbst generieren. Diese werden zu den vom Apache-Server selbst generierten HTTP-Headern hinzugefügt. Der HTTP-Header in Zeile 6 teilt dem Client mit, dass ihm eine Ressource im Format text/plain gesendet wird, d. h. unformatierter Text. Beachten Sie das „\n“ am Ende des Headers, das eine Leerzeile hinter dem Header erzeugt. Dies ist zwingend erforderlich: Diese Leerzeile signalisiert dem HTTP-Client das Ende der HTTP-Header der Antwort. Anschließend folgt die vom Client angeforderte Ressource, in diesem Fall unformatierter Text;
  • Zeile 18: Die an den Client gesendete Ressource ist ein Text, der das aktuelle Datum und die aktuelle Uhrzeit anzeigt.

12.1.2. Zwei Tests

Das vorstehende Skript kann direkt vom Python-Interpreter in einem Befehlsfenster ausgeführt werden, wie wir es bisher getan haben. So lassen sich mögliche Syntax- oder Funktionsfehler ausschließen. Man erhält folgendes Ergebnis:

1
2
3
4
cmd>%python% web_02.py
Content-Type: text/plain

24/06/11 11:16:55

Nachdem das Skript auf diese Weise getestet wurde, kann es im Ordner <cgi-bin> des Apache-Servers abgelegt werden (siehe Abschnitt 12). Starten wir die Anwendung WampServer. Dadurch werden sowohl ein Apache-Webserver als auch eine Datenbank MySQL gestartet. Wir werden vorerst nur den Webserver nutzen. Rufen wir dann mit einem Browser die folgende Seite auf: http://localhost/cgi-bin/web_02.py:

  • in [1]: die angeforderte URL;
  • in [2]: die vom Browser angezeigte Antwort;
  • in [3]: der vom Webbrowser empfangene Quellcode. Es handelt sich tatsächlich um den vom Python-Skript gesendeten Code.

Mit bestimmten Tools (hier Firebug, ein Plugin für den Firefox-Browser) kann man auf die mit dem Server ausgetauschten HTTP-Header zugreifen. Oben hat der Webbrowser die folgenden HTTP-Header empfangen:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 24 Jun 2011 09:35:02 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.5
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

In den Zeilen 7–8 erkennt man den vom Python-Skript gesendeten HTTP-Header. Die vorhergehenden wurden vom Apache-Webserver generiert.

12.1.3. Ein programmierter Client


Das Programm (client_web_02)

Wir schreiben nun ein Skript, das als Client für den zuvor beschriebenen Webdienst fungiert. Dabei nutzen wir die Funktionen des Moduls httplib, das das Schreiben von HTTP-Clients erleichtert.


# -*- coding=utf-8 -*-

import httplib,re

# Konstanten
HOST="localhost"
URL="/cgi-bin/web_02.py"
# Verbindung
connexion=httplib.HTTPConnection(HOST)
# Verfolgung
connexion.set_debuglevel(1)
# Senden der Anfrage
connexion.request("GET", URL)
# Verarbeitung der Antwort
reponse=connexion.getresponse()
# Inhalt
contenu=reponse.read()
# Verbindung beenden
connexion.close()
print "------\n",contenu,"-----\n"
# Abruf der Zeitangaben
elements=re.match(r"^(\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)\s*$",contenu).groups()
print "Jour=%s,Mois=%s,An=%s,Heures=%s,Minutes=%s,Secondes=%s" % (elements[0],elements[1],elements[2],elements[3],elements[4],elements[5])

Hinweise:

  • Zeile 3: Das Modul re wird für reguläre Ausdrücke benötigt, das Modul httplib für die Funktionen der HTTP-Clients;
  • Zeile 9: Es wird eine HTTP-Verbindung mit Port 80 von HOST hergestellt, das in Zeile 6 definiert ist;
  • Zeile 11: Die Protokollierung ermöglicht es, die HTTP-Header der Client-Anfrage und der Server-Antwort einzusehen;
  • Zeile 13: Das Webservice-Modul URL wird aufgerufen. Es gibt zwei Möglichkeiten, diese abzufragen: mithilfe des HTTP-Befehls GET oder POST. Der Unterschied zwischen den beiden wird weiter unten erläutert. Hier wird sie mit dem HTTP-Befehl GET abgefragt;
  • Zeile 15: Die Antwort des Servers wird gelesen. Hier wird die gesamte Antwort abgerufen: HTTP-Header und die vom Client angeforderte Ressource. In seiner Antwort hat der Server den Client möglicherweise aufgefordert, eine Weiterleitung durchzuführen. In diesem Fall führt der Client httplib die Weiterleitung automatisch durch. Die erhaltene Antwort ist somit das Ergebnis dieser Weiterleitung;
  • Zeile 17: Die Antwort besteht aus den HTTP-Headern und dem vom Client angeforderten Dokument. Um nur die HTTP-Header zu erhalten, verwendet man [reponse].getHeaders(). Um das Dokument zu erhalten, verwendet man [reponse].read();
  • Zeile 19: Sobald die Antwort des Webservers vorliegt, wird die Verbindung zu diesem geschlossen;
  • Es ist bekannt, dass das vom Server gesendete Dokument aus einer Textzeile in der Form 15.06.11 14:56:36 besteht. In den Zeilen 22–26 wird ein regulärer Ausdruck verwendet, um die verschiedenen Elemente dieser Zeile zu extrahieren.

12.1.4. Ergebnisse

send: 'GET /cgi-bin/web_02.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Tue, 14 Feb 2012 14:51:07 GMT
header: Server: Apache/2.2.17 (Win32) PHP/5.3.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
------
14/02/12 15:51:07
-----

Jour=14,Mois=02,An=12,Heures=15,Minutes=51,Secondes=07

Anmerkungen:

  • Zeile 1: die vom Client an den Webserver gesendeten HTTP-Header;
  • Zeilen 2–6: die HTTP-Header der Antwort des Webservers;
  • Zeile 8: das vom Server gesendete Dokument;
  • Zeile 11: das Ergebnis der Auswertung;

12.2. Abruf der vom Client gesendeten Parameter durch den Server

Im HTTP-Protokoll stehen einem Client zwei Methoden zur Verfügung, um Parameter an den Webserver zu übergeben:

  1. Er fordert den Dienst „URL“ in der Form

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

wobei die Werte vali zuvor einer Kodierung unterzogen werden müssen, damit bestimmte reservierte Zeichen durch ihren Hexadezimalwert ersetzt werden.

  1. Er fragt den URL des Dienstes in der Form
POST url HTTP/1.0

und fügt dann unter den an den Server gesendeten HTTP-Headern den folgenden Header ein:

Content-length: N

Die vom Client gesendeten Header-Zeilen enden jeweils mit einer Leerzeile. Anschließend kann er seine Daten in folgender Form senden

val1&param2=val2&param3=val3…

wobei die Zeichenfolge „vali“ – wie bei der Methode „GET“ – zuvor kodiert werden muss. Die Anzahl der an den Server gesendeten Zeichen muss N betragen, wobei N der im Header angegebene Wert ist:

Content-length: N

12.2.1. Der Webdienst

Der folgende Webdienst erhält drei Parameter von seinem Client: nom, prenom, age. Er ruft diese aus einer Art Wörterbuch namens cgi.FieldStorage ab, das vom Modul cgi bereitgestellt wird. Der Wert vali eines Parameters parami wird über vali=cgi.FieldStorage().getlist("parami") ermittelt. Man erhält ein Array mit:

  • 0 Elementen, wenn der Parameter parami nicht in der Client-Anfrage enthalten ist;
  • 1 Element, wenn der Parameter parami einmal in der Client-Anfrage vorhanden ist;
  • n Elemente, wenn der Parameter parami n-mal in der Client-Anfrage vorhanden ist.

Sobald die Parameter abgerufen wurden, sendet das Skript sie an den Client zurück.


Das Programm (web_03)


#!D:\Programme\ActivePython\Python2.7.2\python.exe

import cgi

# Header
print "Content-Type: text/plain\n"

# Abruf der vom Client gesendeten Informationen durch den Server
# hier Vorname=P&Nachname=N&Alter=A
formulaire=cgi.FieldStorage()

# sie werden an den Client zurückgesendet
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

Ein Test kann mit einem Webbrowser durchgeführt werden:

In [1] ist die Antwort des Webdienstes URL enthalten. Zu beachten ist das Vorhandensein der drei Parameter nom, prenom und age. In [2] die Antwort des Webdienstes.

12.2.2. Der Client GET


Das Programm (client_web_03_GET)


# -*- coding=utf-8 -*-

import httplib,urllib

# Konstanten
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# Die Parameter müssen vor dem Senden an den Server kodiert werden
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# Die Parameter werden an das Ende des URL angehängt
URL+="?"+params
# Verbindung
connexion=httplib.HTTPConnection(HOST)
# Nachverfolgung
connexion.set_debuglevel(1)
# Senden der Anfrage
connexion.request("GET",URL)
# Verarbeitung der Antwort
reponse=connexion.getresponse()
# Inhalt
contenu=reponse.read()
print contenu,"\n"
# Schließen der Verbindung
connexion.close()

Anmerkungen:

  • Zeilen 8–10: Die Werte der drei an den Webdienst gesendeten Parameter;
  • Zeile 13: Diese müssen kodiert werden. Dies geschieht mithilfe der Methode urlencode des Moduls urllib. Dieses Modul wird in Zeile 3 importiert. Die Methode akzeptiert als Parameter ein Dictionary {param1:val1, param2:val2, ...};
  • Zeile 15: In einem Befehl GET (Zeile 21) muss der Client die kodierten Parameter am Ende des URL des Webdienstes angeben;
  • Die folgenden Zeilen wurden bereits behandelt.

12.2.3. Die Ergebnisse

1
2
3
4
5
6
7
8
dos>%python% client_03_GET.py
send: 'GET /cgi-bin/web_03.py?nom=de+la+Huche&age=42&prenom=Jean-Paul HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Wed, 15 Jun 2011 13:22:15 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du client [['Jean-Paul'],['de la Huche'],['42']]

Anmerkungen:

  • Zeile 2: Beachten Sie die Kodierung der Parameter (Name, Vorname, Alter);
  • Zeile 8: die Antwort des Webdienstes.

12.2.4. Der Client POST

Der Client POST entspricht dem Client GET, mit dem Unterschied, dass die kodierten Parameter nicht mehr Teil des Ziel-URL sind. Sie werden als drittes Argument der Anfrage POST (Zeile 19) übergeben.


Das Programm (client_web_03_POST)


# -*- coding=utf-8 -*-

import httplib,urllib

# Konstanten
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42

# Die Parameter müssen vor dem Senden an den Server kodiert werden
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# Verbindung
connexion=httplib.HTTPConnection(HOST)
# Verfolgung
connexion.set_debuglevel(1)
# Senden der Anfrage
connexion.request("POST",URL,params)
# Verarbeitung der Antwort
reponse=connexion.getresponse()
# Inhalt
contenu=reponse.read()
print contenu,"\n"
# Schließen der Verbindung
connexion.close()

12.2.5. Die Ergebnisse

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\n\r\nnom=de+la+Huche&age=42&prenom=Jean-Paul'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 24 Jun 2011 12:03:31 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du service web [prenom=['Jean-Paul'],nom=['de la Huche'],age=['42']]

Anmerkungen:

  • Beachten Sie in Zeile 2 die vom Client POST verwendete Methode zum Senden der kodierten Parameter:
    • Der HTTP-Header Content-Length gibt die Anzahl der Zeichen an, die an den Webdienst gesendet werden;
    • auf diesen HTTP-Header folgt eine Leerzeile, die das Ende der HTTP-Header kennzeichnet;
    • Anschließend werden die 39 Zeichen der kodierten Parameter gesendet.
  • Zeile 8: Die Antwort des Webdienstes.

12.3. Abrufen der Umgebungsvariablen eines Webdienstes

12.3.1. Der Webdienst

Das Python-CGI-Skript wird in einer Systemumgebung ausgeführt, die über Attribute verfügt. Diese Attribute und ihre Werte sind in einem Wörterbuch mit dem Namen „os.environ“ verfügbar.


Das Programm (web_04)


#!D:\Programme\ActivePython\Python2.7.2\python.exe

import os

# Header
print "Content-Type: text/plain\n"
# Umgebungsinformationen
for (cle,valeur) in os.environ.items():
    print "%s : %s" % (cle,valeur)

Hinweise:

  • Zeile 3: Das Modul os muss importiert werden, um auf die „System“-Variablen zugreifen zu können.

Wenn man das obige Skript direkt ausführt (c.a.d als Konsolenskript und nicht als CGI), erhält man in der Konsole folgende Ergebnisse:

Content-Type: text/plain

TMP : C:\Users\SERGET~1\AppData\Local\Temp
COMPUTERNAME : GPORTPERS3
USERDOMAIN : Gportpers3
VS100COMNTOOLS : D:\Programs\dotnet\Visual Studio 10\Common7\Tools\
VISUALSTUDIODIR : D:\Documents\Visual Studio 2010
PSMODULEPATH : C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
COMMONPROGRAMFILES : C:\Program Files (x86)\Common Files
PROCESSOR_IDENTIFIER : Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
PROGRAMFILES : C:\Program Files (x86)
PROCESSOR_REVISION : 2a07
SYSTEMROOT : C:\Windows
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
PROGRAMFILES(X86) : C:\Program Files (x86)
WINDOWS_TRACING_FLAGS : 3
TEMP : C:\Users\SERGET~1\AppData\Local\Temp
COMMONPROGRAMFILES(X86) : C:\Program Files (x86)\Common Files
PROCESSOR_ARCHITECTURE : x86
ALLUSERSPROFILE : C:\ProgramData
LOCALAPPDATA : C:\Users\Serge TahÚ\AppData\Local
HOMEPATH : \Users\Serge TahÚ
PROGRAMW6432 : C:\Program Files
USERNAME : Serge TahÚ
LOGONSERVER : \\GPORTPERS3
PROMPT : $P$G
SESSIONNAME : Console
PROGRAMDATA : C:\ProgramData
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
FP_NO_HOST_CHECK : NO
WINDIR : C:\Windows
PYTHON : D:\Programs\python\python2.7.2\python
WINDOWS_TRACING_LOGFILE : C:\BVTBin\Tests\installpackage\csilogfile.log
HOMEDRIVE : C:
SYSTEMDRIVE : C:
COMSPEC : C:\Windows\system32\cmd.exe
NUMBER_OF_PROCESSORS : 8
VBOX_INSTALL_PATH : D:\Programs\systeme\Oracle\VirtualBox\
APPDATA : C:\Users\Serge TahÚ\AppData\Roaming
PROCESSOR_LEVEL : 6
PROCESSOR_ARCHITEW6432 : AMD64
COMMONPROGRAMW6432 : C:\Program Files\Common Files
OS : Windows_NT
PUBLIC : C:\Users\Public
USERPROFILE : C:\Users\Serge TahÚ

In einem Webbrowser (dort wird das CGI-Skript ausgeführt) erhält man folgende Ergebnisse:

 

Es ist zu beachten, dass die resultierende Umgebung je nach Ausführungskontext unterschiedlich ist.

12.3.2. Der programmierte Client


Das Programm (client_web_04)


# -*- coding=utf-8 -*-

import httplib

# Konstanten
HOST="localhost"
URL="/cgi-bin/web_04.py"
# Verbindung
connexion=httplib.HTTPConnection(HOST)
# Senden der Anfrage
connexion.request("GET", URL)
# Verarbeitung der Antwort
reponse=connexion.getresponse()
# Inhalt
print reponse.read()

12.3.3. Ergebnisse

SERVER_SOFTWARE : Apache/2.2.17 (Win32) PHP/5.3.5
SCRIPT_NAME : /cgi-bin/web_04.py
SERVER_SIGNATURE :
REQUEST_METHOD : GET
SERVER_PROTOCOL : HTTP/1.1
QUERY_STRING :
SYSTEMROOT : C:\Windows
SERVER_NAME : localhost
REMOTE_ADDR : 127.0.0.1
SERVER_PORT : 80
SERVER_ADDR : 127.0.0.1
DOCUMENT_ROOT : D:/Programs/sgbd/wamp/www/
COMSPEC : C:\Windows\system32\cmd.exe
SCRIPT_FILENAME : D:/Programs/sgbd/wamp/bin/apache/Apache2.2.17/cgi-bin/web_04.py
SERVER_ADMIN : admin@localhost
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.
2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
HTTP_HOST : localhost
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
REQUEST_URI : /cgi-bin/web_04.py
WINDIR : C:\Windows
GATEWAY_INTERFACE : CGI/1.1
REMOTE_PORT : 58468
HTTP_ACCEPT_ENCODING : identity

Es ist zu beachten, dass der programmierte Client nicht genau dieselbe Antwort erhält wie der Webbrowser. Das liegt daran, dass der Webbrowser Informationen an den Webserver gesendet hat, die dieser zur Erstellung seiner Antwort verwendet hat. Der programmierte Client hat hier keine Informationen über sich selbst gesendet.