你用过的每个应用,说到底都是两台电脑在互相递条子。HTTP 就是条子的格式,它简单到可以直接肉眼读。这一页讲清四件事:请求与响应模型、状态码到底在告诉你什么(401 和 403 的区别,一次说清)、不带教条的 REST,还有 CORS 报错的真相。
🎙️ 发布并录制于: ·
整个 HTTP 就是一次交换:客户端发一个 request,也就是一张有格式的条子;服务端回一个 response,另一张条子。然后是大多数人忽略的那一步,服务端就把你忘了。HTTP 是无状态的。你体验过的每一种「已登录」,都是靠每张条子上重新贴一次身份证做出来的,这张身份证就是 cookie 或者 token。
## the request — literally this readable:
GET /users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbG...
## the response:
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 42, "name": "Ada"}
URL 是一个地址,有四个真正干活的部分:协议、楼(host)、房间(path),还有门上贴的便签(query 参数)。学会读它,API 文档就不再吓人了。
https://api.example.com/users/42/orders?status=paid&limit=10
└─┬──┘ └──────┬──────┘└──────┬──────┘ └────────┬─────────┘
scheme host path query params
(how) (which server) (which thing) (options: key=value, &-separated)
?api_key=sk_live_... 这种 key,你就当它已经泄露了。密钥要走请求头(第 07 节),请求头默认不会被写进日志。
method 告诉服务端这是哪一类条子。五个就够用了:GET 读,POST 建,PUT 整体替换,PATCH 改一部分,DELETE 删。它们背后真正值得弄懂的概念是幂等性:同一个请求发两遍安全吗?
GET /users/42 # read — never changes anything
POST /users # create a new one ⚠ twice = two users
PUT /users/42 # replace entirely — twice = same result
PATCH /users/42 # change some fields
DELETE /users/42 # delete — twice = still deleted
Idempotency-Key 请求头的原因:重试过去的请求只会产生一笔扣款,不是两笔。POST 发出去一半网络超时,「到底成没成」这个问题,就是这个词存在的全部理由。
第一位数字就是全部剧情:2xx 成了,3xx 搬走了,4xx 是你错了,5xx 是他们错了。就这一句话,能把你以后遇到的每个 API 报错分成两堆:「改我的请求」和「等一等或者去报障」。还有那对千古纠缠的兄弟,这里一次说清:
200 OK # here you go
201 Created # made it (answer to a good POST)
301 Moved # new address, go there
400 Bad Request # your note is malformed (bad JSON, missing field)
401 Unauthorized # WHO ARE YOU? — no/invalid credentials
403 Forbidden # I know who you are. No. — valid login, no rights
404 Not Found # no such thing at this path
429 Too Many Requests # slow down (rate limit — wait and retry)
500 Internal Error # server crashed. not your fault
503 Unavailable # server overloaded/down. also not your fault
Unauthorized 表达的其实是未认证,一个五十年前的命名事故,所有人都将就着用到今天。
header 是写在信封上的元信息:什么格式、什么语言、谁在问、拿的是什么凭证。body 是信封里那张信,对 API 来说基本都是 JSON。你会反复设置两个请求头,再反复读第三个:
Content-Type: application/json # "the body I'm SENDING is JSON"
Authorization: Bearer <token> # "here's my ID card"
Accept: application/json # "please REPLY in JSON"
# the body (POST /users):
{ "name": "Ada", "country": "UK" }
400 Bad Request,或者一个字段全是 null 的怪结果。因为你忘了写 Content-Type: application/json,服务端把你的 JSON 当表单提交解析了。body 没错,是信封上的标签错了。一个请求头,解释了 Stack Overflow 上十年的提问。
curl 从命令行发 HTTP 请求。它的用处是把你自己的应用挪开去测 API,这也是最好用的一招排查:curl 通、代码不通,bug 在你的代码里;curl 也不通,问题在 API 那边。分而治之。
# read
curl https://api.example.com/users/42
# create (-X method, -H header, -d body)
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name": "Ada"}'
# see the STATUS CODE and headers too (the debugging view)
curl -i https://api.example.com/users/42
两种模式覆盖了大多数 API。API key 是给程序用的长期密码。bearer token 通常有效期很短,靠登录换来,用 Authorization: Bearer … 的形式出示。bearer 的字面意思是谁拿着它,谁就是它。这一个词既说明了安全模型,也就是那句安全警告。
# both ride in the Authorization header:
Authorization: Bearer eyJhbGciOi... # token (often a JWT)
x-api-key: sk_live_4242... # some APIs use a custom header
# keys live in environment variables, never in code:
curl -H "Authorization: Bearer $API_TOKEN" ...
REST 是一套命名约定,不是一种技术:URL 里放东西(名词),方法说要对它做什么(动词)。百分之九十就这些。一个 API 算不算 RESTful,标准是你能不看文档猜出下一个 endpoint,可预测性就是它的全部意义。
# nouns in the URL, verbs in the method:
GET /articles # list articles
POST /articles # create one
GET /articles/7 # read one
PATCH /articles/7 # edit it
DELETE /articles/7 # remove it
GET /articles/7/comments # nested: its comments
✗ POST /getArticleById ✗ GET /deleteUser?id=7 # verbs in URLs = smell
有一天你的前端去调一个 API,控制台开始尖叫。这里有个几乎人人搞错的地方:这个报错不是服务端拒绝了你,是你自己的浏览器在执行一条安全规则:来自站点 A 的 JavaScript 不能读站点 B 的响应,除非 B 明确允许,也就是回一个 Access-Control-Allow-Origin 响应头。证据很简单,同一个请求用 curl 发就完全正常。
Access to fetch at 'https://api.other.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present...
# decode: api.other.com didn't say localhost:3000 may read it.
# the browser fetched the data — then withheld it from your JS.
这几张表能回答百分之九十的 API 问题。
# methods
GET read · POST create (⚠ not retry-safe) · PUT replace
PATCH edit · DELETE remove
# status: first digit = who messed up
2xx ✓ · 3xx moved · 4xx you · 5xx them
401 who are you? → fix the token
403 you specifically? no. → fix the permissions
429 slow down → wait and retry
# the POST that "mysteriously" fails
→ did you send Content-Type: application/json ?
# debugging order
curl -i first. code second.
curl works + code fails → your bug
curl fails too → their bug (or your token)
# CORS error in console
→ browser rule, server must allow it. curl proves the API works.
# secrets
headers ✓ · env vars ✓ · URLs ✗ · git ✗ · frontend ✗
这就是能干活的核心。再花哨的东西,webhook、GraphQL、gRPC,都是同样两张条子的变体。等你想用代码去调 API,Python 加上这一页就是一套完整的工具。