Changes between Initial Version and Version 1 of IOC20_T06


Ignore:
Timestamp:
Mar 26, 2020, 3:06:30 PM (5 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC20_T06

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