Python · Course navigation

HTTP messages: requests, responses, and status codes

Read the text structure of an HTTP/1.1 request and response, and parse a response by hand.

On this page
Before you start

Strings and dictionaries.

  • Read the text structure of an HTTP/1.1 request and response, and parse a response by hand.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

HTTP is the application protocol of the web. A client sends a request and a server sends a response. In HTTP/1.1 these messages are plain text. A request starts with a request line, which has a method, a target, and a version. A response starts with a status line that has the version, a numeric status code, and a reason phrase. Header lines follow, then an empty line, then an optional body.

Methods describe intent: GET reads a resource, and POST submits data. A method is safe if it does not change server state, as with GET, and idempotent if repeating it has the same effect as doing it once, as with GET, PUT, and DELETE. Status codes are grouped by their first digit: 1xx informational, 2xx success, 3xx redirection, 4xx client error, and 5xx server error. Header names are case-insensitive.

The program parses a response by splitting at the blank line, then reading the status line and headers. The Content-Length header tells the receiver how many bytes of body to expect, so it must count bytes rather than characters. HTTP/2 and HTTP/3 keep the same semantics but send the messages in binary frames, and HTTP/3 runs over QUIC instead of TCP.

Code example

Python
request = "\r\n".join(["GET /index.html HTTP/1.1", "Host: example.com", "Accept: text/html", "", ""])
response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"


def parse_response(text):
    head, body = text.split("\r\n\r\n", 1)
    status_line, *header_lines = head.split("\r\n")
    version, status, reason = status_line.split(" ", 2)
    headers = {}
    for line in header_lines:
        name, value = line.split(":", 1)
        headers[name.strip().lower()] = value.strip()
    return int(status), reason, headers, body


method, target, version = request.split("\r\n")[0].split(" ")
print(method, target, version)
status, reason, headers, body = parse_response(response)
print(status, reason)
print(headers["content-length"], len(body.encode()))
print(body)
Output
GET /index.html HTTP/1.1
200 OK
5 5
hello

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1request = "\r\n".join(["GET /index.html HTTP/1.1", "Host: example.com", "Accept: text/html", "", ""])
2response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"
3
4
5def parse_response(text):
6 head, body = text.split("\r\n\r\n", 1)
7 status_line, *header_lines = head.split("\r\n")
8 version, status, reason = status_line.split(" ", 2)
9 headers = {}
10 for line in header_lines:
11 name, value = line.split(":", 1)
12 headers[name.strip().lower()] = value.strip()
13 return int(status), reason, headers, body
14
15
16method, target, version = request.split("\r\n")[0].split(" ")
17print(method, target, version)
18status, reason, headers, body = parse_response(response)
19print(status, reason)
20print(headers["content-length"], len(body.encode()))
21print(body)

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
The first line of a request holds the method, the target path, and the protocol version.GET, /index.html, HTTP/1.1
—
An empty line (two CRLFs in a row) separates the headers from the body.GET, /index.html, HTTP/1.1
—
The status line has the version, the numeric code, and the reason phrase; the phrase may contain spaces, so split at most twice.200, OK
—
Status 200 means success.200, OK
GET /index.html HTTP/1.1
200 OK
Content-Length: 5 matches the five bytes of the body.200, OK
GET /index.html HTTP/1.1
200 OK
5 5
hello

Behind the scenes

How Python processes this code

Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.

Cost and common mistakes

Parsing is O(n) in the message size n: one pass to split and one pass over the header lines.

  • Header names are case-insensitive, so normalize them before comparing. Values are often case-sensitive.
  • Content-Length counts bytes, not characters. For non-ASCII text the two differ.
  • Real parsers must also handle chunked transfer encoding, repeated headers, and malformed input. Use a tested HTTP library in production.

Try it yourself

Which status code class does 404 belong to, and who is at fault?

Reveal answer and reasoning

4xx is a client error. 404 means the server could not find the requested resource; the request, rather than the server's internals, is the problem.

Further reading