Outils pour utilisateurs

Outils du site


programmation_serveur_php_python_nodejs

<markdown> # 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](https://stackoverflow.com/questions/2712825/what-is-mod-php/2712839#2712839), 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](https://stackoverflow.com/questions/5171639/creation-of-new-process-for-each-request-of-web-page/5171656#5171656) et cela se fait très rapidement, lire aussi [cet article](https://abhinavsingh.com/how-does-php-echos-a-hello-world-behind-the-scene/) pour plus de détails)

* On peut [mixer très facilement du HTML et du PHP](https://www.php.net/manual/en/language.basic-syntax.phpmode.php).

## Python

Dans toute la suite, on supposera qu'un classique serveur web Apache tourne sur la machine. On supposera aussi que le `VirtualHost` soit réglé en `AllowOverride All` (c'est la valeur par défaut pour certaines versions d'Apache ; cela permet de définir des paramètres directement dans les fichiers `.htaccess`, ce qui est pratique):

``` <VirtualHost *:80>

  ServerName example.com
  DocumentRoot /home/www/example/
  <Directory />
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost> 

```

### Méthode simple (old-school): mod_cgi

* Installer [mod_cgi](http://httpd.apache.org/docs/current/mod/mod_cgi.html) (mais il est souvent déjà installé) et l'activer avec `a2enmod cgi`.

* Créer un fichier `/home/www/example/app.py` ayant une permission en exécution:

  ```
  #!/usr/bin/python3
  print("Content-Type: text/html")
  print("")
  print("Hello world")
  ````
  Dans `.htaccess`, faire figurer:
  ```
  Options +ExecCGI
  SetEnv PYTHONIOENCODING utf8
  AddHandler cgi-script .py
  ```
  

* Visitez http://example.com/app.py, ça doit marcher!

NB: Utiliser CGI revient à démarrer un nouveau process (= un nouvel interpréteur Python) pour chaque requête, ce qui est potentiellement plus lent (dans mes tests 90ms Python 2.7 / 140ms Python 3, au lieu de 34ms avec `mod_python` ou `mod_wsgi`) que les méthodes détaillées ci-après.

### Méthode simple: mod_python

Il y a une [méthode relativement simple](https://stackoverflow.com/questions/58414076/how-to-let-the-webserver-e-g-apache-call-python-directly/58414570#58414570) 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 !

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](https://stackoverflow.com/questions/219110/how-python-web-frameworks-wsgi-and-cgi-fit-together/520194#520194), 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 !)

### Méthode classique: avec un framework web (Bottle, Flask, etc.)

La méthode la plus courante pour faire du web en Python est assez différente des 2 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](https://bottlepy.org/docs/dev/), 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](https://github.com/bottlepy/bottle/blob/master/bottle.py). * [Flask](https://flask.palletsprojects.com/), 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 avec une règle de réécriture d'URL pour rediriger les requêtes, d'Apache vers le serveur Python

  Mettre ceci dans le `.htaccess` :
  ```
  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 faisant un “reverse proxy”

  Ajouter cela dans la configuration Apache `<VirtualHost>` :
  ```
  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`

  Cette méthode est souvent plébiscitée, voir [ici](https://bottlepy.org/docs/dev/deployment.html#apache-mod-wsgi) pour un exemple complet avec Bottle + `mod_wsgi`.
  On installe ce module Apache avec :
  ```
  apt-get install libapache2-mod-wsgi
  ```
  On remplace la dernière ligne de l'application Bottle (`run(host='localhost', port=8080)`) par `application = bottle.default_app()`, puis on met cela dans la config Apache :
  ```
  <VirtualHost *:80>
    ServerName example.com
    WSGIScriptAlias / /home/www/example/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 quelques problèmes: `import module` qui ne marche pas alors qu'il est [dans le même répertoire](https://stackoverflow.com/questions/59088010/wsgi-importerror-no-module-named-hello-module-in-the-same-directory-of-the-ma), des [Fatal Python error: PyEval_AcquireThread: NULL new thread state](https://stackoverflow.com/questions/18013356/fatal-python-error-pyeval-acquirethread) dans les logs Apache, etc. Bref, à voir à l'usage.

## Javascript

Oui on peut faire du Javascript côté serveur, c'est le cas notamment avec le framework très populaire [NodeJS](https://nodejs.org/).

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)

### A lire…

Pour bien voir la différence CGI / FastCGI / mod_wsgi / mod_python:

https://www.electricmonk.nl/docs/apache_fastcgi_python/apache_fastcgi_python.html

### 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](#methode_simple_mais_bizarrement_assez_peu_mise_en_avant) à ce sujet.

</markdown>

programmation_serveur_php_python_nodejs.txt · Dernière modification : de joseph