方法名是对资源的意图声明,不是对服务器的命令。 把它当命令用,就会写出「GET 里删数据」这种埋雷代码。
上一节列了八个方法。 这一节说清「意图」和「命令」的分别,以及为什么这个分别决定了缓存、重试、爬虫能不能工作。
一、为什么叫「方法」不叫「命令」
「命令」意味着「你去做」,主语是服务器;「方法」意味着「我想对这个资源这样」,主语是资源。
这个区别不是文字游戏。
GET /users/1 说的是「我想要 users/1 的表示」,至于服务器是从数据库取、从缓存取、
还是现场拼一个 JSON,客户端不关心也不该关心。
一旦把它当命令,就会出现「GET /delete?id=1」这种写法——语义上它是个读操作,实际上在删数据。
后果很具体:预取、爬虫、浏览器预加载、代理重试,都会去访问它,数据就没了。
二、幂等性为什么能救命
网络不可靠,客户端不知道响应丢没丢。所以「重试」是必需的能力。但重试安全的前提是操作幂等。
Demo 里 GET 发十次计数仍是 0,POST 发十次涨到 10。
如果客户端在超时后自动重试 POST,用户就下了十次单。
所以工程上的三条规矩:
GET/HEAD/PUT/DELETE 可以自动重试;POST/PATCH 不能自动重试,要带上幂等键
(比如一个 Idempotency-Key 首部,服务端记住处理过的键)。这条是支付类系统的标配。
三、缓存、爬虫、代理都靠方法判断
缓存只看方法:GET/HEAD 的响应可以缓存,POST 的响应默认不缓存。
所以把读操作写成 POST,等于主动放弃所有缓存层。
爬虫也只看方法:它顺着链接爬,用的全是 GET。
如果你的「删除」链接是个 <a href>,搜索引擎爬一遍,数据全没。
PUT 的重试之所以安全,是因为它的语义是「置为这个样子」——你发十次 PUT {"n": 1},
结果永远是一样。
四、动手 Demo:幂等性实测:十次 GET 与十次 POST
服务端维护一个计数器:GET 只读不改,POST 每次加一。各发十次,把每次响应正文都打出来。
hits = {u'GET': 0, u'POST': 0}
def resp(raw, st):
first, hd, body = H.parse_headers(raw)
m = first.split(u' ')[0]
if m != u'GET':
hits[m] = hits.get(m, 0) + 1
return (u'HTTP/1.1 200 OK', [(u'Content-Type', u'text/plain')],
(u'count=%d' % hits.get(m, 0)).encode())
实跑输出:
--------------------------------------------------------------------
幂等:重复发十次,看状态变不变
--------------------------------------------------------------------
GET 十次的结果 count=0 / count=0 / count=0 / count=0 / count=0 / count=0 / count=0 / count=0 / count=0 / count=0
POST 十次的结果 count=1 / count=2 / count=3 / count=4 / count=5 / count=6 / count=7 / count=8 / count=9 / count=10
GET 最终计数 0
POST 最终计数 10
GET 发十次,状态一点没变——**幂等**。
POST 发十次,计数涨到 10——**非幂等**,重复提交会重复生效。
这条差别直接决定了刷新页面会不会重复下单:
浏览器刷新 POST 页面会弹「确认重新提交」,刷新 GET 页面不会。
工程做法是 POST 之后 303 重定向到 GET 页面(Post/Redirect/Get)。
GET 十次全是 count=0——读操作不改变状态,所以幂等。
POST 十次从 count=1 涨到 count=10——每执行一次就多一次副作用,所以不幂等。
这就是「自动重试」这条策略能不能用的唯一判据。
写客户端时只要多问一句「重发一次会不会出事」,答案就出来了。
30 秒实验
- 改成
PUT,让它把计数置为 5(而不是加一),发十次,看结果是不是恒为 5。 - 给 POST 加一个
Idempotency-Key首部,服务端记住键, 同一个键第二次到达直接返回上次结果——这就是支付接口的标准做法。 - 想一想:
DELETE /users/1第二次返回 404,算不算幂等?
常见误区
- 「幂等=没有副作用」——DELETE 有副作用,但仍然幂等。
- 「GET 不能改数据,所以绝对安全」——只要你不写成 GET 删数据就行,协议不阻止你这么干。
- 「POST 也需要缓存」——按标准不缓存,要缓存得显式加首部,而且几乎没人这么干。
自测三问(点开对答案)
1. 为什么「GET 里删数据」特别危险? —— 因为预取、爬虫、代理重试、浏览器预加载都会自动访问 GET 链接,这些访问都不受用户控制。 2. 非幂等操作要怎么安全重试? —— 带幂等键:客户端生成唯一键,服务端记住已处理的键,重复请求直接回放结果。 3. 把读操作写成 POST 有什么代价? —— 放弃所有缓存层,放弃爬虫收录,还要自己处理重试。方法说完。下一节回到连接:为什么 HTTP/1.1 不再用完就关。
- ☐ (本节完,下一节 2.7.1 持久连接)