Aller au menu - Aller au contenu

Icône Écrivez votre site web en C avec la CGI

Avatar
Mise à jour : 07/07/2010
Difficulté : Intermédiaire Intermédiaire Durée d'étude : 2 heures Creative Commons BY-SA
175 visites depuis 7 jours, classé 442/786
Vous aimeriez créer un site dynamique, par exemple pour mettre en place un compteur de visites, un livre d'or, ou encore un web-RPG (pourquoi pas ?)...
Mais vous n'utilisez que le C, et les langages tels que PHP ne vous attirent pas... vous pouvez faire avec vos connaissances et ce tutoriel !
Ce tutoriel présente une introduction à la CGI avec ce langage.

Préparation

Pour que vos scripts soient exploitables, il faut tout d'abord configurer votre serveur web.

Sous Apache, il faudra créer un fichier .htaccess, que vous placerez à la racine du site (en effet, ce fichier fonctionne récursivement : tous les répertoires situés dans le répertoire contenant votre .htaccess seront concernés par les règles que vous définirez dans celui-ci). Le voici :
Code : Apache
1
2
3
AddHandler cgi-script .cgi
Options +ExecCGI
DirectoryIndex index.cgi


Les deux premières lignes indiquent que les fichiers .cgi doivent être exécutés avant d'être envoyés au client. Le comportement par défaut serait d'envoyer le fichier comme s'il s'agissait d'un fichier texte ordinaire. La troisième et dernière ligne dit que votre fichier index.cgi sera exécuté en premier si aucun fichier n'est demandé.

Une dernière chose, le binaire .cgi devra être exécutable (chmod +x) sur votre serveur pour pouvoir être justement exécuté par le serveur web !

Premier script

Pour commencer, nous allons nous intéresser à un classique Hello World sur une page blanche !

Mais avant de nous y atteler, nous devons en connaître plus sur le fonctionnement du protocole HTTP, en particulier ce que nous nous envoyons au client.

Image utilisateur


Comme vous pouvez le voir, une réponse du serveur est composée de deux parties :
  • Les en-têtes qui contiennent des informations sur la réponse ainsi que sur le serveur : une partie est générée par lui-même ;
  • Le contenu qui est simplement ce que le navigateur affichera.

Mais comment devons-nous séparer les deux éléments ? Une simple ligne blanche.

Ainsi, voici une implémentation de notre Hello World :
Code : C
 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
#include <stdio.h>

void haut(char *);
void bas();

int main(void)
{
     printf("Content-Type: text/html;\n\n");
     haut("Ma page en C !");

     printf("Hello World !");

     bas();
     return 0;
}

/* On sépare le squelette HTML du reste du code */
void haut(char *title) {
     printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"fr\" >\n\t<head>");
     printf("\t\t<title>%s</title>", title);
     printf("\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\t</head>\n\t<body>");
}

void bas() {
     printf("\t</body>\n</html>");
}

Notez qu'écrire le HTML avec des printf() est relativement sale. Dans un vrai code, on préférera plutôt lire un fichier le contenant.

Pour le compiler, rien de compliqué, il n'y a rien à inclure, comme vous le voyez :
Code : Console
$ gcc main.c -o index.cgi

Placez ce nouveau fichier à la racine du site ; vous pouvez maintenant essayer de vous connecter à votre serveur HTTP à l'endroit qu'il convient (l'emplacement de votre site) : une page avec comme seul contenu le texte « Hello World », c'est magique !

Qui a dit que les sites en C, c'était impossible ? Malheureusement, plein de monde.

Un compteur de visites

Vu que vous savez afficher des données (c'est compliqué, il faut l'avouer :p ), je vous propose un petit TP qui consistera à faire un compteur de visites, qui s'incrémente à chaque ouverture de la page. J'utiliserai des fichiers dans ma correction, il est donc nécessaire de savoir les manipuler.

Pour ce script, nous allons nous baser sur le précédent, en modifiant un peu la fonction main().

On commence par créer une variable pour les visites, une pour le fichier :

Code : C
1
2
3
4
int main(void)
{
     int visites = 1;
     FILE *fichier = NULL;


On ouvre un fichier compteur.txt et on lit la valeur numérique, en la mettant dans visites :

Code : C
1
2
3
4
fichier = fopen("compteur.txt", "r+");
if(fichier != NULL)
{
    fscanf(fichier, "%d", &visites);


On modifie la valeur du fichier en l'incrémentant :

Code : C
1
2
3
4
5
/* ..... */
    fseek(fichier, 0, SEEK_SET);
    fprintf(fichier, "%d", visites+1);
    fclose(fichier);
}


Et on finit par l'afficher :

Code : C
1
2
3
4
5
6
7
8
/* ..... */
     printf("Content-Type: text/html;\n\n");
     haut("Ma page en C !");
     printf("J'ai eu %d visiteur(s) ! :)", visites);
     bas();

     return 0;
}


Pour les commentaires, c'est le seul moyen que j'ai trouvé pour régler un problème d'indentation du site sans modifier le sens du code, ne vous étonnez pas s'ils ne veulent rien dire. ;)


Voici donc ce que donne un bête script de compteur de visites :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
int main(void)
{
     int visites = 1;
     FILE *fichier = NULL;
     fichier = fopen("compteur.txt", "r+");
     if(fichier != NULL)
     {
          fscanf(fichier, "%d", &visites);
          fseek(fichier, 0, SEEK_SET);
          fprintf(fichier, "%d", visites+1);
          fclose(fichier);
     }

     printf("Content-Type: text/html;\n\n");
     haut("Ma page en C !");
     printf("J'ai eu %d visiteur(s) ! :)", visites);
     bas();
     return 0;
}


N'oubliez par contre pas de créer votre fichier avec les droits nécessaires pour que l'utilisateur de votre serveur web puisse le modifier.

Traiter les données d'un formulaire

Un des points inévitables en CGI est le traitement de formulaires. C'est ce que nous allons traiter (sans jeux de mots) dans cette partie.

Méthode GET


La méthode GET, c'est quand les données sont passées dans l'URL. Pour cela, nous allons récupérer une variable environnement qui contient les données avec la fonction getenv() , qui est définie dans l'en-tête stdlib.h.
Cette variable environnement est QUERY_STRING, son contenu se présentant sous la forme mavar=mavaleur&unevar=unevaleur&[...].

Imaginons que nous voulions récupérer deux nombres a et b passés dans l'URL et les multiplier, voici comment procéder :
Code : C
 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
#include <stdio.h>
#include <stdlib.h> // Ne pas oublier pour getenv()

void haut(char *);
void bas();

int main(void)
{
     int a=0, b=0;

     printf("Content-Type: text/html;\n\n");
     haut("Ma page en C !");

     char *data = getenv("QUERY_STRING"); // Les variables
     if(data == NULL)
     {
          printf("<p>Erreur dans le script !</p>");
     }
     else
     {
          if(sscanf(data, "a=%d&b=%d", &a, &b) != 2) // On récupère les deux chiffres a et b
               printf("<p>Vous devez passer des chiffres en données !");
          else
               printf("<p>%dx%d = %d ! :)</p>", a, b, a*b);
     }
     bas();
     return 0;
}


Et si l'on appelle http://monsite.com/index.cgi?a=4&b=6, nous nous trouvons bien avec ceci :
Code : Console
4x6 = 24 ! :)


Méthode POST


Là, les valeurs sont passées hors de l'URL. Nous les récupérons dans le flux d'entrée stdin. Au niveau de l'utilisation, on va l'utiliser comme un fichier déjà ouvert en lecture seule, et qu'on a pas besoin de fermer.
Ces données sont passées sous la même forme que pour la méthode GET : mon_champs_de_texte=unevaleur&un_textarea=uneautrevaleur.

Si je veux récupérer par exemple un texte entré dans un formulaire, de 80 caractères, dans un champ nommé data et l'afficher (ou en faire ce qu'on veut, même), voici comment il faudrait procéder :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int main(void)
{
     char montexte[80] = "";
     printf("Content-Type: text/html;\n\n");
     haut("Ma page en C !");
     if(fscanf(stdin, "data=%80[^&]s", montexte) < 0)
          printf("<p>Le formulaire a été mal rempli !</p>");
     else
          printf("Voici ce que vous avez écrit : %s", montexte);
     bas();
     return 0;
}


Décoder les données


Si vous avez affiché des chaînes avec des espaces et des caractères dits « spéciaux », vous aurez constaté que pour le commun des mortels, ce n'est pas du tout lisible. Je vous propose donc de décoder cette chaîne avec une fonction.
Pour rendre tout ceci pédagogique, ce sera à vous de la coder (une solution sera proposée).

Il y a donc plusieurs règles :
  • Les espaces sont remplacés par des + ;
  • Les caractères spéciaux (les +, &, %, les retours à la ligne, etc.) sont remplacés par le signe % suivi de leur code ascii traduit en hexadécimal ;
  • Le reste demeure identique.


Et une petite piste, pour récupérer le code sous forme décimale, il suffit de la récupérer avec un %x dans la fonction sscanf.

Correction


Voici donc le code que je vous propose, mais ce n'est bien sûr pas le seul qui soit juste.

Code : C
 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
char *decode(char *str, char *fin)
{
     char *dest = strdup(str);
     if (dest == NULL)
          return NULL;
     char *ret = dest;

     for (; str < fin && *str != '\0'; str++, dest++)
     {
          if (*str == '+')
               *dest = ' ';
          else if (*str == '%')
          {
               ++str;
               if (*str == '\0')
                    break;
               int code = '?';
               sscanf(str, "%2x", &code);
               *dest = code;

               str++;
               if (*str == '\0')
                    break;
          }
          else
               *dest = *str;
     }
     *dest = '\0';
     return ret;
}


Dans le code de la partie précédent, voici comment on l'utiliserait :
Code : C
1
printf("Voici ce que vous avez écrit : %s", unencode(montexte, montexte+80));

[Exercice] Un livre d'or

Comme exercice final d'application, je vous propose la création d'un petit livre d'or suivi évidemment d'une correction détaillée.

Voici donc les fichiers nécessaires à ce petit TP :
Le formulaire :
Code : HTML
1
2
3
4
5
<form method="post" action="form.cgi"> 
   <p><label for="auteur">Votre nom (maximum 20 caractères) :</label><input type="text" name="auteur" id="auteur" /></p> 
   <p><label for="data">Votre message (maximum 100 caractères) :</label><input type="text" name="data" id="data" /></p> 
   <p><input type="submit" value="C'est parti mon kiki §" /></p> 
</form>


La syntaxe du fichier des messages message.txt :
Code : Autre
1
2
3
4
5
Nom
Message
Nom
Message
[....]


Il n'y a rien d'autre à dire sauf peut-être une bonne chance !

Correction


Voici une des corrections possibles. Pour la partie de traitement du formulaire :
Code : C
 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
#define TAILLE_LIGNE 123
#define TAILLE_AUTEUR 23
#define TAILLE_TEXTE 103
 
int main(void)
{
     char auteur[TAILLE_AUTEUR] = "";
     char texte[TAILLE_TEXTE] = "";
     char ligne[TAILLE_LIGNE] = "";
     char *tdecode = NULL;
     FILE *fichier = NULL;
     printf("Content-Type: text/html; charset=utf-8\n\n");
     haut("Livre d'or");
 
     fgets(ligne, TAILLE_LIGNE, stdin);
     tdecode = decode(ligne, ligne+TAILLE_LIGNE);
     if(sscanf(tdecode, "auteur=%20[^&]&data=%100[^&]s", auteur, texte) > 0) {
       free(tdecode);
       if(strlen(auteur) == 0 || strlen(texte) == 0) {
	 printf("Les deux champs doivent être remplis correctement !");
       }
       else {
	 printf("<p>Merci de votre message !</p>");
	 fichier = fopen("messages.txt", "a+");
	 if(fichier != NULL) {
	   fprintf(fichier, "%s\n%s\n", auteur, texte);
	   fclose(fichier);
	 }
       }
     }
 
     fichier = fopen("messages.txt", "r");
     if(fichier == NULL)
       printf("Erreur à la récupération des messages.");
     else {
       while(fgets(auteur, TAILLE_AUTEUR, fichier) != NULL) {
	 if(fgets(texte, TAILLE_TEXTE, fichier) != NULL)
	   {
	     printf("<p>%s a écrit :<br><p style=\"margin-left: 30px; margin-top: -10px;\">« %s »</p></p>", auteur, texte);
	   }
       }
       fclose(fichier);
     }
 
     bas();
     return 0;
}

C'est simple :
  • On crée quelques tableaux de char et un fichier ;
  • On écrit les en-têtes ;
  • On exécute notre petite fonction pour afficher le haut ;
  • On récupère l'entrée standard à maximum 136 caractères (les limites des deux variables, leurs noms, et l'esperluette) ;
  • On décode la ligne et on récupère les valeurs ;
  • On vérifie si l'utilisation a rentré quelque chose ;
  • Si oui, on ouvre le fichier et on ajoute les valeurs sur deux lignes.


Maintenant, pour l'affichage des autres messages, juste avant d'afficher le bas :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fichier = fopen("messages.txt", "r");
if(fichier == NULL) {
    printf("Erreur à la récupération des messages.");
}
else {
       while(fgets(auteur, TAILLE_AUTEUR, fichier) != NULL) {
	 if(fgets(texte, TAILLE_TEXTE, fichier) != NULL)
         {
            printf("<p>%s a écrit :<br><p style=\"margin-left: 30px; margin-top: -10px;\">« %s »</p></p>", auteur, texte);
         }
    }
    fclose(fichier);
}


Par contre, attention, ceci étant un code d'apprentissage, je n'ai pas préféré le mettre, mais ce code contient une faille de sécurité, il ne faut donc pas l'utiliser sur un vrai site : on ne traite pas vraiment ce que l'utilisateur a rentré, donc si c'est du HTML ou du Javascript, c'est potentiellement dangereux.
Sur un vrai site, il faudrait écrire une fonction qui remplace les chevrons d'une chaîne par leur équivalent en HTML : ainsi < devient &lt; et > devient &gt;
Ce tutoriel traite surtout de l'aspect théorique de CGI ; on aurait pu utiliser n'importe quel langage : aussi bien le Python, que le C++ ou encore le Bash.
Sachez aussi qu'une bibliothèque facilitant l'utilisation de la CGI en C existe : Documentation de CGIC.
Si vous voulez utiliser des bases de données, c'est tout à fait possible, l'exemple même avec un des tutoriels de ce site : Utiliser l'API MySQL dans vos programmes.

Ce tutoriel a été inspiré du tutoriel « Aperçu de la CGI avec Python » de Krankkatze que je remercie d'ailleurs pour ses conseils.
Je tiens aussi à remercier coyotte508, gnomnain, Yno, shareman et le canal IRC ##hippie sur Freenode pour leur aide plus ou moins grande dans les codes de ce tutoriel.

Partager

47 commentaires pour "Écrivez votre site web en C avec la CGI"
Note moyenne : 3.43 / 4 (42 votes)
Pseudo Commentaire
Hors ligne pal # Posté le 25/05/2011 à 22:17:53

Citation
Nous vous rappelons que cet espace est réservé aux commentaires à propos des tutoriels, veillez donc à ne pas poser vos questions dans cette section.
Pour cela, rendez-vous sur le forum ! Merci.

M'enfin bon...
Le C s'exécute côté serveur avec la CGI, donc non, on ne peut pas faire de conversation avec...
Tu vas devoir trouver autre chose...

Sinon ce tuto est très sympa et instructif, et nous dit comment gérer les deux "superglobales" de PHP (POST et GET), mais il aurait été intéressant d'ajouter des petits plus comme la redirection ("Location: xxx" avant le header) ou les cookies ("Set-Cookie: xxx" avant le header, puis getenv(HTTP_COOKIE))...
Hors ligne Chat Malade # Posté le 26/05/2011 à 00:45:58
Le Lama druide fait une potion
Avatar

Avis : Très bon Groupe : Bannis

Ce tutoriel n'était qu'un proof of concept à l'origine hein, enfin je vais le modifier à l'occasion pour rajouter ce genre de conneries, je pense.

CNN vient d’annoncer que le nouveau nombre premier découvert est quatre fois plus grand que le record précédent.
 
Hors ligne John Maltys # Posté le 24/02/2012 à 09:19:15

CGI c'est bien mais basique. En général quiconque programme en C utilise des librairie et en C++ il en existe une excellent pour le Web : "Wt". Elle permet d'éviter de réécrire les fonctions de bases et surtout de mal les écrire... De plus avec Wt on se passe de la lenteur de Java puisqu'il offre même le serveur Web. C'est sur on est alors obligé de le faire tourné sur un autre port ce qui en soit ne change pas grand chose les liens peuvent exister entre les deux.
Hors ligne zoup # Posté le 07/03/2012 à 11:50:38

Bonjour,

la question de l'intérêt de CGI revient souvent. Je ne comprends pas pourquoi on n'a pas évoqué le problème de l'embarqué. On a l'impression que le PC représente le seul débouché pour les systèmes programmés à base de microprocesseurs, alors qu'ils ne représentent que quelques % du code qui est exécuté à chaque instant.

Bref, si vous voulez utiliser un système communiquant, embarquant un site web (on peut penser à de la domotique par exemple, ou à votre box, votre routeur, etc.), ce système ne possède que quelques Mo de mémoire, voire quelques centaines de Ko. Dans ces conditions, les faibles ressources requises par CGI sont les bienvenues.

Concernant maintenant, l'exemple du compteur, je pense qu'il faudrait évoquer les limites de l'exemple: que se passe-t-il si 2 clients tentent d'accéder au même moment au serveur?

Je voudrais toutefois terminer par l'aspect positif. Je trouve ce tutoriel très intéressant. Il donne les bases suffisantes pour exploiter CGI pour ceux qui en ont besoin. Merci aux auteurs

A+
Hors ligne sogyam # Posté le 19/04/2012 à 02:20:21
Avatar

Un concept intéressant , de site compilé avec fs intégré (optionnel): klone web server

Cela rejoint le concept des cgi, mais là le C est embarqué dans la page web comme php ,(la page est compilée et est partie prenante du serveur, car c'est une seule et même application !! )

Voir tous les commentaires