人人都能学编程
English 开始学习
核心 · 计算机网络第 4 课 / 共 6 课免费阅读

HTTP 请求与响应 · 计算机网络

浏览器到底给网页服务器发了什么?

课程目录

为什么需要它

HTTP 是形状很严格的纯文本。一旦你能逐行读懂请求和响应,调试 API、重定向死循环或 cookie 问题就不再靠猜,写后端也会变得好理解。

直觉理解

一个请求:

GET /products?page=2 HTTP/1.1     ← 方法、路径、版本
Host: shop.example.com            ← 头部:名称: 值
Accept: application/json
                                  ← 空行表示头部结束
(正文,POST 和 PUT 才有)

响应以状态行开头,HTTP/1.1 200 OK,然后是头部、空行和正文。

状态码看第一位:2xx 成功,3xx 去别处(重定向),4xx 客户端的错(404 找不到、401 没登录、403 没权限),5xx 服务器的错。

方法:GET 读取,POST 创建,PUT/PATCH 修改,DELETE 删除。HTTPS 就是把同样的文字放进加密的 TLS 连接里发送。

定义

状态码

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.
响应开头的三位数,说明请求的结果:200 成功、301 已移走、404 找不到、500 服务器错误。

头部 Header

A `Name: value` line that carries extra information: Content-Type, Cookie, Authorization, Location, Cache-Control.
一行 `名称: 值`,携带额外信息:Content-Type、Cookie、Authorization、Location、Cache-Control。

示例

解析一个请求

动手试试

把文字切成请求行、头部和正文。

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"])
逐步执行
  1. L2空行把头部和正文分开。
  2. L4第一行有三部分。
  3. L8头部名称不区分大小写,所以统一存成小写。
  4. L10partition 在 ? 处把路径和查询分开。GET /products page=2

构造一个响应

动手试试

响应的形状一样:状态行、头部、空行、正文。

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"}))
逐步执行
  1. L5把字典变成 JSON 文字。
  2. L8告诉客户端正文有多少字节。
  3. L10状态行、两个头部、一个空行,然后是 JSON。HTTP/1.1 404 Not Found

常见错误

返回 200 却在里面报错

返回 200 却在正文里写 {"error": …} 的 API,会让相信状态码的客户端、缓存和监控都出错。失败就用 4xx 或 5xx。

401 和 403 分不清

401:不知道你是谁(请登录)。403:知道你是谁,但你没有权限。

小测验

1. 一个页面永久搬到了新网址。哪个状态码和头部告诉浏览器去哪里?

  1. A301 加上 Location
  2. B404 加上 Host
  3. C500 加上 Content-Type
查看答案

答案:A

3xx 表示「去别处」,Location 说明去哪里。

2. 这段代码会输出什么?

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
查看答案

答案:A

只看第一位:2、5、4。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出读一个响应

    准确写出这段程序的输出。

  2. 改需求头部不区分大小写

    get_header(raw, name) 应该返回某个头部的值,没有就返回 None。当请求里写的是 content-type、而你查的是 Content-Type 时,它就找不到。改成比较名称时不区分大小写。

  3. 从零写解析查询字符串

    写函数 parse_query(target):参数是 /search?q=lamp&page=2 这样的请求目标,返回查询参数组成的字典:{"q": "lamp", "page": "2"}。没有 ? 就返回空字典。(不要用 urllib。)

在互动课里练这一课

打开互动课,这些例子都能直接改、直接运行,练习题提交马上判题。免费开始,不用注册。

互动课按顺序解锁:前面的课还没学,会先带你到课程地图。

本课术语

术语中文英文定义
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.