HTTP 协议简单探究 | 青训营

149 阅读2分钟

Hi!这是我的青训营后端笔记的第四篇。本篇中,我们将来探索HTTP协议的底层实现,以实操来体验HTTP协议的使用。

想要完全了解后端,我们不能只沉浸于调用各种现成的框架的API,还要学习框架的设计原理,形成一个自底向上的视角。

首先,我们来了解一下HTTP协议的内容。

HTTP协议

我们都知道OSI七层模型。对于HTTP协议,它属于应用层协议,一般存在于TCP协议上。它是一个不对称的文本协议,分为由客户端发给服务器的请求以及服务器发给客户端的响应组成。一个完整的请求体或响应体都由两个部分组成:头和内容。这两部分之间通过回车换行符 \r\n 隔离开。由于HTTP是一个文本协议,所以头的各个部分由换行\n分开。从上到下,一个完整的HTTP请求或响应由以下几个部分组成:

  1. 请求行/状态行
  2. 请求头/响应头
  3. 分隔符 \r\n
  4. 请求体/响应体

如何从一个流中完整地读取一个请求呢?

我们知道,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