HTTP 请求与应答:URL 全网唯一文件路径、报文结构与短连接
我的github:(https://github.com/xcx55/ubuntu-linux-project)
感谢各位大佬参观我的github!!
源笔记:http2(26-9-8)、http4-别人的应用层协议(26-9-11)、http5 请求报文结构(26-9-12)、字节流数据包两个的区别
上一篇认识了 HTTP 的名字和 DNS,这一篇拆它的真身:一个 URL 到底由什么组成、请求和应答报文长什么样、HTTP 请求的本质为什么是"发文件"。手搓协议攒下的眼光全用上了------协议发来发去就是一个"有结构的字符串"。
一、URL 完整拆解:全网唯一文件路径
一个完整的 URL 长这样:
http://user:pass@www.example.jp:80/dir/index.htm?uid=1#ch1
└─协议┘└─登录信息─┘└──服务器地址──┘└┘└─文件路径─┘└查询┘└片段┘
方案名 (认证,一般省略) 端口号 带层次 字符串 标识符
逐段看:
- 协议方案名 :
http或https; - 登录信息(认证) :
user:pass@------一般省略不写; - 服务器地址:域名或 IP;
- 服务器端口号 :一般服务端 HTTP 协议绑定的端口号都是 80,默认省略;
- 带层次的文件路径 :
/dir/index.htm; - 查询字符串 :
?uid=1,传参用的; - 片段标识符 :
#ch1。
网页的本质就是一个文件
学了前端就知道:网页的本质就是一个文件,申请网页就是申请文件 。那么怎么保证找到对应主机的这个文件?靠 Linux 的老朋友------文件路径!
前面依靠 IP 地址 + 隐藏端口号定位主机,主机内靠路径定位文件。串起来:
ip 唯一 → port 主机唯一 → 主机内路径唯一 → HTTP 本质:全网唯一的文件路径!
所以 HTTP 协议的本质就是 IO。给资源就是给 URL,URL 后面那一串本质就是 IO。
URL 与 URI
- URL :统一资源定位符------定位,就是能找到它(笔记里的比方:学号可以唯一定位到一个学生,学号就可以叫做 URL);
- URI :统一资源标识符------标识,范围更宽;
- URL 本身就是 URI。
URL 传参与特殊符号编码
想给服务器传参?就是 URL 传参 ------百度搜索框里敲的内容,全被拼进了搜索路径(?wd=new%20year&...)。
约束来了:URL 传参传的东西不能干扰对 URL 的解析,但又必须传参 。怎么办?对特殊符号(+、/、.、空格等)进行编码 ------比如空格变 %20。你搜索时 URL 里那串乱码,就是这么来的。
二、两个视角:结构体还是字符串?
同一个 HTTP 协议,两个视角看它:
- 发送者(客户端)视角 :HTTP 协议是一系列结构体进行封装和 JSON 加包------封装过的结构体资源数据,Kv 直接 k、v 序列化组合;
- 接收者(服务端)视角 :HTTP 协议是一种具有层次的 string ,以
\n为分隔符。
而 HTTP 的一个独特点:它是以行为单位、不依赖任何第三方库的协议(没有使用 JSON) 。为什么不用 JSON?降低用户使用 HTTP 的成本------不用引库,拿到的就是纯文本行,string 拆分就能解析。
三、读取完整性:又是那个 while 循环
TCP 是面向字节流 的:发来的数据数量未知、断断续续、发送和接收次数不匹配------不像 UDP 面向数据报,面向信件,发来三封就得到三封,消息天然完整。
所以 HTTP 的接收方必须自己回答两个问题:1、怎么保证读取完整?2、怎么反序列化?
答案和手搓协议时一模一样:
- 必须读取到
\r\n(空行),否则 break------TCP 协议再次读取,while 循环,直到读取成功; - 读取完整的保证,就是靠报头的循环(应用层的完整);
- 反序列化你自己都知道------string 的拆分。
第一层攒字节、第二层切报文,手搓 len\r\n value \r\n 时练的两层循环咬合,在 HTTP 里原样复现。
四、请求报文:请求行 + 报头 + 空行 + 正文
HTTP REQUEST
┌────────────────────────────────────────┐
│ 请求行: 请求方法 空格 URI 空格 HTTP版本 换行符 │ ← GET /a/b/c.html HTTP/1.1
├────────────────────────────────────────┤
│ 请求报头: Key:[空格]Value 换行符 │ ← 一行一条,可有多条
│ Key:[空格]Value 换行符 │
│ ... │
├────────────────────────────────────────┤
│ 空行: 换行符 ← 必须! │
├────────────────────────────────────────┤
│ 请求正文: DATA ← 可选的 │
└────────────────────────────────────────┘
请求行的三个字段
- 请求方法 :一般就两种------GET (一般是获取)和 POST(一般是上传);
- URI:ip 和端口以及路径------找到唯一主机的唯一文件;
- HTTP 版本 :最常用 http1.1。注意携带的是客户端的 HTTP 版本。
为什么协议要有版本?想想 App 怎么知道软件需要更新------版本被以配置文件的方式记录下来。用版本检测可以规避问题、新增不同的功能、支持不同的功能。
报头与空行
报头是 Key: Value 结构,一行一个,装的是"本次 client 需求的信息和服务端的相对信息"(Host、Connection、User-Agent、Cookie 等等)。报头和正文之间依据空行隔开 ------这个空行就是上一节"读到 \r\n 才算完整"的那个 \r\n,也是报头结束的标志。
分割报文,还是尽量先广度后局部:先按空行切出报头和正文,再按行拆报头,最后按结构拆请求行。
一个真实的请求
POST http://job.xjtu.edu.cn/companyLogin.do HTTP/1.1
Host: job.xjtu.edu.cn
Connection: keep-alive
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) ...
Cookie: JSESSIONID=D628A75845A74D29D991DB47A461E4FC; ...
username=hgtz2222&password=222222222
登录场景:正文里带着账号密码(这就是 POST 上传),报头里 Content-Length 告诉你正文多长,Cookie 带着会话状态。
五、应答报文:状态行是灵魂
HTTP 不像我们的自定义协议那样 request 和 response 共用一套格式------它的请求和应答是不一样的! (回想网络计算器:去是 size+两个数+操作,回来是 size+一个结构体,格式本来就允许不同。HTTP 做了更高级的封装,前字符串进行了统一,但应答有应答自己的行。)
HTTP RESPONSE
┌────────────────────────────────────────┐
│ 状态行: HTTP版本 空格 状态码 空格 状态码描述 换行符 │
├────────────────────────────────────────┤
│ 响应报头: Key:[空格]Value 换行符 │
├────────────────────────────────────────┤
│ 空行: 换行符 │
├────────────────────────────────────────┤
│ 响应正文: DATA(视频/文件/文章/css/js 等不同类型文件)│
└────────────────────────────────────────┘
- 状态行 :HTTP 版本(这次是 httpserver 的版本)+ 状态码 + 状态码描述;
- 状态码 :但凡网络还在、客户端发出来了请求,server 必须回复应答 ------请求出错了怎么办?比如 404 NOT FOUND。依据 server 的状态码,客户端为用户或者 server 采取措施;
- 状态码描述:对状态码的描述,人类可以识别的文字(404 和 NOT FOUND 一起表示请求状态)。
六、HTTP 请求的本质:把 Linux 目录下的文件发给客户端
把所有封装拆掉,看到底:
HTTP 超文本请求的本质:起始就是通过网络服务器,把 Linux 机器上特定目录下的文件,以 http 应答的方式返回给 client(浏览器或者 app)。
- server 端源代码的地方,就是根目录的地方;
- 一般来说我们请求直接写
www.xxx.com/,后面是客户端主动加上index.html这个文件名------你不写文件名,浏览器帮你补默认首页; - 前端就是这样的:客户端的东西解析得太多了,帮我们进行一系列复杂的请求或者写入数据。
短连接
浏览器 ── HTTP request ──→ 服务器
浏览器 ←── HTTP response ── 服务器
│
close(短连接)
浏览器拿到应答后进一步处理
一次 request + 一次 response,服务器就 close------这就是 HTTP 无连接特性的体现:每次请求都要建立新的连接。
七、依样画葫芦:写一个仿 HTTP 的自定义协议
请求和应答的轮廓都齐了,笔记里的原话:"依据这个就可以仿照这个写自定义协议了"------Response 报文和 request 报文都是不同的,就是字符串的格式不相同;请求行/状态行 + Kv 报头 + 空行 + 正文,四段式骨架照搬,内容自己定。手搓协议的功课,到这里就能照着 HTTP 抄作业了。
总结
- URL = 协议 + 服务器地址(+端口 80) + 文件路径 + 查询字符串;HTTP 本质 = 全网唯一文件路径,网页就是文件;
- URL 就是传参,特殊符号必须编码(空格 →
%20)才不干扰解析; - HTTP 以行为单位、不依赖第三方库:发送者看是结构体序列化,接收者看是层次字符串;
- 面向字节流 → 必须循环读到
\r\n才算完整;空行是报头与正文的分界; - 请求行:方法(GET/POST) + URI + 版本;应答状态行:版本 + 状态码 + 描述,server 必须应答;
- HTTP 请求本质 = 把服务器 Linux 特定目录的文件返回给客户端,默认补
index.html;一问一答即短连接。
下一篇换个视角往上爬:为什么需要协议、协议栈为什么叫"栈"、IP 和 MAC 各管什么------把网络分层再往下捅一层。