Activité : Entrée, sortie et sortie d’erreur

Situation

Dans le but de vous familiariser avec les opérations d’entrée/sortie et les instructions de choix du Bash (Bourne Again Shell) de Linux, vous décidez de réaliser un serveur HTTP élémentaire.

Consigne

Réalisez le serveur HTTP en suivant les instructions et répondez aux questions qui vous sont posées.

Attention : Ce serveur HTTP est réalisé à des fins pédagogiques uniquement, il ne doit en aucun cas être utilisé en production.

Objectifs

À la fin de ce travail, vous devez :

  1. Être capable d’utiliser la commande case.
  2. Être capable de rediriger un fichier vers l’entrée standard d’une commande (<), et de rediriger la sortie standard ou la sortie d’erreur standard vers un fichier (>et 2>).
  3. Être capable de rediriger la sortie standard d’une commande vers la sortie d’erreur standard du script avec >&2.

Résultat attendu

  • Un bref rapport qui décrit votre travail et votre démarche.
  • Votre script Bash.

Ressources

Document:

Logiciel :

Mise en route

Créez un répertoire pour votre projet dans votre répertoire personnel. Dans ce répertoire, créez un fichier nommé « httpsrv.sh » et copiez le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env bash

documentRoot=./doc

# Lire la ligne de requête depuis l’entrée standard (stdin).

read -r request_line
read -r http_method uri http_version <<< $request_line

# Affiche les données de la requête dans la sortie d’erreur standard (stderr).

printf "Requète reçue : \n" >&2
printf "   METHOD  : %s\n" $http_method >&2
printf "   URI     : %s\n" $uri >&2
printf "   VERSION : %s\n" $http_version >&2

# Si l’URI est la racine, demander une redirection vers l’index.

# TODO (4) (seulement lorsque tout le reste est fait)
# Si l’URI est "/", envoyez une réponse HTTP avec le statut 301.



# Éssaie d’associer l’URL à un fichier.

case $uri in

/index)
    filename="$documentRoot/index.html"
    mimetype="text/html"
    ;;

/info/intro)
    filename="$documentRoot/fichier1.html"
    mimetype="text/html"
    ;;

/page2)
    filename="$documentRoot/fichier2.html"
    mimetype="text/html"
    ;;

*.html)
    filename="$documentRoot$uri"
    mimetype="text/html"
    ;;

# TODO (3)
# Ajouter un cas pour les fichiers .css.

esac

# Test si l’URL a pu être associée à un fichier.

# TODO (2)
# Tester si le fichier existe avant d’écrire la réponse avec
# le statut 200 et si le fichier n’existe pas écrire une
# avec le statut 400    

# Générer la réponse avec le statut 200 et le contenu du fichier

printf "   Fichier : '%s'\n" $filename >&2

# Écrit la ligne de statut (Status-Line)
printf "HTTP/1.1 200 Ok\r\n" 
# Écrit les en-têtes (headers).
printf "Content-Type: %s\r\n" $mimetype
printf "Content-length: %s\r\n" $(stat -c'%s' "$filename")
printf "Connection : close\r\n"
# Écrit les caractères CRLF qui marquent la fin des en-têtes.
printf "\r\n"
# Écrit le contenu (message-body)

# TODO (1)
# Replacer la commande echo par une commande qui permet d'afficher dans
# la sortie standard le contenu du fichier dont le chemin se trouve dans 
# la variable $filename. 
echo "<html><body><h1>Impec ! ça fonctionne !</h1></body><html>"

exit 0
Fig. 1 – httpsrv.sh

Dans le répertoire du projet, créez un répertoire « doc ». Dans ce répertoire, créez un fichier nommé « fichier1.html » et copiez le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Fichier 1</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css">
    </head>
    <body>
        <div class="content">
            <h1>Introduction</h1>
            <p>
                Ceci est le contenu du fichier "fichier1.html". Ce contenu a été
                utilisé pour construire la représentation de la ressource
                identifiée par l’URL "/info/into" que vous avez fournie dans
                votre requête.
            </p>
            <p>
                La ressource est le document html contenu dans ce fichier et non
                pas le fichier lui-même. L’URL identifie cette ressource et ne
                donne aucune indication sur la manière dont celle-ci construite
                ou stockée.
            </p>
        </div>
    </body>
</html>
Fig. 2 – fichier1.html

Créez ensuite un fichier « fichier2.html » et copiez le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Fichier 2</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css">
    </head>
    <body>
        <div class="content">
            <h1>Page 2</h1>
            <p>
                Ceci est le contenu du fichier "fichier2.html". Ce contenu a été
                utilisé pour construire la représentation de la ressource
                identifiée par l’URL "/page2" que vous avez fournie dans
                votre requête.
            </p>
            <p>
                La ressource est le document html contenu dans ce fichier et non
                pas le fichier lui-même. L’URL identifie cette ressource et ne
                donne aucune indication sur la manière dont celle-ci construite
                ou stockée.
            </p>
        </div>
    </body>
</html>
Fig. 3 – fichier2.html

Créez encore un fichier « index.html » avec le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Index</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css">
    </head>
    <body>
        <div class="content">
            <h1>Index</h1>
            <p>
                Cette page est servie par un serveur HTTP réalisé à l’aide
                d’un script shell qui accepte une requête HTTP de l’entrée
                standard (stdin) et qui écrit la réponse correspondante dans
                la sortie standard (stdout). Un utilitaire comme netcast (nc)
                ou socat est utilisé pour écouter un port et rediriger l’entrée
                et la sortie standard du script vers la connexion TCP lorsque 
                celle-ci est établie.
            </p>
            <ul>
                <li><a href="/info/intro">introduction</a></li>
                <li><a href="/page2">page 2</a></li>
                <li><a href="/other/fichier3.html">page 3</a></li>
            </ul>
        </div>
    </body>
</html>
Fig. 4 – index.html

Questions

Question1

Si ce n’est pas fait, ouvrez une fenêtre de terminal et allez dans le répertoire du projet. Dans ce répertoire, lancez la commande suivante :

1
dev@vmdev:~/projects/httpsrv$ chmod +x httpsrv.sh

Expliquez ce que fait cette commande.

Question 2

Exécutez la commande suivante :

1
dev@vmdev:~/projects/httpsrv$ ./httpsrv.sh

Le script attend que vous saisissiez une ligne de texte. Tapez le texte suivant et appuyé sur « Entrée » :

1
GET /index HTTP/1.1

Observez le résultat.

Exécutez maintenant la commande suivante :

1
dev@vmdev:~/projects/httpsrv$ ./httpsrv.sh >response.txt

Le script attend de nouveau que vous saisissiez une ligne de texte. Tapez le même texte que précédemment et observez le résultat. Vous pouvez afficher le contenu du fichier « response.txt » à l’aide de la commande suivante :

1
dev@vmdev:~/projects/httpsrv$ cat response.txt

Créez un fichier de texte dans le répertoire du projet à l’aide de la commande ci-dessous et affichez son contenu à l’aide de la commande cat.

1
dev@vmdev:~/projects/httpsrv$ echo "GET /index HTTP/1.1" >request.txt

Exécutez la commande ci-dessous et observez le résultat.

1
dev@vmdev:~/projects/httpsrv$ ./httpsrv.sh 2>log.txt <request.txt

Étudiez le code du script et expliquez ce que vous avez observé.

Question 3

Reprenez le code du script et complétez les TODO 1, 2 et 3. Comment vous y prenez-vous pour vérifier que votre code fonctionne correctement ?

Question 4

Dans le répertoire du projet, dans le répertoire « doc », créez un répertoire « css » et créez un fichier « style.css » avec le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
body, h1, h2, h3, h4, h5, h6, p, ul, ol, dl {
  font-family: Tahoma, Geneva, Helvetica, sans-serif;
  font-size: 120%;
  color: #333;
  line-height: 150%;
}

h1 {
  font-size: 1.7em;
  border-bottom: 1px solid #333
}

.content {
  max-width: 45em;
  margin-left: auto;
  margin-right: auto;
}
Fig. 5 – style.css

Créez un répertoire « other » et, à l’interieur, un fichier « fichier3.html » avec le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Fichier 3</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css">
    </head>
    <body>
        <div class="content">
            <h1>Page 3</h1>
            <p>
                Ceci est le contenu du fichier "fichier3.html". Ce contenu a été
                utilisé pour construire la représentation de la ressource
                identifiée par l’URL que vous avez fournie dans votre requête.
           </p>
           <p>
                La ressource est le document html contenu dans ce fichier et non
                pas le fichier lui-même. L’URL identifie cette ressource et ne
                donne aucune indication sur la manière dont celle-ci construite
                ou stockée.
            </p>
        </div>
    </body>
</html>
Fig. 6 – fichier3.html

Enfin, dans le répertoire du projet, créez un fichier « httpd.sh » avec le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
port=$1

if [ "$port" = "" ]
then
    port="8080"
fi

printf "\nLe serveur HTTP a démarré sur localhost et écoute le port %s.\n" $port
printf "Appuyez sur ctrl-c pour quitter.\n"

# Va dans le répertoire du script et sauvegarde le répertoire courant.
pushd $(dirname "$0") > /dev/null

# Affecte le chemin du répertoire courant (le répertoire du script)
# à la variable "basedir".
basedir=$(pwd)

# Restaure le répertoire courant précédent.
popd > /dev/null

# Ecoute le port 8080 et exécute le script httpsrv.sh dans
# un nouveau processus pour chaque entrante.
socat TCP4-LISTEN:$port,reuseaddr,fork EXEC:"$basedir/httpsrv.sh"
Fig. 7 – httpd.sh

Exécutez les commandes suivantes pour installer l’utilitaire socat et rendre le script exécutable :

1
2
dev@vmdev:~/projects/httpsrv$ sudo apt-get install socat
dev@vmdev:~/projects/httpsrv$ chmod +x httpd.sh

Vous pouvez maintenant lancer le serveur HTTP avec la commande :

1
dev@vmdev:~/projects/httpsrv$ ./httpd.sh 8080

Ouvrez un navigateur et tapez l’url suivante : http://localhost:8080/index

Expliquez comment cela fonctionne.

Question 5

Complétez le TODO 4 dans le fichier « httpsrv.sh ». Naviguez sur la racine à plusieurs reprises, que constatez-vous ? Ce comportement est-il normal ?