一次访问发生了什么:请求与响应
到第 27 节为止,任务管理程序只在本地读写 tasks.json:任务来自你自己的文件,也只保存在你的文件里。要让任务来自远端——比如一台服务器上维护的任务列表——先得看清一次「上网」到底发生了什么。
本节不写代码。用一条命令把本机变成服务器,再用浏览器和命令行各访问一次,从两边看到同一个请求。
本节结束时,你能说出一次访问里客户端发了哪四样东西、服务器回了哪三样,并能从服务器日志里认出自己刚发出的请求。
本篇的命令统一写 py,macOS / Linux 换成 python3。
一次访问就是一次对话
打开一个网页,背后是一次请求加一次响应:
- 客户端是发出请求的一方。浏览器是客户端,你在后面几节写的 Python 程序也是客户端。
- 服务器是回应请求的一方。它在某台电脑上等着,收到请求就回一条响应。
请求固定带四样东西:
| 部分 | 例子 | 说明 |
|---|---|---|
| 方法 | GET |
想做什么:GET 取数据,POST 提交数据 |
| 地址 | http://127.0.0.1:8000/tasks.json |
找谁 |
| 请求头 | User-Agent: curl/7.55.1 |
附带的说明,一行一条 |
| 请求体 | 提交的数据 | 可以没有;GET 通常没有 |
响应固定带三样东西:状态码(这次成没成)、响应头(附带的说明,一行一条)、响应体(要的数据)。后面每一节都围着这几样转。
把地址拆开
http://127.0.0.1:8000/tasks.json 从左到右是五段:
| 段 | 值 | 含义 |
|---|---|---|
| 协议 | http |
用什么规矩说话;加了加密的是 https |
| 主机 | 127.0.0.1 |
服务器的地址;这个地址固定表示本机 |
| 端口 | 8000 |
同一台机器上,不同服务用不同端口 |
| 路径 | /tasks.json |
要服务器上的哪个东西 |
| 查询 | 没有 | 以 ? 开头,附带的筛选条件,第 4 节讲 |
每台电脑都认识 127.0.0.1,它指向自己。所以你可以在这台电脑上跑一个服务器,同时用浏览器访问它,请求和响应都不出本机。
把本机变成服务器
Python 自带一个简单的文件服务器。先进入学习目录,再启动它:
cd python-foundations
py -m http.server 8000 --bind 127.0.0.1-m http.server 是让 Python 运行自带的 http.server 模块,8000 是端口,--bind 127.0.0.1 让它只接受本机的访问。运行后它不会退出,而是停在那里等请求:
Serving HTTP on 127.0.0.1 port 8000 (http://127.0.0.1:8000/) ...这个命令窗口现在是服务器视角:每收到一次请求,它就打印一行日志。收到请求之前,窗口里只有上面这一行。
服务器提供的是启动时所在目录的文件。所以第一步的 cd python-foundations 不是可选的——起错目录,浏览器里就会看到别的文件夹。
从浏览器访问
在浏览器地址栏输入:
http://127.0.0.1:8000/tasks.json浏览器显示 tasks.json 的内容。第 27 节生成过这个文件;学习目录里没有的话,新建一个,内容如下:
[
{
"id": 1,
"title": "学习 Python",
"done": false
}
]回到跑服务器的命令窗口,多了一行:
127.0.0.1 - - [07/Oct/2026 21:09:12] "GET /tasks.json HTTP/1.1" 200 -这行日志从左到右是:发出请求的地址、时间、请求行(方法 GET、路径 /tasks.json、协议版本 HTTP/1.1)、状态码 200。浏览器每次刷新、每次重新打开这个地址,都是一次新的请求,日志里就多一行。
换一个不存在的路径
把地址改成 http://127.0.0.1:8000/tasks.json.bak,浏览器显示「404 Not Found」。服务器窗口里这次是两行:
127.0.0.1 - - [07/Oct/2026 21:09:17] code 404, message File not found
127.0.0.1 - - [07/Oct/2026 21:09:17] "GET /tasks.json.bak HTTP/1.1" 404 -请求本身是发出去的,服务器也回话了,只是回的是「你要的东西我没有」——用状态码 404 表示。地址栏输错、文件名写错、服务器起在了别的目录,看到的都是它。
状态码第一位就够用
状态码是三位数,看第一位就知道大方向:
| 开头 | 含义 | 例子 |
|---|---|---|
| 2xx | 成功 | 200 正常返回 |
| 3xx | 东西在别处 | 301 目录少了结尾的斜杠,转过去 |
| 4xx | 请求这边有问题 | 404 路径不存在;401 没带密钥(第 7 节) |
| 5xx | 服务器那边出错 | 500 服务器自己的代码报错了 |
从命令行看完整的响应
浏览器只给你看响应体。想看状态码和响应头,用 curl——Windows、macOS、Linux 都自带这个命令。另开一个终端窗口(不要关掉服务器那个):
curl -i http://127.0.0.1:8000/tasks.jsonHTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.4
Date: Wed, 07 Oct 2026 13:09:12 GMT
Content-type: application/json
Content-Length: 73
Last-Modified: Wed, 07 Oct 2026 13:09:09 GMT
[
{
"id": 1,
"title": "学习 Python",
"done": false
}
]- 第一行是协议版本、状态码和一句说明。
- 空行之前是响应头:
Content-Type说明响应体是什么类型(这里是 JSON),Content-Length是响应体的字节数,Server留言用了哪个程序——Python/3.14.4是你电脑上的 Python 版本。 - 空行之后是响应体,也就是浏览器里看到的那段。
- 服务器回的是
HTTP/1.0——Python 这个自带服务器只实现了这个版本,字段的位置和含义都一样,不影响阅读。
把 -i 换成 -v,还能看到自己发出的请求头,> 开头是发出去的,< 开头是收到的:
> GET /tasks.json HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.55.1
> Accept: */*
< HTTP/1.0 200 OK
< Content-type: application/jsonUser-Agent 会写上是哪个客户端在发请求,版本号以你机器上的 curl 为准,每个人看到的不一样。
完整示例
- 在
python-foundations目录启动服务器:py -m http.server 8000 --bind 127.0.0.1。 - 浏览器打开
http://127.0.0.1:8000/tasks.json,确认看到了任务列表。 - 另开一个终端,执行
curl -i http://127.0.0.1:8000/tasks.json。 - 回到服务器窗口,日志里应该有对应的行。
预期结果
第 4 步的日志(时间和顺序按你的实际操作):
Serving HTTP on 127.0.0.1 port 8000 (http://127.0.0.1:8000/) ...
127.0.0.1 - - [07/Oct/2026 21:09:12] "GET /tasks.json HTTP/1.1" 200 -
127.0.0.1 - - [07/Oct/2026 21:09:12] "GET /tasks.json HTTP/1.1" 200 -两行对应两次访问:一次来自浏览器,一次来自 curl。服务器不看是谁发的,只看请求。
服务器窗口是「输出」不是「输入」:里面出现的行都不要复制到终端里执行。要停止服务器,按 Ctrl + C:
Keyboard interrupt received, exiting.常见错误
浏览器显示「无法访问此网站」或「拒绝了我们的连接请求」:服务器没在运行,或者已经被你关掉。看那个窗口里有没有 Serving HTTP on ...,没有就重新执行启动命令。
浏览器显示的是一个文件列表,但里面没有自己的文件:服务器起在了别的目录。http.server 提供的是启动时所在目录,按 Ctrl + C 停掉,cd python-foundations 之后重新启动。
启动后怎么访问都没有新日志:多半是之前那个服务器窗口还在跑。macOS / Linux 上重复占用端口会直接报 Address already in use,起不来;Windows 上更隐蔽——第二个服务器照样显示 Serving HTTP on ...,但请求全被先前那个接走了,新窗口一行日志也没有。关掉多余的窗口,或换一个端口(例如 8001,访问地址里也要跟着改)。
can't open file ... http.server:命令里的 -m 漏了,py http.server 被当成「运行一个叫 http.server 的文件」。-m 后面跟的是模块名。
小结
- 一次访问是一次请求加一次响应。请求带方法、地址、请求头、请求体;响应带状态码、响应头、响应体。
- URL 由协议、主机、端口、路径、查询组成。
127.0.0.1是本机,8000是端口。 py -m http.server 8000 --bind 127.0.0.1把当前目录变成服务器,那个窗口的日志就是服务器视角。- 状态码看第一位:2 成功、3 换地方、4 请求这边有问题、5 服务器那边出错。
curl -i 地址一次看到状态码、响应头和响应体;-v还能看到自己发出的请求头。
练习
-
排查:浏览器打开
http://127.0.0.1:8000/tasks.json显示 404,而服务器窗口里多了一行"GET /tasks.json HTTP/1.1" 404 -。问题出在哪一段上?写出你会敲的命令,把文件找回来。 -
排查:页面明明打开了,服务器窗口里却一行新日志都没有。说出最可能的原因,以及你的验证方法。
-
面试常问:只凭一次访问的响应头,怎么判断对方用的是不是 Python 自带的
http.server?另外,200、301、404、500分别出现在什么情况?其中三个你能在本机复现,写出复现用的地址。
验收:第 1 题判断为「请求到了服务器,但它提供的目录里没有这个文件」,修复是先确认文件在哪个目录、再从这个目录重启服务器;第 2 题判断为「接走请求的不是你启动的那个进程」(端口被别的程序占用,或开了第二个实例),验证方法是换一个端口重启并再次访问,看日志是否出现;第 3 题依据 Server: 头,四个状态码各能说出一种情况。
查看提示
第 1 题:日志里有这一行,说明请求已经到了服务器;服务器只是没找到文件。用 dir(macOS / Linux 用 ls)看看当前目录里有没有 tasks.json,再想想这个服务器是从哪个目录启动的。第 2 题:日志只由「真正收到请求的那个进程」打印。第 3 题:curl -i 看第一行和响应头;301 可以用目录地址少写斜杠复现。
查看参考答案
第 1 题:日志里有 "GET /tasks.json HTTP/1.1" 404 -,说明请求送到了服务器,服务器在自己提供的目录里没找到这个文件。位置只可能错在两处:文件不在那里,或者服务器不是从那个目录启动的。先找文件:
cd python-foundations
dir tasks.json找到了就说明是第二种情况:服务器是从别的目录启动的,按 Ctrl + C 停掉,cd 到 python-foundations 再启动,然后重新访问。
第 2 题:日志由「收到请求的那个进程」打印。你的请求被别的进程接走了——最常见的是 8000 端口上还跑着另一个程序,或者你又启动了一个服务器。这在 macOS / Linux 上会直接报 Address already in use,Windows 上更隐蔽:第二个服务器照样显示 Serving HTTP on ...,但一个请求也收不到。
验证方法:换一个端口重启,再访问一次。
py -m http.server 8001 --bind 127.0.0.1浏览器打开 http://127.0.0.1:8001/tasks.json,日志立刻出现新行,就说明之前那次访问被别的进程接走了;顺手关掉多余的窗口。
第 3 题:看响应头里的 Server:
Server: SimpleHTTP/0.6 Python/3.14.4SimpleHTTP 是 Python 自带 http.server 的实现名。四个状态码:
| 状态码 | 什么情况 | 本机复现 |
|---|---|---|
200 |
请求成功,响应体就是你要的东西 | http://127.0.0.1:8000/tasks.json |
301 |
东西在别处,客户端要换个地址再来 | 先 mkdir sub,再访问 http://127.0.0.1:8000/sub(末尾不写斜杠) |
404 |
服务器上找不到这个路径 | http://127.0.0.1:8000/tasks.json.bak |
500 |
服务器自己的代码出错 | 本机这个文件服务器不会 500;第 5 节的实验服务里 GET /flaky 会 |
排查时的共同起点是「先看状态码属于哪一类」:2xx 是成功,3xx 换地方,4xx 请求这边有问题,5xx 服务器那边有问题。它决定你接下来该看自己的代码、看地址,还是去看对方的服务。