Projektüberblick
Tinyhttpd ist ein leichtgewichtiger HTTP-Server, der als Lernprojekt für die Grundlagen der Webserver-Entwicklung konzipiert ist. Das Projekt bietet eine minimale, aber vollständige Implementierung eines HTTP-Servers mit CGI-Unterstützung (Common Gateway Interface). Der Quellcode ist bewusst einfach gehalten, um Entwicklern das Verständnis der Kernkonzepte von HTTP-Servern zu erleichtern (README.md:1-5).
Der Server unterstützt sowohl statische Dateiauslieferung als auch dynamische Inhalte durch CGI-Programme. Die Implementierung nutzt POSIX-Threads (oder alternative Forking-Mechanismen für Linux) zur Behandlung gleichzeitiger Verbindungen. Die CGI-Funktionalität ermöglicht die Ausführung von Perl-Skripten und anderen ausführbaren Programmen zur dynamischen Inhaltserzeugung.
Technologie-Stack und Voraussetzungen
| Komponente | Beschreibung | Version/Anforderung |
|---|---|---|
| Sprache | C | Standard C |
| Threading | POSIX Threads | pthread-Bibliothek |
| CGI-Unterstützung | Perl | Perl + perl-cgi Modul |
| Socket-API | BSD Sockets | -lsocket (Solaris) / Standard (Linux) |
| Zielplattform | Unix-ähnlich | Linux, Solaris |
Plattform-spezifische Kompilierung
Für Linux-Systeme sind folgende Anpassungen erforderlich:
- Auskommentieren von
#include <pthread.h> - Auskommentieren der Variable
newthread - Auskommentieren der
pthread_create()Aufrufe - Aktivieren des direkten
accept_request()Aufrufs - Entfernen von
-lsocketaus dem Makefile
Projektstruktur
tinyhttpd/
├── htdocs/ # Statische Webdateien und CGI-Skripte
│ ├── index.html # Standard-Startseite
│ └── *.cgi # CGI-Programme (Perl, Shell, etc.)
├── simpleclient.c # Einfacher HTTP-Client zum Testen
├── httpd.c # Hauptimplementierung des Servers
├── Makefile # Build-Konfiguration
└── README.md # Projektdokumentation
Kernmodule und deren Verantwortlichkeiten
Modulübersicht
Der Server besteht aus mehreren funktionalen Modulen, die jeweils spezifische Aufgaben innerhalb des HTTP-Anfragezyklus übernehmen. Die folgende Tabelle fasst die Kernfunktionen zusammen:
| Funktion | Verantwortlichkeit | Eingabe | Ausgabe |
|---|---|---|---|
main | Einstiegspunkt, Server-Start | Kommandozeilenargumente | Exit-Code |
startup | Socket-Initialisierung und Port-Bindung | Portnummer | Server-Socket |
accept_request | HTTP-Anfrageverarbeitung | Client-Socket | HTTP-Antwort |
execute_cgi | CGI-Programmausführung | Pfad, Methode, Daten | CGI-Ausgabe |
cat | Dateiinhalt lesen und senden | Dateipfad, Socket | Dateiinhalt |
get_line | Zeilenweise Socket-Lesung | Socket | Zeilenstring |
headers | HTTP-Header generieren | Status, Socket | Header-Bytes |
bad_request | 400-Fehler senden | Socket | Fehlerantwort |
not_found | 404-Fehler senden | Socket | Fehlerantwort |
cannot_execute | 500-Fehler bei CGI senden | Socket | Fehlerantwort |
unimplemented | 501-Fehler senden | Socket | Fehlerantwort |
error_die | Fehlerbehandlung und Exit | Fehlernachricht | Exit |
Modul 1: Server-Initialisierung (startup)
Verantwortlichkeitsgrenzen:
- Erstellt den Server-Socket
- Führt
bind()undlisten()Systemaufrufe aus - Weist einen Port zu (festgelegt oder zufällig)
- Gibt den Socket-Deskriptor an den Aufrufer zurück
Nicht verantwortlich für:
- Annahme von Client-Verbindungen
- Anfrageverarbeitung
- Thread-Management
Eintrittspunkt und API:
c1int startup(u_short *port);
Wichtige Datenstrukturen:
struct sockaddr_in: Server-Adressinformationenu_short *port: Zeiger auf Portnummer (Eingabe/Ausgabe)
Aufrufkette:
socket()erstellt TCP-Socketsetsockopt()aktiviert SO_REUSEADDRbind()weist Adresse zulisten()aktiviert Warteschlange
Fehlerbehandlung:
- Bei Socket-Erstellung:
perror()und Exit - Bei Bind-Fehler:
perror()und Exit - Bei Port 0: Automatische Portzuweisung durch System
Modul 2: HTTP-Anfrageverarbeitung (accept_request)
Verantwortlichkeitsgrenzen:
- Liest und parst HTTP-Anfragezeile
- Extrahiert HTTP-Methode (GET/POST)
- Verarbeitet URL und Query-String
- Entscheidet zwischen statischer Auslieferung und CGI
Nicht verantwortlich für:
- Socket-Erstellung
- CGI-Prozess-Management (delegiert an
execute_cgi)
Eintrittspunkt und API:
c1void accept_request(int client);
Wichtige Datenstrukturen:
char method[255]: HTTP-Methodechar url[255]: Angeforderte URLchar path[512]: Dateisystempfadchar *query_string: GET-Parameter
Aufrufkette:
get_line()liest Anfragezeile- Parsing von Methode und URL
- Pfadkonstruktion mit
htdocs-Präfix - Verzeichnisprüfung →
index.htmlanhängen - Verzweigung:
serve_file()oderexecute_cgi()
Fehlerbehandlung:
- Ungültige Methode →
unimplemented() - Datei nicht gefunden →
not_found() - Pfad außerhalb htdocs →
bad_request()
Modul 3: CGI-Ausführung (execute_cgi)
Verantwortlichkeitsgrenzen:
- Erstellt Pipes für Interprozesskommunikation
- Führt
fork()zur Prozessisolierung durch - Setzt CGI-Umgebungsvariablen
- Leitet STDIN/STDOUT um
- Führt CGI-Programm via
execl()aus
Nicht verantwortlich für:
- HTTP-Header-Parsing
- Statische Dateiauslieferung
Eintrittspunkt und API:
c1void execute_cgi(int client, const char *path, 2 const char *method, const char *query_string);
Wichtige Datenstrukturen:
int cgi_input[2]: Pipe für CGI-Eingabeint cgi_output[2]: Pipe für CGI-Ausgabepid_t pid: Prozess-ID des Kindprozesses- Umgebungsvariablen:
REQUEST_METHOD,QUERY_STRING,CONTENT_LENGTH
Aufrufkette (Elternprozess):
- Schließt nicht benötigte Pipe-Enden
- Bei POST: Schreibt Daten in
cgi_input[1] - Liest CGI-Ausgabe von
cgi_output[0] - Sendet Ausgabe an Client-Socket
waitpid()wartet auf Kindprozess
Aufrufkette (Kindprozess):
dup2()leitet STDOUT aufcgi_output[1]umdup2()leitet STDIN aufcgi_input[0]umsetenv()setzt Umgebungsvariablenexecl()ersetzt Prozess durch CGI-Programm
Fehlerbehandlung:
- Pipe-Erstellung fehlgeschlagen →
cannot_execute() - Fork fehlgeschlagen →
cannot_execute() - execl fehlgeschlagen → Exit
Modul 4: Hilfsfunktionen (cat, get_line, headers)
cat-Funktion:
- Liest Dateiinhalt blockweise
- Schreibt direkt in Socket
- Verantwortlich für statische Dateiauslieferung
get_line-Funktion:
- Liest Zeichen für Zeichen vom Socket
- Normalisiert Zeilenenden (\r\n → \n)
- Handelt Timeout und Pufferüberlauf
headers-Funktion:
- Sendet HTTP-Statuszeile
- Sendet Standard-Header (Server, Content-Type)
- Terminiert Header-Block mit Leerzeile
Systemarchitektur
正在加载图表渲染器...
Architektur-Erklärung:
-
Netzwerk-Layer: Das
startup()-Modul initialisiert den Server-Socket und bindet ihn an einen Port. Deraccept()-Aufruf wartet auf eingehende Verbindungen und erstellt für jede Verbindung einen neuen Client-Socket. -
Anfrage-Verarbeitung:
accept_request()ist die zentrale Verarbeitungsfunktion. Sie nutztget_line()zum zeilenweisen Lesen der HTTP-Anfrage und entscheidet basierend auf Methode und URL über den weiteren Verarbeitungspfad. -
Statische Inhalte: Bei einfachen GET-Anfragen ohne Parameter wird
serve_file()aufgerufen, das übercat()den Dateiinhalt direkt an den Client sendet. -
CGI-Subsystem: Für dynamische Inhalte (POST, GET mit Parametern, ausführbare Dateien) wird
execute_cgi()aktiviert. Dieses Modul nutztfork()zur Prozessisolierung und Pipes zur Kommunikation zwischen Eltern- und Kindprozess. -
Fehlerbehandlung: Verschiedene Fehlerfunktionen behandeln spezifische Fehlerszenarien und senden entsprechende HTTP-Statuscodes an den Client.
HTTP-Anfrageverarbeitungsfluss
正在加载图表渲染器...
Ablauf-Erklärung:
-
Verbindungsannahme: Der Server akzeptiert eine TCP-Verbindung und erstellt einen neuen Thread (oder ruft direkt
accept_request()auf). -
Anfrage-Parsing:
accept_request()liest die HTTP-Anfragezeile, extrahiert Methode und URL, und konstruiert den Dateisystempfad. -
Verzweigung: Bei GET-Anfragen ohne Parameter wird die Datei direkt ausgeliefert. Bei POST oder GET mit Parametern wird die CGI-Verarbeitung aktiviert.
-
CGI-Prozess: Der Elternprozess erstellt zwei Pipes und forkt einen Kindprozess. Der Kindprozess leitet STDIN/STDOUT um und führt das CGI-Programm aus. Der Elternprozess sendet ggf. POST-Daten und liest die CGI-Ausgabe.
-
Antwort: Die generierte Antwort wird an den Client gesendet und die Verbindung geschlossen.
CGI-Implementierung und Interprozesskommunikation
Pipe-Architektur
Die CGI-Implementierung nutzt zwei unidirektionale Pipes für die Kommunikation zwischen Server und CGI-Programm:
Pipe-Konfiguration:
| Pipe | Richtung | Elternprozess | Kindprozess |
|---|---|---|---|
cgi_input | Server → CGI | Schreibt in [1] | Liest von [0] (STDIN) |
cgi_output | CGI → Server | Liest von [0] | Schreibt in [1] (STDOUT) |
Anfangszustand (nach pipe()-Aufruf):
cgi_input: [0] ← Lesen [1] ← Schreiben
cgi_output: [0] ← Lesen [1] ← Schreiben
Endzustand (nach dup2() und close()):
Kindprozess:
STDIN → cgi_input[0]
STDOUT → cgi_output[1]
Elternprozess:
Schreibt POST-Daten → cgi_input[1]
Liest CGI-Ausgabe ← cgi_output[0]
Umgebungsvariablen
Das CGI-Modul setzt folgende Umgebungsvariablen für die Kommunikation mit dem CGI-Programm:
| Variable | Beschreibung | Beispielwert |
|---|---|---|
REQUEST_METHOD | HTTP-Methode | GET oder POST |
QUERY_STRING | URL-Parameter | name=value&foo=bar |
CONTENT_LENGTH | Länge der POST-Daten | 42 |
Datenfluss bei POST-Anfragen
正在加载图表渲染器...
Datenfluss-Erklärung:
- Der Elternprozess empfängt POST-Daten vom Client
- Daten werden über
cgi_input[1]in die Pipe geschrieben - Der Kindprozess liest die Daten von
cgi_input[0]über STDIN - Das CGI-Programm verarbeitet die Daten
- Die Ausgabe wird über STDOUT an
cgi_output[1]gesendet - Der Elternprozess liest von
cgi_output[0]und sendet an den Client
Kernfunktionen im Detail
Fehlerbehandlungsmodul
Der Server implementiert mehrere spezialisierte Fehlerfunktionen:
bad_request(): Sendet HTTP 400 Bad Request
- Wird aufgerufen bei malformed HTTP-Anfragen
- Schreibt Standard-Fehlerseite an Client
not_found(): Sendet HTTP 404 Not Found
- Wird aufgerufen wenn angeforderte Datei nicht existiert
- Generiert HTML-Fehlerseite
cannot_execute(): Sendet HTTP 500 Internal Server Error
- Wird aufgerufen bei CGI-Ausführungsfehlern
- Meldet Server-seitige Probleme
unimplemented(): Sendet HTTP 501 Not Implemented
- Wird aufgerufen bei nicht unterstützten HTTP-Methoden
- Listet unterstützte Methoden auf
error_die(): Kritische Fehlerbehandlung
- Schreibt Fehlermeldung via
perror() - Beendet Prozess mit Exit
Empfohlene Lesereihenfolge
Für ein tiefgreifendes Verständnis des Quellcodes wird folgende Reihenfolge empfohlen:
- main() — Einstiegspunkt und Server-Lebenszyklus
- startup() — Socket-Initialisierung
- accept_request() — Kern-Verarbeitungslogik
- execute_cgi() — CGI-Implementierung
Nach dem Verständnis des Hauptflusses sollten die Hilfsfunktionen (cat, get_line, headers) und Fehlerbehandlungsfunktionen studiert werden.
Quantifizierte Projektkennzahlen
| Metrik | Wert |
|---|---|
| Kernfunktionen | 12 |
| Unterstützte HTTP-Methoden | 2 (GET, POST) |
| CGI-Umgebungsvariablen | 3 |
| Pipes pro CGI-Aufruf | 2 |
| Prozesse pro CGI-Anfrage | 2 (Eltern + Kind) |
| Standard-Webverzeichnis | 1 (htdocs) |
| Fehlerbehandlungsfunktionen | 5 |
Anwendungsgebiete
Bildungszwecke:
- Verständnis von HTTP-Protokoll-Grundlagen
- Lernen von Socket-Programmierung
- Verstehen von CGI-Mechanismen
- Studium von Interprozesskommunikation
Einschränkungen:
- Nicht für Produktionsumgebungen geeignet
- Keine HTTPS-Unterstützung
- Eingeschränkte Sicherheitsfunktionen
- Kein HTTP/1.1 Keep-Alive
Erweiterungsmöglichkeiten:
- Hinzufügen von HTTPS/TLS-Unterstützung
- Implementierung von HTTP/1.1 Persistent Connections
- Erweiterte MIME-Type-Erkennung
- Logging-Funktionalität
- Konfigurationsdatei-Unterstützung
Berichts-Navigationsübersicht
正在加载图表渲染器...
Lesepfad-Erklärung:
- Projektüberblick (aktuelle Seite): Einführung in Tinyhttpd, Kernmodule und Systemarchitektur
- Architektur: Detaillierte Analyse der Komponentenstruktur und Abhängigkeiten
- Datenfluss: Tiefgehende Untersuchung der HTTP-Anfrageverarbeitung und CGI-Kommunikation
- API-Design: Schnittstellenbeschreibung und Funktions-APIs
- Deployment: Build-Prozess und Betriebshinweise
