Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

How HTTP Works: Requests, Responses, Headers and Status Codes

Read a real HTTP exchange line by line: methods, URLs, headers, status code classes and cookies, tested with curl against a local server.

Beginner11 min readBy NotHoly

What HTTP is

HTTP is the protocol that browsers, apps and APIs use to exchange messages. RFC 9110 defines what the messages mean: methods, status codes, header fields and so on. RFC 9112 defines how HTTP/1.1 messages look as plain text on a TCP connection. The MDN overview is a shorter, browser-oriented introduction, and it works well alongside the RFCs.

The pattern is simple. A client sends a request, and a server returns one response. The client always starts the exchange. Plain HTTP does not encrypt anything, and it does not remember who you are between requests. Encryption comes from TLS, which the next article covers. Memory between requests comes from cookies and tokens, which we reach further down.

Why it matters

Most web behavior you will ever study passes through HTTP. A form sends a value in a body, an API trusts a header, a redirect moves the user somewhere else, and a cache keeps the wrong copy of a page. When you later use a proxy such as OWASP ZAP to inspect traffic, it shows you these same messages. If you cannot read a raw request, the proxy output is hard to use.

The shape of a message

An HTTP/1.1 request is plain text in this form:

http
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.12.1
Accept: */*

The first line is the request line: the method, the request target and the protocol version. Each following line is a header field, written as name: value. A blank line ends the header section. If the message has a body, it comes after that blank line. On the wire, every line ends with CRLF (a carriage return and a line feed), which editors hide from you.

A response has the same layout, with a status line at the top: version, status code and reason phrase.

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 15

<h1>hello</h1>

Methods

The method states what the client wants done. RFC 9110 defines GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS and TRACE. PATCH comes from RFC 5789.

Two properties matter when you read traffic:

  • Safe methods (GET, HEAD, OPTIONS, TRACE) are meant to be read-only from the client's point of view. RFC 9110, section 9.2.1.
  • Idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) can be repeated with the same intended effect as one request. RFC 9110, section 9.2.2. POST is neither safe nor idempotent.

These properties are promises about meaning. The server still decides what actually happens. A GET handler that deletes a record breaks the contract, and a security reviewer will look for exactly that mistake.

URLs and the request target

Take the URL http://127.0.0.1:8000/missing?lang=en#top and split it into parts:

  • Scheme: http, which tells the client which protocol to use.
  • Authority: 127.0.0.1:8000, the host and the optional port.
  • Path: /missing.
  • Query: lang=en, after the ?.
  • Fragment: top, after the #.

The client does not send the scheme or the fragment to the server. The request line carries the path and the query. The authority goes into the Host header. HTTP/1.1 requires Host because many sites can share one IP address, and the server uses that header to decide which site should answer.

Headers

Header names are case-insensitive, so Content-Type and content-type are the same field (RFC 9110, section 5.1). Header values are a different matter: many are case-sensitive, so do not assume they are not.

Request headers you will see most often:

  • Host: required in every HTTP/1.1 request. RFC 9112, section 3.2, says a server must answer 400 when it is missing.
  • User-Agent: the client software.
  • Accept: the media types the client can handle.
  • Cookie: the cookies the browser sends back to the server.
  • Authorization: credentials, often a bearer token.
  • Content-Type and Content-Length: describe the body, when there is one.

Response headers you will see most often:

  • Content-Type: the media type of the body, such as text/html.
  • Content-Length: the size of the body in bytes.
  • Date: the server's clock, in GMT.
  • Server: the software name. Many servers print it. Production setups often hide it.
  • Last-Modified: when the server last changed the resource.
  • Set-Cookie: asks the browser to store a cookie.

Headers are data the sender controls. A browser sends what its software sends, and a script or a proxy can send anything it likes. X-Forwarded-For is the classic example: a proxy adds it, but a client can send it too. Never trust a header only because it looks official.

Status codes

A status code has three digits. The first digit gives the class, as defined in RFC 9110, section 15:

Class Meaning Examples
1xx Informational. The request is received and processing continues 100 Continue
2xx Success 200 OK, 201 Created, 204 No Content
3xx Redirection. The client must take another step 301 Moved Permanently, 304 Not Modified
4xx Client error. The request has a problem 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed
5xx Server error. The server failed to handle a valid request 500, 501 Not Implemented, 502, 503

Even when a client does not recognize a specific code, it can act on the class. A 4xx means the request should change. A 5xx means the same request might work later.

Two codes get confused often. A 401 means the request lacks valid credentials. A 403 means the server understood the request and refuses it, even with credentials.

The reason phrase, such as Not Found, is text the server chooses. RFC 9112, section 4, says clients should ignore it. Write logic against the number, never the words.

Cookies

Cookies are not part of RFC 9110. They are defined in RFC 6265. The cycle works like this:

  1. The server sends Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.
  2. The browser stores the value along with its attributes.
  3. On later requests to a matching site, the browser sends Cookie: session=abc123.

A session cookie is an identifier the server hands out. It is not a password, and it is not private to the user. Whoever holds the value can usually act as that session until it expires or is revoked. HttpOnly keeps the value away from page scripts. Secure allows it only over HTTPS. SameSite limits when the browser sends it on cross-site requests. The web security articles return to these attributes.

Hands-on lab

You need curl and Python 3. Windows 11 with Git Bash ships curl, and most Linux and macOS systems include it. The server is Python's built-in http.server. Run it only on your own machine, bound to 127.0.0.1, and never expose it to a network.

The output below comes from running these commands on Windows 11 with Git Bash, curl 8.12.1 and Python 3.12.10. On this machine python3 was not on the Git Bash path, so the server was started with the full path to the Python 3.12 executable. Use python3, py -3 or python as your system allows. Your dates, times and version strings will differ. Curl's progress meter is removed from the output below, and long HTML bodies are shortened.

Step 1: create the site and start the server

In the first terminal:

bash
mkdir httplab && cd httplab && echo '<h1>hello</h1>' > index.html
python3 -m http.server 8000 --bind 127.0.0.1

Leave this terminal open. The server writes one log line per request, so each request you send below appears here.

Step 2: read a complete exchange

In the second terminal:

bash
curl -v http://127.0.0.1:8000/

Output:

text
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:16:48 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 
<h1>hello</h1>
* shutting down connection #0

Read it in this order:

  • Lines starting with * are curl's notes about the connection.
  • Lines starting with > are what curl sent. Curl added Host, User-Agent and Accept on its own.
  • Lines starting with < are what the server returned.
  • The empty > line ends the request headers. The empty < line ends the response headers.
  • <h1>hello</h1> is the body. Content-Length: 15 matches the file, because echo added a newline after the tag.
  • HTTP 1.0, assume close after body shows a mismatch. Curl asked for HTTP/1.1, but the server answered with HTTP/1.0. Python's simple server speaks HTTP/1.0, so curl closes the connection once the body is read.

Step 3: request a file that does not exist

bash
curl -i http://127.0.0.1:8000/missing

The -i flag prints the response headers together with the body. Output, with the HTML body shortened:

text
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 404</p>
        <p>Message: File not found.</p>
        <p>Error code explanation: 404 - Nothing matches the given URI.</p>
    </body>
</html>

Observe the first terminal. It logged:

text
127.0.0.1 - - [08/Nov/2026 05:16:49] code 404, message File not found
127.0.0.1 - - [08/Nov/2026 05:16:49] "GET /missing HTTP/1.1" 404 -

The status code is 404, a client-class code: the path maps to no resource. The reason phrase File not found is the server's own wording. The body is an HTML page the server generated for the error. Any status can carry any body, so read both.

The log line shows 05:16 and the Date header shows 08:16. The log uses local time and the header uses GMT, so the two differ by the local offset.

Step 4: send a method the server does not implement

bash
curl -i -X POST http://127.0.0.1:8000/

Output, with the HTML body shortened:

text
HTTP/1.0 501 Unsupported method ('POST')
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 357

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: 501 - Server does not support this operation.</p>
    </body>
</html>

The log line reads code 501, message Unsupported method ('POST'). The -X flag changes only the method. Curl sent the same headers as before.

The simple server has no POST handler at all, so 501 Not Implemented is the honest answer. RFC 9110, section 15.6.2, defines 501 for a server that does not support the functionality a request needs. A real application that knows the resource but refuses POST on it would normally answer 405 Method Not Allowed instead. A 405 response must include an Allow header that lists the methods the resource accepts.

Step 5: ask for headers only with HEAD

bash
curl -I http://127.0.0.1:8000/index.html

Output:

text
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:50 GMT
Content-type: text/html
Content-Length: 15
Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT

The -I flag sends a HEAD request. The status and the headers match what GET returns, but no body follows. Content-Length still says 15, because it describes the representation a GET would return. RFC 9110, section 9.3.2, defines this behavior. The log shows "HEAD /index.html HTTP/1.1" 200 -.

HEAD is useful when you want to check whether a file exists, how big it is, or when it last changed, without downloading it.

bash
curl -v -H 'Cookie: session=demo123' http://127.0.0.1:8000/

The relevant lines of the output:

text
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: session=demo123
> 
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:18:28 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 

The body line is left out here. The server answered with the same 200 as before, because it has no cookie logic and ignores the header. This is the point of the exercise. The browser sends cookies, but the server decides what they mean. A server that never sets a cookie will not act on one you send.

Step 7: stop the server

Press Ctrl+C in the first terminal when you finish.

Common mistakes

  • Reading 200 as success. A 200 can carry an error page. Check the body and what the application did.
  • Treating GET as harmless. Safe describes the client's intent. If a GET changes data, the server is broken, and attackers will try it.
  • Writing logic against the reason phrase. Clients should use the number. Servers are free to change the words.
  • Trusting client headers. Host, User-Agent, Cookie and X-Forwarded-For all come from the client side. Validate them on the server.
  • Assuming HTTP is private. Plain HTTP is readable by anyone on the path between you and the server. Real sites use HTTPS for that reason. The lab is fine only because the traffic never leaves 127.0.0.1.
  • Expecting -X to change the headers. It changes the method only. Add headers with -H.

How to go further

  • Read RFC 9110, section 9 (methods) and section 15 (status codes). It is long, so skim the sections you need.
  • Read RFC 9112 for message framing: how a client and server agree on where a body ends, including Content-Length and Transfer-Encoding.
  • Read the MDN HTTP overview for the browser view of the same concepts.
  • Next, what is HTTPS and TLS shows the same exchange with encryption added. If you have not read it yet, what a port is explains the :8000 part of the URL.

A proxy such as ZAP displays these same messages, which is why this article supports the later ones on web vulnerabilities. NotHoly's API at api.notholy.com/v1 is also an HTTP API with JSON bodies, served over TLS, so the headers and status codes above apply when you call it.

Sources

  1. RFC 9110: HTTP Semantics
  2. RFC 9112: HTTP/1.1
  3. MDN Web Docs: An overview of HTTP
  4. RFC 6265: HTTP State Management Mechanism (cookies)

More in Networks and the web

Networks and the web

What Is TLS? How HTTPS Protects Traffic

TLS 1.3 gives HTTPS confidentiality, integrity and server authentication. Learn the handshake, certificate chains and what the padlock does not prove.

Beginner11 min read