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 ServerName test1.fr ServerAlias *.test1.fr DocumentRoot "C:/www/test1" Options Indexes FollowSymLinks ExecCGI AllowOverride None Order allow,deny Allow from all Alias /img "C:/www/common/img" Order allow,deny Allow from all DirectoryIndex index.html ErrorDocument 404 /missing.html ErrorLog "C:/www/test1/logs/error.log" LogLevel info 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 AddHandler cgi-script .cgi .py 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 .. code-block:: python #!C:/Python/Python27/python.exe print "Content-Type: text/html" print print "" print "" print "" print "Hello" print "" .. note:: la première ligne contient le path de l'interpréteur python exemple de résultat ------------------- .. figure:: data/20130508_7.png il est possible de faire afficher un fichier html par python .. code-block:: 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 ServerName test1.fr ServerAlias *.test1.fr DocumentRoot "C:/www/test1" AllowOverride None Options None Order allow,deny Allow from all Alias /img "C:/www/common/img" Order allow,deny Allow from all DirectoryIndex index.html ErrorDocument 404 /missing.html ErrorLog "C:/www/test1/logs/error.log" LogLevel info 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 WSGIScriptAlias /myapp "C:/www/test1/wsgi-scripts/myapp.wsgi" Order allow,deny Allow from all 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 .. code-block:: python 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 ------------------- .. figure:: data/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 .. code-block:: python # 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('Hello {{name}}!', name=name) @route('/') def index(name='World'): return 'Hello Index!' application = bottle.default_app() et le résultat .. figure:: data/20130508_9.png .. figure:: data/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 .. code-block:: python # 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*