零到全栈(读懂 HTTP,零依赖写一个 API)

上一篇我们亲手调通了两个真实的 API------用 curl 问 ipify 拿到自己的公网 IP,照着官方文档让 DeepSeek 回了一句话------也弄清了 API 的本质:一个程序对外提供能力的固定入口,按规范发请求、收 JSON,与内部用什么语言实现无关;Python 环境也随之备齐:venv 给项目隔出专属环境,pip 装好 requests 并用它在代码里调了一次 API,依赖记进了 requirements.txt;但到目前为止,我们一直站在调用方一侧;这一篇要换到被调用方------先看懂 HTTP 这套对话规范,再用纯标准库手搓出第一个属于自己的 API

看懂 HTTP,手搓第一个 API:

这一部分先不写代码------把 HTTP 请求和响应的规范看明白:一次网络对话,双方到底各说了什么、按什么格式说;然后再用 Python 把这套规范亲手实现出来(零依赖,纯标准库),用 curl、浏览器逐一验证;规范在前,代码在后------代码只是规范的一种实现

API 和 HTTP:

Python 已装好,API 是什么也知道了,这一部分就用 Python 手搓一个 API 出来

先回忆调用 API 的经历:用 curl 调过两个真实的 API;写过 api_demo.py,用 Python (requests) 调过一次 ipify;注意一个共同点:到目前为止,我们一直站在调用方这一边------发请求、收 JSON 的那一边

而我们的目标,是给自己的网站写一个 API;这意味着要换到另一边去:做那个 "被调用方"------"一直守着、接到请求、回一段 JSON" 的程序

动手之前要先搞清楚:这一来一回的网络对话里,双方传的到底是什么?调用方发来的 "请求" 长什么样?我们回的 "响应" 又该长什么样?这两样东西有明确的规范------这套规范就是 HTTP;之前提过:API 没有另起炉灶,直接用了浏览器上网用的那套规矩;所以要手搓 API,就绕不开 HTTP------要接住请求、按规矩回话,就得把这套规矩本体看清楚;这一部分的主要知识点,其实是 HTTP

动身之前,先把 HTTP 和 API 这两个词的关系摆正:

  • API 是一个程序对外提供能力的入口------而这个入口上的对话协议用的是 HTTP

  • HTTP 是一套通信规范,管的是 "两个程序之间怎么对话":对话是什么格式、有哪几种问法、怎么表示成功和失败;它不是为 API 发明的:浏览器加载的每一个网页、每一张图片,走的都是 HTTP(包括此前用 Nginx 返回 html 页面)

也就是说,我们其实已经用过很多次 HTTP 了,只是从来没深入看一眼全貌:

  • 用浏览器发起网页请求时,页面上显示的只是正文,其余信息隐藏着,Chrome 上要用 F12 才能看到

  • 在终端用 curl 向 API 发请求时,终端默认只打印响应的 "正文",其余全被它省略了

  • 用 Python 的 requests 发请求也一样,resp.json() 拿到的只是正文,并非完整的 HTTP 信息

下面就看看 HTTP 请求、响应信息的全貌

HTTP 的一去一回:请求和响应

HTTP 规定的一次对话,就是一去一回、两段有固定格式的文本:调用方发过去的那段叫请求 (request),服务方回过来的那段叫响应 (response);两段文本的结构几乎对称:

复制代码
请求(去)                 响应(回)
├─ 请求行                 ├─ 状态行
├─ 请求头(若干行)         ├─ 响应头(若干行)
├─ (一个空行)            ├─ (一个空行)
└─ 请求体                 └─ 响应体

先看请求这一侧的四个部分:

请求行------整个请求的第一行,永远只有一行,说清三件事:方法、路径、协议版本;方法:这次是来干什么的(GET 是 "取数据",POST 是 "提交内容"------认过脸的那两位,稍后还有一张脸谱表);路径:要访问对方的哪个资源;协议版本:先不用管,稍后交代

请求头 ------若干行 "附加说明",一行一条,格式统一是 名字: 值:"我要找哪台服务器" "我是谁" "我提交的内容是什么格式"......都写在这里

一个空行------分界线,意思是 "头说完了,下面是体"

请求体------真正要提交的内容;不是必须有,取数据的请求一般就没有体

再看响应这一侧;它的四部分------状态行、响应头、空行、响应体------和请求几乎对称,唯一的结构差别在第一行:请求的第一行叫请求行,响应的第一行叫状态行

状态行------一行说清三件事:协议版本、状态码、简短说明;最重要的是中间那个状态码:一个数字,表态 "这次处理得怎么样",后面跟着一句人类友好的简短说明(OK、Not Found);200 是成功;404 是没找到------这个数字之前见得够多了,那时是 Nginx 替我们回的;记个家族规律就行:2xx 成功,4xx 请求方的问题,5xx 服务方的问题

响应头------同样一行一条的 "附加说明";最重要的一条是 Content-Type:我给你的这段内容是什么格式------application/json 就是在说 "按 JSON 来解析它";它有多大威力,一会儿动手实验

空行 + 响应体 ------和请求侧一样:空行分界,体是正文。我们平时在终端、在浏览器页面上 "看到" 的,基本都只是响应体------那行 {"ip": ...} 是响应体,DeepSeek 回的那段 choices JSON 也是;它们上面顶着的状态行和一排头,当时全被工具省略了

把一去一回摆在一起,规律就出来了:

复制代码
请求 = 请求行 + 头 + 空行 + 体;响应 = 状态行 + 头 + 空行 + 体

格式对称,全是纯文本------我们和任何服务器之间的每一次对话,本质就是这样两段文本的往返

用 curl -v 验证:

到这里,上面说的一切都还只是 "纸面上的说法";空口无凭------curl 有一个 -v 参数(verbose,"把过程全说出来"),能把一次调用的请求原文、响应原文全部亮出来

把查 IP 那条命令加上 -v,再跑一次:

bash 复制代码
curl -v "https://api.ipify.org?format=json"

这回输出多了一大截;先学会认行首的三种记号:

  • * 开头:curl 的过程旁白------建立连接、加密握手之类,全部跳过

  • > 开头:发出去的请求原文

  • < 开头:收到的响应原文

把 * 的行掠过去,剩下的就是一次完整的 "一去一回"(具体的值每个人会不同):

bash 复制代码
> GET /?format=json HTTP/2          ← 请求行:方法 + 路径 + 协议版本
> Host: api.ipify.org               ← 请求头,从这行开始
> User-Agent: curl/8.7.1
> Accept: */*
>                                   ← 空行:请求头完;注意:GET 没有请求体
< HTTP/2 200                        ← 状态行:协议版本 + 状态码
< date: Mon, 13 Jul 2026 05:38:00 GMT
< content-type: application/json    ← 响应头:内容是 JSON 格式
< content-length: 22
< server: cloudflare
(还有几条,略)
<                                   ← 空行:响应头完
{"ip":"114.86.123.45"}              ← 响应体------之前看到的,只有这一行

看到了吧:> 那段是请求行、头、空行;< 那段是状态行、头、空行、体------理论里的每个部件都能逐行指认出来;几个需要了解的细节:

  • 请求行里方法是 GET(取数据);路径是 URL 去掉协议和域名之后剩下的那段------? 后面的 format=json 叫查询参数,跟在路径后面(还记得为什么给网址加引号吗?防的就是这个 ? 被终端误解)

  • 三行请求头:Host------要找哪台服务器;User-Agent(常简称 UA)------我是谁,用什么工具、什么浏览器发的这个请求;Accept------我能接受什么格式的回应;注意:这条命令里我们一个头都没写,它们全是 curl 自动带上的

两个小注:这里看到的版本多半是 HTTP/2,它和老一些的 HTTP/1.1 在显示上有两处小差别------头的名字统一小写、状态行状态码后面不带 OK 那句简短说明,其余一模一样;另外如果电脑开着网络代理,< 里可能先冒出一行 HTTP/1.1 200 Connection established------那是代理隧道的痕迹,跳过它

再验证一个带请求体的;给调用 DeepSeek 的那条长命令也加上 -v 跑一次(key 用自己的;如果 key 已经删了,对照下面的输出看就行);这次 > 的部分是:

bash 复制代码
> POST /chat/completions HTTP/2     ← 请求行:方法换成了 POST
> Host: api.deepseek.com            ← URL 拆出来的
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/json    ← 我们用 -H 写的那行,原样成为一行请求头
> Authorization: Bearer sk-****     ← 我们用 -H 写的另一行(身份钥匙)
> Content-Length: 333               ← curl 自动算好:请求体有多长
>                                   ← 空行:头到此为止

咦,说好的请求体呢?-v 不回显请求体,但紧跟着有一句旁白说明了请求体已经发送:

bash 复制代码
* upload completely sent off: 333 bytes

说的就是它------我们用 -d 写的那段 JSON,此刻已经作为请求体发了出去,长度正好是 Content-Length 说的那个数一一对应:URL 拆成 Host + 路径;-H 添的是请求头;-d 填的是请求体;那条看起来吓人的长命令,不过是在拼一段规范文本------现在亲眼看到它拼出来的样子了

关于方法,两个可能冒出来的疑问:

疑问一:我从头到尾没说过 "用 GET" 或 "用 POST",是谁决定的?

是 curl 替我们决定的,规则很简单:默认发 GET;一旦用了 -d(有请求体要提交),自动改发 POST;查 IP 那条没有 -d,所以是 GET;DeepSeek 那条带着 -d,所以是 POST------证据刚刚都在 -v 里看过了:两个请求行的第一个词

疑问二:调 DeepSeek 我也 "取回了数据" 啊------凭什么它算 POST,而不算 GET,或者 "POST + GET"?

因为 GET / POST 描述的不是 "数据往哪边流";看刚才的报文就明白了:无论方法是什么,一次调用永远是完整的一来一回------GET 也发出去了一段请求文本,POST 也收回来了一段响应文本;响应从来都有,它不需要、也不由方法来 "申请"

方法描述的只有一件事:这次请求的意图

  • GET:"把某样东西给我"------通常不带请求体

  • POST:"我提交一段内容,请你处理"------内容就放在请求体里

所以调 DeepSeek 是一次 POST:我们的意图是提交一段对话让它处理;它回来的那段 choices JSON,是这次 POST 的响应,而不是另一次 GET

顺便把账记到正确的科目上:GET、POST 是 HTTP 的方法,不是 API 的发明------浏览器打开一个网页,发的就是 GET;在网页上提交一张表单,发的往往就是 POST;API 只是沿用了这套问法

常见的方法、常见的头:

到这里我们认识了两个方法和七八个头,HTTP 定义的不止这些;下面几张表把常见的列出来------不需要记住,大致看一下建立直觉就行:以后在真实报文里撞见,能大致猜到它在说什么

方法就是 "请求的意图",这张表其实就是几种常见的意图:

方法 意图(一句话直觉)
GET 把某样东西给我
POST 我提交一段内容,请你处理
PUT 用我给的内容,把某样东西整个换掉
PATCH 把某样东西改一部分
DELETE 把某样东西删掉
HEAD 跟 GET 一样,但只要头、不要体------探路用
OPTIONS 询问:我能对这个资源做什么?

常见的请求头------调用方的自我交代:

请求头 在说什么
Host 我要找哪台服务器
User-Agent 我是谁(什么工具、什么浏览器)
Accept 我能接受什么格式的回应
Accept-Language 我偏好什么语言
Content-Type 我提交的请求体是什么格式
Content-Length 我提交的请求体有多长
Authorization 我的身份凭证(那个 Bearer sk-... 就放这儿)
Cookie 我随身带的 "小纸条"

常见的响应头------服务方对内容的交代:

响应头 在说什么
Content-Type 我回的体是什么格式(本部分的主角)
Content-Length 我回的体有多长
Server 我是什么服务器软件
Date 我是什么时间处理的
Cache-Control 这份内容可以缓存、能存多久
Set-Cookie 给调用方发一张 "小纸条",下次来记得带上
Location 内容搬家了,去这个新地址找(配合 3xx 跳转用)
Access-Control-Allow-Origin 允许哪些来源的网页调用我

这些还不是 HTTP 完整的清单,但日常开发了解这些就够了;以后遇到陌生的头,先查再用

认识 HTTP/1.1 和 HTTP/2:

报文里反复出现的 HTTP/1.1、HTTP/2 是协议的版本号,其实还有个更新的 HTTP/3;它们有一些小差异,但差别不大:方法、路径、状态码、头、体这套语义都一样------一般不需要管,知道这件事就行

手搓 API 要照顾到什么?

做一个 API,就是做那个 "接话、回话" 的程序;但规范里的部件这么多,哪些是必须处理的、缺了就不 work 的呢?

请求这一侧:

  • 请求行里的方法和路径,每个请求都必带,我们作为 API 的提供者也必须读取请求行------不看方法,就分不清对方是来取数据还是来提交内容;不看路径,就分不清对方要访问哪个资源,因为不同的路径得给不同的回应

  • 请求头那一排,一般的请求都会带;这些信息可读可不读,建议按需读取------需要用到哪条再读哪条

响应这一侧:

  • 状态行------必须返回;不回状态码,这就不是一段 HTTP 响应,调用方会直接报错

  • Content-Type 响应头------严格说规范允许省略,但省了调用方就只能猜我们回的是什么格式------实践里按必写对待

  • 那个空行------必须;它是 "头" 和 "体" 之间唯一的分界线;漏了它,调用方会把正文误当成头来解析,全盘皆乱

  • 响应体------真正要给对方的内容;规范上可以没有体,但做 API 回数据,它就是主角(我们的 JSON 就放这儿)

从这个角度看,手搓一个 API 要处理的事还不少------HTTP 规范的每一样都得亲手写;好在 Python 标准库里有一个专门处理 HTTP 的模块:http.server;接下来就用它,把这份清单逐项落实

用 Python 实现这套规范:

我们要实现的接口,就是以后前端真的会来调用的那一个:GET /api/profile,返回主页要显示的内容(现在这些内容写死在前端 site.js 里);先挑 site.js 里 home 的前两个字段意思一下就行------现在的重点是 HTTP,不是数据本身;等前端真的来调这个接口时,再把返回的结构和 home 完整对齐

回到建好的 ~/zero-to-tech/backend/,照例先激活环境(虽然 http.server 是标准库成员、不激活也能跑,但 "进项目先激活" 这个习惯值得从现在养成):

bash 复制代码
source .venv/bin/activate

然后新建 main.py:

python 复制代码
from http.server import BaseHTTPRequestHandler, HTTPServer
import json

profile = {
    "heroTitle": "关于我",
    "heroSubtitle": "项目,创意,灵感,心得,我的作品",
}

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/api/profile":
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            body = json.dumps(profile, ensure_ascii=False)  # ensure_ascii=False:让中文原样输出
            self.wfile.write(body.encode("utf-8"))
        else:
            self.send_response(404)
            self.end_headers()

print("后端已启动:http://localhost:8000/api/profile")
HTTPServer(("", 8000), Handler).serve_forever()

对着刚才的规范,逐行看这段代码分别实现了规范的哪一部分:

代码 对应规范里的
def do_GET(self): 请求行里的方法------方法是 GET 的请求,归这个函数管
self.path 请求行里的路径------拿它判断对方要访问哪个资源
self.send_response(200) 响应的状态行------回一个 200
self.send_header(...) 响应头------一行一条,我们写了 Content-Type 这一条
self.end_headers() 那个空行------"头写完了",头和体的分界线
self.wfile.write(...) 响应体------我们的 JSON(encode 是因为网络上传输的是字节,文本要先编码)
else 分支的 404 状态码 404------没找到;以前是 Nginx 替我们回,现在轮到我们自己回

规范里的每一个部件都能在代码里找到------这段代码没干别的,就是老老实实按 HTTP 规范 "接话、回话"

再对照那份 "缺了就不 work" 的清单,把必须项在代码里点一遍名:

  • self.send_response(200)------那条必须的状态行;不写它,回出去的就不是一段 HTTP 响应

  • self.end_headers()------那个必须的空行;名字里带着 headers,干的活其实是 "头到此为止"------漏了它,调用方会把正文误当成头

  • self.send_header("Content-Type", ...)------那条按必写对待的头,告诉调用方 "这是 JSON"

这三行不是可有可无的样板:前两行属于 "硬要求",删掉其中任何一行,curl 那头直接报错,因为收到的根本不是一段合法的 HTTP 响应;Content-Type 那行删掉倒是还能跑通,但调用方就只能猜我们回的是什么格式了------"规范必须" 和 "实践必写" 的区别,落到了代码上(404 分支里同样是先 send_response 再 end_headers,硬要求一样不能省,只是没有 "体")

最后一行代码里还有个数字值得交代:

python 复制代码
HTTPServer(("", 8000), Handler).serve_forever()

这是指定 http 服务在 8000 端口上启动;回想讲过的:IP 找到机器,端口找到机器上的某个程序------一台电脑可以同时跑很多网络程序,各守各的端口;80(HTTP)和 443(HTTPS)是网页的默认端口(地址栏里不写端口号时走的就是它们),一般留给正式服务;开发时挑一个没被占用的端口就行;8000 是 Python 圈的习惯值(python3 -m http.server 默认就用它),就像前端圈 Vite 爱用 5173、Next 爱用 3000------都只是习惯,不是规定:改成 9000 也照样跑,只是调用时的地址要跟着变

跑起来:

bash 复制代码
python3 main.py

终端打印 "后端已启动",然后------光标停住不动了;别慌,这不是卡死:还记得吗,后端是一个 "一直运行、守着等请求" 的程序;现在它真的出现在终端里了:serve_forever() 就是字面意思------守在 8000 端口,永远等着;想停掉它,按 Ctrl + C

换一个新的终端窗口(旧的那个正跑着服务呢),调用它:

bash 复制代码
curl http://localhost:8000/api/profile

回来一段 JSON;再用浏览器打开 http://localhost:8000/api/profile------同样的 JSON

成了:我们写出了自己的第一个 API;之前对 ipify、对 DeepSeek 做的事,现在别人也可以对我们做了

这时候回头看一眼跑着服务的那个终端,多了几行东西:

bash 复制代码
127.0.0.1 - - [20/Sep/2026 15:42:10] "GET /api/profile HTTP/1.1" 200 -
127.0.0.1 - - [20/Sep/2026 15:42:31] "GET /api/profile HTTP/1.1" 200 -
127.0.0.1 - - [20/Sep/2026 15:42:31] "GET /favicon.ico HTTP/1.1" 404 -

每来一个请求记一行,这就是服务的访问日志;注意那行 favicon.ico:我们并没有请求它,是浏览器自动多发了一个请求想要标签页小图标,我们没有,它吃了个 404------凭着请求里带的信息,服务端可以把每个来访者的动作看得清清楚楚

再来一次 curl -v:这回是自己服务器的报文

现在做一次漂亮的闭环:前面用 -v 验证过和 ipify、DeepSeek 之间的对话,现在对自己刚写出来的服务器来一次:

bash 复制代码
curl -v http://localhost:8000/api/profile

还是那三种记号------> 请求原文、< 响应原文、* 旁白:

bash 复制代码
> GET /api/profile HTTP/1.1        ← 请求行!
> Host: localhost:8000             ← 请求头
> User-Agent: curl/8.7.1
> Accept: */*
>                                  ← 空行,头结束
< HTTP/1.0 200 OK                  ← 状态行!
< Content-Type: application/json   ← 我们写的那行头,躺在这
<
{"heroTitle": "关于我", ...}        ← 响应体

和前面 ipify 的那两段逐行对得上------只不过这一回,< 那一段的每一行都是我们自己的代码生成的;规范 → 代码 → 真实报文,三点连成一线

小字注两条:响应第一行是 HTTP/1.0 200 OK------我们这个极简服务器用的老版本协议,状态码后面带着那句简短说明,就是前面说过的样子;响应头里还多了 Server、Date 两条我们没写的------是 http.server 自动替我们加的,服务器顺带做了自我介绍

浏览器视角:F12 里的同一份报文

Chrome 打开 http://localhost:8000/api/profile,按 F12 → Network 面板 → 刷新 → 点开那条请求:

  • General:Request URL、Request Method: GET、Status Code: 200

  • Response Headers:我们 send_header 写的那两行,原样躺着

  • Request Headers:浏览器发出的请求头------一会儿的实验里细看

F12 的 Network 之前用过,那时看加载顺序;今天点进单个请求的内部------curl -v 看到的和这里看到的,是同一套东西的两个视角

动手改两处,做两个实验:

前面说过,Content-Type 不是 "规范必须" 但是 "实践必写"------那就做个小实验,看看为什么;另外还说过服务端可以把来访者看得清清楚楚,也通过实验看一下;打开 main.py,一次改两处

改动一:临时加一个 /hello 路径,写到 else 那一行的上面:

python 复制代码
        elif self.path == "/hello":
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")   # ← 一会儿改成 text/plain 再试
            self.end_headers()
            self.wfile.write("<h1>你好,HTTP</h1>".encode("utf-8"))

后面那截 charset=utf-8 是给浏览器多交代一句:内容按 UTF-8 解码------响应体里有中文,不交代这一句,浏览器可能猜错编码,页面上就是乱码,又一个 "头是关于内容的说明" 的例子

改动二:在 do_GET 的开头加两行打印(给实验二用):

python 复制代码
    def do_GET(self):
        print(self.headers)          # 收到的请求头
        print(self.client_address)   # 请求是从哪个地址来的
        ...

改完,重启服务(Ctrl + C 再 python3 main.py),两个实验连着做

实验一:响应头的威力

规范里说 Content-Type 是 "告诉对方内容是什么格式";空口无凭------浏览器打开 http://localhost:8000/hello:一个大标题,浏览器把内容当网页渲染了

现在把代码里的 text/html 改成 text/plain,重启,刷新------变成了原样的一行字,<h1> 标签直接露了出来

内容一个字没变,头一变,对方的处理方式就变了:响应头不是内容本身,而是 "关于内容的说明"------application/json 同理,它让调用方知道该按 JSON 解析响应体

所以也可以说,在浏览器眼里,"网页" 和 "API 数据" 并没有本质区别------都是一段 HTTP 响应,差别只在 Content-Type:text/html 就当网页渲染,application/json 就当数据处理;所谓 "做 API",从 HTTP 的角度看,不过是选择返回 JSON 而不是返回 HTML

实验二:服务端能拿到什么?

响应这一侧摸透了,回头看请求这一侧------刚才加的那两行 print 派上用场

先用 curl 调用一次 /api/profile,看服务端终端打印了什么:

bash 复制代码
Host: localhost:8000
User-Agent: curl/8.7.1
Accept: */*

再用浏览器访问一次:

bash 复制代码
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...
Accept: text/html,application/xhtml+xml,...
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate
...

一大串,但请注意两件事:

  • 这些头我们一行都没写过------是 curl、浏览器自动带上的;User-Agent 就是 "我是谁" 的自我介绍:curl 老老实实报名字和版本,浏览器报出一长串型号;服务端一眼就能分辨请求是浏览器来的还是程序来的

  • 这正是规范图里 "请求头" 那一段------在 F12 的 Request Headers 里看到的,就是它们出发前的样子;现在我们站在服务端,看到了它们被接收

另外,之前写过 api_demo.py,当时用它请求 ipify 的 API;也可以试一下用它来请求我们自己的 http://localhost:8000/api/profile,观察服务端拿到了什么样的 User-Agent

改动二还有第二行 print(self.client_address),打出来的是类似 ('127.0.0.1', 54321) 的一对值------发起这次请求的 IP 和端口;本机访问本机,所以是 127.0.0.1;要是别的机器来调用,这里就是对方的 IP;也就是说,服务端天然看得见每个请求从哪个地址来------ipify 能报出我们的公网 IP,根源就在这

一句边界感:

服务端天然能看到 UA、IP、语言偏好这些元信息------这是很多功能的地基(访问统计、防刷、按语言返回内容),也是一个提醒:我们在网络上发出的每个请求,都比自己以为的更 "透明"------这也是为什么不要在不信任的网站上乱发请求

(两个实验做完:把 /hello 那段删掉,保持 main.py 干净;那两行 print 留着也无妨------这份手搓版接下来就会存档成纪念版)

数数我们干了多少杂活:

最后清点一下劳动;为了按规范 "把一段 JSON 发出去",我们亲手做了:

  • 路由:if / elif 自己判断路径,还得记着写 else 兜底 404

  • 状态行:每个分支自己 send_response

  • 响应头:一行一行自己 send_header------一个接口两行,十个接口二十行

  • 空行:连 "头结束" 都要自己 end_headers

  • 响应体:自己 dumps、自己 encode

  • 而这才一个接口,还只是 GET------要是 POST,还得自己从请求体里读字节、解析 JSON、校验字段全不全......

一个接口尚且如此,真实项目几十个接口,这么写下去是要出人命的

好消息是:这些杂活不属于任何一个具体项目------它们属于 HTTP 规范,谁写后端都得来一遍。无论用什么语言写后端------Python、JavaScript、Go------写的都是对同一套规范的实现,所以 API 的概念才和语言无关

一模一样的事,就有人打包做好了给大家复用,打包好的那个东西叫框架;接下来就让 FastAPI 登场:我们会看到这一节的全部杂活在框架里缩成几行------而正因为亲手按规范搓过一遍,我们会确切地知道它替我们干了什么(框架会把状态码和头都藏起来,但它们一直都在------我们已经摸过一遍,以后想看,curl -v 和 F12 里随时都在)

相关推荐
不是株7 小时前
零到全栈(认识数据库:选型、SQL 与注入防御)
全栈
SL_staff1 天前
物模型是设备的数字身份证:属性、事件、服务如何决定IoT系统扩展性
java·物联网·全栈
SL_staff1 天前
JVS私有化交付为何敢承诺100%源码开放与无兜底风险?
java·低代码·全栈
濮水大叔2 天前
Cabloy全栈框架的两个SSR入口:Vona集成式SSR vs Zova独立式SSR
typescript·node.js·全栈
sweet丶2 天前
为什么SIGKILL崩溃无法捕获?
全栈
SL_staff2 天前
城商行营销翻车复盘:JVS-Rules 如何用工程化机制保障规则变更的可溯性与稳定性
java·开源·全栈
狂师2 天前
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
程序员·aigc·全栈
Karl_wei2 天前
AI时代,程序员的技能都有哪些变化
openai·ai编程·全栈