Changes between Initial Version and Version 1 of IOC_T05


Ignore:
Timestamp:
Apr 2, 2021, 7:29:04 AM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC_T05

    v1 v1  
     1= TP4 : Serveur WEB minimaliste
     2
     3== Objectif ==
     4
     5Le but de ce TME est de créer un site web consultable par un browser web permettant d'accéder à des capteurs.
     6
     7Vous allez commencer par travailler sur les Raspberry Pi 1 et vous devrez changer l'état des leds.
     8Vous 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 avec MQTT.
     9
     10Ce 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.
     11
     12Pour ce faire, nous allons procéder en deux temps.
     131. Nous allons faire communiquer un programme python avec un programme C par FIFO.
     142. Nous allons créer un serveur local sur le PC de développement et le faire communiquer avec le programme C.
     153. Nous allons mettre le serveur sur une raspberry PI et communiquer avec le programme C
     164. Nous allons remplacer le programme C par le programme de contrôle des LEDs.
     17
     18== 1. Communication par FIFO ==
     19
     20Pour démarrer, vous allez récupérer une [attachment:writer_reader.tgz archive] constituer de 4 fichiers: 2 lecteurs et 2 écrivains.
     21Les deux lecteurs sont interchangeables, le premier est en C, le second en Python.
     22Les deux écrivains sont aussi interchangeables.
     23{{{
     24writer_reader
     25├── Makefile
     26├── reader.c    : lit une fifo et affiche le message reçu jusqu'à recevoir le message end
     27├── reader.py   : idem reader.c mais en python
     28├── writer.c    : écrit dans une fifo 5 fois et écrit le message end
     29└── writer.py   : idem writer.c mais en python
     30}}}
     31
     32Vous pouvez tester les programmes qui vous sont proposés.
     33Je 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 [https://python.developpez.com/cours python] c'est le moment de vous y mettre, mais ce n'est rédhibitoire pour aujourd'hui.
     34Ces 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).
     35
     36**writer.py**
     37- Dans quel répertoire est créée la fifo ?
     38- Quelle différence mkfifo et open ?
     39- Pourquoi tester que la fifo existe ?
     40- À quoi sert flush ?
     41- Pourquoi ne ferme-t-on pas la fifo ?
     42
     43{{{#!python
     44#!/usr/bin/env python
     45import os, time
     46
     47pipe_name = '/tmp/myfifo'
     48
     49if not os.path.exists(pipe_name):
     50    os.mkfifo(pipe_name)
     51
     52pipe_out = open(pipe_name,'w')
     53
     54i=0;
     55while i < 5:
     56    pipe_out.write("hello %d fois from python\n" % (i+1,))
     57    pipe_out.flush()
     58    time.sleep(1)
     59    i=i+1
     60
     61pipe_out.write("end\n")
     62}}}
     63
     64**reader.py**
     65- Que fait readline ?
     66
     67{{{#!python
     68#!/usr/bin/env python
     69import os, time
     70
     71pipe_name = '/tmp/myfifo'
     72
     73if not os.path.exists(pipe_name):
     74    os.mkfifo(pipe_name)
     75
     76pipe_in = open(pipe_name,'r')
     77while str != "end\n" :
     78    str = pipe_in.readline()
     79    print '%s' % str,
     80
     81}}}
     82Vous 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.
     83
     84- Expliquez le phénomène.
     85
     86== 2. Création d'un serveur fake ==
     87
     88Le but de cette première partie est de réaliser le programme suivant:
     89
     90[[Image(htdocs:png/fake2server.png,nolink,400px)]]
     91
     92- fake lit une valeur sur stdin et place la valeur lue dans une variable.
     93- Lorsque l'on tape plusieurs valeurs de suite la nouvelle valeur écrase l'ancienne.
     94- fake est toujours en fonctionnement.
     95- fake attends aussi un message de la fifo s2f.
     96- lorsqu'il reçoit un message, il l'affiche et il renvoie dans la fifo f2s la dernière valeur lue sur stdin.
     97
     98Vous commencez par récupérer l'[attachment:fake.tgz archive] qui donne un point de départ.
     99{{{
     100fake
     101├── Makefile
     102├── server.py
     103└── fake.c
     104}}}
     105
     106- Dans un premier terminal, compilez et démarrez fake.
     107- Dans un autre terminal, exécuter ./server.py
     108  - Le programme "server" Python est lancé et arrêté, il se comporte comme se comportera le script CGI.
     109- Quand le server python démarre,
     110  - il envoie un message sur la fifo s2f
     111  - puis il lit la fifo s2f et affiche le résultat.
     112
     113Vous devez :
     1141. modifier le nom des fifo pour éviter les conflits (changer p. ex. fw avec vos initiales)
     1151. modifier le select dans fake pour lire les deux fifos d'entrées stdin et s2f.
     1161. 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.
     117
     118**fake.c**
     119{{{#!c
     120#include <fcntl.h>
     121#include <stdio.h>
     122#include <stdlib.h>
     123#include <sys/time.h>
     124#include <sys/stat.h>
     125#include <unistd.h>
     126#include <string.h>
     127
     128#define MAXServerResquest 1024
     129
     130int main()
     131{
     132    int     f2s, s2f;                                       // fifo file descriptors
     133    char    *f2sName = "/tmp/f2s_fw";                       // filo names
     134    char    *s2fName = "/tmp/s2f_fw";                       //
     135    char    serverRequest[MAXServerResquest];               // buffer for the request
     136    fd_set  rfds;                                           // flag for select
     137    struct  timeval tv;                                     // timeout
     138    tv.tv_sec = 1;                                          // 1 second
     139    tv.tv_usec = 0;                                         //
     140
     141    mkfifo(s2fName, 0666);                                  // fifo creation
     142    mkfifo(f2sName, 0666);
     143
     144    /* open both fifos */
     145    s2f = open(s2fName, O_RDWR);                            // fifo openning
     146    f2s = open(f2sName, O_RDWR);
     147
     148    do {
     149        FD_ZERO(&rfds);                                     // erase all flags
     150        FD_SET(s2f, &rfds);                                 // wait for s2f
     151
     152        if (select(s2f+1, &rfds, NULL, NULL, &tv) != 0) {   // wait until timeout
     153            if (FD_ISSET(s2f, &rfds)) {                     // something to read
     154                int nbchar;
     155                if ((nbchar = read(s2f, serverRequest, MAXServerResquest)) == 0) break;
     156                serverRequest[nbchar]=0;
     157                fprintf(stderr,"%s", serverRequest);
     158                write(f2s, serverRequest, nbchar);
     159            }
     160        }
     161    }
     162    while (1);
     163
     164    close(f2s);
     165    close(s2f);
     166
     167    return 0;
     168}
     169}}}
     170
     171**server.py**
     172{{{#!python
     173#!/usr/bin/env python
     174import os, time
     175
     176s2fName = '/tmp/s2f_fw'
     177if not os.path.exists(s2fName):
     178   os.mkfifo(s2fName)
     179s2f = open(s2fName,'w+')
     180
     181f2sName = '/tmp/f2s_fw'
     182if not os.path.exists(f2sName):
     183   os.mkfifo(f2sName)
     184f2s = open(f2sName,'r')
     185
     186s2f.write("w hello\n")
     187s2f.flush()
     188str = f2s.readline()
     189print '%s' % str,
     190
     191f2s.close()
     192s2f.close()
     193}}}
     194
     195== 3. Création d'un serveur web ==
     196
     197Nous allons maintenant créer le vrai server http.
     198Dans l'[attachment:server-fake.tgz 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.
     199{{{
     200server-fake
     201├── fake
     202│   ├── Makefile
     203│   └── fake.c
     204└── server
     205    ├── server.py
     206    └── www
     207        ├── cgi-bin
     208        │   ├── led.py
     209        │   └── main.py
     210        ├── img
     211        │   └── peri.png
     212        └── index.html
     213}}}
     214
     215**Pour tester le server http**
     216
     217- 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.
     218  {{{
     219  cd fake
     220  make
     221  ./fake
     222  }}}
     223- Dans un second terminal, lancez le server.py. C'est un server http en python.
     224  {{{
     225  cd server/www
     226  ../server.py
     227  }}}
     228- Sur votre navigateur préféré, visualisez la page index.html à l'adresse `localhost:8X00` (ou `127.0.0.1:8X00`)
     229  - Vous devez voir apparaitre un logo et une case avec un bouton enter.
     230  - La page `index.html` contient deux "frames":
     231    - Le premier avec le logo.
     232    - 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`.
     233      - 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.
     234  - Lorsque vous écrivez quelque chose dans la case, la page index.html demande l'exécution de script `cgi-bin/led.py`
     235  - 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.
     236    - Elle affiche ce qui a été reçu de la fifo `f2s`
     237    - Elle est remplacée au bout d'une seconde par la page `main.py` grace à une commande `<META>`
     238
     239**server.py**
     240Le server écoute le port 8X00 et affiche la page index.htlm présente dans le répertoire wwww.
     241{{{
     242 X est une valeur entre 0 et 3, puisque nous allons avoir 4 serveurs HTTP par Raspberry.
     243}}}
     244 
     245{{{#!python
     246#!/usr/bin/env python
     247import BaseHTTPServer
     248import CGIHTTPServer
     249import cgitb; cgitb.enable()
     250
     251server = BaseHTTPServer.HTTPServer
     252handler = CGIHTTPServer.CGIHTTPRequestHandler
     253server_address = ("", 8X00)
     254handler.cgi_directories = ["/cgi-bin"]
     255
     256httpd = server(server_address, handler)
     257httpd.serve_forever()
     258}}}
     259
     260**index.html**
     261{{{
     262<html>
     263 <head><title>Peri Web Server</title></head>
     264 <frameset rows="100,*" frameborder=0>
     265  <frame src="img/peri.png">
     266  <frame src="cgi-bin/main.py">
     267 </frameset>
     268</html>
     269}}}
     270
     271**main.py**
     272{{{#!python
     273#!/usr/bin/env python
     274
     275html="""
     276<head>
     277  <title>Peri Web Server</title>
     278</head>
     279<body>
     280LEDS:<br/>
     281<form method="POST" action="led.py">
     282  <input name="val" cols="20"></input>
     283  <input type="submit" value="Entrer">
     284</form>
     285</body>
     286"""
     287
     288print html
     289}}}
     290
     291**led.py**
     292{{{#!python
     293#!/usr/bin/env python
     294import cgi, os, time,sys
     295form = cgi.FieldStorage()
     296val = form.getvalue('val')
     297
     298s2fName = '/tmp/s2f_fw'
     299f2sName = '/tmp/f2s_fw'
     300s2f = open(s2fName,'w+')
     301f2s = open(f2sName,'r',0)
     302
     303s2f.write("w %s\n" % val)
     304s2f.flush()
     305res = f2s.readline()
     306f2s.close()
     307s2f.close()
     308
     309html="""
     310<head>
     311  <title>Peri Web Server</title>
     312  <META HTTP-EQUIV="Refresh" CONTENT="1; URL=/cgi-bin/main.py">
     313</head>
     314<body>
     315LEDS:<br/>
     316<form method="POST" action="led.py">
     317  <input name="val" cols="20"></input>
     318  <input type="submit" value="Entrer">
     319  set %s
     320</form>
     321</body>
     322""" % (val,)
     323
     324print html
     325}}}
     326
     327**Pour l'exécution sur la carte !RaspberryPi**
     328- Vous devez copier tout `server-fake` sur la carte de votre choix. Vous ne pouvez pas être plus de deux binômes par carte.
     329- Vous lancez fake et le server http comme précédement.
     330- Sur votre navigateur, vous devez mettre une exception au proxy pour l'adresse du routeur des !RaspberryPi `peri`.
     331- Si vous êtes sur la carte **20**, vous mettez comme URL peri:**8X20**. Vous faites de manière semblable pour les autres cartes.
     332- 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**.
     333- 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.
     334
     335**Accès aux leds et au bouton poussoir par le serveur**
     336
     337- Modifier fake.c en ledbp.c et led.py pour commander les leds et lire le bouton poussoir
     338
     339  [[Image(htdocs:png/ledbp2server.png,nolink,400px)]]