FlowingDev

HTTP-Nachrichten zerlegt: So entstehen die Rohdaten des Webs

Lerne die Anatomie von rohen HTTP-Nachrichten, von der Startzeile über die Header bis hin zu komplexen Multipart-Bodys, um zu verstehen, wie Web-Clients und Server kommunizieren.

Tool ausprobieren: HTTP-Nachrichten-Builder

In einem Satz

Eine HTTP-Nachricht ist ein formatierter Block aus reinem Text, den Webbrowser und Server austauschen. Sie funktioniert wie eine Kombination aus Versandetikett, Bedienungsanleitung und Paketinhalt für jede einzelne Interaktion im Web.

Das Problem, das es löst

In der Ursuppe des Webs der frühen 1990er Jahre waren die Dinge einfach. Ein Browser brauchte eine Möglichkeit, einen Server zu fragen: „Hey, kann ich die Datei science.html haben?“ und der Server brauchte eine Möglichkeit zu antworten: „Klar, hier ist sie“ oder „Sorry, konnte sie nicht finden.“ Diese Konversation brauchte Regeln – ein Protokoll. Dieses Protokoll wurde HTTP, das Hypertext Transfer Protocol.

Das „Problem“, das es löste, war die Schaffung einer universellen, eindeutigen Sprache für das Web. Ohne ein Standardformat könnte ein Server die Anfrage in einer einzigen Zeile erwarten, während ein anderer vielleicht ein Haiku bräuchte. Es wäre das reinste Chaos. Das ursprüngliche HTTP/0.9 war kinderleicht: GET /die-seite-die-ich-will.html. Der Server schickte dann einfach das HTML zurück.

Aber das Web blieb nicht so einfach. Wir mussten Daten an den Server senden, um Formulare auszufüllen. Wir mussten verschiedene Inhaltstypen wie Bilder und später JSON verarbeiten. Wir brauchten Sicherheit, Caching und eine Möglichkeit für Browser, sich selbst zu beschreiben. Die einfache einzeilige Anfrage entwickelte sich zu einer strukturierten, mehrteiligen „Nachricht“ mit einer Startzeile, einem Block von Metadaten (Headers) und einem optionalen Body für die eigentliche Nutzlast (Payload). Das manuelle Erstellen dieser Nachrichten wurde zur grundlegenden Fähigkeit für jeden, der direkt mit Web-Infrastruktur, APIs oder Sicherheit arbeitet. So wurde das Problem gelöst, wie man immer komplexere Geschäfte über den einfachen Anfrage-Antwort-Dialog des Webs abwickeln kann.

Wie es unter der Haube funktioniert

Im Grunde ist eine HTTP-Nachricht nur Text. Man könnte sie buchstäblich in ein Terminal tippen und an einen Server weiterleiten (pipen), wenn man Lust dazu hätte. Dieser Text ist in drei Teile gegliedert: eine Startzeile, einen Block mit Headern und einen optionalen Body. Alle Teile sind durch spezielle Zeilenumbrüche (\r\n oder CRLF für „Carriage Return, Line Feed“) voneinander getrennt.

Es gibt zwei Arten von Nachrichten: Requests (Client an Server) und Responses (Server an Client). Sie sehen fast identisch aus, haben aber eine unterschiedliche erste Zeile.

Anatomie einer Request-Nachricht

Das ist dein Browser, der nach etwas fragt.

GET /documentation/guides/http-builder HTTP/1.1
Host: flowing.dev
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,*/*
Accept-Language: en-US,en;q=0.5
Connection: keep-alive

<-- Der Body käme hierhin, aber GET-Requests haben normalerweise keinen -->
  1. Die Startzeile: GET /documentation/guides/http-builder HTTP/1.1

    • GET: Die HTTP-Methode (oder das Verb). Das ist es, was du tun willst. GET ruft Daten ab, POST sendet neue Daten, PUT aktualisiert bestehende Daten, DELETE entfernt Daten.
    • /documentation/...: Der Ressourcen-Pfad. In Kombination mit dem Host-Header ergibt dies die vollständige URL.
    • HTTP/1.1: Die Protokollversion.
  2. Die Headers: Eine Liste von Schlüssel-Wert-Paaren, die wichtige Metadaten über den Request liefern.

    • Host: flowing.dev: Für wen ist dieser Request bestimmt? Dieser Header ist in HTTP/1.1 zwingend erforderlich.
    • User-Agent: Mozilla/5.0...: Wer sendet diesen Request? Der Browser identifiziert sich hier selbst.
    • Accept: text/html,*/*: Welches Antwortformat kann ich verstehen? Hier bevorzugt der Browser HTML, akzeptiert aber alles.
  3. Die Leerzeile: Nach dem letzten Header signalisiert eine einzelne Leerzeile (\r\n) „Die Header sind fertig, jetzt kommt der Body.“ Das ist nicht verhandelbar. Lässt man sie weg, geht alles kaputt.

  4. Der Body: Die eigentliche Datennutzlast (Payload). Bei einem GET-Request ist er normalerweise leer. Bei einem POST oder PUT stehen hier deine Formulardaten oder dein JSON-Payload.

Anatomie einer Response-Nachricht

Das ist der Server, der auf den Request antwortet.

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 15328
Server: Vercel
Date: Mon, 25 Sep 2023 10:30:00 GMT
Cache-Control: public, max-age=0, must-revalidate

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>...</body>
</html>
  1. Die Statuszeile: HTTP/1.1 200 OK

    • HTTP/1.1: Die Protokollversion, wie beim Request.
    • 200: Der Status-Code. Eine dreistellige Zahl, die das Ergebnis zusammenfasst. 2xx bedeutet Erfolg, 3xx bedeutet Umleitung, 4xx bedeutet, du (der Client) hast Mist gebaut, und 5xx bedeutet, ich (der Server) habe Mist gebaut.
    • OK: Die Reason Phrase. Eine für Menschen lesbare Zusammenfassung des Status-Codes.
  2. Die Headers: Metadaten über die Response.

    • Content-Type: text/html: „Der Body, den ich dir schicke, ist HTML.“ Das ist entscheidend, damit der Browser weiß, wie er den Payload rendern soll.
    • Content-Length: 15328: „Der Body ist genau 15.328 Bytes lang.“
    • Set-Cookie: ...: So weisen Server die Browser an, Cookies zu speichern.
    • Cache-Control: ...: Anweisungen, wie der Browser oder zwischengeschaltete Proxies diese Response cachen sollen.
  3. Der Body: Die Ressource, die der Client angefordert hat – HTML, CSS, ein JSON-Objekt, Bilddaten usw.

Body-Vielfalt: Die Payload kodieren

Wenn ein Request einen Body hat, braucht er einen Content-Type-Header, um sein Format zu erklären. Die drei häufigsten sind:

  • application/x-www-form-urlencoded: Der Standard für HTML-Formulare der alten Schule. Es ist einfach ein Query-String im Body.

    name=Grace+Hopper&title=Rear+Admiral
    
  • application/json: Der König der modernen APIs. Der Body ist ein JSON-String.

    {
      "name": "Grace Hopper",
      "title": "Rear Admiral"
    }
    
  • multipart/form-data: Das Format zum Senden von Formularen, die Datei-Uploads enthalten. Es ist wie eine Nachricht in einer Nachricht. Der Body wird in Teile zerlegt, die jeweils durch einen „Boundary“-String getrennt sind. Jeder Teil kann seine eigenen Mini-Header (wie Content-Disposition und Content-Type) und seinen eigenen Inhalt haben.

    POST /profiles/edit HTTP/1.1
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
    
    ----WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="username"
    
    ada_lovelace
    ----WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="avatar"; filename="portrait.jpg"
    Content-Type: image/jpeg
    
    <...rohe Binärdaten des Bildes kommen hierhin...>
    ----WebKitFormBoundary7MA4YWxkTrZu0gW--
    

Geschichten aus der Praxis

Der Fall des fehlenden Content-Type

Ein Entwickler baute seine erste REST-API. Der Endpunkt sollte einen JSON-Payload akzeptieren, um einen neuen Benutzer anzulegen. Er schrieb den Server-Code und testete ihn mit einem Kommandozeilen-Tool, wobei er ein perfekt valides JSON-Objekt sendete. Aber der Server antwortete immer wieder mit 400 Bad Request. Er starrte zwei Stunden lang auf sein JSON, überzeugt davon, ein Komma übersehen zu haben. In seiner Verzweiflung bat er einen erfahrenen Entwickler um Hilfe. Der Senior-Entwickler warf einen Blick auf den Request und fragte: „Wo ist dein Content-Type-Header?“ Der Entwickler hatte die JSON-Daten gesendet, aber dem Server nie mitgeteilt, dass es sich um JSON handelte. Das Server-Framework, das den Standard x-www-form-urlencoded erwartete, versuchte, das JSON als Query-String zu parsen, scheiterte kläglich und lehnte den Request ab.

Lektion: Der Body einer Nachricht ist ohne den Content-Type-Header, der ihm Kontext gibt, bedeutungslos. Du musst dein Paket korrekt beschriften.

Das Multipart-Durcheinander

Ein Team erstellte eine „Einstellungen“-Seite, auf der ein Benutzer seinen Namen ändern und optional ein neues Profilbild hochladen konnte. Der Junior-Frontend-Entwickler implementierte dies mit zwei separaten API-Aufrufen: einem PUT-Request mit dem Benutzernamen in einem JSON-Body und, falls ein Bild ausgewählt wurde, einem POST-Request mit den Bilddaten. Es funktionierte, war aber umständlich und schuf Race Conditions. Was, wenn die Namensänderung erfolgreich war, aber der Bildupload fehlschlug? Der Benutzer wäre in einem inkonsistenten Zustand zurückgeblieben. Ein Backend-Ingenieur sah den Netzwerkverkehr und nahm ihn beiseite. „Das ist ein perfekter Anwendungsfall für multipart/form-data“, erklärte sie. Sie überarbeiteten den Code, um einen einzigen POST-Request mit zwei Teilen zu erstellen: einen für das Namensfeld und einen für die Bilddatei. Das vereinfachte den Code und machte das gesamte Update zu einer atomaren Operation.

Lektion: multipart ist nicht nur für Dateien da. Es ist dazu da, eine gemischte Tüte von Daten – Textfelder, Dateien, verschiedene Inhaltstypen – in einem einzigen, zuverlässigen Request zu senden.

Der Geist im Cache

Eine E-Commerce-Website hatte einen Flash-Sale, aber die Benutzer beschwerten sich, dass sie veraltete Preise sahen. Das Ops-Team war ratlos; ihr serverseitiges Caching war korrekt konfiguriert. Eine Web-Performance-Expertin wurde gerufen. Anstatt die Entwicklertools des Browsers zu verwenden, nutzte sie ein Tool, um die rohe HTTP-Response für eine Produktseite zu inspizieren. Sie fand den Schuldigen sofort. Ein falsch konfigurierter Load Balancer vor den Webservern fügte seinen eigenen Cache-Control: public, max-age=3600-Header ein und überschrieb damit den vom Server beabsichtigten Cache-Control: no-cache-Header. Dieser „abtrünnige“ Header wies Browser und CDNs an, die Preise für eine Stunde zu cachen, egal was der Anwendungsserver sagte.

Lektion: Die rohe HTTP-Nachricht ist die ultimative Quelle der Wahrheit. High-Level-Tools können manchmal Details verbergen oder falsch interpretieren, die im Text selbst sonnenklar sind.

Häufige Fehler und Fallen

  • Die Leerzeile vergessen. Eine HTTP-Nachricht muss ein CRLF (\r\n) zwischen den Headern und dem Body haben. Fehlt es, denken Parser, dein Body sei nur ein weiterer fehlerhafter Header, und der Request schlägt fehl.
  • Falscher Content-Length. Wenn du einen Content-Length-Header deklarierst, muss sein Wert die exakte Byte-Größe des Bodys sein. Ist er zu klein, werden deine Daten abgeschnitten. Ist er zu groß, wartet der Server ewig auf Bytes, die niemals ankommen.
  • Falscher Content-Type. Einen JSON-Body zu senden, ihn aber als text/plain zu kennzeichnen, ist ein Rezept für einen 4xx-Fehler. Header und Body müssen übereinstimmen.
  • CRLF vs. LF. Die offizielle Spezifikation verlangt \r\n für Zeilenumbrüche. Die meisten modernen Server sind tolerant und akzeptieren auch ein einfaches \n (Line Feed). Sich darauf zu verlassen, kann jedoch dazu führen, dass dein Request bei älteren, strengeren Servern, Proxies oder Firewalls fehlschlägt.
  • Sonderzeichen kodieren. Zu vergessen, Daten in einem Query-String oder einem x-www-form-urlencoded-Body zu URL-kodieren, ist ein klassischer Bug. Ein Leerzeichen muss zu %20 werden, ein & zu %26 und so weiter, sonst riskierst du, deine Daten zu korrumpieren.

Warum du das auf dem Schirm haben solltest

Meistens erledigen dein Browser, Framework oder deine Bibliothek (wie axios oder requests) die schmutzigen Details des Erstellens von HTTP-Nachrichten für dich. Aber du solltest wissen, wie man es manuell macht, wenn:

  • Du tief in einer Debugging-Session steckst. Wenn ein API-Aufruf nicht funktioniert und die Fehlermeldung vage ist, ist das Untersuchen oder Nachbauen der rohen HTTP-Nachricht der letzte Schiedsrichter. Damit siehst du genau, was über die Leitung geht, frei von jeglicher Abstraktion.
  • Du eine API baust oder testest. Die Nachrichtenstruktur zu verstehen ist fundamental, um gute API-Endpunkte zu entwerfen und effektive Integrationstests zu schreiben. Sicherheitstester verbringen ihre Tage damit, fehlerhafte Nachrichten zu basteln, um Schwachstellen zu finden.
  • Du eine Website scrapest. Um erfolgreich einen echten Browser zu imitieren und Anti-Bot-Maßnahmen zu umgehen, musst du oft einen Request mit einer sehr spezifischen Kombination von Headern (User-Agent, Referer, Accept-* usw.) konstruieren.
  • Du mit Webhooks arbeitest. Wenn deine Anwendung einen Webhook von einem Dienst wie Stripe oder GitHub empfängt, bist du der Empfänger eines rohen HTTP-Requests. Du musst seine Header (z. B. für Sicherheitssignaturen) und seinen Body parsen, um auf das Ereignis zu reagieren.

Zu wissen, wie man eine HTTP-Nachricht von Grund auf zusammenbaut, ist, als wüsste ein Mechaniker, wie ein Verbrennungsmotor funktioniert. Man macht es nicht jeden Tag, aber wenn etwas schief geht, ist dieses grundlegende Wissen unbezahlbar.

Tauch tiefer ein

  • An overview of HTTP on MDN - Der beste Startpunkt für einen verständlichen High-Level-Guide.
  • RFC 9112: HTTP/1.1 - Die primäre technische Spezifikation für die Syntax von HTTP/1.1-Nachrichten. Sie ist dicht, aber maßgebend.
  • HTTP headers on MDN - Eine umfassende und durchsuchbare Referenz für jeden Standard-HTTP-Header.
  • POST on MDN - Ein praktischer Leitfaden, der Details zu den verschiedenen Content-Type-Bodys für POST-Requests enthält.
  • Wikipedia: Hypertext Transfer Protocol - Ein solider Überblick über die Geschichte und den Kontext von HTTP.

Theorie erledigt. Zeit, loszulegen — 100 % in deinem Browser.

Tool ausprobieren: HTTP-Nachrichten-Builder