最近写了一个项目,对于 HTTP 的前后端增进了一篇了解,所以写一篇文章记录一下。
http 协议是目前应用最广泛的网络协议,构建于底层的 TCP 协议之上。
http 协议是一个基于 文本 1 的 应用层 协议。而传统上口头的“HTTP”一般并不狭义的指代这个协议本身,还包括了与之相关的一系列协议、软件等等。
这说明 http 协议传输的内容是可以被很容易的解析的,其底层并不复杂。在很多单片机的实现之中,http 包的收发是直接手工一行行书写 printf()
的。
这说明 http 协议是一个顶层协议,与之同级的包括以下这些,希望你听说过以下的一部分或全部:
- ftp 协议,用于进行文件传输
- ssh 协议,一般用于远程连接
- jdbc 协议,用于数据库连接
- DNS 协议,用于 DNS 服务
- SMTP/POP 协议,用于邮件服务
- https 协议,是 http 的一个拓展,通过在 TCP 协议层上插入了一层 SSL/TLS 协议来保证安全性。
一般来说,这些应用层协议可以通过 {协议名}://xxx
的 url 来获取资源。现在我们先明确,一个 http url 的格式长这样:
”http://[user]:[password]@{主机名}[:端口]{/路径}?[查询参数]#[片段]“
http 规定,不填写端口号的情况下使用 80 端口。
关于「主机」和「端口」是什么,我们留到 TCP 协议的时候再讲(挖坑233
「路径」和 「查询参数」,我们接下来讲。
「片段」不会实际发动到服务器,而是告诉浏览器在获取到资源之后直接跳转到这个位置。
我们说,http 是一个协议,意思是说它只是一个『纸面约定』,没有规定任何实现。
首先我们要知道的是,http 协议包括两种,请求(request)和响应(response),有一些特性指引在请求包里应用,有些只能在响应包里应用,但大部分是通用的。
一般来说,不同的语言都要自己完成对于 http 协议的实现,例如 Go 的 net/http,Python 的 urllib3,等等。这些都是客户端,可以发送请求并处理响应;
一些软件也提供了 http 协议的实现,例如 Apache/Nginx,基于 Apache 的 Tomcat 等。一般其性能较高,且包括了许多实用工具,如反向代理等。这些软件是服务端,可以接受请求并返回响应;
很多开发者都知道,http 有 GET、POST 等几种方法,但是对于具体是怎样的,并不明确。
http 传输的一个包有以下内容:
- 请求头/响应头
- 起始行,单独一行,包含一些额外信息;
- 头部(headers),包含了若干键值对,用于给后面的内容做说明;
- 实体(body),也称请求体/响应体,一般是要传输的主要内容,但需要的情况下可以不传输。
- body 译为实体并不广泛接受,下文提及时我会一律使用 “实体(body)”来表述这个东西,以免产生歧义。
- 实体(body)的内容不一而足,不可能也没有必要一一介绍,后文会介绍一些对于实体(body)描述/给出限制的方法。
请求和响应的起始行格式不同,但都是一行里三个用空格分开的词,分别举例如下:
GET /api/nht/blog/example HTTP/1.1
<方法> <路径> <版本>
- 关于「方法」的信息,下面会讲;
- 这里的「路径」包括了 url 中的「路径」和「查询参数」几个部分。
- 「版本」 90% 都是 “HTTP/1.1”,尤其是比较小的网站。
- 如果是支持 http/2 的页面,那么就是“HTTP/2”
HTTP/1.1 200 OK
<版本> <状态码> <信息>
- 「版本」和前面的说法一样;
- 平时说的 “404 Not Found”,“403 Fobbiden” 之类的,就是这里的「状态码」和「信息」了。
- 这两个信息用于给客户端提供信息。
- 常见的状态码:
- 2xx:请求成功
- 200 OK,请求成功;
- 204 No Content,请求成功,但是没有 body 部分
- 206 Partial Content,请求成功,但是body内容只是一部分,要的话得继续请求
- 一般用于分块传输的情况
- 3xx:重定向
- 301 Moved Permanently,永久重定向
- 302 Found,临时重定向,一般例如主机维护等情况使用
- 4xx:客户端错误
- 400 Bad Request,没有说明错误类型
- 403 Fobbiden,拒绝访问
- 404 Not Found,找不到资源
- 5xx 内部错误
- 500 Internal,没有进一步说明
- 502 Bad Gateway,代理错误
- 2xx:请求成功
起始行用一个换行符(CRLF)结束。
头部(headers)是若干个无序的键值对,每行一对。键(一般称为字段)和值之间用冒号分隔。
字段不区分大小写,不能含有空格或者下划线(建议用短横代替)。
除非语义允许(如设置 Cookie),否则键是不重复的。
http 规定了一些标准头(这里头指的是头部字段),但也允许传输更多自定义头,类似标准库和第三方库的关系。
下面介绍一些常用头。
- 通用字段:没有限制;
- Date:报文生成时间,一般出现在响应中。
- Content-xxx,说明实体(body)的实际内容,下面和 Accept 一起细讲
- 注意请求的时候也可以加这个字段,因为上传文件等信息是需要这些描述的
- 请求字段:只会出现在请求头中;
- Host,说明请求发送到何方,必填。
- User-Agent,说明客户端使用何种浏览器(君子协议),一般称 UA。
- Cookie,说明客户端使用的 cookie 内容,用分号连接多个。
- Accept-xxx,说明客户端接受的内容,下面细讲
- 响应字段:只会出现在响应头中;
- Set-Cookie,设置 Cookie
- 实体(body)字段:用于描述实体(body)的信息。
- Content-Length,描述实体(body)长度,如果没有说明长度不定,需要分块传输。
这个系列是重要的控制和描述实体(body)内容的头部信息,成对出现,包括:
- Accept/Content-Type:MIME,标记资源类型
- MIME 是一个标记类型的系统,最早用于电子邮件,格式为 {大类型}/{小类型}。例如:
- text/html,text/css
- image/gif,image/jpeg,image/png
- audio/mpeg、video/mp4
- application/json,application/javascript、application/pdf
- MIME 是一个标记类型的系统,最早用于电子邮件,格式为 {大类型}/{小类型}。例如:
- Content-Encoding:none|gzip|br,标记返回资源压缩方法
- Accept-Language/Content-Language,标记客户端接受的语言种类。
- zh-CN, zh, en,etc
只讲几种常用的,有些不常用的不讲了。
- HEAD:只获取头部,不需要实体(body)
- 对于实体(body),http 协议没有做过多的控制
- 头部携带了大量描述信息,是 http 的精华和重中之重。
- 因此,专门提供一个请求方法来获取头部,是有必要的。
- 例如获取 cookie 等情况即可使用此方法
- GET:获取资源,可以理解为读取或者下载数据
- 不包含实体(body)
- 是 http 最古老的方法,可以追溯到远古的 http/0.9 还没有彻底成型的版本
- DELETE:删除资源
- POST:向资源提交数据,相当于写入或上传数据
- 可以包含实体(body)
- PUT:类似 POST,RESTful 架构建议用这个方法来更新资源。
- 可以包含实体(body)
- OPTIONS:列出可对资源实行的方法
可以注意到 DELETE POST GET PUT 分别对应数据库的增删查改,当然这只是对应,http 并不禁止你所有操作都通过 POST,甚至都通过 UNLINK(http/2 定义的新方法,语义为断开链接)进行。
HTTP 设计的时候是无状态(statusless)的,服务器没有记忆能力。
使用 Cookie 技术来提供历史信息,cookie 由用户的浏览器储存和维护,实际传输的时候通过头部传输。参见前文中请求和响应的 Cookie/set-Cookie 字段。
cookie 是明文的,不能存储用户密码等信息,这时候就要在服务器保存一些信息。这些信息称为「session」。但由于服务器不能分辨用户,所以 session 的应用还是基于 cookie 的,一般通过生成一个口令的方式完成。
对于后端开发来说,如果不明了如何从请求中获取信息,编码过程就变成了依靠机械记忆的工作,因此有必要对这个部分进行总结。
- 查询参数(query),由请求的起始行第二个路径参数解析提供;
- 这是最常用的参数,RESTful 建议每个获取数组的请求都在这里添加参数;
- 包括单页限制,单页偏移,排序字段,升序/降序。
- 路径(path),由请求的起始行第二个路径参数解析提供;
- 狭义,指的是 url 的路径,由于前后分离,路径被绑定给某个函数处理,因而可以包含信息。
- 请求体(body),这要求请求方法不能是 GET;
- 尽管相当一部分时候根本没有请求体,但是如果有了,请求体肯定是不可或缺的。
- cookie,由请求头中的 Cookie 字段获取;
- 这是保持连接状态的重要手段。
- 请求头(header),由请求头部本身获取。
- 这个相对不常用一些。
https 是一个基于 http 的协议,添加了 ssl/tls 协议层来保证安全,默认端口号为 443,其他和HTTP/1.1 没有区别。主要解决了 http 明文传输,可能会遇到中间人攻击(被传输路径中间上的某一部篡改内容)的问题
首先明确一下,和 TCP/IP 的情况不同, SSL/TLS 并不是两个协议,自始至终只有一个协议,只是它原来叫 SSL,后来改名叫 TLS 了(有一些不本质的变化,影响不大)。
基本上来说,这个协议干了两件事情:
- 使用公钥加密,保证传输数据没有经过中间人篡改;
- 通过第三方的数字证书,保证你获取到的公钥本身没有经过篡改。
- 否则的话,攻击者用自己的公钥替换服务器给的,通信双方还是不能建立互信;
- 第三方认证机构(Certification Authority,CA)的公钥一般内嵌在操作系统/浏览器中。
在语义上,2 和 1 是完全兼容的。但在语法上给出了一些重大变化:
- 使用二进制格式传输;
- 允许头部压缩;
- 服务器主动推送数据,不必等待客户端浏览器解析请求。
[](https://developer.mozilla.org/zh-CN/docs/Web/HTTP )
Footnotes
-
指目前通用流行的 http/1.1;http/2 使用二进制编码,但是现在应用很少所以本文不考虑。 ↩