Python et Httpd une affaire qui marche

Afin de rendre le web dynamique il est possible d’utiliser différents interpreteur et différents language

Le serveur httpd de la fondation apache permet de mettre plusieurs laguages. Nous allons nous arrêter sur le language Python.

Cet article a pour but de présenter les différentes possibilités de mise en place de python au sein d’un serveur httpd

CGI Python avec httpd

Cette solution est la plus ancienne.

Le principe est que le serveur web lance via un interpréteur python un code python.

Donc si on pose comme question http://test1.fr/test.py , httpd va lancer l’interpreteur python en lui passant pour paramètre test.py. La réponse du programme test.py sera alors traiter comme un réponse http de la part du serveur httpd

En Python, l’excellent module cgi de la bibliothèque standard rend cela encore plus facile.

exemple de mise en place

Je ne reviens pas dans la configuration d’un site pour httpd. Le fichier de configuration qui permet le cgi python prend cette forme

# conf/site-available/test1.conf
<VirtualHost *:80>
        ServerName test1.fr
        ServerAlias *.test1.fr

    DocumentRoot "C:/www/test1"
    <Directory "C:/www/test1">
        Options Indexes FollowSymLinks ExecCGI
            AllowOverride None
            Order allow,deny
            Allow from all
        </Directory>

        Alias /img "C:/www/common/img"
    <Directory "C:/www/common/img">
                Order allow,deny
                Allow from all
        </Directory>

        <IfModule dir_module>
        DirectoryIndex index.html
        </IfModule>
        ErrorDocument 404 /missing.html

        ErrorLog "C:/www/test1/logs/error.log"
        LogLevel info

        <IfModule log_config_module>
            LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
            LogFormat "%h %l %u %t \"%r\" %>s %b" common
            #CustomLog "C:/www/test1/logs/access.log" common
            CustomLog "|bin/rotatelogs.exe C:/www/test1/logs/access-%Y_%m_%d_%H_%M.log 86400" common
            #CustomLog "|bin/rotatelogs.exe C:/www/test1/logs/access-%Y_%m_%d.log 5M" common
        </IfModule>
        <IfModule mime_module>
        AddHandler cgi-script .cgi .py
        </IfModule>
</VirtualHost>

Il faut noter la présence des directives

  • Options Indexes FollowSymLinks ExecCGI
  • AddHandler cgi-script .cgi .py

exemple de fichier python

le fichier test.py

C:
 |_ www
    |_ test1
        |_ test.py
#!C:/Python/Python27/python.exe
print "Content-Type: text/html"
print
print "<html><head>"
print ""
print "</head><body>"
print "Hello"
print "</body></html>"

Note

la première ligne contient le path de l’interpréteur python

exemple de résultat

_images/20130508_7.png

il est possible de faire afficher un fichier html par python

#!C:/Python/Python27/python.exe
print "Content-Type: text/html"
print ""
path = 'C:/www/test1/index.html'
fichier = open(path,'r')
for i in fichier.readlines():
        print i

un premier benchmark avec l’url http://test1.fr/test.py

C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/test.py
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test1.fr (be patient)
Finished 1111 requests


Server Software:        Apache/2.2.22
Server Hostname:        test1.fr
Server Port:            80

Document Path:          /test.py
Document Length:        45 bytes

Concurrency Level:      5
Time taken for tests:   30.038 seconds
Complete requests:      1111
Failed requests:        0
Write errors:           0
Total transferred:      199256 bytes
HTML transferred:       49995 bytes
Requests per second:    36.99 [#/sec] (mean)
Time per request:       135.185 [ms] (mean)
Time per request:       27.037 [ms] (mean, across all concurrent requests)
Transfer rate:          6.48 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.6      2      13
Processing:    87  133  37.9    117     318
Waiting:       83  129  37.2    114     315
Total:         89  135  37.9    119     319

Percentage of the requests served within a certain time (ms)
  50%    119
  66%    132
  75%    154
  80%    168
  90%    195
  95%    213
  98%    230
  99%    239
 100%    319 (longest request)

conclusion

Même si cette solution est simple à mettre en place elle a beaucoup d’inconvénient:

  • chaque demande lance un interpreteur complet: ce qui est très gourmand en ressource
  • il n’y a pas de multualisation de ressource: si un programme fait appel à une base de donnée chaque demande va générer une ouverture et une fermeture de session vis à vis du SGBD

Cette solution est rarement employée.

mod_python

mod_python permet à l’instar du php d’intégrer directement du code python dans des pages html. Le principale inconvénient est la mise en cache des pages: ce qui signifie que toutes modification de code impose un redémarrage du serveur httpd.

On peut trouver mod_python à l’adresse http://www.modpython.org/

A priori cette méthode n’est plus maintenu pour python > 2.5 sur plateforme windows

WSGI

Le module permettant à python de faire du wsgi est disponible sur http://code.google.com/p/modwsgi/.

Décrit dans le PEP 333, indépendant du serveur HTTP, WSGI est un standard d’interface entre le serveur HTTP et Python. Il est plutôt prévu pour fournir aux développeurs d’environnements une interface sur laquelle s’appuyer, afin de garantir que leur environnement tournera sur tous les serveurs HTTP.

WSGI a plusieurs propriétés intéressantes :

  • Rapide (pour mon application, qui utilise un SGBD, mesuré avec echoping, le gain de performance est net, la médiane passe de 0,4 s (CGI) à 0,1 s (WSGI), sur la machine locale).
  • Une mise en œuvre de WSGI pour Apache, mod_wsgi, a un mode « démon » où les processus qui exécutent le code Python ne sont pas dans le serveur HTTP mais sont des démons séparés, comme avec FastCGI. Ainsi, une bogue dans le code Python (par exemple une fuite de mémoire) n’affectera pas le processus Apache (l’un des problèmes récurrents de mod_python ou de mod_perl).
  • Modèle de programmation suffisamment simple pour que les applications CGI puissent être portées très vite.

exemple de mise en place

Il suffit de mettre le fichier .so dans le répertoire modules d’apache et de la renommer mod_wsgi.so

C:
 |_ Program Files
    |_ Apache Software Foundation
        |_ Apache2.2
           |_ modules
              |_ mod_wsgi.so

Je ne reviens pas dans la configuration d’un site pour httpd. Le fichier de configuration qui permet le WSGI python prend cette forme

# conf/site-available/test1.conf
LoadModule wsgi_module modules/mod_wsgi.so
<VirtualHost *:80>
        ServerName test1.fr
        ServerAlias *.test1.fr

    DocumentRoot "C:/www/test1"
    <Directory "C:/www/test1">
            AllowOverride None
            Options None
            Order allow,deny
            Allow from all
        </Directory>

        Alias /img "C:/www/common/img"
    <Directory "C:/www/common/img">
                Order allow,deny
                Allow from all
        </Directory>

        <IfModule dir_module>
        DirectoryIndex index.html
        </IfModule>
        ErrorDocument 404 /missing.html

        ErrorLog "C:/www/test1/logs/error.log"
        LogLevel info

        <IfModule log_config_module>
            LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
            LogFormat "%h %l %u %t \"%r\" %>s %b" common
            #CustomLog "C:/www/test1/logs/access.log" common
            CustomLog "|bin/rotatelogs.exe C:/www/test1/logs/access-%Y_%m_%d_%H_%M.log 86400" common
            #CustomLog "|bin/rotatelogs.exe C:/www/test1/logs/access-%Y_%m_%d.log 5M" common
        </IfModule>

    WSGIScriptAlias /myapp "C:/www/test1/wsgi-scripts/myapp.wsgi"
    <Directory "C:/www/test1/wsgi-scripts">
            Order allow,deny
            Allow from all
    </Directory>


</VirtualHost>

Il faut noter la présence des directives

  • LoadModule qui permet de prendre en charge le module WSGI
  • WSGIScriptAlias qui permet d’identifier le script wsgi

La configuration indique une structure suivante

C:
 |_ www
    |_ test1
        |_ wsgi-scripts
           |_ myapp.wsgi

exemple de fichier python

fichier myapp.wsgi

C:
 |_ www
    |_ test1
        |_ wsgi-scripts
           |_ myapp.wsgi
def application(environ, start_response):
    status = '200 OK'
    output = 'Hello World!'

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]

exemple de résultat

_images/20130508_8.png

utilisé bottle en mode wsgi

Il est possible d’utiliser bottle en mode wsgi ce qui rend l’application pkus performante et plus simple à écrire

fichier myapp.wsgi utilisant bottle

# Change working directory so relative paths (and template lookup) work again
import os
os.chdir(os.path.dirname(__file__))

import bottle
from bottle import route, run, template

# ... build or import your bottle application here ...
# Do NOT use bottle.run() with mod_wsgi
@route('/hello/:name')
def getname(name='World'):
    return template('<b>Hello {{name}}</b>!', name=name)

@route('/')
def index(name='World'):
    return '<b>Hello Index</b>!'

application = bottle.default_app()

et le résultat

_images/20130508_9.png
_images/20130508_10.png

installtion sous linux

cela reste simple

apt-get install apache2 libapache2-mod-wsgi python-dev python-pip python-bottle

il faut par la suite configurer apache dans /etc/apache2 (ne configurer que apache2.conf et site-available/test1.conf en modificant les paths par rapport à windows )

et ajouter le répertoire wsgi-scripts

cd /var/www
mkdir wsgi-scripts
chown www-data:www-data wsgi-scripts

Benchmark

Il est possible de réaliser un benchmark afin de voir l’impact de l’utilisation de WSGI avec python/bottle

Nous pouvons imaginer la structure suivante

C:
 |_ www
    |_ test1
        |_ index.html
        |_ wsgi-scripts
           |_ myapp.wsgi

Le fichier myapp.wsgi

# Change working directory so relative paths (and template lookup) work again
import os
os.chdir(os.path.dirname(__file__))

import bottle
from bottle import route, run, template, static_file

import time

@route('/sleep')
def sleep():
    time.sleep(1)
    return static_file('index.html', root='C:/www/test1')

@route('/')
def index():
    return static_file('index.html', root='C:/www/test1')

application = bottle.default_app()

un premier benchmark avec l’url http://test1.fr/index.html

C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/index.html
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test1.fr (be patient)
Completed 5000 requests
Finished 7699 requests


Server Software:        Apache/2.2.22
Server Hostname:        test1.fr
Server Port:            80

Document Path:          /index.html
Document Length:        39 bytes

Concurrency Level:      5
Time taken for tests:   30.013 seconds
Complete requests:      7699
Failed requests:        0
Write errors:           0
Total transferred:      2502175 bytes
HTML transferred:       300261 bytes
Requests per second:    256.52 [#/sec] (mean)
Time per request:       19.491 [ms] (mean)
Time per request:       3.898 [ms] (mean, across all concurrent requests)
Transfer rate:          81.42 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.5      2       4
Processing:    13   17   0.5     17      21
Waiting:        3   10   4.4     10      18
Total:         14   19   0.6     20      22
WARNING: The median and mean for the total time are not within a normal deviation
        These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
  50%     20
  66%     20
  75%     20
  80%     20
  90%     20
  95%     21
  98%     21
  99%     21
 100%     22 (longest request)

temps moyen de traitement 3.898 ms

un deuxième benchmark avec l’url http://test1.fr/myapp

Note

Il faut noter que http://test1.fr/myapp et http://test1.fr/index.html donne le même résultat. Donc les différences observées ne proviennent que du module wsgi

C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/myapp
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test1.fr (be patient)
Completed 5000 requests
Finished 7634 requests


Server Software:        Apache/2.2.22
Server Hostname:        test1.fr
Server Port:            80

Document Path:          /myapp
Document Length:        39 bytes

Concurrency Level:      5
Time taken for tests:   30.005 seconds
Complete requests:      7634
Failed requests:        0
Write errors:           0
Total transferred:      2168056 bytes
HTML transferred:       297726 bytes
Requests per second:    254.43 [#/sec] (mean)
Time per request:       19.652 [ms] (mean)
Time per request:       3.930 [ms] (mean, across all concurrent requests)
Transfer rate:          70.56 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.6      2       4
Processing:    13   17   0.7     18      21
Waiting:        3   13   3.5     13      18
Total:         14   19   0.6     20      22
WARNING: The median and mean for the processing time are not within a normal deviation
        These results are probably not that reliable.
WARNING: The median and mean for the total time are not within a normal deviation
        These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
  50%     20
  66%     20
  75%     20
  80%     20
  90%     21
  95%     21
  98%     21
  99%     21
 100%     22 (longest request)

temps moyen de traitement 3.930 ms soit moins d‘1% d’impact

il faut noter que seul le module wsgi peut impacter le temps de réponse

d’où le résultat du benchmark http://test1.fr/myapp/sleep

C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http
://test1.fr/myapp/sleep
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test1.fr (be patient)
Finished 146 requests


Server Software:        Apache/2.2.22
Server Hostname:        test1.fr
Server Port:            80

Document Path:          /myapp/sleep
Document Length:        39 bytes

Concurrency Level:      5
Time taken for tests:   30.177 seconds
Complete requests:      146
Failed requests:        0
Write errors:           0
Total transferred:      41464 bytes
HTML transferred:       5694 bytes
Requests per second:    4.84 [#/sec] (mean)
Time per request:       1033.451 [ms] (mean)
Time per request:       206.690 [ms] (mean, across all concurrent requests)
Transfer rate:          1.34 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.5      2       3
Processing:  1003 1004   1.5   1004    1017
Waiting:     1001 1002   1.2   1002    1011
Total:       1005 1006   1.5   1006    1019

Percentage of the requests served within a certain time (ms)
  50%   1006
  66%   1006
  75%   1006
  80%   1006
  90%   1007
  95%   1007
  98%   1011
  99%   1013
 100%   1019 (longest request)

Il faut aussi noter que l’utilisation seul de bottle (sans apache) produit un temps de traitement plus de 2 fois plus long

C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://127.0.0.1:8081/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Finished 4275 requests


Server Software:        WSGIServer/0.1
Server Hostname:        127.0.0.1
Server Port:            8081

Document Path:          /
Document Length:        6362 bytes

Concurrency Level:      5
Time taken for tests:   30.003 seconds
Complete requests:      4275
Failed requests:        0
Write errors:           0
Total transferred:      27992700 bytes
HTML transferred:       27197550 bytes
Requests per second:    142.49 [#/sec] (mean)
Time per request:       35.091 [ms] (mean)
Time per request:       7.018 [ms] (mean, across all concurrent requests)
Transfer rate:          911.13 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.5      2       5
Processing:    13   33   1.6     32      44
Waiting:        9   28   1.6     28      39
Total:         14   35   1.6     34      47

Percentage of the requests served within a certain time (ms)
  50%     34
  66%     35
  75%     35
  80%     36
  90%     37
  95%     37
  98%     39
  99%     41
 100%     47 (longest request)

Point sur les performances

le test concernait la délivrance du fichier index.html

  apache CGI WSGI bottle
temps moyen de traitement (ms) 3.898 27.03 3.930 7.018

la solution la plus adaptée en python est le WSGI