Skip to content

12. Webdienste in Python

  

Python-Skripte können von einem Webserver ausgeführt werden. Der Server ist es, der auf Client-Anfragen wartet. Aus Sicht des Clients entspricht der Aufruf eines Webdienstes der Anforderung der URL dieses Dienstes. Der Client kann in jeder beliebigen Sprache geschrieben sein, einschließlich Python. Wir müssen wissen, wie man mit einem Webdienst „kommuniziert“, d. h. das HTTP-Protokoll verstehen, das für die Kommunikation zwischen einem Webserver und seinen Clients verwendet wird. Dies ist der Zweck der folgenden Programme.

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

 

Es reicht nicht aus, Python-Skripte einfach nur 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 eingefügt:

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

Leser sollten diesen Pfad an ihre 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:\Programs\ActivePython\Python2.7.2\python.exe

import time

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

#    dispatch time to customer
  #  localtime: number of milliseconds since 01/01/1970
  #    "date-time display format
  #    d: 2-digit day
  #    m: 2-digit month
  #    y: 2-digit year
  #    H: hour 0.23
  #    M: minutes
  #    S: seconds

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

Hinweise:

  • Zeile 6: Das Skript muss einige der HTTP-Header für die 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 eine Ressource im Format „text/plain“, d. h. als unformatierter Text, gesendet wird. Beachten Sie das „\n“ am Ende des Headers, das eine Leerzeile nach dem Header erzeugt. Dies ist zwingend erforderlich: Erst diese Leerzeile signalisiert dem HTTP-Client das Ende der HTTP-Header in der Antwort. Als Nächstes folgt die vom Client angeforderte Ressource, in diesem Fall unformatierter Text;
  • Zeile 18: Die an den Client gesendete Ressource ist 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. Dies hilft, Syntax- oder Laufzeitfehler zu vermeiden. Es ergibt sich folgendes Ergebnis:

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

24/06/11 11:16:55

Sobald das Skript auf diese Weise getestet wurde, kann es im Verzeichnis <cgi-bin> des Apache-Servers abgelegt werden (siehe Abschnitt 12). Starten wir die WampServer-Anwendung. Dadurch werden sowohl ein Apache-Webserver als auch ein MySQL-Datenbankserver gestartet. Vorerst werden wir nur den Webserver verwenden. Geben Sie dann in einem Browser die folgende URL ein: 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. Dies ist tatsächlich der vom Python-Skript gesendete Code.

Mit bestimmten Tools (hier Firebug, ein Firefox-Browser-Plugin) können Sie 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

Die Zeilen 7–8 sind als der vom Python-Skript gesendete HTTP-Header erkennbar. Die vorangehenden Zeilen wurden vom Apache-Webserver generiert.

12.1.3. Ein programmierter Client


Das Programm (client_web_02)

Wir werden nun ein Skript schreiben, das als Client für den vorherigen Webdienst fungiert. Wir werden die Funktionen des Moduls httplib nutzen, das das Schreiben von HTTP-Clients vereinfacht.

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

import httplib,re

#    constant
HOST="localhost"
URL="/cgi-bin/web_02.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
#    locking connection
connexion.close()
print "------\n",contenu,"-----\n"
#    recovery of time elements
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 HTTP-Client-Funktionen;
  • Zeile 9: Es wird eine HTTP-Verbindung über Port 80 auf dem in Zeile 6 definierten HOST hergestellt;
  • Zeile 11: Über das Protokoll können Sie die HTTP-Header der Client-Anfrage und der Server-Antwort einsehen;
  • Zeile 13: Die URL des Webdienstes wird angefordert. Es gibt zwei Möglichkeiten, diese anzufordern: mit einem HTTP-GET- oder einem HTTP-POST-Befehl. Der Unterschied zwischen den beiden wird später erläutert. Hier wird die Anfrage mit dem HTTP-GET-Befehl gestellt;
  • 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 angewiesen, eine Umleitung durchzuführen. In diesem Fall führt der httplib-Client die Umleitung automatisch durch. Die erhaltene Antwort ist daher diejenige, die sich aus der Umleitung ergibt;
  • Zeile 17: Die Antwort besteht aus den HTTP-Headern und dem vom Client angeforderten Dokument. Um nur die HTTP-Header abzurufen, verwenden Sie [response].getHeaders(). Um das Dokument abzurufen, verwenden Sie [response].read();
  • Zeile 19: Sobald die Antwort vom Webserver empfangen wurde, wird die Verbindung zu ihm geschlossen;
  • Wir wissen, dass das vom Server gesendete Dokument eine Textzeile im Format 15/06/11 14:56:36 ist. Zeilen 22–26: Wir verwenden einen regulären Ausdruck, 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 in der Antwort des Webservers;
  • Zeile 8: das vom Server gesendete Dokument;
  • Zeile 11: das Ergebnis der Verarbeitung;

12.2. Der Server ruft die vom Client gesendeten Parameter ab

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

  1. Er fordert die Service-URL in der Form

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

wobei die gültigen Werte zunächst kodiert werden müssen, sodass bestimmte reservierte Zeichen durch ihre Hexadezimalwerte ersetzt werden.

  1. Es fordert die Service-URL 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 übrigen vom Client gesendeten Header enden mit einer Leerzeile. Anschließend kann er seine Daten in der Form

val1&param2=val2&param3=val3…

, wobei die Werte, wie bei der GET-Methode, zuvor kodiert werden müssen. 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 empfängt drei Parameter von seinem Client: last_name, first_name und age. Er ruft diese aus einer dictionary-ähnlichen Struktur namens cgi.FieldStorage ab, die vom cgi-Modul bereitgestellt wird. Der Wert vali eines Parameters parami wird mit vali = cgi.FieldStorage().getlist("parami") ermittelt. Dies gibt ein Array mit

  • 0 Elementen, wenn der Parameter parami in der Anfrage des Clients nicht vorhanden ist;
  • 1 Element, wenn der Parameter parami einmal in der Anfrage des Clients 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:\Programs\ActivePython\Python2.7.2\python.exe

import cgi

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

#  server retrieves information sent by the client
#    here firstname=P&lastname=N&age=A
formulaire=cgi.FieldStorage()

#  we send them back to the customer
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))

Ein Test kann über einen Webbrowser durchgeführt werden:

In [1] die URL des Webdienstes. Beachten Sie das Vorhandensein der drei Parameter last_name, first_name und age. In [2] die Antwort des Webdienstes.

12.2.2. Der GET-Client


Das Programm (client_web_03_GET)

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

import httplib,urllib

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

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#  parameters are set at the end of URL
URL+="?"+params
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("GET",URL)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
connexion.close()

Anmerkungen:

  • Zeilen 8–10: die Werte der 3 an den Webdienst gesendeten Parameter;
  • Zeile 13: Diese müssen kodiert werden. Dies geschieht mithilfe der Methode urlencode aus dem Modul urllib. Dieses Modul wird in Zeile 3 importiert. Die Methode nimmt ein Wörterbuch {param1:val1, param2:val2, ...} als Parameter entgegen;
  • Zeile 15: Bei einer GET-Anfrage (Zeile 21) muss der Client die kodierten Parameter an das Ende der URL des Webdienstes setzen;
  • 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 (last_name, first_name, age);
  • Zeile 8: Die Antwort des Webdienstes.

12.2.4. Der POST-Client

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


Das Programm (client_web_03_POST)

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

import httplib,urllib

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

#  parameters must be encoded before being sent to the server
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
#    connection
connexion=httplib.HTTPConnection(HOST)
#    follow-up
connexion.set_debuglevel(1)
#  send request
connexion.request("POST",URL,params)
#    response processing
reponse=connexion.getresponse()
#    content
contenu=reponse.read()
print contenu,"\n"
#  closing the connection
connexion.close()

12.2.5. 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:

  • Anmerkung zu Zeile 2: Die vom Client verwendete POST-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 anzeigt;
    • anschließend werden die 39 Zeichen der kodierten Parameter gesendet.
  • Zeile 8: Die Antwort des Webdienstes.

12.3. Abrufen von Umgebungsvariablen aus einem Webdienst

12.3.1. Der Webdienst

Das Python-CGI-Skript läuft in einer Systemumgebung, die über Attribute verfügt. Diese Attribute und ihre Werte sind im os.environ-Wörterbuch verfügbar.


Das Programm (web_04)

1
2
3
4
5
6
7
8
9
#   !D:\Programs\ActivePython\Python2.7.2\python.exe

import os

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

Hinweise:

  • Zeile 3: Sie müssen das os-Modul importieren, um auf die „system“-Variablen zugreifen zu können.

Wenn Sie das obige Skript direkt ausführen (d. h. als Konsolenskript statt als CGI-Skript), werden in der Konsole folgende Ergebnisse angezeigt:

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 (in dem das CGI-Skript ausgeführt wird) werden folgende Ergebnisse angezeigt:

 

Beachten Sie, dass die ermittelte Umgebung je nach Ausführungskontext unterschiedlich ist.

12.3.2. Der programmierte Client


Das Programm (client_web_04)

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

import httplib

#    constant
HOST="localhost"
URL="/cgi-bin/web_04.py"
#    connection
connexion=httplib.HTTPConnection(HOST)
#  send request
connexion.request("GET", URL)
#    response processing
reponse=connexion.getresponse()
#    content
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

Beachten Sie, dass der programmierte Client nicht genau dieselbe Antwort erhält wie der Webbrowser. Das liegt daran, dass der Browser Informationen an den Webserver gesendet hat, die der Server zur Generierung seiner Antwort verwendet hat. Der programmierte Client hat hingegen keine Informationen über sich selbst gesendet.