Interactions client - serveur (FLASK)


Le yeti explorateur de mathématique et d'informatique. yeti

TP basé sur la manipulation d'un serveur Flask.

Objectif : visualiser des interactions entre le client et le seveur (qui seront ici une seule et même machine)

Le modèle client serveur

Pour simplifier le schéma client-serveur désigne un mode de communication entre programmes : l'un qualifié de client envoie des requêtes; l'autre, le serveur, y répond.

Dans le cas du WEB, le client est le navigateur et protocole utilisé pour communiquer est HTTP.

Dans notre TP le client et le serveur seront situés tous les deux sur votre ordinateur.

Il existe de nombreux serveurs web, mais le plus utilisé se nomme Apache. Nous n'allons pas utiliser Apache, car nous allons travailler avec le framework Python Flask.

Un framework, pour simplifier, est un ensemble de logiciels qui serviront d'architecture (ou de base) pour créer un autre logiciel.

Dans notre cas, Flask (module Python) nous permettra de créer notre serveur pour héberger quelques pages rédigées en html et css.

Vous utilisez un navigateur internet pour accéder à ses pages, le navigateur sera donc le ... client ! Bien vu 😉

 

Installation de Flask

  1. Créez un dossier nommé TP_Flask
  2. Depuis Visual Studio Code, ouvrez ce dossier  
  3. Nous allons installer Flask , pour cela ouvrez la console (Terminal / nouveau terminal ) OU encore CTRL + ù
    Entrez la commande : pip install flask
    Si tout ce passe bien vous obtiendrez un résultat similaire à celui-ci :
  4. Flask étant installé, vous n'aurez plus besoin de réaliser les étapes 1 à 3 pour les prochaines fois où vous utiliserez Flask.
  5. Créez un fichier nommé views.py (il sera placé dans le répertoire TP_Flask )
    Pour faire cela, comme nous sommes dans le bon dossier, le plus simple est de cliquer sur nouveau fichier et de renseigner le nom du fichier (ne pas oublier l'extension .py), voir image ci-dessous :
  6. Saisissez le code suivant :
    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        return "Salut le monde !"
    
    if __name__ == '__main__':
        app.run(debug=True)
  7. Démarrez ensuite Flask en exécutant ce fichier python. Pour cela plusieurs solutions :
    • Si vous avez installé l'extension Python de Visual Studio Code, vous cliquez simplement sur la flèche verte en haut à droite de la fenêtre
    • Sinon, ouvrez une console et entrez le code suivant : python .\views.py (c'est en fait ce que fait VSC si vous avez installé l'extension Python)
      Dans tous les cas, si cela fonctionne, vous devriez avoir le résultat suivant qui apparaît dans la console :



      Ce code signifie que le serveur est bien lancé à l'adresse IP 127.0.0.1 et sur le port 5000. Pour visualiser le résultat nous allons utiliser un navigateur internet.
  8. Là encore, plusieurs méthodes. Je vous conseille dans tous les cas d'utiliser un navigateur récent (Firefox ou Chrome). Pour changer de navigateur par défaut voir ici.
  • Méthode 1 : faites un CTRL + Clic sur l'adresse IP que vous voyez dans la console. Cela ouvrira votre navigateur par défaut à la bonne adresse
  • Méthode 2 : ouvrez votre navigateur internet et dans la barre d'adresse entrez : http://127.0.0.1:5000/  (ou encore http://localhost:5000/ )

Vous devriez obtenir un page html très simple avec un beau message de bienvenue ! 😯

Si cela ne fonctionne pas, contactez votre professeur en notant bien les éventuels message d'erreurs obtenus.

Analyse et un peu de théorie

Comment comprendre le code que vous avez utilisé ? 😱 Rassurez-vous, vous devez le comprendre mais pas savoir le produire en partant de rien !

Allons-y étape par étape :

from flask import Flask

Permet d'importer le module flask

app = Flask(__name__)

Créer un objet app : cette ligne est systématique nécessaire.

@app.route('/')

Nous utilisons ici un décorateur (cette notion de décorateur ne sera pas traitée en NSI). Vous devez juste comprendre la fonction qui suit ce décorateur ("index"), sera exécutée dans le cas où le serveur web recevra une requête HTTP avec une URL correspondant à la racine du site ('/'), c'est à dire, dans notre exemple, le cas où on saisie dans la barre d'adresse "127.0.0.1:5000/" ou "localhost:5000/" (ou simplement "localhost:5000")

def index():
    return "Salut le monde !"

C'est la fonction qui sera appelée lorsqu'un client demandera l'adresse localhost:5000/
Elle revoie toujours le même contenu, on parlera de contenu "statique". Plus tard nous verrons comment faire évoluer ce contenu en fonction de paramètres.

if __name__ == '__main__':
    app.run(debug=True)

 Ces 2 lignes permettent d'exécuter le mode "debug". Ces lignes seront nécessaire pour le développement de nos application. Dans la console de VSC nous voyons ainsi les requêtes et les éventuelles erreurs. C'est un très bon moyen de visualiser les requêtes vers le serveur. Par exemple lorsque vous avez ouvert le navigateur à l'adresse 127.0.0.1:5000/ vous avez eu une ligne qui est apparu dans la console :

127.0.0.1 - - [04/May/2020 14:55:09] "GET / HTTP/1.1" 200 -

Nous voyons ainsi l'adresse du client (ici 127.0.0.1) qui effectue une requête HTTP ( version 1.1)  de type GET. Le code 200 signifie le succès de la requête .
Si dans le navigateur vous entrez l'adresse 127.0.0.1:5000/accueil vous aurez sur votre navigateur un message d'erreur. Sur la console, vous verrez le message suivant :

127.0.0.1 - - [04/May/2020 14:59:27] "GET /accueil HTTP/1.1" 404 -

 Nous voyons ainsi l'adresse du client (ici 127.0.0.1) qui effectue une requête HTTP ( version 1.1)  de type GET. Mais cette fois le code 404 signifie la page demandée n'est pas trouvée . LA fameuse erreur 404 du WEB 😁

 

Un nouvelle page 🤩

Notre objectif va être de réaliser une page "contact". Pour cela la route à utiliser sera 127.0.0.1:5000/contact. Ainsi le décorateur devient :

 

@app.route('/contact')

 

Ensuite, il faut créer une nouvelle fonction qui renvoie des informations sur le webmaster (ou webmestre 🥖) , voici un exemple (à modifier !) de code :

 

def presentation():
    message = "<h1> Présentation du webmaster </h1>"
    message += "<h2> Mikael LE MENTEC </h2>"
    message += "<p> Professeur de <strong>mathématiques</strong> et de <strong>NSI</strong> </p>"
    message += """<a href="mailto:lesmathsduyeti@orange.fr"> Mail  </a> """
    return message

 

Ce qui donne au final le code ci-dessous pour le fichier views.py

 

from flask import Flask
app = Flask(__name__)


@app.route('/')
def index():
    return "Salut le monde !"

@app.route('/contact')
def presentation():
    message = "<h1> Présentation du webmaster </h1>"
    message += "<h2> Mikael LE MENTEC </h2>"
    message += "<p> Professeur de <strong>mathématiques</strong> et de <strong>NSI</strong> </p>"
    message += """<a href="mailto:lesmathsduyeti@orange.fr"> Mail  </a> """
    return message

if __name__ == '__main__':
    app.run(debug=True)

Enregistrez vos modifications. Le serveur étant en mode "DEBUG", il va redémarrer pour mettre à jour les fichiers qu'il va distribuer au client.

Testez-le en appelant la page  127.0.0.1:5000/contact depuis votre navigateur internet. Vous pouvez forcer l'actualisation des pages si nécessaire.

En vous inspirant de l'exemple précédent, créez une page décrivant votre activité préférée 😉

La page devra être accessible depuis l'adresse http://127.0.0.1/loisir

@app.route('/loisir')
def loisir():
    message = "<h1> Présentation de la course à pied </h1>"
    message += "<h2> Pourquoi la course à pied </h2>"
    message += "<p> La nature, l'exercice physique sont des plaisirs à savourer ! </p>"
    return message

Le modèle MVC.

Pour l'instant tout fonctionne mais il y a encore des choses que l'on peut améliorer :

  • il n'y a pas d'interaction avec l'utilisateur, nous verrons cela avec les paramètres des fonctions et les formulaires
  • et taper du code html dans un fonction python, ce n'est pas trop propre ! 😇

Pour cette dernière remarque, nous allons parler des templates ou gabarits. Mais avant, un peu de théorie sur le modèle MVC.

Nous parlons souvent de l’architecture MVC (ce n'est pas uniquement lié à Flask). Il s’agit d’un modèle distinguant plusieurs rôles précis d’une application, qui doivent être accomplis. Comme son nom l’indique, l’architecture (ou « patron ») Modèle-Vue-Contrôleur est composée de trois entités distinctes, chacune ayant son propre rôle à remplir. Voici un schéma qui résume cela :

Le MVC permet de bien organiser son code source. Il va vous aider à savoir quels fichiers créer, mais surtout à définir leur rôle. Le but de MVC est justement de séparer la logique du code en trois parties que l'on retrouve dans des fichiers distincts.

  • Modèle : cette partie gère les données de votre site. Son rôle est d'aller récupérer les informations « brutes » dans la base de données, de les organiser et de les assembler pour qu'elles puissent ensuite être traitées par le contrôleur. On y trouve donc entre autres les requêtes aux bases de données (programme de Terminale NSI).

  • Vue : cette partie se concentre sur l'affichage. Elle ne fait presque aucun calcul et se contente de récupérer des variables pour savoir ce qu'elle doit afficher. On y trouve essentiellement du code HTML mais aussi quelques boucles et conditions python très simples, pour afficher par exemple une liste de messages.

  • Contrôleur : cette partie gère la logique du code qui prend des décisions. C'est en quelque sorte l'intermédiaire entre le modèle et la vue : le contrôleur va demander au modèle les données, les analyser, prendre des décisions et renvoyer le texte à afficher à la vue. Le contrôleur contient exclusivement du python. C'est notamment lui qui détermine si le visiteur a le droit de voir la page ou non (gestion des droits d'accès).

  • Template : cette partie est le modèle de la page HTML qui sera utilisée par la vue pour générer la page HTML envoyée au client. On peut la voir comme un texte à trous dans lesquels seront insérées les données calculées par le contrôleur.
    Dans l'exemple ci-dessus {{titre}} est remplacé par "Flask c'est cool" car la variable titre devait contenir cette valeur. Ainsi la vue renvoyée est calculée à l'aide de variables.

 Passons à la pratique😁

Un exemple de template

Notre objectif est de créer une page d'accueil plus sympathique. Nous allons donc modifier la fonction index

  1. Créer un dossier templates dans le dossier TP_flask
  2. Dans ce dossier, créer un fichier index.html avec le code suivant :
    <!doctype html>
    <html lang="fr">
    	<head>
    		<meta charset="utf-8">
    		<title>Ma page d'accueil</title>
    	</head>
    	<body>
          <h1>Un site qui déchire.</h1>
          <h2>Bonjour cher visiteur !</h2>
          <p>Vous voici sur mon site à moi.</p>
          <a href="http://127.0.0.1:5000/contact">Lien vers les contacts.</a>
    	</body>
    </html>
  3. Nous voulons maintenant afficher cette page lors de l'accès à l'adresse 127.0.0.1/
    Il faut dans un premier temps importer un nouvel objet, modifiez la première ligne du fichier views.py comme ceci :
    from flask import Flask,render_template
    Puis modifions la fonction index comme cela :
    @app.route('/')
    def index():
        return render_template("index.html")
    Visualisez le résultat dans votre navigateur. Testez le lien vers les contacts.

Modifiez la fonction qui permet l'affichage du contact 📇  pour quelle utilise un template.

Créez un fichier contact.html dans le dossier templates

Dans ce dossiez écrivez un code semblable à celui-ci

<h1> Présentation du webmaster </h1>
<h2> Mikael LE MENTEC </h2>
<p> Professeur de <strong>mathématiques</strong> et de <strong>NSI</strong> </p>
<a href="mailto:lesmathsduyeti@orange.fr"> Mail  </a>

Dans le fichier views.py , modifiez la fonction presentation comme cela : 

@app.route('/contact')
def presentation():
    return render_template("contact.html")

Des pages un peu moins statiques !

Pour l'instant, le serveur Flask créer toujours les même pages. Mais Flask permet de générer des vues (pages HTML) en fonction de paramètres, de formulaires ...

Commençons par améliorer l'affichage de notre page d'accueil en personnalisant l'affichage de la salutation.

Modifiez la page index.html comme cela :

<!doctype html>
<html lang="fr">
	<head>
		<meta charset="utf-8">
		<title>Ma page d'accueil</title>
	</head>
	<body>
      <h1>Un site qui déchire.</h1>
      <h2>Bonjour {{prenom}} {{nom}} !</h2>
      <p>Vous voici sur mon site à moi.</p>
      <a href="./contact">Lien vers les contacts.</a>
	</body>
</html>

Remarquez le code {{prenom}} {{nom}} . Le contrôleur remplacera ces variables par celles qui seront fournies par la fonction index.
Modifions donc la fonction index du fichier views.py comme qui suit :

@app.route('/')
def index():
    p = "mikaël"
    n = "le mentec"
    return render_template("index.html",prenom = p, nom = n)

Enregistrez et allez voir le résultat dans votre navigateur. 

Pour l'instant, il faut changer à la main les variables pour que le nom affiché soit le bon, MAIS ce n'est que le début. Nous verrons plus tard comment, avec un formulaire, nous pourrons adapter la page à l'utilisateur.

Un dernier raffinement modifiez le fichier html ainsi : Bonjour {{prenom|capitalize}} {{nom|upper}} !

Le prénom et le nom seront affiché avec la bonne "casse" et cela même si les variables ne sont pas bien écrites.

Autre défaut qui me dérange, nous n'utilisons pas vraiment le potentiel d'un ordinateur, demandons des tâches plus complexes (mais pas trop , ce n'est qu'un modeste cours d'introduction ... 😉 )

Voici le fichier views.py dans le lequel deux imports sont réalisés. L'heure exacte du serveur et un calcul arithmétique sont effectués et stockés dans les variables heure, minute ...

from flask import Flask,render_template
import datetime
from math import pi
app = Flask(__name__)


@app.route('/')
def index():
    p = "mikaël"
    n = "le mentec"
    date = datetime.datetime.now()
    heure = date.hour
    minute = date.minute
    seconde = date.second
    r = 2
    aire = pi*r**2
    return render_template("index.html",prenom = p, nom = n, heure = heure, minute=minute, seconde=seconde, rayon = r, aire = aire)

Dans le fichier index.html ces variables sont affichées via les appels {{heure}} {{minute}}  etc ...

<!doctype html>
<html lang="fr">
	<head>
		<meta charset="utf-8">
		<title>Ma page d'accueil</title>
	</head>
	<body>
      <h1>Un site qui déchire.</h1>
      <h2>Bonjour {{prenom|capitalize}} {{nom|upper}} !</h2>
      <p>Vous voici sur mon site à moi.</p>
      <p> Il est {{heure}} h {{minute}} m {{seconde}} s (heure du serveur !)</p>
      <p> Pour info, l'aire d'un disque de rayon {{rayon}} cm est d'environ {{aire}} cm²</p>
      <a href="./contact">Lien vers les contacts.</a>
	</body>
</html>

Testez l'affichage dans votre navigateur.

Modifiez la fonction contact et le template associé pour :

  • Calculer le nombre de jours depuis votre date de naissance
  • Afficher une phrase du genre : "Je suis né le xx/xx/xxxx cela fait xxxx jours"

Pour vous aider :

  • importez le module datetime de python
  • datetime.datetime(a,m,j) permet de créer un objet date de l'année a, mois m et jours j
  • Pour récupérer le nombre de jour d'un objet date, if faut ajouter .days à la suite de l'objet date

 Voici le code à ajouter à votre fichier views.py

@app.route('/contact')
def presentation():
    date_naissance = datetime.datetime(1998, 7, 12)
    duree = datetime.datetime.now() - date_naissance
    jours = duree.days
    return render_template("contact.html",naissance = date_naissance ,jours=jours)

Et volà le code à écrire dans le fichier contat.html

<h1> Présentation du webmaster </h1>
<h2> Mikael LE MENTEC </h2>
<p> Professeur de <strong>mathématiques</strong> et de <strong>NSI</strong> </p>
<p>Je suis né le {{naissance.day}}/{{naissance.month}}/{{naissance.year}} cela fait exactement {{jours}} jours</p>
<a href="mailto:lesmathsduyeti@orange.fr"> Mail  </a>

Utilisation d'un formulaire (méthode GET)

Modifiez le fichier index.html comme ceci :

 

<!doctype html>
<html lang="fr">
	<head>
		<meta charset="utf-8">
		<title>Ma page d'accueil</title>
	</head>
	<body>
      <h1>Un site qui déchire.</h1>
      <h2>Bonjour {{prenom|capitalize}} {{nom|upper}} !</h2>
      <p>Vous voici sur mon site à moi.</p>
      <p> Il est {{heure}} h {{minute}} m {{seconde}} s (heure du serveur !)</p>
      <p> Pour info, l'aire d'un disque de rayon {{rayon}} cm est d'environ {{aire}} cm²</p>
      
      
      <form action="http://127.0.0.1:5000/resultat" method="get">
            <label>Nom</label> : <input type="text" name="nom" />
            <label>Prénom</label> : <input type="text" name="prenom" />
            <input type="submit" value="Envoyer" />
      </form>
      </body>
      <footer><a href="127.0.0.1/contact">Lien vers les contacts.</a></footer>
</html>

 

Affichez la page d'accueil et complétez le formulaire qui est apparu. Quand vous cliquez sur le bouton Envoyer, que se passe-t-il ? 🤔

Un message d'erreur, mais en même temps c'est logique !

 

<form action="http://127.0.0.1:5000/resultat" method="get">

 

Ce code signifie que l'envoi du formulaire se fait avec la méthode GET et que la page affichée ensuite sera celle de l'adresse 127.0.0.1/resultat. Or pour l'instant, cette adresse n'est pas répertoriée dans nos fichiers. Il n'y a pas de route associée.

Autre remarque, observez bien l'adresse obtenue http://localhost:5000/resultat?nom=LE+MENTEC&prenom=Mikael

Comme vous pouvez le constater, les données envoyées apparaissent dans l'adresse. C'est la méthode GET qui veut cela.
Avec cette méthode, les données du formulaire seront encodées dans une URL. Celle-ci est composée du nom de la page ou du script à charger avec les données de formulaire empaquetée dans une chaîne. Les données sont séparées de l'adresse de la page pas le code ? et entre elles par le code & .

Pas très sécurisé comme méthode, le fait de voir les données dans l'adresse a plusieurs inconvénient :

  • Les données de formulaire doivent être uniquement des codes ASCII.
  • La taille d'une URL est limitée à par le serveur, souvent un peu plus de 2000 caractères, en comprenant les codes d'échappement.
  • Il est recommandé d'utiliser cette méthode dans des cas où l'on ne modifie pas de base de données pour des raisons évidentes de sécurité. Dans un cas comme le notre cela suffira car nous allons simplement lire les données pour fair un affichage.

Modifions le fichier views.py comme ceci :

Ajoutez l'import suivant :

 

from flask import Flask,render_template,request

 

Et ensuite ajoutez la fonction suivante : 

@app.route('/resultat',methods = ['GET','POST'])
def salutation():
    if request.method == 'GET':
        return request.args
    else: # request.methode != 'GET'
        return "post"

Cette fonction sera appelée lors de l'envoi du formulaire. 

  • if request.method == 'GET': permet de choisir une action si la page est obtenue via une requête de type GET
  • request.args va s'afficher, vous pouvez constater que c'est un objet de type dictionnaire. Pour accéder au nom il va falloir utiliser la syntaxe des dictionnaires : request.args['nom']

Créer un fichier resultat.html dans le dossier templates

<!doctype html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Salutations !</title>
    </head>
    <body>
        <p>Bonjour, en fait vous vous nommez {{prenom}} {{nom}} !</p>
    </body>
</html>

Notez que nous avons besoin des variables prenom et nom. Nous allons les récupérer dans notre dictionnaire request.args

Modifiez le fichier views.py comme ceci :

@app.route('/resultat',methods = ['GET','POST'])
def salutation():
    if request.method == 'GET':
        prenom_visiteur = request.args['prenom']
        nom_visiteur = request.args['nom']
        return render_template('resultat.html',prenom=prenom_visiteur,nom=nom_visiteur)
    elif request.method == 'POST':
        return "post"

Testez l'envoi d'un formulaire. Maintenant vous devriez avoir un message correct.

 

Utilisation d'un formulaire (méthode POST)

Voyons l'autre méthode possible pour envoyer un formulaire. Modifiez la méthode d'envoi du formulaire (dans le fichier index.html )ainsi :

<form action="http://127.0.0.1:5000/resultat" method="post">

Si vous cliquez sur le bouton du formulaire, pas d'erreur, vous devriez voir écrit "post" sur votre page web. (rechargez la page html si cela ne se produit pas)

Notez aussi que les valeurs entrées dans le formulaire ne sont plus transmisent par l'URL. Mais elle sont quand même transmisent ! 😅

Pour les voir, modifiez le fichier views.py comme cela :

@app.route('/resultat',methods = ['GET','POST'])
def salutation():
    if request.method == 'GET':
        prenom_visiteur = request.args['prenom']
        nom_visiteur = request.args['nom']
        return render_template('resultat.html',prenom=prenom_visiteur,nom=nom_visiteur)
    elif request.method == 'POST':
        return request.form

Vous devriez reconnaître un dictionnaire qui ressemble furieusement au dictionnaire de la méthode GET. 

Nous touchons au but, modifiez le fichier views.py comme ceci :

@app.route('/resultat',methods = ['GET','POST'])
def salutation():
    if request.method == 'GET':
        prenom_visiteur = request.args['prenom']
        nom_visiteur = request.args['nom']
    elif request.method == 'POST':
        prenom_visiteur = request.form['prenom']
        nom_visiteur = request.form['nom']
    return render_template('resultat.html',prenom=prenom_visiteur,nom=nom_visiteur)

Testez ce code, notez bien la différence entre les méthodes GET et POST pour récupérer les données. L'usage qui en est fait par contre lui reste inchangé.

La méthode POST est indispensable pour

  • des codes non ASCII,
  • des données de taille importante,
  • et elle est recommandée pour modifier les données sur le serveur, et pour les données sensible comme expliqué par le W3C.

Vous trouverez ci-dessous les fichiers de ce TP au cas où vous avez perdu le fil.

Fichiers du TP

Objectif créer une page pour coder (ou décoder) un texte codé par la méthode de césar.

Visitez ce site pour comprendre le codage de césar

Pour réaliser cet exercice, il faut :

  • une page "cesar.html" avec un fomulaire pour saisir le texte à coder et pour saisir la clé de codage
  • une page "codage.html" qui sera appelée après l'envoi du formulaire et qui affichera le texte codé.

Je vous donne le code de la fonction cesar

def cesar(texte, cle):
    """
    Fonction de codage par décalage (césar)
    Entrées : une chaine de caractères et un entier
    Sortie : une chaine de caractère
    """
    texte_code = ""
    for char in texte:
        if char.isalpha(): #si le caractère est une lettre on décale
            nouvelIndice = ord(char) + cle
            #Pour rester dans l'aplphabet, on décale de 26 en arrière si besoin
            if ord(char) <= ord('z') < nouvelIndice or ord(char) <= ord('Z') < nouvelIndice: 
                nouvelIndice -= 26
            lettrefinale = chr(nouvelIndice)
            texte_code += lettrefinale
        else: #si le caractère n'est pas une lettre, on n'y touche pas
            texte_code += char
    return texte_code

 

Voici le fichier contenant la réponse.

Corrections