Everyone Can Learn Program
中文 Start learning
Core · NetworksLesson 4 of 6Free to read

HTTP requests and responses · Computer Networks

What does a browser actually send to a web server?

Course contents

Why you need it

HTTP is plain text with a strict shape. Once you can read a request and a response line by line, debugging an API, a redirect loop or a cookie problem stops being guesswork, and writing a backend makes sense.

The intuition

A request:

GET /products?page=2 HTTP/1.1     ← method, path, version
Host: shop.example.com            ← headers: name: value
Accept: application/json
                                  ← an empty line ends the headers
(body, for POST and PUT)

A response starts with a status line, HTTP/1.1 200 OK, then headers, an empty line and the body.

Status codes by first digit: 2xx success, 3xx go elsewhere (redirect), 4xx the client's mistake (404 not found, 401 not logged in, 403 not allowed), 5xx the server's mistake.

Methods: GET reads, POST creates, PUT/PATCH change, DELETE removes. HTTPS is the same text sent inside an encrypted TLS connection.

Definition

Status code

A three-digit number at the start of a response that says how the request went: 200 OK, 301 moved, 404 not found, 500 server error.

Header

A `Name: value` line that carries extra information: Content-Type, Cookie, Authorization, Location, Cache-Control.

Examples

Parse a request

Try it yourself

Split the text into the request line, the headers and the body.

raw = "GET /products?page=2 HTTP/1.1\r\nHost: shop.example.com\r\nAccept: application/json\r\nCookie: session=abc123\r\n\r\n"
head, body = raw.split("\r\n\r\n", 1)
lines = head.split("\r\n")
method, target, version = lines[0].split(" ")
headers = {}
for line in lines[1:]:
    name, value = line.split(": ", 1)
    headers[name.lower()] = value
path, _, query = target.partition("?")
print(method, path, query)
print(headers["host"], headers["accept"])
Step by step
  1. L2The empty line separates headers from body.
  2. L4The first line has three parts.
  3. L8Header names are case-insensitive, so store them in lower case.
  4. L10partition splits the path from the query at ?.GET /products page=2

Build a response

Try it yourself

A response is the same shape: status line, headers, empty line, body.

import json

def respond(status, data):
    reasons = {200: "OK", 404: "Not Found"}
    body = json.dumps(data)
    return (f"HTTP/1.1 {status} {reasons[status]}\r\n"
            f"Content-Type: application/json\r\n"
            f"Content-Length: {len(body)}\r\n\r\n" + body)

print(respond(404, {"error": "no such product"}))
Step by step
  1. L5Turn the dict into JSON text.
  2. L8Tells the client how many bytes of body to read.
  3. L10Status line, two headers, an empty line, then the JSON.HTTP/1.1 404 Not Found

Common mistakes

200 with an error inside

An API that returns 200 with {"error": …} breaks clients, caches and monitoring that trust the status code. Use 4xx or 5xx for failures.

401 versus 403

401: we don't know who you are (log in). 403: we know, and you're not allowed.

Quick quiz

1. A page moved permanently to a new URL. Which status and header tell the browser where to go?

  1. A301 with Location
  2. B404 with Host
  3. C500 with Content-Type
Show the answer

Answer: A

3xx means "go elsewhere", and Location says where.

2. What does this print?

def kind(status):
    return {2: "success", 3: "redirect", 4: "client error", 5: "server error"}[status // 100]

print(kind(201), "/", kind(503), "/", kind(403))
  1. A
    success / server error / client error
  2. B
    success / client error / server error
  3. C
    redirect / server error / client error
Show the answer

Answer: A

Only the first digit matters: 2, 5 and 4.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictRead a response

    Type exactly what this program prints.

  2. ModifyHeaders are case-insensitive

    get_header(raw, name) should return a header's value or None. It fails when the request writes content-type but you ask for Content-Type. Compare names without caring about case.

  3. WriteParse a query string

    Write parse_query(target) that takes a request target like /search?q=lamp&page=2 and returns a dict of its query parameters: {"q": "lamp", "page": "2"}. No ? means an empty dict. (Don't use urllib.)

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
HTTP超文本传输协议The text protocol browsers and servers use: requests and responses.
Status code状态码The three-digit result of an HTTP request, such as 404.
HTTP headerHTTP 头部A Name: value line with extra information about a request or response.