HTTP

HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。

  • 超文本:超越了普通文本的文本(文字、图片、视频等信息)
  • 传输:HTTP 是一个在计算机里专门用来在两点之间传输数据的约定和规范
  • 协议:两点之间的一种规范和约束

HTTP 不是用于从互联网服务器传输超文本到本地浏览器的协议,也可以是服务器到服务器,所以采用两点之间的描述会更准确。

优缺点

特点

  • 语义上的自由,处理规定的基本格式之外,其余的各个部分都没有严格的限制;并且不仅可以传输文本,还能传输图片、视频等信息
  • HTTP是基于TCP/IP的,因此TCP所具有的可靠性HTTP也具有。
  • 每次 http 请求都是独立、无关的,默认不需要保留状态信息——无状态。(这里的状态是指通信过程的上下文信息
  • 请求和应答一一对应

缺点

  • 无状态 所谓的优点和缺点还是要分场景来看的,对于 HTTP 而言,最具争议的地方在于它的无状态
    • 在需要长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息,那么这时候无状态就是 http 的缺点了。
    • 但与此同时,另外一些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态反而减少了网络开销,成为了 http 的优点。
  • 明文传输 即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。这当然对于调试提供了便利,但同时也让 HTTP 的报文信息暴露给了外界。
  • 队头阻塞问题 当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。

HTTP报文结构

对于 TCP 而言,在传输的时候分为两个部分: **TCP头 **和 数据部分。而 HTTP 类似,也是 header + body 的结构

起始行

对于请求报文来说,起始行类似下面这样:

GET /home HTTP/1.1

也就是方法 + 路径 + http版本。(请求行 - 通用信息头 - 请求头 - 实体头 - 报文主体)

对于响应报文来说,起始行一般张这个样:

HTTP/1.1 200 OK

响应报文的起始行也叫做状态行。由 http版本 + 状态码 + 原因 三部分组成。(状态行 - 通用信息头 - 响应头 - 实体头 - 报文主体)

注意:在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循 ABNF 语法规范。

头部

展示一下请求头和响应头在报文中的位置

image-20230217220432501

不管是请求头还是响应头,其中的字段是相当多的,而且牵扯到http非常多的特性,这里就不一一列举的,重点看看这些头部字段的格式:

  • 字段名不区分大小写
  • 字段名不允许出现空格,不可以出现下划线_
  • 字段名后面必须紧接着:

空行

很重要,用来区分开头部和实体。问: 如果说在头部中间故意加一个空行会怎么样?那么空行后的内容全部被视为实体

实体

就是具体的数据了,也就是 body 部分。请求报文对应请求体, 响应报文对应响应体。

常见的HTTP请求

http/1.1 规定了以下请求方法(注意,都是大写):

  • GET 通常用来获取资源
  • HEAD 获取资源的元信息
  • POST 提交数据,即上传数据
  • PUT 修改数据
  • DELETE 删除资源(几乎用不到)
  • CONNECT 建立连接隧道,用于代理服务器
  • OPTIONS 列出可对资源实行的请求方法,预检请求,用来跨域请求
  • TRACE 追踪请求-响应的传输路径

POST和GET

区别

首先最直观的是语义上的区别。而后又有这样一些具体的差别:

  • 从缓存的角度 GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。
  • 从编码的角度 GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
  • 从参数的角度 GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
  • 从幂等性的角度 GET是幂等的,而POST不是。
  • 从TCP的角度 GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分(预检请求),如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)

Get 方法的含义是请求从服务器获取资源,这个资源可以是静态的文本、页面、图片视频等 比如,你打开我的文章,浏览器就会发送 GET 请求给服务器,服务器就会返回文章的所有文字及资源。

POST 方法是向 URI 指定的资源提交数据,数据就放在报文的 body 里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器,服务器处理好之后将处理的结果返回给客户端。

注意: http协议并未get和post是没有字节限制的,但是服务端和浏览器是可以限制字节大小的

幂等性安全问题

在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。

所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。

很明显 GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。

POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。

状态码

image-20230217223224925

1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少

200 OK 是最常见的成功状态码,表示一切正常。如果是非 HEAD 请求,服务器返回的响应头都会有 body 数据。

204 No Content 也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据

206 Partial Content 是应用于 HTTP 分块下载或断电续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。

301 Moved Permanently 表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。

302 Found 表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问 301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。

304 Not Modified 不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制。

400 Bad Request 表示客户端请求的报文有错误,但只是个笼统的错误。

403 Forbidden 表示服务器禁止访问资源,并不是客户端的请求出错。

404 Not Found 表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。

500 Internal Server Error 与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道

501 Not Implemented 表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思

502 Bad Gateway 通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。

503 Service Unavailable 表示服务器当前很忙,暂时无法响应服务器,类似“网络服务正忙,请稍后重试”的意思。

HTTP常见的字段

Host

客户端发送请求时,用来指定服务器的域名图片

Host: www.A.com

有了 Host 字段,就可以将请求发往同一台服务器上的不同网站

Content-Length

服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据长度。图片

Content-Length: 1000

如上面则是告诉浏览器,本次服务器回应的数据长度是 1000 个字节,后面的字节就属于下一个回应了。

Connection

Connection 字段最常用于客户端要求服务器使用 TCP 持久连接,以便其他请求复用图片HTTP/1.1 版本的默认连接都是持久连接,但为了兼容老版本的 HTTP,需要指定 Connection 首部字段的值为 Keep-Alive

Connection: keep-alive

一个可以复用的 TCP 连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段。

Content-Encoding

Content-Encoding 字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式图片

Content-Encoding: gzip

上面表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压 客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法

Accept-Encoding: gzip, deflate

Content-Type

Content-Type 字段用于服务器回应时,告诉客户端,本次数据是什么格式图片

Content-Type: text/html; charset=utf-8

上面的类型表明,发送的是网页,而且编码是UTF-8。客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。

Accept: */*

上面代码中,客户端声明自己可以接受任何格式的数据

Accept

对于 Accept 系列字段的介绍分为四个部分:数据格式、压缩方式、支持语言和字符集

数据格式

HTTP 从 MIME type (Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展) 取了一部分来标记报文 body 部分的数据类型,这些类型体现在Content-Type这个字段,当然这是针对于发送端而言,接收端想要收到特定类型的数据,也可以用Accept字段。具体而言,这两个字段的取值可以分为下面几类:

  • text text/html, text/plain, text/css 等
  • image image/gif, image/jpeg, image/png 等
  • audio/video audio/mpeg, video/mp4 等
  • application application/json, application/javascript, application/pdf, application/octet-stream

压缩方式

当然一般这些数据都是会进行编码压缩的,采取什么样的压缩方式就体现在了发送方的 Content-Encoding 字段上, 同样的,接收什么样的压缩方式体现在了接受方的Accept-Encoding字段上。这个字段的取值有下面几种:

  • gzip 当今最流行的压缩格式
  • deflate 另外一种著名的压缩格式
  • br 一种专门为 HTTP 发明的压缩算法
// 发送端
Content-Encoding: gzip

// 接收端
Accept-Encoding: gzip

支持语言

对于发送方而言,还有一个Content-Language字段,在需要实现国际化的方案当中,可以用来指定支持的语言,在接受方对应的字段为Accept-Language。如:

// 发送端
Content-Language: zh-CN, zh, en

// 接收端
Accept-Language: zh-CN, zh, en

字符集

最后是一个比较特殊的字段, 在接收端对应为Accept-Charset,指定可以接受的字符集,而在发送端并没有对应的Content-Charset, 而是直接放在了Content-Type中,以charset属性指定。如:

// 发送端
Content-Type: text/html; charset=utf-8

// 接收端
Accept-Charset: charset=utf-8

image-20230217225314651

HTTP的传输

对于几百 M 甚至上 G 的大文件来说,如果要一口气全部传输过来显然是不现实的,会有大量的等待时间,严重影响用户体验。因此,HTTP 针对这一场景,采取了范围请求的解决方案,允许客户端仅仅请求一个资源的一部分。

如何支持

支持大文件传输的前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头

HTTP/1.1 200 OK
...
Accept-Ranges: bytes
Content-Length: 146515

在响应中存在Accept-Ranges首部(并且它的值不为 “none”),那么表示该服务器支持范围请求在上面的响应中;

Accept-Ranges: bytes 表示界定范围的单位是 bytes ;

Content-Length也是有效信息,因为它提供了要检索的图片的完整大小。

如果站点未发送Accept-Ranges首部,那么它们有可能不支持范围请求。一些站点会明确将其值设置为 “none”,以此来表明不支持。在这种情况下,某些应用的下载管理器会将暂停按钮禁用。

curl -I https://www.youtube.com/watch?v=EwTZ2xpQwpA

HTTP/1.1 200 OK
...
Accept-Ranges: none

Range 字段拆解

而对于客户端而言,它需要指定请求哪一部分,通过 Range 这个请求头字段确定,格式为bytes=x-y。接下来就来讨论一下这个 Range 的书写格式:

  • 0-499 表示从开始到第 499 个字节。
  • 500- 表示从第 500 字节到文件终点。
  • -100 表示文件的最后100个字节。

服务器收到请求之后,首先验证范围是否合法,如果越界了那么返回416错误码,否则读取相应片段,返回206状态码。同时,服务器需要添加 Content-Range 字段,这个字段的格式根据请求头中Range字段的不同而有所差异。具体来说,请求单段数据和请求多段数据,响应头是不一样的。举个例子:

// 单段数据
curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"
Range: bytes=0-9

// 多段数据
curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"
Range: bytes=0-9, 30-39

单段数据

对于单段数据的请求,返回的响应如下:

HTTP/1.1 206 Partial Content
Content-Length: 10
Accept-Ranges: bytes
Content-Range: bytes 0-9/100

i am xxxxx

值得注意的是Content-Range字段,0-9表示请求的返回,100表示资源的总大小,很好理解。

多端数据

接下来我们看看多段请求的情况。得到的响应会是下面这个形式:

HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000010101
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes


--00000010101
Content-Type: text/plain
Content-Range: bytes 0-9/96

i am xxxxx
--00000010101
Content-Type: text/plain
Content-Range: bytes 20-29/96

eex jspy e
--00000010101--

这个时候出现了一个非常关键的字段Content-Type: multipart/byteranges;boundary=00000010101,它代表了信息量是这样的:

  • 请求一定是多段数据请求
  • 响应体中的分隔符是 00000010101

因此,在响应体中各段数据之间会由这里指定的分隔符分开,而且在最后的分隔末尾添上–表示结束。以上就是 http 针对大文件传输所采用的手段。