Guide pour mettre en place une architecture MVC en C pour un serveur web

Dans le développement logiciel, l’architecture MVC (Modèle-Vue-Contrôleur) est une approche couramment utilisée pour structurer les applications, en particulier les applications web. Elle permet de séparer la logique métier, l’interface utilisateur et la gestion des interactions, ce qui facilite la maintenance, l’évolutivité et la collaboration au sein des équipes.

Dans le cadre de ce guide, nous explorerons comment mettre en œuvre cette architecture en C pour créer un petit serveur web. Bien que le langage C soit généralement considéré comme bas niveau par rapport aux frameworks modernes, il offre un contrôle précis des ressources, une performance optimale et une base solide pour comprendre les principes fondamentaux des serveurs web.

Cet article vise à fournir un guide pratique pour comprendre et appliquer l’architecture MVC à un serveur web en C. Nous aborderons les concepts théoriques, les outils nécessaires, la structure du projet, ainsi qu’un exemple concret pour consolider vos acquis. Que vous soyez un développeur expérimenté ou un débutant curieux d’apprendre, ce guide vous aidera à mieux appréhender les bases de l’architecture MVC dans un environnement C.

Sommaire

Qu’est-ce que l’architecture MVC ?

L’architecture MVC (Modèle-Vue-Contrôleur) est un modèle de conception logiciel qui vise à organiser le code en séparant les responsabilités en trois composants principaux : le Modèle, la Vue et le Contrôleur. Chacun de ces composants joue un rôle distinct dans le fonctionnement de l’application.

Le Modèle


Le Modèle représente la logique métier et les données de l’application. Il est responsable de gérer les données, de les stocker et de les récupérer. Dans une application web, cela inclut souvent les interactions avec une base de données ou d’autres sources de données.
Exemple : Dans un serveur web, le Modèle pourrait inclure une structure de données pour stocker des informations sur les utilisateurs et des fonctions pour lire ou écrire ces données.

La Vue


La Vue gère l’affichage des données à l’utilisateur. Elle est chargée de transformer les données du Modèle en une interface compréhensible, souvent sous forme de pages HTML dans une application web.
Exemple : Une Vue pourrait générer une page web qui affiche une liste de produits récupérés depuis le Modèle.

Le Contrôleur


Le Contrôleur agit comme un intermédiaire entre la Vue et le Modèle. Il gère les requêtes de l’utilisateur, appelle les fonctions du Modèle pour traiter les données nécessaires, puis envoie ces données à la Vue pour un affichage.
Exemple : Dans un serveur web, le Contrôleur pourrait traiter une requête HTTP GET, récupérer les informations nécessaires depuis le Modèle, puis renvoyer une réponse HTML via la Vue.

Pourquoi adopter l’architecture MVC ?

  • Séparation des responsabilités : Chaque composant se concentre sur une tâche spécifique, ce qui rend le code plus clair et plus facile à maintenir.
  • Réutilisabilité : Le Modèle peut être réutilisé avec différentes Vues, et les Vues peuvent être modifiées sans affecter le Modèle.
  • Facilité de test : Tester les composants individuellement devient plus simple, car chaque partie est isolée.

L’architecture MVC est idéale pour structurer des applications complexes tout en assurant une séparation logique entre les différentes parties du système. Dans le contexte d’un serveur web en C, elle permet de mieux gérer les interactions entre les requêtes HTTP, les données et leur affichage.

Pourquoi choisir C pour un serveur web ?

Le langage C, bien qu’ancien, reste un choix pertinent pour le développement de serveurs web, notamment en raison de ses caractéristiques uniques et de ses avantages spécifiques. Voici pourquoi C peut être une option idéale pour implémenter un serveur web basé sur l’architecture MVC.

Performance et efficacité


Le C est un langage compilé qui produit du code natif, offrant ainsi une performance optimale. Cette rapidité est cruciale pour un serveur web qui doit traiter de nombreuses requêtes en temps réel.
Exemple : De nombreux serveurs web populaires, comme Nginx et Apache HTTP Server, sont partiellement ou entièrement écrits en C pour exploiter cette performance.

Contrôle granulaire des ressources


Le C permet un contrôle précis des ressources système, comme la mémoire et les sockets réseau. Cela est essentiel pour construire des serveurs web efficaces qui gèrent les connexions simultanées sans gaspiller les ressources.
Exemple : Avec des fonctions comme malloc et free, un développeur peut optimiser manuellement l’allocation et la libération de mémoire.

Portabilité


Les applications en C peuvent être compilées et exécutées sur presque toutes les plateformes, des systèmes embarqués aux serveurs haute performance. Cette flexibilité rend le C attractif pour les projets nécessitant une large compatibilité.
Exemple : Un serveur web écrit en C peut être adapté pour fonctionner sur un système Linux, Windows, ou même un microcontrôleur.

Base solide pour l’apprentissage


Utiliser le C pour un serveur web permet de comprendre les fondations des protocoles réseau (comme HTTP) et des concepts bas-niveau, tels que la gestion des sockets ou des threads.
Exemple : En développant un serveur HTTP en C, vous manipulerez directement les sockets, ce qui approfondit votre compréhension des communications réseau.

Écosystème mature


Le C bénéficie d’un vaste écosystème de bibliothèques et d’outils, facilitant le développement des fonctionnalités d’un serveur web. Des bibliothèques comme libcurl pour les requêtes HTTP ou pthreads pour la gestion des threads peuvent accélérer le processus de développement.

Cas d’usage du C dans les serveurs web


Un serveur web développé en C peut être idéal pour :

  • Des systèmes embarqués nécessitant un serveur HTTP léger.
  • Des projets nécessitant un contrôle total sur les performances réseau.
  • Des environnements contraints où chaque cycle CPU et chaque octet de mémoire comptent.

En somme, le C est un langage puissant pour construire des serveurs web performants et personnalisés, tout en offrant un contrôle total sur l’architecture et les ressources du système.

Prérequis pour implémenter MVC en C

Avant de commencer le développement d’un serveur web suivant l’architecture MVC en C, il est crucial de s’assurer que vous disposez des outils, des bibliothèques, et des connaissances nécessaires. Voici une liste des prérequis indispensables pour mener à bien ce projet.

Compétences en programmation C


Une bonne compréhension des concepts fondamentaux de la programmation en C est essentielle :

  • Gestion de la mémoire (allocation, libération, pointeurs).
  • Manipulation des fichiers (lecture et écriture).
  • Structures de données (listes, tableaux, structures).
  • Programmation réseau (sockets TCP/IP).

Outils de développement


Pour développer efficacement un projet MVC en C, vous aurez besoin des outils suivants :

  • Un compilateur C : GCC (GNU Compiler Collection) ou Clang pour compiler votre code.
  • Un débogueur : GDB pour identifier et résoudre les erreurs dans votre code.
  • Un éditeur de code : Visual Studio Code, Vim, ou un IDE comme CLion pour écrire et organiser votre code.

Bibliothèques nécessaires


Certaines bibliothèques C peuvent accélérer le développement de votre serveur web :

  • Librairies réseau : Utilisez des bibliothèques natives comme sys/socket.h ou des abstractions comme libuv pour gérer les connexions.
  • JSON : Une bibliothèque comme cJSON ou jansson pour gérer les données au format JSON, si nécessaire.
  • HTML/CSS Templates : Bien que minimalistes, des utilitaires pour générer des réponses HTML dynamiques peuvent être utiles.

Environnement de développement


Configurez un environnement adapté à votre projet :

  • Un système d’exploitation basé sur Unix (comme Linux) est recommandé pour sa compatibilité avec les outils de développement en C.
  • Une structure de projet claire, avec des dossiers séparés pour les fichiers source (src), les en-têtes (include), et les ressources statiques (static).

Connaissance des protocoles réseau


Le serveur web nécessitera des compétences en réseau :

  • Comprendre le protocole HTTP (mécanismes GET, POST, etc.).
  • Savoir configurer et gérer des sockets TCP pour établir une communication entre le client et le serveur.

Exemple de structure de projet initial


Une structure MVC typique pour un serveur web en C pourrait ressembler à ceci :

project/
│
├── src/
│   ├── model.c        // Gestion des données
│   ├── view.c         // Génération des réponses
│   ├── controller.c   // Gestion des requêtes
│   └── main.c         // Point d'entrée
│
├── include/
│   ├── model.h
│   ├── view.h
│   └── controller.h
│
├── static/
│   └── index.html     // Ressources statiques
│
└── Makefile           // Automatisation de la compilation

Configurer un compilateur avec Makefile


Pour gérer efficacement votre projet, un fichier Makefile peut automatiser la compilation. Voici un exemple simple :

CC = gcc
CFLAGS = -Wall -Wextra -Iinclude
SRC = src/main.c src/model.c src/view.c src/controller.c
OBJ = $(SRC:.c=.o)
OUT = server

all: $(OUT)

$(OUT): $(OBJ)
    $(CC) $(CFLAGS) -o $@ $^

clean:
    rm -f $(OBJ) $(OUT)

Avec ces prérequis en place, vous serez prêt à commencer l’implémentation du serveur web MVC en C de manière organisée et efficace.

Structure du projet

Pour implémenter efficacement un serveur web en C basé sur l’architecture MVC, il est essentiel de bien structurer le projet. Une organisation claire facilite la lisibilité du code, la maintenance et le développement collaboratif. Voici une structure typique à suivre.

Organisation générale


Le projet est divisé en plusieurs répertoires pour séparer les différents composants de l’architecture MVC. Voici une structure recommandée :

project/
│
├── src/              // Fichiers source C
│   ├── model.c       // Gestion des données
│   ├── view.c        // Génération des réponses
│   ├── controller.c  // Gestion des requêtes
│   └── main.c        // Point d'entrée du programme
│
├── include/          // Fichiers d'en-têtes
│   ├── model.h
│   ├── view.h
│   └── controller.h
│
├── static/           // Fichiers statiques (HTML, CSS, JS)
│   ├── index.html    // Page d'accueil
│   ├── styles.css    // Feuille de style
│   └── script.js     // Scripts JavaScript
│
├── build/            // Fichiers compilés et binaires
│
├── tests/            // Tests unitaires
│   ├── test_model.c
│   ├── test_view.c
│   └── test_controller.c
│
└── Makefile          // Script pour compiler le projet

Description des composants

1. Répertoire src/
Contient le code source des trois composants principaux de l’architecture MVC.

  • model.c : Gère la logique métier et les données (par exemple, lecture/écriture dans un fichier ou une base de données).
  • view.c : Génère des réponses HTTP ou HTML basées sur les données fournies par le Modèle.
  • controller.c : Traite les requêtes HTTP, appelle les fonctions du Modèle et envoie les réponses générées par la Vue.
  • main.c : Point d’entrée du programme, initialisant le serveur et liant les composants entre eux.

2. Répertoire include/
Contient les fichiers d’en-têtes pour déclarer les fonctions et les structures utilisées dans le code source.

3. Répertoire static/
Stocke les ressources statiques comme les fichiers HTML, CSS, et JavaScript qui seront servis par le serveur.

4. Répertoire build/
Répertoire généré pour stocker les fichiers objets et les binaires compilés.

5. Répertoire tests/
Contient les tests unitaires pour valider chaque composant du projet.

Exemple de contenu de fichier

model.c (Gestion des données) :

#include "model.h"

void get_data(char *buffer, size_t size) {
    snprintf(buffer, size, "Hello, MVC in C!");
}

view.c (Génération de réponses) :

#include "view.h"

void render_response(const char *data) {
    printf("HTTP/1.1 200 OK\nContent-Type: text/plain\n\n%s\n", data);
}

controller.c (Gestion des requêtes) :

#include "controller.h"
#include "model.h"
#include "view.h"

void handle_request() {
    char data[128];
    get_data(data, sizeof(data));
    render_response(data);
}

main.c (Point d’entrée) :

#include "controller.h"

int main() {
    // Initialiser le serveur (simplifié)
    handle_request();
    return 0;
}

Automatisation avec Makefile


Un Makefile gère la compilation et l’organisation du projet.

CC = gcc
CFLAGS = -Wall -Wextra -Iinclude
SRC = src/main.c src/model.c src/view.c src/controller.c
OBJ = $(SRC:.c=.o)
OUT = server

all: $(OUT)

$(OUT): $(OBJ)
    $(CC) $(CFLAGS) -o $@ $^

clean:
    rm -f $(OBJ) $(OUT)

Avec cette structure, le projet sera bien organisé, et chaque composant de l’architecture MVC pourra être développé et testé indépendamment.

Implémentation de la couche Modèle

La couche Modèle est responsable de la gestion des données dans un serveur web. Elle peut inclure des opérations comme la lecture et l’écriture de données, leur validation, ou l’interaction avec une base de données ou des fichiers. Voici comment implémenter cette couche dans un projet MVC en C.

Objectifs de la couche Modèle

  • Fournir des fonctions pour récupérer, stocker et manipuler des données.
  • Assurer la cohérence et la validité des données.
  • Isoler la logique métier des autres composants (Vue et Contrôleur).

Structure de la couche Modèle


La couche Modèle est généralement composée d’un fichier source (model.c) et d’un fichier d’en-tête (model.h). Voici leur rôle :

  • model.h : Déclare les structures et les fonctions utilisées par le Modèle.
  • model.c : Contient l’implémentation des fonctions du Modèle.

Exemple d’implémentation

model.h

#ifndef MODEL_H
#define MODEL_H

#include <stddef.h>

// Structure de données pour stocker des informations
typedef struct {
    int id;
    char name[50];
    float price;
} Product;

// Fonction pour récupérer un produit par ID
int get_product_by_id(int id, Product *product);

// Fonction pour ajouter un produit
int add_product(const Product *product);

// Fonction pour lister tous les produits
size_t list_products(Product *products, size_t max_products);

#endif // MODEL_H

model.c

#include "model.h"
#include <string.h>

// Exemple de stockage en mémoire (peut être remplacé par une base de données)
#define MAX_PRODUCTS 100
static Product database[MAX_PRODUCTS];
static size_t product_count = 0;

// Récupérer un produit par ID
int get_product_by_id(int id, Product *product) {
    for (size_t i = 0; i < product_count; ++i) {
        if (database[i].id == id) {
            *product = database[i];
            return 0; // Succès
        }
    }
    return -1; // Produit non trouvé
}

// Ajouter un produit
int add_product(const Product *product) {
    if (product_count >= MAX_PRODUCTS) {
        return -1; // Pas de place disponible
    }
    database[product_count++] = *product;
    return 0; // Succès
}

// Lister tous les produits
size_t list_products(Product *products, size_t max_products) {
    size_t count = product_count < max_products ? product_count : max_products;
    memcpy(products, database, count * sizeof(Product));
    return count;
}

Explications des fonctions

1. get_product_by_id

  • Cherche un produit dans la base de données à l’aide de son ID.
  • Retourne 0 si le produit est trouvé, sinon -1.

2. add_product

  • Ajoute un produit dans la base de données si de l’espace est disponible.
  • Retourne 0 pour un ajout réussi ou -1 en cas d’échec.

3. list_products

  • Copie les produits disponibles dans un tableau fourni par l’utilisateur.
  • Retourne le nombre de produits copiés.

Exemple d’utilisation

Voici comment utiliser le Modèle dans le code du serveur :

#include "model.h"
#include <stdio.h>

int main() {
    Product new_product = {1, "Laptop", 999.99};
    Product found_product;
    Product product_list[10];

    // Ajouter un produit
    if (add_product(&new_product) == 0) {
        printf("Produit ajouté avec succès.\n");
    }

    // Récupérer un produit par ID
    if (get_product_by_id(1, &found_product) == 0) {
        printf("Produit trouvé : %s à %.2f €\n", found_product.name, found_product.price);
    }

    // Lister les produits
    size_t count = list_products(product_list, 10);
    printf("Liste des produits (%zu):\n", count);
    for (size_t i = 0; i < count; ++i) {
        printf("ID: %d, Nom: %s, Prix: %.2f\n", product_list[i].id, product_list[i].name, product_list[i].price);
    }

    return 0;
}

Avantages de cette structure

  • Modularité : La logique métier est isolée, facilitant les modifications futures.
  • Réutilisabilité : Les fonctions du Modèle peuvent être appelées par différentes parties de l’application.
  • Simplicité : En utilisant un stockage en mémoire, l’implémentation initiale reste simple tout en permettant une transition facile vers une base de données si nécessaire.

La couche Modèle joue un rôle fondamental dans l’architecture MVC en assurant une gestion efficace des données, leur validité et leur disponibilité pour les autres composants.

Implémentation de la couche Vue

La couche Vue est responsable de la génération des réponses utilisateur. Dans un serveur web, cela correspond à la création des pages HTML ou autres formats de sortie (JSON, XML, etc.) en fonction des données fournies par le Modèle.

Objectifs de la couche Vue

  • Générer du contenu dynamique basé sur les données reçues.
  • Gérer les formats de sortie comme HTML, JSON ou texte brut.
  • Isoler la logique d’affichage des autres composants (Modèle et Contrôleur).

Structure de la couche Vue


La couche Vue est composée d’un fichier source (view.c) et d’un fichier d’en-tête (view.h).

view.h

#ifndef VIEW_H
#define VIEW_H

#include "model.h"

// Fonction pour générer une réponse HTML
void render_html(const Product *products, size_t count);

// Fonction pour générer une réponse JSON
void render_json(const Product *products, size_t count);

// Fonction pour afficher un message d'erreur
void render_error(const char *message);

#endif // VIEW_H

view.c

#include "view.h"
#include <stdio.h>

void render_html(const Product *products, size_t count) {
    printf("HTTP/1.1 200 OK\n");
    printf("Content-Type: text/html\n\n");
    printf("<!DOCTYPE html>\n<html>\n<head>\n<title>Liste des produits</title>\n</head>\n<body>\n");
    printf("<h1>Produits disponibles</h1>\n<ul>\n");
    for (size_t i = 0; i < count; ++i) {
        printf("<li>ID: %d, Nom: %s, Prix: %.2f €</li>\n", products[i].id, products[i].name, products[i].price);
    }
    printf("</ul>\n</body>\n</html>\n");
}

void render_json(const Product *products, size_t count) {
    printf("HTTP/1.1 200 OK\n");
    printf("Content-Type: application/json\n\n");
    printf("[\n");
    for (size_t i = 0; i < count; ++i) {
        printf("  {\n");
        printf("    \"id\": %d,\n", products[i].id);
        printf("    \"name\": \"%s\",\n", products[i].name);
        printf("    \"price\": %.2f\n", products[i].price);
        printf("  }%s\n", (i == count - 1) ? "" : ",");
    }
    printf("]\n");
}

void render_error(const char *message) {
    printf("HTTP/1.1 400 Bad Request\n");
    printf("Content-Type: text/plain\n\n");
    printf("Erreur : %s\n", message);
}

Explications des fonctions

1. render_html

  • Génère une page HTML dynamique pour afficher une liste de produits.
  • Utilise une structure en balises HTML pour formater les données de manière lisible.

2. render_json

  • Produit une réponse au format JSON, souvent utilisée pour les API REST.
  • Génère un tableau d’objets JSON représentant les produits.

3. render_error

  • Affiche un message d’erreur simple en texte brut avec le code de réponse HTTP 400 Bad Request.

Exemple d’utilisation

Voici comment utiliser la Vue pour afficher une liste de produits ou des messages d’erreur :

#include "view.h"
#include "model.h"

int main() {
    Product products[] = {
        {1, "Laptop", 999.99},
        {2, "Smartphone", 499.99},
        {3, "Tablet", 299.99}
    };

    // Rendu HTML
    render_html(products, 3);

    // Rendu JSON
    render_json(products, 3);

    // Affichage d'une erreur
    render_error("Produit introuvable");

    return 0;
}

Bonnes pratiques pour la couche Vue

  • Minimalisme : La couche Vue doit se limiter à l’affichage et éviter toute logique métier.
  • Réutilisabilité : Créez des fonctions génériques qui peuvent être réutilisées pour différents formats ou scénarios.
  • Modularité : Organisez les templates HTML et les formats de sortie dans des fonctions distinctes pour faciliter la maintenance.

Améliorations potentielles

  • Ajouter un système de templates plus avancé pour générer du HTML.
  • Inclure des en-têtes HTTP dynamiques (comme des cookies ou des directives de cache).
  • Intégrer un support pour d’autres formats, comme XML ou CSV.

La couche Vue joue un rôle crucial dans un serveur web en fournissant une interface utilisateur accessible, qu’elle soit destinée à un navigateur ou à un client API. En suivant ces principes, votre serveur sera capable de répondre efficacement aux besoins des utilisateurs tout en restant flexible et maintenable.

Implémentation de la couche Contrôleur

La couche Contrôleur est au cœur de l’interaction entre les autres composants de l’architecture MVC. Elle reçoit les requêtes utilisateur (par exemple, des requêtes HTTP), décide de la logique à appliquer en s’appuyant sur le Modèle, et envoie les résultats à la Vue pour leur affichage.

Objectifs de la couche Contrôleur

  • Recevoir et analyser les requêtes des utilisateurs.
  • Interagir avec le Modèle pour récupérer ou modifier les données.
  • Transmettre les données pertinentes à la Vue pour la génération des réponses.

Structure de la couche Contrôleur


La couche Contrôleur est composée d’un fichier source (controller.c) et d’un fichier d’en-tête (controller.h).

controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

// Fonction pour traiter une requête HTTP
void handle_request(const char *method, const char *path);

#endif // CONTROLLER_H

controller.c

#include "controller.h"
#include "model.h"
#include "view.h"
#include <string.h>

void handle_request(const char *method, const char *path) {
    if (strcmp(method, "GET") == 0) {
        if (strcmp(path, "/products") == 0) {
            // Récupérer tous les produits
            Product products[10];
            size_t count = list_products(products, 10);
            render_html(products, count); // Rendu en HTML
        } else if (strncmp(path, "/products/", 10) == 0) {
            // Récupérer un produit spécifique par ID
            int id = atoi(path + 10);
            Product product;
            if (get_product_by_id(id, &product) == 0) {
                render_json(&product, 1); // Rendu en JSON
            } else {
                render_error("Produit non trouvé");
            }
        } else {
            render_error("Route non reconnue");
        }
    } else {
        render_error("Méthode non supportée");
    }
}

Explications des fonctions

1. handle_request

  • Analyse les paramètres de la requête (method et path).
  • Utilise des fonctions conditionnelles pour déterminer l’action à effectuer en fonction de la route demandée.
  • Interagit avec le Modèle pour récupérer ou modifier des données.
  • Utilise la Vue pour générer une réponse appropriée (HTML, JSON, ou message d’erreur).

Exemple d’utilisation

Voici un exemple d’utilisation de la couche Contrôleur dans un serveur HTTP minimaliste :

#include "controller.h"
#include <stdio.h>
#include <string.h>

int main() {
    char method[10];
    char path[100];

    // Simuler une requête HTTP
    strcpy(method, "GET");
    strcpy(path, "/products");

    // Traiter la requête
    handle_request(method, path);

    return 0;
}

Extensions possibles

  • Support pour d’autres méthodes HTTP : Ajouter des gestionnaires pour POST, PUT, DELETE, etc.
  • Gestion des paramètres de requête : Analyser les chaînes de requête (par exemple, /products?id=2).
  • Middleware : Implémenter des couches intermédiaires pour gérer les authentifications ou les validations de données.
  • Système de routage : Construire un tableau ou une liste de routes pour centraliser leur gestion.

Bonnes pratiques pour la couche Contrôleur

  1. Minimiser la logique métier : Confier cette tâche au Modèle pour préserver la séparation des responsabilités.
  2. Utiliser des fonctions réutilisables : Créer des fonctions pour chaque route et les appeler depuis handle_request pour une meilleure organisation.
  3. Gestion des erreurs : Assurer un retour d’erreurs clair pour les requêtes mal formées ou les routes non reconnues.

Améliorations potentielles

  • Ajouter des logs pour suivre les requêtes et leurs résultats.
  • Intégrer un système de gestion de sessions pour les utilisateurs authentifiés.
  • Étendre la compatibilité avec des formats d’entrée/sortie spécifiques comme XML ou des en-têtes HTTP personnalisés.

La couche Contrôleur est essentielle pour orchestrer les interactions dans une application MVC. Elle permet de connecter efficacement les requêtes utilisateur avec les données et leur présentation, tout en maintenant une organisation claire et maintenable du code.

Exemple pratique : petit serveur web MVC en C

Dans cette section, nous allons assembler les concepts précédents pour construire un petit serveur web fonctionnant avec l’architecture MVC. Cet exemple inclut un serveur minimaliste capable de gérer des requêtes HTTP simples et de répondre en HTML ou JSON.


1. Configuration initiale

Structure du projet
La structure du projet reste conforme à ce qui a été présenté dans [a5. Structure du projet]. Assurez-vous que les fichiers suivants sont présents et correctement configurés :

  • model.c et model.h
  • view.c et view.h
  • controller.c et controller.h
  • main.c

Outils nécessaires

  • Un compilateur C (GCC ou Clang).
  • Utilisation de nc (netcat) ou d’un navigateur pour tester le serveur.

2. Serveur web minimaliste

Voici le fichier main.c pour démarrer un serveur HTTP simple.

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "controller.h"

#define PORT 8080
#define BUFFER_SIZE 1024

void start_server() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // Création du socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Échec de la création du socket");
        exit(EXIT_FAILURE);
    }

    // Configuration de l'adresse
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Liaison du socket à l'adresse et au port
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Échec du bind");
        exit(EXIT_FAILURE);
    }

    // Mise en écoute
    if (listen(server_fd, 3) < 0) {
        perror("Échec de la mise en écoute");
        exit(EXIT_FAILURE);
    }

    printf("Serveur en écoute sur le port %d...\n", PORT);

    while (1) {
        // Acceptation d'une nouvelle connexion
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
            perror("Échec de l'acceptation");
            exit(EXIT_FAILURE);
        }

        // Lecture de la requête
        read(new_socket, buffer, BUFFER_SIZE);
        printf("Requête reçue :\n%s\n", buffer);

        // Analyse de la méthode et du chemin
        char method[10], path[100];
        sscanf(buffer, "%s %s", method, path);

        // Traitement de la requête
        dup2(new_socket, STDOUT_FILENO); // Redirige la sortie vers le socket
        handle_request(method, path);
        close(new_socket);
    }
}

3. Exemple d’interaction avec les autres couches

Traitement des requêtes dans le Contrôleur
Le fichier controller.c traite les requêtes via la fonction handle_request (voir [a8. Implémentation de la couche Contrôleur]). Il route les requêtes vers la Vue après avoir obtenu les données nécessaires depuis le Modèle.

Génération des réponses dans la Vue
La couche Vue (view.c) génère des réponses au format HTML ou JSON (voir [a7. Implémentation de la couche Vue]).

Gestion des données dans le Modèle
Le Modèle (model.c) gère les données statiques ou dynamiques (voir [a6. Implémentation de la couche Modèle]).


4. Test du serveur

Compilez et démarrez le serveur :

gcc -o server main.c src/*.c -Iinclude
./server

Envoyez une requête via curl ou un navigateur :

  • Pour récupérer tous les produits :
  curl http://localhost:8080/products

Réponse :

  <html>
  <body>
    <ul>
      <li>ID: 1, Nom: Laptop, Prix: 999.99 €</li>
      <li>ID: 2, Nom: Smartphone, Prix: 499.99 €</li>
    </ul>
  </body>
  </html>
  • Pour récupérer un produit spécifique :
  curl http://localhost:8080/products/1

Réponse :

  {
    "id": 1,
    "name": "Laptop",
    "price": 999.99
  }
  • En cas de route non reconnue :
  curl http://localhost:8080/unknown

Réponse :

  Erreur : Route non reconnue

5. Améliorations possibles

  • Gestion des paramètres de requête : Ajout de paramètres comme /products?id=1&format=json.
  • Authentification : Mise en place d’un système de gestion des utilisateurs.
  • Persistance des données : Remplacement du stockage en mémoire par une base de données (SQLite, MySQL).

Cet exemple illustre un serveur web minimaliste basé sur l’architecture MVC. Vous pouvez l’étendre pour répondre à des besoins plus complexes, tout en conservant une structure claire et modulaire.

Conclusion

Dans cet article, nous avons exploré comment mettre en œuvre un serveur web minimaliste en C en suivant l’architecture MVC (Modèle-Vue-Contrôleur). Nous avons détaillé les rôles et responsabilités des trois couches :

  1. Le Modèle : Gestion des données avec des fonctions pour stocker, récupérer et manipuler des informations.
  2. La Vue : Génération des réponses utilisateur en HTML ou JSON, offrant une interface claire et lisible.
  3. Le Contrôleur : Intermédiaire orchestrant les interactions entre le Modèle et la Vue en fonction des requêtes utilisateur.

Nous avons également présenté un exemple fonctionnel de serveur web permettant de traiter des requêtes HTTP simples et de répondre dynamiquement en fonction des données du Modèle.

Points clés :

  • Simplicité et modularité : L’architecture MVC rend le code clair, évolutif et facile à maintenir.
  • Base solide pour des projets complexes : Ce projet sert de point de départ pour des fonctionnalités avancées comme la gestion de sessions, l’authentification ou la connexion à une base de données.
  • Approche pédagogique : En travaillant avec C, vous obtenez une compréhension approfondie des concepts fondamentaux, comme les sockets réseau et le traitement des requêtes HTTP.

En poursuivant le développement, vous pourriez intégrer des fonctionnalités comme une gestion avancée des routes, un système de templates pour la Vue, ou des outils pour tester automatiquement les différents composants. L’architecture MVC garantit que ces ajouts restent bien structurés et cohérents.

Grâce à cette implémentation, vous disposez maintenant des bases nécessaires pour concevoir des serveurs web performants et adaptés aux besoins modernes.

Sommaire