Réseau en langage C - utilisation du protocole HTTP

Publié le par mleg

Cet article sera donc consacré à l'informatique. Il suppose quelques connaissances, basiques, sur le langage C, les sockets BSD et le réseau.

Il vise plusieurs objectifs :

  • Aider quiconque veut pratiquer le C et les sockets, en proposant un exercice concret et sa correction.
  • Poser les bases du développement éventuel de programmes utilisant le protocole HTTP (navigateurs, serveurs).
  • Montrer une partie du fonctionnement interne de ces programmes.
L'intégralité du code fut testée sous Windows XP ; cependant, m'étant efforcé de rester le plus portable possible, très peu (voire pas du tout) de modifications devraient être nécessaires au bon fonctionnement du programme sous Linux.

Evidemment, il est probable qu'il subsiste quelques erreurs. Merci de me les signaler à l'adresse suivante (forme anti-spam) : metapsycopac at hotmail dot com ou, bien sûr, en commentaire.

Pour plus de renseignement sur le protocole HTTP, voir les liens en fin de page.

Note :
    Les caractères de fin de ligne dans les chaînes de caractères ont été involontairement remplacés par des 'n'. Merci de ne pas y faire attention.

Définition du projet / libellé de l'exercice

Il s'agit de réaliser un programme en langage C chargé de se connecter à un serveur, de lui envoyer des requêtes HTTP et d'afficher les réponses.

Les requêtes seront lues dans un fichier indiqué en premier paramètre ; le second argument spécifié sera un autre fichier où sera inscrite la réponse.
Si plus de deux arguments sont spécifiés, un message d'erreur sera délivré.
Si un seul fichier est indiqué, le programme tentera d'y lire la requête.
S'il n'y parvient pas, un message d'erreur sera délivré.
Si le fichier peut être ouvert, il imprimera la réponse sur la sortie standard.
Si aucun fichier n'est spécifié, seront utilisées l'entrée et la sortie standard.

Spécifications / correction algorithmique

Le projet étant relativement court et simple, je n'ai pas jugé nécessaire d'établir un algorithme très détaillé. En revanche, je me suis penché plus avant sur l'écriture du pseudo-code.

L'algorithme de base, très général, donc :

  • Lire la requete à envoyer (fichier, entrée standard)
  • Se connecter au serveur
  • Envoyer la requête
  • Attendre la réponse
  • Se déconnecter
  • Ecrire la réponse (fichier, sortie standard)
Le pseudo-code ci-dessous, découlant de cet algo, n'est pas exhaustif : il s'agit de ma propre base de travail. Le lecteur est libre de le compléter, notamment concernant la partie réseau :

main.c :

DEBUT main()
    NB choix

    SI (il y a trop d'arguments)
        signaler_erreur()
        quitter()
    FIN SI
   
    TANT QUE (choix ne vaut pas 0)
        afficher_menu()
   
        choix := demander_choix()
   
        SELON (choix)
            SI (choix vaut 1)
                requete()
            SI (choix vaut 2)
                choix := 0
        FIN SELON
    FIN TANT QUE

    attendre()
    RETOURNER _FIN
FIN main()

requete.c :

DEBUT taper_requete()
    S chaine
    I i
   
    TANT QUE (le caractère entré n'est pas EOF)
        SI (i a dépassé la taille de la chaine)
            chaine = réallouer()
        FIN SI
        placer_caractere()
        i++
    FIN TANT QUE

    RETOURNER chaine
FIN taper_requete

DEBUT lire_requete()
    F fp
    S requete
    NB nb
   
    fp := ouvrir_fichier()
   
    SI (impossible d'ouvrir le fichier)
        signaler_erreur()
       
        attendre()
        quitter()
    FIN SI
   
    nb := compter_caracteres()
   
    requete := allouer(nb)
   
    requete := remplir_chaine()
   
    RETOURNER requete
FIN lire_requete

DEBUT trouver_serveur(S requete)
    S serveur, temp

    temp := rechercher_chaine("Host" dans requete)
   
    nettoyer(temp)
   
    serveur := couper_chaine()
   
    RETOURNER serveur
FIN trouver_serveur()

DEBUT ecrire_reponse()
    SI (il n'y avait deux arguments)
        F fp
       
        fp := ouvrir_fichier()
       
        SI (on a pu ouvrir le fichier)
            ecrire(dans le fichier)
        SINON
            quitter()
    SINON
        ecrire(sur la sortie standard)
FIN ecrire_reponse()

DEBUT requete()
    S requete
    S site

    SELON (le nombre d'arguments)
        SI (il n'y en a aucun)
            requete := taper_requete()
            nettoyer_chaine()
        SI (il n'y en a qu'un)
            requete := lire_requete()
        SI (il y en a deux)
            requete := lire_requete()
    FIN SELON
   
    site := trouver_serveur()
   
    connecter(site)
   
    envoyer_requete()
    attendre_reponse()
   
    ecrire_reponse()
   
    deconnecter(site)
FIN requete()

Codage / correction syntaxique

Enfin, voici donc la source. Si vous débusquez une (ou plusieurs) erreur(s), merci de me la (les) signaler dans un commentaire.

Quelques précisions utiles :

  • La requête est une simple chaîne de caractère.
  • Pour indiquer la fin de la requête lors d'une entrée "directe" (ie sur stdin),  taper EOF (^Z sous windows).

Now, enjoy !

main.h :

#ifndef MAIN_H
#define MAIN_H

#define DEBUG 1

void purge(void);
void clean(char* s);
char* type_s(int size_buf);
int main(int argc, char *argv[]);

#endif

main.c :

#include "main.h"
#include "requete.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define NBMAX_ARG 2

void purge(void)
{
    int c;
   
    while ((c = getchar()) != 'n' && c != EOF)
        ;


void clean(char* s)
{
    char* p = strchr(s, 'n');
   
    if (p != NULL)
        *p = 0;   
    else {
        purge();
    }
}           

/* Corrigé par  Emmanuel Delahaye. Merci ! */
char *type_s (size_t size_buf)
{
    char *s = malloc ((size_buf  + 1) * sizeof *s);

    if (s != NULL)
    {
        int k = 2;
        size_t i;

        for (i = 0; s != NULL && (s[i] = getchar ()) != EOF; i++)
        {
             if (i == size_buf * k)
             {
                char *tmp = realloc (s, (size_buf + 1) * k);
                if (tmp != NULL)
                {
                   s = tmp;
                   k++;
                }
                else
                {
                   free (s), s = NULL;
                }
             }
          }

          if (s != NULL)
          {
             s[i] = 0;
          }
       }
       return s;
}

int t_int(void)
{
    char c = getchar();
   
    purge();
   
    if (isdigit(c))
        return (int) c - '0';
    return -1;
}  

int main(int argc, char *argv[])
{
    int choix = 1;
   
    printf("PostC - by mleg n");
    printf("n");
   
    if (--argc > NBMAX_ARG) {
        printf("Error: PostC [first_file] [second_file] n");
       
        getchar();
        exit(EXIT_FAILURE);
    }   
   
    while (choix) {
        printf("1. Envoyer/recevoir n");
        printf("2. Quitter n");
        printf("n");
       
        printf("You choose : n");
        choix = t_int();
       
        switch (choix) {
            case 1:
                requete(argc, argv + 1);
                printf("n");
                break;
            case 2:
                choix = 0;
                break;
            case -1: default:
                printf("Error: Can't you really be serious ? n");
                printf("n");
                break;
        }   
    }   
   
    getchar();
    return EXIT_SUCCESS;
}

requete.h :

#ifndef REQUETE_H
#define REQUETE_H

#include <stdio.h>

char* cut_len(char* s, size_t len);
int c_count(FILE* fp);
char* r_read(char* fname);
char* find_s(char* req);
void write_answer(char* fname, char* s);

void requete(int ac, char *av[]);

#endif

requete.c :

#include "requete.h"
#include "sock.h"
#include "main.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE_S 100

#define HOST "Host: "

char* cut_len(char* s, size_t len)
{
    return s = s + len;
}       

int c_count(FILE* fp)
{   
    int nb = 0;   
   
    while (fgetc(fp) != EOF)
        nb++;
 
    return nb;
}

char* r_read(char* fname)
{
    FILE* fp = fopen(fname , "r");
    char* s = malloc(c_count(fp) * sizeof *s + 1);
    int c, i;
   
    if (fp != NULL && s != NULL) {
        rewind(fp);
       
        for (i = 0; (c = fgetc(fp)) != EOF; i++)
            s[i] = c;  

        s[i] = 0;
       
        fclose(fp);
        return s;
    }
    else
        return NULL;       
}

char* find_s(char* req)
{
    char *s, *temp = malloc(SIZE_S * sizeof *temp);

    s = strstr(req, HOST);
   
    if (s != NULL && temp != NULL) {
        int i;
        char *p;
       
        for (i = 0; (temp[i] = s[i]) != 'n'; i++)
             ;
       
        temp = cut_len(temp, strlen(HOST));
        clean(temp);
       
        if ((p = strchr(temp, 'r')) != NULL)
            *p = 0;
       
        return temp;
    }
    return NULL;
}  

void write_answer(char* fname, char* s)
{
    if (fname != NULL) {
        FILE* fp = fopen(fname, "w");
       
        if (fp != NULL) {
            fputs(s, fp);
        }
        else {
            printf("Error: Cannot open file %s. n", fname);   
           
            getchar();
            exit(EXIT_FAILURE);
        }   
    }
    else {
        printf("%s n", s);
    }       


void requete(int ac, char *av[])
{
    char *s_req = NULL, *serv = NULL, *rep = NULL;
    char* fout = NULL;
    int err = 0;
   
    switch (ac) {
        case 0:
            printf("Please write here your HTTP request. "
            "Type EOF to stop. n");
            s_req = type_s(SIZE_S);
            break;       
        case 1:
            s_req = r_read(*av);
            break;
        case 2:
            s_req = r_read(*av++);
            fout = *av;
            break;
    }   
   
    if (s_req != NULL) {
        serv = find_s(s_req);
       
        if (serv != NULL) {
            rep = sock(serv, s_req);
           
            if (rep != NULL) {
                write_answer(fout, rep);
               
                free(s_req), s_req = NULL;
                free(serv), serv = NULL;
                free(rep), rep = NULL;
            }
            else {
                printf("Error: No answer. n");
                err = 1;
            }       
        }
        else {
            printf("Error: No host specified. n");
            err = 1;
        }   
    }
    else {
        printf("Error: Cannot read anything. n");
        err = 1;
    }
    if (err) {
        getchar();
        exit(EXIT_FAILURE);
    }       
}   

sock.h :

#ifndef SOCK_H
#define SOCK_H

    #if defined (WIN32)
        #include <winsock2.h>
   
    #elif defined (linux)
        #include <sys/types.h>
        #include <sys/socket.h>
        #include <netinet/in.h>
        #include <arpa/inet.h>
        #include <unistd.h>
       
        typedef struct hostent HOSTENT;
    #else
        #error not defined for this platform
    #endif

void init_fini(int n);

void mzero(char *s, size_t size);
void send_to(SOCKET sock, char* req);
char* wait_answer(SOCKET sock);

char* sock(char* s_name, char* req);

#endif

sock.c :

#include "sock.h"
#include "main.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <unistd.h>

    #if defined (WIN32)
        #include <winsock2.h>
   
    #elif defined (linux)
        #include <sys/types.h>
        #include <sys/socket.h>
        #include <netinet/in.h>
        #include <arpa/inet.h>
        #include <unistd.h>
       
        typedef struct hostent HOSTENT;
    #else
        #error not defined for this platform
    #endif

#define IP_SIZE 15
#define REP_MAX 1024

#define HTTP 80
#define PORT HTTP

typedef struct sockaddr SOCK_ADDR;

#if defined (WIN32)
/* (windows) init_fini: avertit le processus de l'utilisation future/passée
des sockets */
void init_fini(int n)
{       
    if (!n) {
        WSADATA wsa_data;
        int err = WSAStartup(MAKEWORD (2, 2), &wsa_data);
   
        if (err) {
#if (DEBUG)
            printf("Error: Cannot find <winsock2.h> n");
#endif
            getchar();
            exit(EXIT_FAILURE);
        }
    }
    else
        WSACleanup();
}
#endif

void mzero(char *s, size_t size)
{
    while (size--)
        s[size] = 0;
}   

/* Emmanuel Delahaye's algorithm */
void send_to(SOCKET sock, char* req)
{
    size_t const len = strlen(req);
    size_t sent = 0;
 
    while (sent != len) {
        int n = send(sock, req + sent, len, 0);
    
        if (n >= 0) {
            sent = sent + n;
        }
        else {
            printf("Error: Cannot send some datas : %s n", req);
        }
    }
}

char* wait_answer(SOCKET sock)
{
    char* rep = malloc(REP_MAX * sizeof *rep);
   
    if (rep != NULL) {
        int n = recv(sock, rep, REP_MAX, 0);
       
        if (n != SOCKET_ERROR) {
            rep[n] = 0;
           
            return rep;
        }   
    }
    return NULL;   
}         

char* sock(char* s_name, char* req)
{
    SOCKADDR_IN dest_addr;
    HOSTENT* host;
    SOCKET sock;
   
    char ip[IP_SIZE + 1], *rep = NULL;
    int sock_err;

#if defined (WIN32)   
    init_fini(0);        /* Initialisation (windows) */
#endif
   
    host = gethostbyname(s_name);
   
    if (host != NULL) {
        dest_addr.sin_addr = *((struct in_addr *) host->h_addr);
    }   
    else {
        printf("Error: Cannot find host "%s". n", s_name);
        printf("Try with the IP adress : n");
       
        fgets(ip, sizeof ip, stdin);
        clean(ip);
       
        printf("n");
       
        dest_addr.sin_addr.s_addr = inet_addr(ip);
    }   
   
    /* initialisation de `my_addr' */
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(PORT);
    mzero(dest_addr.sin_zero, sizeof dest_addr.sin_zero);
   
    sock = socket(AF_INET, SOCK_STREAM, 0);
   
    if (sock != INVALID_SOCKET) {
#if (DEBUG)
        printf("Socket %d is now oppened in TCP/IP mode. n", sock);
#endif
        sock_err = connect(sock, (SOCK_ADDR *) &dest_addr, sizeof(SOCK_ADDR));
       
        if (sock_err != SOCKET_ERROR) {
#if (DEBUG)
            printf("Connected to %s (%s ?) on port %d. n",
                inet_ntoa(dest_addr.sin_addr), s_name,
                ntohs(dest_addr.sin_port));
            printf("n");
#endif 
            send_to(sock, req);
             
            rep = wait_answer(sock);
               
            if (rep != NULL) {               
                close(sock), sock = INVALID_SOCKET;
                  
#if defined (WIN32)
                init_fini(1);        /* Fin d'utilisation des sockets */
#endif         
                return rep;      
            }
            else {
                printf("Error: Memory allocation has failed (`rep'). n");
            }   
        }
        else {
            printf("Error: Cannot connect to the asked host. n");
            perror("Diagnostic");
        }                    
    }
    else {
        printf("Error: Cannot open any socket in TCP/IP mode");
        perror("Diagnostic");
    }
    return NULL;           
}  

Liens utiles

RFC 1945 : HTTP 1.0
Les requêtes HTTP

Publié dans Informatique

Pour être informé des derniers articles, inscrivez vous :
Commenter cet article
M
De rien, à votre service ;) .
Répondre
R
Good job mleg :) merci.
Répondre
R
tu devrait coloriser tes codes et mettre un peu tout ça en forme pour qu'on s'y retrouve ;) j'apprend le C actuelement mais je vais y aller tout doux parce que les codes géants comme ça mouhaha ^^" @+
Répondre
M
Bonjour.C'est moi qui devrait remercier...C'est gentil d'être passé.Merci encore.
Répondre
E
Salut,Je vois que mes petits conseils ne sont pas complètement inutiles... Ca fait plaisir,MerciEmmanuel
Répondre