Hi!这是我的青训营后端笔记的第四篇。本篇中,我们将来探索HTTP协议的底层实现,以实操来体验HTTP协议的使用。
想要完全了解后端,我们不能只沉浸于调用各种现成的框架的API,还要学习框架的设计原理,形成一个自底向上的视角。
首先,我们来了解一下HTTP协议的内容。
HTTP协议
我们都知道OSI七层模型。对于HTTP协议,它属于应用层协议,一般存在于TCP协议上。它是一个不对称的文本协议,分为由客户端发给服务器的请求以及服务器发给客户端的响应组成。一个完整的请求体或响应体都由两个部分组成:头和内容。这两部分之间通过回车换行符 \r\n 隔离开。由于HTTP是一个文本协议,所以头的各个部分由换行\n分开。从上到下,一个完整的HTTP请求或响应由以下几个部分组成:
- 请求行/状态行
- 请求头/响应头
- 分隔符
\r\n - 请求体/响应体
如何从一个流中完整地读取一个请求呢?
我们知道,TCP是以流的方式体现出来的。流本身没有大小,只能一个字节一个字节地读取。现在考虑你是一个服务器,我们要通过哪些标志来判断该请求已经发送完成了呢?
对于头来说,你可能想到了判断是否读到了 \r\n。这是正确的。对于请求体来说,由于其可能包含任何内容,我们无法用分隔符的方式来判断是否结束。这时就需要在HTTP请求头和响应头中加入一个特殊的头 Content-Length 来标识请求体或响应体的长度。只有当拥有这个标头时,我们才能确定HTTP请求体或响应体的结束。
在请求体的头一行,我们依次表明我们要使用的HTTP方法和想要访问的服务器路径。 在响应体的头一行,我们依次表明HTTP协议的版本、状态码和状态码的文本解释。
例如,以下是由curl生成的请求和服务器返回的响应:
> GET /get HTTP/1.1
> User-Agent: curl/7.29.0
> Host: httpbin.org
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 253
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.29.0",
"X-Amzn-Trace-Id": "Root=1-64eb5aa2-14adafbe1672ab762c7c3a33"
},
"origin": "120.6.44.68",
"url": "https://httpbin.org/get"
}
可以看到,当请求体为空时,头部可以忽略 Content-Length。