HTTP on the Wire

See the raw HTTP text your browser sends, and learn how that text becomes a structured request inside your app.

HTTP on the Wire
Photo by Miguel Ángel Padriñán Alba / Unsplash

What the Client Actually Sends

Ever happen to build a FastAPI application and serve it using Uvicorn. Before Uvicorn or FastAPI ever see a request, what actually travels across the wire are raw bytes: either unencrypted (plain HTTP) or encrypted via SSL/TLS (HTTPS). If you want to really understand what your app is handling, it helps to peek under the hood and look at those raw requests.

In this post we’ll look at:

  • What a raw HTTP request and response look like.
  • The structure of request line, headers, body.
  • Why the body is arbitrary bytes.
  • How Content-Type guides interpretation.

By the end, you’ll be able to recognize the anatomy of any HTTP message and even try generating some yourself with curl.

Anatomy of an HTTP request

Every HTTP request has three sections:

  1. Request line - method, path, and version
  2. Headers - key-value pairs, one per line
  3. Body - optional, can contain text or binary

Here’s a simple GET request as it appears on the wire:

GET /hello HTTP/1.1
Host: example.com
User-Agent: curl/8.1.2
Accept: */*

Notice the blank line at the end - it marks the end of headers.

Example: POST with JSON body

A request with a body includes a Content-Type header so the server knows how to interpret the bytes.

POST /api/data HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 18

{"name": "Alice"}

The body here is JSON text, but to HTTP it’s just bytes.

Try it yourself:

curl -v -X POST https://example.com/api/data \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice"}'

Example: multipart file upload

Multipart request structure with boundaries

File uploads use multipart/form-data. This format introduces boundaries that separate different parts of the body.

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----12345

------12345
Content-Disposition: form-data; name="file"; filename="hello.txt"
Content-Type: text/plain

Hello world!
------12345--

Key points:

  • Each part starts with --boundary.
  • Each part has its own headers.
  • File bytes appear after the headers.

Try with curl:

curl -v -F "file=@hello.txt" https://example.com/upload

Why Content-Type matters

The server does not guess the format of the body. Instead:

  • application/json → parse body as JSON text.
  • multipart/form-data → parse into parts.
  • application/octet-stream → treat as raw binary.

Without this header, the server may misinterpret the body or reject the request.

Conclusion

At the raw level, HTTP is just structured text plus bytes. Understanding this helps demystify what Uvicorn later parses into ASGI scopes.

In the next post, we’ll see how those raw bytes enter Uvicorn through the socket and emerge as structured data for your app.