思维导图

17104ea1fdee5669.png

HTTP报文结构

header + body的结构,具体而言: 起始行 + 头部 + 空行 + 实体

1、起始行

  • 请求报文来说,起始行类似下面这样: GET /home HTTP/1.1,**方法 + 路径 + http版本**。
  • 对于响应报文来说,起始行一般是这个样: HTTP/1.1 200 OK,**也叫做状态行,**由http版本、状态码和原因三部分组成。

    2、头部

    头部字段的格式:

  • 字段名不区分大小写

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

    3、空行

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

    4、实体

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

    HTTP 的请求方法

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

  • GET: 通常用来获取资源

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

    GET 和 POST 有什么区别

  • 缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。

  • 编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
  • 参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
  • 幂等性的角度,GET是幂等的,而POST不是。(幂等表示执行相同的操作,结果也是相同的)
  • TCP的角度,GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)

    理解 URI

    URI, 全称为(Uniform Resource Identifier), 也就是统一资源标识符,它的作用很简单,就是区分互联网上不同的资源。
    但是,它并不是我们常说的网址, 网址指的是URL, 实际上URI包含了URNURL两个部分,由于 URL 过于普及,就默认将 URI 视为 URL 了。

    URI 的结构

    170ffd677629b70d.png
    scheme 表示协议名,比如http, https, file等等。后面必须和:**//连在一起。
    user:passwd@ 表示登录主机时的用户信息,不过很不安全,不推荐使用,也不常用。
    host:port表示主机名和端口。
    path表示请求路径,标记资源所在位置。
    query表示查询参数,为key=val这种形式,多个键值对之间用&隔开。
    fragment表示 URI 所定位的资源内的一个锚点**,浏览器可以根据这个锚点跳转到对应的位置。

例子

  1. https://www.baidu.com/s?wd=HTTP&rsv_spt=1

这个 URI 中,httpsscheme部分,www.baidu.comhost:port部分(注意,http 和 https 的默认端口分别为80、443),/spath部分,而wd=HTTP&rsv_spt=1就是query部分。

URI 编码

URI 只能使用ASCII, ASCII 之外的字符是不支持显示的,而且还有一部分符号是界定符,如果不加以处理就会导致解析出错。
因此,URI 引入了编码机制,将所有非 ASCII 码字符界定符转为十六进制字节值,然后在前面加个%
如,空格被转义成了%20三元被转义成了%E4%B8%89%E5%85%83

HTTP 状态码

RFC 规定 HTTP 的状态码为三位数,被分为五类:

  • 1xx: 表示目前是协议处理的中间状态,还需要后续操作。
  • 2xx: 表示成功状态。
  • 3xx: 重定向状态,资源位置发生变动,需要重新请求。
  • 4xx: 请求报文有误。
  • 5xx: 服务器端发生错误。

    HTTP 的特点

    特点

  1. 灵活可扩展,主要体现在两个方面。一个是语义上的自由,只规定了基本格式,比如空格分隔单词,换行分隔字段,其他的各个部分都没有严格的语法限制。另一个是传输形式的多样性,不仅仅可以传输文本,还能传输图片、视频等任意数据,非常方便。
  2. 可靠传输。HTTP 基于 TCP/IP,因此把这一特性继承了下来。这属于 TCP 的特性,不具体介绍了。
  3. 请求-应答。也就是一发一收有来有回, 当然这个请求方和应答方不单单指客户端和服务器之间,如果某台服务器作为代理来连接后端的服务端,那么这台服务器也会扮演请求方的角色。
  4. 无状态。这里的状态是指通信过程的上下文信息,而每次 http 请求都是独立、无关的,默认不需要保留状态信息。

    缺点

    无状态

    所谓的优点和缺点还是要分场景来看的,对于 HTTP 而言,最具争议的地方在于它的无状态
    在需要长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息,那么这时候无状态就是 http 的缺点了。
    但与此同时,另外一些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态反而减少了网络开销,成为了 http 的优点。

    明文传输

    即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。
    这当然对于调试提供了便利,但同时也让 HTTP 的报文信息暴露给了外界,给攻击者也提供了便利。WIFI陷阱就是利用 HTTP 明文传输的缺点,诱导你连上热点,然后疯狂抓你所有的流量,从而拿到你的敏感信息。

    队头阻塞问题

    当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。

    Accept 系列字段

    数据格式

    MIME(Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展)。它首先用在电子邮件系统中,让邮件可以发任意类型的数据,这对于 HTTP 来说也是通用的。
    因此,HTTP 从MIME type取了一部分来标记报文 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
    

    HTTP 是怎么传输定长和不定长的数据

    定长包体

    对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度。
    Content-Length对于 http 传输过程起到了十分关键的作用,如果设置不当可以直接导致传输失败。

    不定长包体

    这里就必须介绍另外一个 http 头部字段了:

    Transfer-Encoding: chunked
    

    表示分块传输数据,设置这个字段后会自动产生两个效果:

  • Content-Length 字段会被忽略

  • 基于长连接持续推送动态内容

    HTTP 如何处理大文件的传输

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

    Accept-Ranges: none
    

    Range 字段拆解

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

  • 0-499表示从开始到第 499 个字节。

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

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

// 单段数据
Range: bytes=0-9
// 多段数据
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 针对大文件传输所采用的手段。

HTTP 中如何处理表单数据的提交

在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:

  • application/x-www-form-urlencoded
  • multipart/form-data

    application/x-www-form-urlencoded

  • 其中的数据会被编码成以&分隔的键值对

  • 字符以URL编码方式编码。

    multipart/form-data

  • 请求头中的Content-Type字段会包含boundary,且boundary的值有浏览器默认指定。例: Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe

  • 数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,如Content-Type,在最后的分隔符会加上--表示结束。

multipart/form-data 格式最大的特点在于:每一个表单元素都是独立的资源表述
在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。

HTTP1.1 如何解决 HTTP 的队头阻塞问题

HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,但值得注意的是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是著名的HTTP队头阻塞问题。