Outils pour utilisateurs

Outils du site


programmation_serveur_php_python_nodejs

Comment programmer un serveur web ?

  • Côté client (c'est à dire ce qui s'affiche dans le navigateur du visiteur du site), on fait du HTML (contenu) + CSS (mise en page) + Javascript (aspect dynamique des pages).

  • Mais comment programmer ce qu'il se passe côté serveur ? Exemple : lorsqu'on poste sur Faceb**k, Twitter, lorsqu'on uploade une photo en ligne, il faut bien que du code s'exécute sur un serveur distant pour enregistrer tout cela dans une base de données, etc.

C'est ce que nous allons voir ici.

PHP

PHP est le langage par excellence pour programmer côté serveur. Statistique 2018 :

According to W3Techs' data, PHP is used by 78.9% of all websites with a known server-side programming language.

Pour démarrer avec PHP, la méthode est assez simple :

  • Avoir un hébergement web (et un serveur web qui tourne dessus, comme par exemple Apache ou Nginx, mais c'est souvent le cas pré-installé)

  • Créer un fichier index.php à la racine de son site :

      <?php 
      echo "Hello world";
      ?>
        
  • C'est tout! Il suffit d'ouvrir https://example.com/monsite/index.php dans son navigateur et ce "code" est executé pour en faire une page web.

Avantages :

  • Il y a souvent rien à installer, car Apache et PHP sont installés par défaut sur les hébergements webs mutualisés. En gros, que se passe-t-il ? Lorsque Apache reçoit une requête pour http://example.com/monsite/index.php, il voit que c'est un .php et il passe la requête en interne à PHP (via mod_php, donc sans avoir à démarrer un nouveau process) qui exécute le code et produit une chaîne de caractères (string) en sortie, et Apache livre cela au "client" dans son navigateur.

  • Cela est très rapide (à chaque requête, le process Apache qui s'occupe de cette requête démarre un nouveau thread pour PHP et cela se fait très rapidement, lire aussi cet article pour plus de détails)

Python

Méthode simple (mais bizarrement assez peu mise en avant)

Il y a une méthode toute simple pour faire de la programmation web en Python, avec une logique similaire à celle détaillée en PHP :

  1. Installer le module mod_python :

     apt-get install libapache2-mod-python
  2. Créer un fichier .htaccess à la racine du site, pour indiquer que les fichiers .py doivent être traités par Python (*), contenant ceci :

     AddHandler mod_python .py
     PythonHandler mod_python.publisher
  3. Créer un fichier test.py :

     def index(req):
         return("<html><body>Hello world</body></html>")
  4. Ouvrir https://www.example.com/test.py dans le navigateur, ça marche !

(*) Pour pouvoir définir des Handler directement dans le .htaccess, il faut que le VirtualHost soit réglé en AllowOverride All, c'est la valeur par défaut pour certaines versions d'Apache.

Avantages :

  • logique toute simple, identique à ce qu'on fait en PHP

  • pas de librairie / framework à utiliser, c'est donc très léger

  • "Embedded mod_python embeds Python inside Apache; no process is forked", voir ici, ce qui semble donc optimisé niveau performance (l'interpréteur Python n'a pas besoin de redémarrer un nouveau process pour chaque requête !)

La méthode classique

La méthode "classique" pour faire du web en Python est assez différente des méthodes présentées précédemment.

Contrairement à avoir, comme précédemment, pour chaque requête, un script qui s'exécute puis se termine, il s'agit ici d'avoir un process Python qui tourne en permanence, et attend les requêtes au fur et à mesure, dans une boucle infinie (Event loop).

Exemple :

from bottle import route, run, template

@route('/hello/<name>')
def hello(name):
    return template('<b>Bonjour {{name}}</b>!', name=name)
    
@route('/')
def index():
    return 'Bienvenue sur le site!'

run(host='localhost', port=8080)  # boucle infinie, qui attend des requêtes sur le port 8080

On utilise classiquement un framework pour ça :

  • bottle.py, c'est le plus simple que j'ai testé et il marche super bien. Avantage: c'est un micro-framework en 1 seul fichier .py.
  • Flask, grosso modo pareil, peut-être un peu plus complet
  • Django, réputé comme très complet, jamais testé personnellement

Le code précédent fonctionne bien, mais "écoute" sur le port 8080, donc on peut y accéder avec https://example.com:8080/. Comment donc "relier" ce process Python avec Apache qui écoute, quant à lui, sur le port 80 ?

  • soit en faisant un .htaccess qui indique à Apache de rediriger les requêtes vers le serveur Python

      RewriteEngine On
      RewriteRule /(.*) http://localhost:8080/$1 [P,L]
        

    Il faut aussi se débrouiller manuellement pour que le script Python tourne sans arrêt, même si on ferme la fenêtre du terminal / le SSH. Exemple dans Bash :

      nohup python mysever.py &
        

    ou avec screen (voir un tuto à ce sujet) : screen -S pythonserver, python myserver.py, puis CTRL+A+D pour "détacher" le terminal, ou avec systemd.

  • soit en ajoutant ça dans la configuration Apache <VirtualHost> (méthode appelée "proxy / reverse proxy") :

      ProxyPass / http://localhost:8080/
      ProxyPassReverse / http://localhost:8080/
        

    De même que pour le point précédent, il faut lancer le script python manuellement et s'assurer qu'il tourne continuellement.

  • soit avec mod_wsgi (méthode souvent plébiscitée, voir ici pour un exemple complet, mais méthode que je ne recommanderai pas personnellement)

    Faire:

      apt-get install libapache2-mod-wsgi
        

    Puis remplacer la dernière ligne (run(host='localhost', port=8080)) par application = bottle.default_app(), puis mettre ça dans la config Apache :

      <VirtualHost *:80>
        ServerName example.com
        WSGIScriptAlias / /var/www/test_wsgi/app.py
        <Directory />
          AllowOverride All
          Require all granted
        </Directory>
      </VirtualHost>     
     

    L'avantage de cette méthode avec mod_wsgi est que c'est Apache qui va s'occuper tout seul de lancer le script Python et il n'y a plus besoin à la main de démarrer le script .py et de s'arranger pour qu'il tourne continuellement (donc plus besoin de nohup ou screen comme dans l'exemple précédent).

    Par contre, en testant mod_wsgi je suis tombé sur plein de problèmes: import module qui ne marche pas alors qu'il est dans le même répertoire, des Fatal Python error: PyEval_AcquireThread: NULL new thread state dans les logs Apache, etc. Bref, pas top.

Javascript

Oui on peut faire du Javascript côté serveur, c'est le cas notamment avec le framework très populaire NodeJS.

Le principe est le même que dans le paragraphe précédemment, on a un process qui tourne en permanence, avec une boucle d'événements (event loop). On le démarre avec :

node app.js

Voici le code pour app.js:

var express = require('express');
var app = express();
app.get('/', function (req, res) {
  res.send('Hello World!');
});
app.listen(8080, function () {
  console.log('Example app running!');
});

C

Pour les puristes ! In progress...

A voir (divers)

CGI

mod_cgi

Un serveur utilisant CGI démarre un nouveau process pour chaque requête. Cela peut ajouter pas mal de surcharge, mais c'est parfois la seule option, surtout sur les hébergements basiques.

A écrire : un exemple simple montrant un VirtualHost + .htaccess utilisant CGI avec une application binaire quelconque, une application Python, etc.

Comparaison de performance mod_php vs. mod_python

Créons un fichier test.py:

import time
def index(req):
    return(str(time.time()))

un fichier test.php:

<?php
    echo time();
?>

le .htaccess qui permet d'utiliser mod_python:

AddHandler mod_python .py
PythonHandler mod_python.publisher

et enfin index.html pour tester le temps que prend chaque requête:

<div id="a">Click test.py</div>
<div id="b">Click test.php</div>

<script type="text/javascript">
var start;

document.getElementById("a").onclick = function() { 
    start = Date.now()
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "test.py");
    xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); console.log(Date.now() - start); } }
    xhr.send("");
}

document.getElementById("b").onclick = function() { 
    start = Date.now()
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "test.php");
    xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); console.log(Date.now() - start); } }
    xhr.send("");
}
</script>

Ouvrons index.html dans le navigateur et regardons le contenu de la console.

Bilan :

  • la différence entre mod_php et mod_python est imperceptible, j'ai environ 35 millisecondes dans les deux cas, ce qui correspond à mon ping avec le serveur

  • je craignais que mod_python relance un interpréteur Python pour chaque requête (ce qui aurait été loooonnnnng... probablement au moins 100 ou 200 ms?) mais fort heureusement, non, donc c'est parfait! Voir aussi ici à ce sujet.

programmation_serveur_php_python_nodejs.txt · Dernière modification: 2019/11/29 18:45 par joseph