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"])- L2空行把头部和正文分开。
- L4第一行有三部分。
- L8头部名称不区分大小写,所以统一存成小写。
- 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"}))- L5把字典变成 JSON 文字。
- L8告诉客户端正文有多少字节。
- L10状态行、两个头部、一个空行,然后是 JSON。
HTTP/1.1 404 Not Found
常见错误
返回 200 却在里面报错
返回 200 却在正文里写 {"error": …} 的 API,会让相信状态码的客户端、缓存和监控都出错。失败就用 4xx 或 5xx。
401 和 403 分不清
401:不知道你是谁(请登录)。403:知道你是谁,但你没有权限。
小测验
1. 一个页面永久搬到了新网址。哪个状态码和头部告诉浏览器去哪里?
查看答案
答案: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))查看答案
答案:A
只看第一位:2、5、4。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出读一个响应
准确写出这段程序的输出。
- 改需求头部不区分大小写
get_header(raw, name)应该返回某个头部的值,没有就返回None。当请求里写的是content-type、而你查的是Content-Type时,它就找不到。改成比较名称时不区分大小写。 - 从零写解析查询字符串
写函数
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 header | HTTP 头部 | A Name: value line with extra information about a request or response. |