Skip to content

12. Web services in Python

  

Python scripts can be executed by a WEB server. This server will listen for client requests. From the client’s perspective, calling a WEB service is equivalent to requesting the URL of that service. The client can be written in any language, including Python. We need to know how to "communicate" with a WEB service, that is, understand the Http communication protocol between a web server and its clients. This is the purpose of the following programs.

The web service scripts will be executed by the Apache web server on WampServer. They must be placed in a specific directory: <WampServer>\bin\apache\apachex.y.z\cgi-bin, where <WampServer> is the installation directory of WampServer and x.y.z is the version of the Apache web server.

 

Placing the Python scripts in the <cgi-bin> folder is not enough. The script must specify the path to the Python interpreter to be used on the first line. This path is included as a comment:

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

The reader should adapt this path to their own environment.

12.1. Client/server date and time application

Our first web service will be a date and time service: the client receives the current date and time.

12.1.1. The server


The program (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())
 

Notes:

  • line 6: the script must generate some of the Http headers in the response to the client itself. These will be added to the Http headers generated by the Apache server itself. The Http header on line 6 tells the client that a resource in text/plain format—i.e., unformatted text—will be sent. Note the "\n" at the end of the header, which will generate a blank line after the header. This is mandatory: it is this blank line that signals to the client Http the end of the Http headers in the response. Next comes the resource requested by the client, in this case unformatted text;
  • line 18: the resource sent to the client is text displaying the current date and time.

12.1.2. Two tests

The previous script can be run directly by the Python interpreter in a command window, as we have done so far. This helps eliminate any syntax or runtime errors. The following result is obtained:

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

24/06/11 11:16:55

Once the script has been tested in this way, it can be placed in the <cgi-bin> directory of the Apache server (see paragraph 12). Let’s launch the WampServer application. This starts both an Apache web server and a MySQL database management system. For now, we will only use the web server. Then, using a browser, let’s request the following URL: URL://localhost/cgi-bin/web_02.py:

  • in [1]: the requested URL;
  • to [2]: the response displayed by the browser;
  • in [3]: the source code received by the web browser. This is indeed the one sent by the Python script.

With certain tools (here Firebug, a Firefox browser plugin), you can access the headers exchanged with the server. Above, the web browser received the following headers:

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

Lines 7–8 are recognizable as the Http header sent by the Python script. The preceding lines were generated by the Apache web server.

12.1.3. A scheduled client


The program (client_web_02)

We will now write a script that will act as a client for the previous web service. We will use the features of the httplib module, which makes it easier to write clients Http.


# -*- coding=utf-8 -*-
 
import httplib,re
 
# constants
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])
 

Notes:

  • line 3: the re module is required for regular expressions, the httplib module for the clients and Http functions;
  • line 9: a Http connection is created using port 80 of HOST, defined on line 6;
  • line 11: the trace allows you to view the headers of the client request and the server response;
  • line 13: the URL web service is requested. There are two ways to request it: using a Http, GET, or POST command. The difference between the two is explained later. Here, it will be requested using the command Http GET;
  • Line 15: The server's response is read. The entire response is retrieved here: headers Http and the resource requested by the client. In its response, the server may have instructed the client to redirect. In this case, the httplib client automatically performs the redirection. The response obtained is therefore the one resulting from the redirection;
  • line 17: the response consists of the headers Http and the document requested by the client. To retrieve only the Http headers, use [reponse].getHeaders(). To retrieve the document, use [reponse].read();
  • line 19: once the response from the web server is received, the connection to it is closed;
  • We know that the document sent by the server is a line of text in the format 15/06/11 14:56:36. Lines 22–26: We use a regular expression to extract the various elements of this line.

12.1.4. Results

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/'
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

Notes:

  • line 1: the headers Http sent by the client to the web server;
  • Lines 2–6: the Http headers from the web server’s response;
  • line 8: the document sent by the server;
  • line 11: the result of its processing;

12.2. Retrieval by the server of parameters sent by the client

In the Http protocol, a client has two methods for passing parameters to the web server:

  1. it requests the URL service in the form

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

where the valid values must first be encoded so that certain reserved characters are replaced by their hexadecimal values.

  1. it requests the URL service in the form
POST url HTTP/1.0

then, among the headers sent to the server, includes the following header:

Content-length: N

The rest of the headers sent by the client end with a blank line. It can then send its data in the form

val1&param2=val2&param3=val3…

where the values must, as with the GET method, be encoded beforehand. The number of characters sent to the server must be N, where N is the value declared in the header:

Content-length: N

12.2.1. The web service

The following web service receives 3 parameters from its client: last_name, first_name, age. It retrieves them from a sort of dictionary named cgi.FieldStorage provided by the CGI module. The vali value of a parameter parami is obtained via vali=cgi.FieldStorage().getlist("parami"). We get an array of:

  • 0 elements if the parami parameter is not present in the client’s request;
  • 1 element if the parami parameter is present once in the client request;
  • n elements if the parami parameter is present n times in the client request.

Once the parameters have been retrieved, the script returns them to the client.


The program (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"))

A test can be performed using a web browser:

In [1], the URL from the web service. Note the presence of the three parameters last_name, first_name, age. In [2], the response from the web service.

12.2.2. The client GET


The program (client_web_03_GET)


# -*- coding=utf-8 -*-
 
import httplib,urllib
 
# constants
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()

Notes:

  • lines 8–10: the values of the 3 parameters sent to the web service;
  • line 13: these must be encoded. This is done using the urlencode method from the urllib module. This module is imported on line 3. The method takes a dictionary {param1:val1, param2:val2, ...} as a parameter;
  • line 15: in a GET command (line 21), the client must place the encoded parameters at the end of the URL from the web service;
  • The following lines have already been discussed.

12.2.3. The results

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/'
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']]

Notes:

  • line 2: note the encoding of the parameters (last_name, first_name, age);
  • line 8: the web service response.

12.2.4. The client POST

The client POST is similar to the client GET, except that the encoded parameters are no longer part of the target URL. They are passed as the third argument of the POST request (line 19).


The program (client_web_03_POST)


# -*- coding=utf-8 -*-
 
import httplib,urllib
 
# constants
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. The results

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\rnHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\nname=of+the+Huche&age=42&firstname=Jean-Paul'
reply: 'HTTP/1.1 200 OK/'
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'],name=['de la Huche'],age=['42']]

Notes:

  • Note line 2: the method used by the client POST to send the encoded parameters:
    • the Http Content-Length header indicates the number of characters that will be sent to the web service;
    • this Http header is then followed by an empty line indicating the end of the headers;
    • then the 39 characters of the encoded parameters are sent.
  • Line 8: the web service response.

12.3. Retrieving environment variables from a web service

12.3.1. The web service

The Python CGI script runs in a system environment that has attributes. Its attributes and their values are available in a dictionary named os.environ.


The program (web_04)


#!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)

Notes:

  • line 3: you must import the os module to access "system" variables.

If you run the script above directly (c.a.d. as a console script, not CGI), you will see the following results in the console:

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 a web browser (where the CGI script is executed), the following results are obtained:

 

Note that depending on the execution context, the environment obtained is not the same.

12.3.2. The programmed client


The program (client_web_04)


# -*- coding=utf-8 -*-
 
import httplib
 
# constants
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. Results

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

Note that the programmed client does not receive exactly the same response as the web browser. This is because the browser sent information to the web server that the server used to generate its response. Here, the programmed client did not send any information about itself.