12. Webservices in Python
![]() |
Python-scripts kunnen worden uitgevoerd door een server WEB. Deze server luistert naar verzoeken van clients. Vanuit het perspectief van de client komt het aanroepen van een service WEB neer op het opvragen van de URL van die service. De client kan in elke gewenste taal worden geschreven, met name in Python. We moeten kunnen „communiceren“ met een WEB-service, dat wil zeggen het Http-communicatieprotocol tussen een webserver en zijn clients begrijpen. Dat is het doel van de volgende programma’s.
De webservicescripts worden uitgevoerd door de Apache-webserver van WampServer. Ze moeten in een specifieke map worden geplaatst: <WampServer>\bin\apache\apachex.y.z\cgi-bin, waarbij <WampServer> de installatiemap van WampServer is en x.y.z de versie van de Apache-webserver is.
![]() |
Het is niet voldoende om de Python-scripts in de map <cgi-bin> te plaatsen. Het script moet op de eerste regel het pad naar de te gebruiken Python-interpreter vermelden. Dit pad wordt als opmerking weergegeven:
De lezer zal dit pad aanpassen aan zijn eigen omgeving.
12.1. Client/server-toepassing voor datum en tijd
Onze eerste webservice wordt een datum- en tijdservice: de client ontvangt de huidige datum en tijd.
12.1.1. De server
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import time
# headers
print "Content-Type: text/plain\n"
# tijd naar de client verzenden
# lokale tijd: aantal milliseconden sinds 01/01/1970
# "weergaveformaat datum-tijd
# d: dag in twee cijfers
# m: maand (2 cijfers)
# y: jaar in twee cijfers
# H: uur 0,23
# M: minuten
# S: seconden
print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())
Opmerkingen:
- regel 6: het script moet zelf een aantal HTTP-headers van het antwoord aan de client genereren. Deze worden toegevoegd aan de HTTP-headers die door de Apache-server zelf worden gegenereerd. De HTTP-header op regel 6 geeft aan de client door dat er een bron in het formaat text/plain wordt verzonden, dat wil zeggen onopgemaakte tekst. Let op de "\n" aan het einde van de header, die een lege regel achter de header genereert. Dit is verplicht: deze lege regel geeft aan de HTTP-client aan dat de HTTP-headers van het antwoord zijn afgelopen. Daarna volgt de door de client opgevraagde bron, in dit geval onopgemaakte tekst;
- regel 18: de naar de client verzonden bron is een tekst die de huidige datum en tijd weergeeft.
12.1.2. Twee tests
Het bovenstaande script kan rechtstreeks door de Python-interpreter in een opdrachtvenster worden uitgevoerd, zoals we tot nu toe hebben gedaan. Zo kunnen eventuele syntaxis- of uitvoeringsfouten worden opgespoord. Het resultaat is als volgt:
Nadat het script op deze manier is getest, kunnen we het in de map <cgi-bin> van de Apache-server plaatsen (zie paragraaf 12). Laten we de applicatie WampServer starten. Hiermee worden zowel een Apache-webserver als een MySQL-database gestart. We gebruiken voorlopig alleen de webserver. Vraag vervolgens met een browser de volgende URL op: http://localhost/cgi-bin/web_02.py:
![]() |
- in [1]: de opgevraagde URL;
- in [2]: het door de browser weergegeven antwoord;
- in [3]: de broncode die door de webbrowser is ontvangen. Dit is inderdaad de code die door het Python-script is verzonden.
Met bepaalde tools (in dit geval Firebug, een plug-in voor de Firefox-browser) kun je de HTTP-headers bekijken die met de server worden uitgewisseld. Hierboven heeft de webbrowser de volgende HTTP-headers ontvangen:
Op regel 7 en 8 herkennen we de HTTP-header die door het Python-script is verzonden. De voorgaande headers zijn gegenereerd door de Apache-webserver.
12.1.3. Een geprogrammeerde client
We schrijven nu een script dat als client voor de vorige webservice zal fungeren. We maken gebruik van de functionaliteiten van de module httplib, die het schrijven van HTTP-clients vergemakkelijkt.
# -*- coding=utf-8 -*-
import httplib,re
# constanten
HOST="localhost"
URL="/cgi-bin/web_02.py"
# verbinding
connexion=httplib.HTTPConnection(HOST)
# opvolging
connexion.set_debuglevel(1)
# verzoek verzenden
connexion.request("GET", URL)
# verwerking van het antwoord
reponse=connexion.getresponse()
# inhoud
contenu=reponse.read()
# verbinding verbreken
connexion.close()
print "------\n",contenu,"-----\n"
# tijdgegevens ophalen
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])
Opmerkingen:
- regel 3: de module re is nodig voor reguliere expressies, de module httplib voor de functies van HTTP-clients;
- regel 9: er wordt een HTTP-verbinding tot stand gebracht met poort 80 van HOST, zoals gedefinieerd op regel 6;
- regel 11: via de log kunt u de HTTP-headers van het verzoek van de client en het antwoord van de server bekijken;
- regel 13: de URL van de webservice wordt opgevraagd. Er zijn twee manieren om deze op te vragen: met behulp van een HTTP-commando GET of POST. Het verschil tussen beide wordt verderop uitgelegd. Hier wordt deze opgevraagd met het HTTP-commando GET;
- regel 15: het antwoord van de server wordt gelezen. Hier wordt het volledige antwoord ontvangen: de HTTP-headers en de door de client opgevraagde bron. In zijn antwoord kan de server de client hebben gevraagd om door te sturen. In dat geval voert de client httplib de omleiding automatisch uit. Het ontvangen antwoord is dus het resultaat van de omleiding;
- regel 17: het antwoord bestaat uit de HTTP-headers en het door de client opgevraagde document. Om alleen de HTTP-headers op te halen, gebruiken we [reponse].getHeaders(). Om het document op te halen, gebruiken we [reponse].read();
- regel 19: zodra het antwoord van de webserver is ontvangen, wordt de verbinding met de server verbroken;
- we weten dat het door de server verzonden document bestaat uit één tekstregel in de vorm 15/06/11 14:56:36. In de regels 22-26 gebruiken we een reguliere expressie om de verschillende elementen van deze regel te extraheren.
12.1.4. Resultaten
Opmerkingen:
- regel 1: de HTTP-headers die door de client naar de webserver zijn verzonden;
- regels 2-6: de HTTP-headers van het antwoord van de webserver;
- regel 8: het door de server verzonden document;
- regel 11: het resultaat van de verwerking ervan;
12.2. Het ophalen door de server van de door de client verzonden parameters
In het HTTP-protocol heeft een client twee manieren om parameters door te geven aan de webserver:
- hij vraagt de URL van de dienst aan in de vorm
GET url?param1=val1¶m2=val2¶m3=val3… HTTP/1.0
waarbij de waarden vali eerst moeten worden gecodeerd, zodat bepaalde gereserveerde tekens worden vervangen door hun hexadecimale waarde.
- de URL van de dienst opvragen in de vorm
en voegt vervolgens aan de HTTP-headers die naar de server worden verzonden de volgende header toe:
De rest van de door de client verzonden headers eindigt met een lege regel. De client kan zijn gegevens vervolgens verzenden in de vorm
waarbij de vali, net als bij de methode GET, vooraf moeten worden gecodeerd. Het aantal tekens dat naar de server wordt verzonden, moet N zijn, waarbij N de waarde is die in de header is opgegeven:
12.2.1. De webservice
De volgende webservice ontvangt drie parameters van zijn client: nom, prenom, age. Deze worden opgehaald uit een soort woordenboek met de naam cgi.FieldStorage, dat wordt aangeleverd door de module cgi. De waarde vali van een parameter parami wordt verkregen via vali=cgi.FieldStorage().getlist("parami"). Dit levert een array op met:
- 0 elementen als de parameter parami niet in het verzoek van de klant voorkomt;
- 1 element als de parameter parami één keer in het verzoek van de klant voorkomt;
- n elementen als de parameter parami n keer in het verzoek van de klant voorkomt.
Zodra de parameters zijn opgehaald, stuurt het script deze terug naar de klant.
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import cgi
# headers
print "Content-Type: text/plain\n"
# het ophalen door de server van de door de client verzonden informatie
# hier voornaam=P&achternaam=N&leeftijd=A
formulaire=cgi.FieldStorage()
# deze worden teruggestuurd naar de client
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))
Er kan een test worden uitgevoerd met een webbrowser:
![]() |
In [1], de URL van de webservice. Let op de aanwezigheid van de drie parameters nom, prenom, age. In [2] het antwoord van de webservice.
12.2.2. De client GET
# -*- coding=utf-8 -*-
import httplib,urllib
# constanten
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42
# de parameters moeten worden gecodeerd voordat ze naar de server worden verzonden
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# de parameters worden aan het einde van de URL geplaatst
URL+="?"+params
# verbinding
connexion=httplib.HTTPConnection(HOST)
# tracking
connexion.set_debuglevel(1)
# het verzoek verzenden
connexion.request("GET",URL)
# verwerking van het antwoord
reponse=connexion.getresponse()
# inhoud
contenu=reponse.read()
print contenu,"\n"
# verbinding verbreken
connexion.close()
Opmerkingen:
- regels 8-10: de waarden van de 3 parameters die naar de webservice worden verzonden;
- regel 13: deze moeten worden gecodeerd. Dit gebeurt met behulp van de methode urlencode van de module urllib. Deze module wordt geïmporteerd op regel 3. De methode accepteert als parameter een woordenboek {param1:val1, param2:val2, ...};
- regel 15: in een GET-opdracht (regel 21) moet de client de gecodeerde parameters aan het einde van de URL van de webservice plaatsen;
- de volgende regels zijn al besproken.
12.2.3. De resultaten
Opmerkingen:
- regel 2: let op de codering van de parameters (naam, voornaam, leeftijd);
- regel 8: het antwoord van de webservice.
12.2.4. De client POST
De client POST is vergelijkbaar met de client GET, behalve dat de gecodeerde parameters niet langer deel uitmaken van het doel URL. Ze worden doorgegeven als derde argument van het verzoek POST (regel 19).
# -*- coding=utf-8 -*-
import httplib,urllib
# constanten
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42
# de parameters moeten worden gecodeerd voordat ze naar de server worden verzonden
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# verbinding
connexion=httplib.HTTPConnection(HOST)
# tracking
connexion.set_debuglevel(1)
# het verzoek verzenden
connexion.request("POST",URL,params)
# verwerking van het antwoord
reponse=connexion.getresponse()
# inhoud
contenu=reponse.read()
print contenu,"\n"
# verbinding verbreken
connexion.close()
12.2.5. De resultaten
Opmerkingen:
- let op regel 2, de methode die door de client POST wordt gebruikt om de gecodeerde parameters te verzenden:
- de HTTP-header Content-Length geeft het aantal tekens aan dat naar de webservice wordt verzonden;
- deze HTTP-header wordt vervolgens gevolgd door een lege regel die het einde van de HTTP-headers aangeeft;
- daarna worden de 39 tekens van de gecodeerde parameters verzonden.
- regel 8: het antwoord van de webservice.
12.3. Het ophalen van omgevingsvariabelen van een webservice
12.3.1. De webservice
Het Python-CGI-script wordt uitgevoerd in een systeemomgeving die over attributen beschikt. Deze attributen en hun waarden zijn beschikbaar in een woordenboek met de naam os.environ.
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import os
# headers
print "Content-Type: text/plain\n"
# omgevingsinformatie
for (cle,valeur) in os.environ.items():
print "%s : %s" % (cle,valeur)
Opmerkingen:
- regel 3: de module os moet worden geïmporteerd om over de "systeemvariabelen" te kunnen beschikken.
Als je het bovenstaande script direct uitvoert (c.a.d als consolescript en niet als cgi), krijg je in de console de volgende resultaten:
In een webbrowser (dan wordt het CGI-script uitgevoerd) krijg je de volgende resultaten:
![]() |
Merk op dat de verkregen omgeving niet dezelfde is, afhankelijk van de uitvoeringscontext.
12.3.2. Het geprogrammeerde clientprogramma
# -*- coding=utf-8 -*-
import httplib
# constanten
HOST="localhost"
URL="/cgi-bin/web_04.py"
# verbinding
connexion=httplib.HTTPConnection(HOST)
# verzoek verzenden
connexion.request("GET", URL)
# verwerking van het antwoord
reponse=connexion.getresponse()
# inhoud
print reponse.read()
12.3.3. Resultaten
Merk op dat de geprogrammeerde client niet precies hetzelfde antwoord ontvangt als de webbrowser. Dit komt doordat de webbrowser informatie naar de webserver heeft gestuurd die door de webserver is gebruikt om het antwoord samen te stellen. In dit geval heeft de geprogrammeerde client geen informatie over zichzelf verzonden.




