LabHub
学习 学习路径 课程

网络基础

亲手收发 HTTP

在 LabHub 中继续学习

目标

HTTP 并不难,它只不过是在 TCP 上传递约定格式的文字。 但由于库替我们遵守了这些约定,一旦出现偏差,我们反而读不出究竟哪里出了问题。

这里将亲手遵守这些约定,并逐一查看被库隐藏起来的内容。

材料

python3 /opt/lab/netplan/httpsrv.py 8080 &

这是一个小型服务器,会对不同路径故意给出不同的响应

/            Content-Length 로 길이를 알려 준다
/chunked     길이를 모른 채 조각으로 나눠 보낸다
/slow        첫 조각을 보내고 잠시 쉰다
/nolength    길이도 조각도 없이, 연결을 닫아 끝을 알린다
/moved  /seeother  /keepme    301 · 303 · 307

需要留下的文件

01-raw.txt        손으로 친 요청과 그 응답
02-nohost.txt     Host 를 뺐을 때와 1.0 으로 보냈을 때
03-length.txt     본문의 끝을 알리는 세 방법
04-keepalive.txt  한 연결에 요청 두 번
05-redirect.txt   301 · 303 · 307 의 차이
06-timing.txt     구간별 시간
07-notes.md       다음에 읽을 사람에게

手写请求

不使用任何库,在 TCP 连接中直接写入 HTTP 请求字符串来获取 / 的响应,并保存到 01-raw.txt。发出的内容和收到的内容都要保留。

只用 bash 就能完成。

exec 3<>/dev/tcp/127.0.0.1/8080
printf 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
timeout 3 cat <&3

每行末尾必须是 \r\n,并且在报头末尾还要额外加一个空行。这个空行就是‘报头到此结束’的信号。

尝试省略 Host

发送不带 Host 报头的 HTTP/1.1 请求,再用 HTTP/1.0 发送同样的请求,并把结果保存到 02-nohost.txt。用一行说明为什么它从 1.1 开始成为必需项。

在 1.1 中,返回 400 Bad Request 是规则。这不是服务器的偏好,而是规范要求。

为了实现一个地址承载多个网站,这个报头成为必需项。服务器只能通过 Host 判断请求发往哪个网站。

正文在哪里结束

分别获取 //chunked/nolength,将表示正文结束的三种方式记录到 03-length.txt。不要处理 chunked 数据,要保留原始字节

curl 会自动合并 chunked 数据,因此看不到原始形式。请通过 /dev/tcp 直接接收。

每个分块前都会带有一行十六进制长度,最后由一个长度为 0 的分块表示结束。没有这个 0,接收方就会一直等待。

一个连接,两次请求

保持一个连接处于打开状态,发送两次请求并接收两个响应,将结果保存到 04-keepalive.txt。用一行说明复用连接能节省什么。

省略 Connection: close 后,服务器会保持连接。按照第一个响应的 Content-Length 读取完毕,再通过同一个 fd 发送第二个请求。

使用 Python 可以很方便地精确读取指定长度。

301、303 与 307 有何不同

分别请求三个路径,将响应保存到 05-redirect.txt,并写明三者的差异。尤其要说明重新请求时是否可以改变方法

三者都会提供 Location,但重新请求的方式不同。

支付请求遇到重定向时,使用哪一种状态码直接决定了正常与事故。

用数字指出缓慢的阶段

分别测量 //slowcurl 时间指标,将结果保存到 06-timing.txt,并写明哪个阶段的耗时增加了。还要说明这些值是累计值。

curl -sS -o /dev/null -w 'dns=%{time_namelookup} tcp=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' http://127.0.0.1:8080/

这些数字是累计值。如果 time_connect 为 0.32,它表示到建立连接为止共耗时 0.32 秒,而不是建立连接这个阶段本身耗时 0.32 秒。各阶段耗时需要通过相减得到。

/slow 在发送第一个字节后才暂停,因此 ttfb 很快,只有 total 会变大。

写给下一位读者

从这里看到的内容中选择至少四项,整理到 07-notes.md。不要只写做了什么,要写明为什么会这样

把它当作几个月后的自己会阅读的文档来写。‘接收了 /chunked’没有帮助;‘无法预先知道长度时就分块发送,并用长度为 0 的分块表示结束——没有这个 0,接收方就会一直等待’才有帮助。