wiki:Peri19_T07

TP8 : Serveur WEB minimaliste

Objectif

Le but de ce TME est de créer un site web consultable par un browser web permettant d'accéder à des capteurs.

Vous allez commencer par travailler sur les Raspberry Pi 1 et vous devrez changer l'état des leds. Vous pourrez mettre ensuite le serveur sur une RaspberryPi? 3 mais vous ne pourrez pas commander de capteurs tant que vous n'aurez pas écrit une application communiquant avec l'ESP32 en BLE.

Ce premier serveur web est écrit en Python, à la réception des requêtes du client, il exécute des scripts CGI (Common Gateway Interface) écrit également en Python pour produire des pages HTML dynamiques. Les scripts CGI devront communiquer avec le programme écrit en C contrôlant les LEDs. La communication entre les scripts et le programme de contrôle se fera par fifo UNIX. Le programme en C sera : soit la version permettant le contrôle depuis une application utilisateur (avec les droits de root), soit par une application utilisateur qui communique avec un driver. Je ne demande pas cette seconde possibilité parce que vous allez devoir insérer un module dans le noyau et c'est une difficulté supplémentaire qui n'apporte rien vis-à-vis de ce que vous allez voir aujourd'hui, mais je ne vous interdit pas de le faire.

Pour ce faire, nous allons procéder en deux temps.

  1. Nous allons faire communiquer un programme python avec un programme C par FIFO.
  2. Nous allons créer un serveur local sur le PC de développement et le faire communiquer avec le programme C.
  3. Nous allons mettre le serveur sur une raspberry PI et communiquer avec le programme C
  4. Nous allons remplacer le programme C par le programme de contrôle des LEDs.

1. Communication par FIFO

Pour démarrer, vous allez récupérer une archive constituer de 4 fichiers: 2 lecteurs et 2 écrivains. Les deux lecteurs sont interchangeables, le premier est en C, le second en Python. Les deux écrivains sont aussi interchangeables.

writer_reader
├── Makefile
├── reader.c    : lit une fifo et affiche le message reçu jusqu'à recevoir le message end
├── reader.py   : idem reader.c mais en python
├── writer.c    : écrit dans une fifo 5 fois et écrit le message end
└── writer.py   : idem writer.c mais en python

Vous pouvez tester les programmes qui vous sont proposés. Je vous demande de lire les codes en commençant par les programmes python, en répondant aux questions suivantes. Si vous ne connaissez pas le langage python c'est le moment de vous y mettre, mais ce n'est rédhibitoire pour aujourd'hui. Ces questions ne sont pas exhaustives, l'idée c'est d'avoir une "compréhension" de ce qu’est dans le code (vous devrez utiliser Google pour ça).

writer.py

  • Dans quel répertoire est créée la fifo ?
  • Quelle différence mkfifo et open ?
  • Pourquoi tester que la fifo existe ?
  • À quoi sert flush ?
  • Pourquoi ne ferme-t-on pas la fifo ?
#!/usr/bin/env python
import os, time

pipe_name = '/tmp/myfifo'

if not os.path.exists(pipe_name):
    os.mkfifo(pipe_name)

pipe_out = open(pipe_name,'w')

i=0;
while i < 5:
    pipe_out.write("hello %d fois from python\n" % (i+1,))
    pipe_out.flush()
    time.sleep(1)
    i=i+1

pipe_out.write("end\n")

reader.py

  • Que fait readline ?
#!/usr/bin/env python
import os, time

pipe_name = '/tmp/myfifo'

if not os.path.exists(pipe_name):
    os.mkfifo(pipe_name)

pipe_in = open(pipe_name,'r')
while str != "end\n" :
    str = pipe_in.readline()
    print '%s' % str,

Vous allez remarquer que lorsque le vous lancer un écrivain (en C ou en Pyhton) rien ne se passe tant que vous n'avez pas lancé un lecteur.

  • Expliquez le phénomène.

2. Création d'un serveur fake

Le but de cette première partie est de réaliser le programme suivant:

fake2server.png

  • fake lit une valeur sur stdin et place la valeur lue dans une variable.
  • Lorsque l'on tape plusieurs valeurs de suite la nouvelle valeur écrase l'ancienne.
  • fake est toujours en fonctionnement.
  • fake attends aussi un message de la fifo s2f.
  • lorsqu'il reçoit un message, il l'affiche et il renvoie dans la fifo f2s la dernière valeur lue sur stdin.

Vous commencez par récupérer l'archive qui donne un point de départ.

fake
├── Makefile
├── server.py
└── fake.c
  • Dans un premier terminal, compilez et démarrez fake.
  • Dans un autre terminal, exécuter ./server.py
    • Le programme "server" Python est lancé et arrêté, il se comporte comme se comportera le script CGI.
  • Quand le server python démarre,
    • il envoie un message sur la fifo s2f
    • puis il lit la fifo s2f et affiche le résultat.

Vous devez :

  1. modifier le nom des fifo pour éviter les conflits (changer p. ex. fw avec vos initiales)
  2. modifier le select dans fake pour lire les deux fifos d'entrées stdin et s2f.
  3. modifier server.py pour lire la valeur lue sur stdin afin que ce que server.py envoi ne soit pas "w hello" mais soit une chaine tapée au clavier. Le but est de "simuler" le comportement du script CGI et de vous obliger à écrire un peu de code Python.

fake.c

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

#define MAXServerResquest 1024

int main()
{
    int     f2s, s2f;                                       // fifo file descriptors
    char    *f2sName = "/tmp/f2s_fw";                       // filo names
    char    *s2fName = "/tmp/s2f_fw";                       //
    char    serverRequest[MAXServerResquest];               // buffer for the request
    fd_set  rfds;                                           // flag for select
    struct  timeval tv;                                     // timeout
    tv.tv_sec = 1;                                          // 1 second
    tv.tv_usec = 0;                                         //

    mkfifo(s2fName, 0666);                                  // fifo creation
    mkfifo(f2sName, 0666);

    /* open both fifos */
    s2f = open(s2fName, O_RDWR);                            // fifo openning
    f2s = open(f2sName, O_RDWR);

    do {
        FD_ZERO(&rfds);                                     // erase all flags
        FD_SET(s2f, &rfds);                                 // wait for s2f

        if (select(s2f+1, &rfds, NULL, NULL, &tv) != 0) {   // wait until timeout
            if (FD_ISSET(s2f, &rfds)) {                     // something to read
                int nbchar;
                if ((nbchar = read(s2f, serverRequest, MAXServerResquest)) == 0) break;
                serverRequest[nbchar]=0;
                fprintf(stderr,"%s", serverRequest);
                write(f2s, serverRequest, nbchar);
            }
        }
    }
    while (1);

    close(f2s);
    close(s2f);

    return 0;
}

server.py

#!/usr/bin/env python
import os, time

s2fName = '/tmp/s2f_fw'
if not os.path.exists(s2fName):
   os.mkfifo(s2fName)
s2f = open(s2fName,'w+')

f2sName = '/tmp/f2s_fw'
if not os.path.exists(f2sName):
   os.mkfifo(f2sName)
f2s = open(f2sName,'r')

s2f.write("w hello\n")
s2f.flush()
str = f2s.readline()
print '%s' % str,

f2s.close()
s2f.close()

3. Création d'un serveur web

Nous allons maintenant créer le vrai server http. Dans l'archive vous trouvez un squelette de server que vous allez d'abord tester avant de l'exécuter sur la carte RaspberryPi pour la commande des leds.

server-fake
├── fake
│   ├── Makefile
│   └── fake.c
└── server
    ├── server.py
    └── www
        ├── cgi-bin
        │   ├── led.py
        │   └── main.py
        ├── img
        │   └── peri.png
        └── index.html

Pour tester le server http

  • Dans un premier terminal, après l'avoir compilé, lancez le programme fake. Il s'agit du même programme fake.c que précédemment, qui reçoit une requête depuis une fifo s2f et qui renvoie une réponse.
    cd fake
    make
    ./fake
    
  • Dans un second terminal, lancez le server.py. C'est un server http en python.
    cd server/www
    ../server.py
    
  • Sur votre navigateur préféré, visualisez la page index.html à l'adresse localhost:8X00 (ou 127.0.0.1:8X00)
    • Vous devez voir apparaitre un logo et une case avec un bouton enter.
    • La page index.html contient deux "frames":
      • Le premier avec le logo.
      • Le second est contient la case et le bouton. Le code html de cette case est obtenu par l'exécution du programme Python cgi-bin/main.py.
        • Notez qu'il n'est pas très utile d'avoir produit cette page par un programme python, car la page n'est pas dynamique (son code est toujours le même) mais c'est pour donner la possibilité de la rendre dynamique.
    • Lorsque vous écrivez quelque chose dans la case, la page index.html demande l'exécution de script cgi-bin/led.py
    • le script led.py envoi le contenu de la case sur la fifo s2f attendue par fake et produit une page presque identique à main.py avec deux différences.
      • Elle affiche ce qui a été reçu de la fifo f2s
      • Elle est remplacée au bout d'une seconde par la page main.py grace à une commande <META>

server.py Le server écoute le port 8X00 et affiche la page index.htlm présente dans le répertoire wwww.

 X est une valeur entre 0 et 3, puisque nous allons avoir 4 serveurs HTTP par Raspberry. 

#!/usr/bin/env python
import BaseHTTPServer
import CGIHTTPServer
import cgitb; cgitb.enable()

server = BaseHTTPServer.HTTPServer
handler = CGIHTTPServer.CGIHTTPRequestHandler
server_address = ("", 8X00)
handler.cgi_directories = ["/cgi-bin"]

httpd = server(server_address, handler)
httpd.serve_forever()

index.html

<html>
 <head><title>Peri Web Server</title></head>
 <frameset rows="100,*" frameborder=0>
  <frame src="img/peri.png">
  <frame src="cgi-bin/main.py">
 </frameset>
</html>

main.py

#!/usr/bin/env python

html="""
<head>
  <title>Peri Web Server</title>
</head>
<body>
LEDS:<br/>
<form method="POST" action="led.py">
  <input name="val" cols="20"></input>
  <input type="submit" value="Entrer">
</form>
</body>
"""

print html

led.py

#!/usr/bin/env python
import cgi, os, time,sys
form = cgi.FieldStorage()
val = form.getvalue('val')

s2fName = '/tmp/s2f_fw'
f2sName = '/tmp/f2s_fw'
s2f = open(s2fName,'w+')
f2s = open(f2sName,'r',0)

s2f.write("w %s\n" % val)
s2f.flush()
res = f2s.readline()
f2s.close()
s2f.close()

html="""
<head>
  <title>Peri Web Server</title>
  <META HTTP-EQUIV="Refresh" CONTENT="1; URL=/cgi-bin/main.py">
</head>
<body>
LEDS:<br/>
<form method="POST" action="led.py">
  <input name="val" cols="20"></input>
  <input type="submit" value="Entrer">
  set %s
</form>
</body>
""" % (val,)

print html

Pour l'exécution sur la carte RaspberryPi

  • Vous devez copier tout server-fake sur la carte de votre choix. Vous ne pouvez pas être plus de deux binômes par carte.
  • Vous lancez fake et le server http comme précédement.
  • Sur votre navigateur, vous devez mettre une exception au proxy pour l'adresse du routeur des RaspberryPi peri.
  • Si vous êtes sur la carte 20, vous mettez comme URL peri:8X20. Vous faites de manière semblable pour les autres cartes.
  • Si vous êtes le second binôme sur la carte *20*, l'URL est peri:8120, et dans le script server.py vous devez écouter le port 8100.
  • Le routeur des RaspberryPi a été programmé pour renvoyer les requêtes http reçues sur le port 8xyy avec x=(0 ou 1) et yy=(20,21,...,26) sur le carte yy et le port 8X00.

Accès aux leds et au bouton poussoir par le serveur

  • Modifier fake.c en ledbp.c et led.py pour commander les leds et lire le bouton poussoir

ledbp2server.png

Last modified 5 years ago Last modified on Apr 5, 2019, 5:52:44 PM

Attachments (3)

Download all attachments as: .zip