HTTP基础笔记

164 阅读23分钟

基本特点

  1. 应用层协议
  2. 用来在两点之间传输数据,不能用于广播、寻址或路由
  3. 可以传输文字、图片、音频、视频等超文本数据
  4. 无状态
  5. 明文传输
  6. 请求应答

报文结构

1. 基本结构

  1. 起始行(start line):描述请求或响应的基本信息;
  2. 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  3. 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据
  4. 报文必须有 header,但可以没有 body,而且在 header 之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”

image.png

2. 请求行(请求报文的起始行)

由三部分组成

  1. 请求方法:是一个动词,如 GET/POST,表示对资源的操作;
  2. 请求目标:通常是一个 URI,标记了请求方法要操作的资源;
  3. 版本号:表示报文使用的 HTTP 协议版本
  4. 这三个部分通常使用空格(space)来分隔,最后要用 CRLF 换行表示结束

image.png

image.png

3. 状态行(响应报文的起始行)

由三部分组成

  1. 版本号:表示报文使用的 HTTP 协议版本;
  2. 状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;
  3. 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因

image.png

image.png

4. 头部字段

请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头或响应头

  1. 用“:”分隔,
  2. 不区分大小写
  3. 顺序任意
  4. 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”
  5. HTTP/1.1 里唯一要求必须提供的头字段是 Host,它必须出现在请求头里,标记虚拟主机名
  6. 除了规定的标准头,也可以任意添加自定义字段,实现功能扩展

标准请求方法

  • GET:获取资源,可以理解为读取或者下载数据;
  • HEAD:获取资源的元信息;
  • POST:向资源提交数据,相当于写入或上传数据;
  • PUT:类似 POST
  • DELETE:删除资源;
  • CONNECT:建立特殊的连接隧道;
  • OPTIONS:列出可对资源实行的方法;
  • TRACE:追踪请求 - 响应的传输路径

URI

image.png

  • scheme 叫“方案名”或者“协议名”,表示资源应该使用哪种协议来访问;
  • host:port”表示资源所在的主机名和端口号;
  • path 标记资源所在的位置;
  • query 表示对资源附加的额外要求;
  • 在 URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP报文后会无法正确处理

HTTP优缺点

  • HTTP 最大的优点是简单、灵活和易于扩展;
  • HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
  • HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
  • HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
  • HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间

实体数据

数据类型使用的头字段

  • 客户端用 Accept 头告诉服务器希望接收什么样的数据
  1. Accept字段标记的是客户端可理解的 MIME type,可以用“,”做分隔符列出多个类型
  2. Accept: text/html,application/xml,image/webp,image/png 告诉服务器:“我能够看懂 HTML、XML 的文本,还有 webp 和 png 的图片,请给我这四类格式的数据
  • 服务器用 Content 头告诉客户端实际发送了什么样的数据
  1. 响应报文里用头字段Content-Type告诉实体数据的真实类型
  2. Content-Type: image/png 浏览器看到报文里的类型是一个 PNG 文件,就会在页面上显示出图像
  • Accept-Encoding字段标记的是客户端支持的压缩格式,用“,”列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段Content-Encoding
  • 如果请求报文里没有 Accept-Encoding 字段,就表示客户端不支持压缩数据;如果响应报文里没有 Content-Encoding 字段,就表示响应数据没有被压缩

语言类型使用的头字段

  • Accept-Language字段标记了客户端可理解的自然语言,也允许用“,”做分隔符列出多个类型
  • 服务器在响应报文里用头字段Content-Language告诉客户端实体数据使用的实际语言类型
  • 字符集在 HTTP 里使用的请求头字段是Accept-Charset,响应头里却没有对应的 Content-Charset,而是在Content-Type字段的数据类型后面用“charset=xxx”来表示,这点需要特别注意

image.png

内容协商的质量值

  • 在 HTTP 协议里用 AcceptAccept-EncodingAccept-Language 等请求头字段进行内容协商的时候,还可以用一种特殊的“q”参数表示权重来设定优先级
  • 权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”

image.png

表示浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出 HTML 或者 XML

内容协商的结果

  • 服务器会在响应头里多加一个Vary字段,记录服务器在内容协商时参考的请求头字段,给出一点信息

image.png

表示服务器依据了 Accept-EncodingUser-Agent 和 Accept 这三个头字段,然后决定了发回的响应报文

  • 每当 Accept 等请求头变化时,Vary 也会随着响应报文一起变化。也就是说,同一个 URI 可能会有多个不同的“版本”,主要用在传输链路中间的代理服务器实现缓存服务

传输大文件

1. 数据压缩

  • 使用:浏览器在发送请求时都会带着“Accept-Encoding”头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br 等,这样服务器就可以从中选择一种压缩算法,放进“Content-Encoding”响应头里,再把原数据压缩后发给浏览器
  • 缺点:gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,再用 gzip 处理也不会变小

2. 分块传输

分块传输的头部参数

  • 分块传输编码,在响应报文里用头字段Transfer-Encoding: chunked来表示,意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送
  • Transfer-Encoding: chunkedContent-Length这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知

分块传输的规则

  • 每个分块包含两个部分,长度头和数据块;
  • 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
  • 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
  • 最后用一个长度为 0 的块表示结束,即“0\r\n\r\n

image.png

3.范围请求

基本概念

当在看某穿越剧,想跳过片头,直接看正片,想拖动进度条快进几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力,HTTP 协议为了满足这样的需求,提出了范围请求(range requests)的概念,允许客户端在请求头里使用专用字段来表示只获取文件的一部分

  • 范围请求不是 Web 服务器必备的功能,可以实现也可以不实现
  • 要实现的话,服务器必须在响应头里使用字段Accept-Ranges: bytes明确告知客户端:“我是支持范围请求的”
  • 如果不支持的话,服务器可以发送Accept-Ranges: none,或者干脆不发送Accept-Ranges字段

请求过程

1. 客户端请求头携带Range,格式是bytes=x-y,其中的 x 和 y 是以字节为单位的数据范围

  • Range 的格式灵活,起点 x 和终点 y 可以省略,能够很方便地表示正数或者倒数的范围
  • x、y 表示的是偏移量,范围必须从 0 计数,前 10 个字节表示为“0-9”,第二个 10 字节表示为“10-19”,而“0-10”实际上是前 11 个字节

假设文件是 100 个字节

  • “0-”表示从文档起点到文档终点,相当于“0-99”,即整个文件;
  • “10-”是从第 10 个字节开始到文档末尾,相当于“10-99”;
  • “-1”是文档的最后一个字节,相当于“99-99”;
  • “-10”是从文档末尾倒数 10 个字节,相当于“90-99”

2. 服务端收到Range 字段, 会进行下面4个步骤

  1. 检查范围是否合法,比如文件只有 100 个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你的范围请求有误,我无法处理,请再检查一下”
  2. 范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content”, 表示 body 只是原数据的一部分
  3. 服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式是bytes x-y/length,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。
  4. 发送数据

多段数据

  • 支持在 Range 头里使用多个“x-y”,一次性获取多个片段数据
  • 需要使用一种特殊的 MIME 类型:multipart/byteranges,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”给出段之间的分隔标记
  • 每一个分段必须以“- -boundary”开始(前面加两个“-”),之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据,最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束 image.png

连接管理

1. 短连接

HTTP 协议底层的数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接

短连接有个很明显的缺陷:

在 TCP协议里,建立连接和关闭连接都是非常“昂贵”的操作。TCP 建立连接要有“三次握手”,发送 3 个数据包,需要 1 个 RTT;关闭连接是“四次挥手”,4 个数据包需要 2 个 RTT。每次连接和断开连接都要握手操作,效率太低

2. 长连接

既然 TCP 的连接和关闭非常耗时间,那么就把这个时间成本由原来的一个“请求 - 应答”均摊到多个“请求 - 应答”上

image.png

3. 连接相关的头字段

在 HTTP/1.1 中的连接都会默认启用长连接,只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的 TCP 连接,在这个连接上收发数据

明确使用长连接机制

  1. 请求头里使用字段Connection,值是keep-alive
  2. 不管客户端是否显式要求长连接,如果服务器支持长连接,它总会在响应报文里放一个Connection: keep-alive字段

长连接的问题

因为 TCP 连接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。如果有大量的空闲长连接只连不发,就会很快耗尽服务器的资源,导致服务器无法为真正有需要的用户提供服务

客户端关闭长连接

  1. 请求头里加上Connection: close字段
  2. 服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接

服务端关闭长连接

  • 假设服务端用nginx
  1. 使用keepalive_timeout指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
  2. 使用keepalive_requests指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。
  • 客户端和服务器都可以在报文里附加通用头字段Keep-Alive: timeout=value,限定长连接的超时时间。

4. 队头阻塞

“队头阻塞”与短连接和长连接无关,而是由 HTTP 基本的“请求 - 应答”模型所导致的

产生的原因

  • HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理
  • 如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待

image.png

解决办法

  1. 并发连接

对一个域名发起多个长连接,用数量来解决质量的问题

  • 同一域名建立多个长连接,分散请求(并发处理)
  • 太多的长连接会占用服务器资源,所以浏览器限制同一域名最多只能同时建立6-8个长连接
  1. 域名分片

使用多个域名指向同一台服务器,每个域名建立最大可建立数的长连接

上面一个域名可以建立6-8个长连接,那么n个域名就是 n * 6 ~ n * 8

重定向

重定向的过程

301 是“永久重定向”,302 是“临时重定向”,浏览器收到这两个状态码就会跳转到新的 URI,这里有个响应头Location很重要

image.png

  1. “重定向”实际上发送了两次 HTTP 请求,第一个请求返回了 302,然后第二个请求就被重定向到了Location头的值“/index.html
  2. Location字段属于响应字段,必须出现在响应报文里。只有配合 301/302 状态码才有意义,它标记了服务器要求重定向的 URI,这里就是要求浏览器跳转到“index.html”

重定向状态码

  1. 永久重定向301
  • 原 URI 已经“永久”性地不存在了,今后的所有请求都必须改用新的 URI
  • 历史记录、更新书签,下次可能就会直接用新的 URI 访问,省去了再次跳转的成本。
  • 搜索引擎的爬虫看到 301,也会更新索引库,不再使用老的 URI
  1. 临时重定向302
  • 原 URI 处于“临时维护”状态,新的 URI 是起“顶包”作用的“临时工”
  • 原来的 URI 仍然有效,但暂时不可用,所以只会执行简单的跳转页面,不记录新的 URI,也不会有其他的多余动作,下次访问还是用原 URI

应用场景

  1. 301永久重定向
  • 域名、服务器、网站架构发生了大幅度的改变,比如启用了新域名、服务器切换到了新机房、网站目录层次重构,原来的 URI 已经不能用了,必须用 301“永久重定向”,通知浏览器和搜索引擎更新到新地址,这也是搜索引擎优化(SEO)要考虑的因素之一
  1. 302临时重定向
  • 原来的 URI 在将来的某个时间点还会恢复正常,常见的应用场景就是系统维护,把网站重定向到一个通知页面,告诉用户过一会儿再来访问。另一种用法就是“服务降级”,比如在双十一促销的时候,把订单查询、领积分等不重要的功能入口暂时关闭,保证核心服务能够正常运行

重定向的问题

1. 性能损耗

  • 重定向的机制决定了一个跳转会有两次请求 - 应答,比正常的访问多了一次,所以重定向应当适度使用,决不能滥用

2. 循环跳转

  • 如果重定向的策略设置欠考虑,可能会出现“A=>B=>C=>A”的无限循环,不停地在这个链路里转圈圈,HTTP 协议特别规定,浏览器必须具有检测“循环跳转”的能力,在发现这种情况时应当停止发送请求并给出错误提示

HTTP中的COOKIE

HTTP是无状态协议,服务端如何区分不同用户,用的就是cookie

工作过程

  1. 浏览器首次发起请求,服务器通过在响应头添加Set-Cookie字段,格式是key=value,跟着响应报文一起返回
  2. 浏览器收到响应报文看到里面有Set-Cookie,就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器
  3. 服务器看到请求头有Cookie字段,拿出里面的值,识别出用户的身份
  4. 服务器可以在响应头里添加多个 Set-Cookie存储多个key=value。浏览器发送时不需要用多个 Cookie 字段,只要在一行里用“;”隔开就行

image.png

  • 注意,Cookie 是由浏览器负责存储的,如果换浏览器或者换台电脑,新的浏览器里没有服务器对应的 Cookie,只能再走一遍 Set-Cookie 流程

相关属性

1. 有效期

有效期就是为了让cookie在一定时间内有效,超出这个时间马上失效过期,有两个相关属性ExpiresMax-Age,两者同时出现的情况下,浏览器会优先采用 Max-Age 计算失效期

  • Expires

绝对时间点

  • Max-Age

相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间

Expires 标记的过期时间是“GMT 2019  6  7  8  19 分”
 Max-Age 则只有 10 秒,如果现在是 6  6 号零点,那么 Cookie 的实际有效期就是“6  6 号零点过 10 秒”

2. 作用域

DomainPath指定了 Cookie 所属的域名和路径,浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不一致,就不会在请求头里发送 Cookie

  • 这两个属性可以为不同的域名和路径分别设置各自的 Cookie,比如“/19-1”用一个 Cookie,“/19-2”再用另外一个 Cookie,两者互不干扰
  • Path 用/或者直接省略表示域名下的任意路径都允许使用 Cookie,让服务器自己去挑

3. 安全性

HttpOnly

JS 脚本里可以用 document.cookie 来读写 Cookie 数据,这就带来了安全隐患,有可能会导致“跨站脚本”(XSS)攻击窃取数据

HttpOnly会告诉浏览器,此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API

SameSite

  • 可以防范跨站请求伪造(XSRF)攻击
  • 设置成SameSite=Strict可以严格限定 Cookie 不能随着跳转链接跨站发送
  • 设置成SameSite=Lax允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送

Secure

  • 表示这个 Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送
  • Cookie 本身不是加密的,浏览器里还是以明文的形式存在

Cookie的应用

身份识别

  • 用户点击登录就会向服务器发起一个登录请求
  • 服务器响应报文添加Set-Cookie,携带用户信息参数
  • 以后请求其它接口都会带上这个身份标识Cookie,就完成了身份识别

广告跟踪

当访问某些网页时,会看到一些广告,这背后是一些广告商,这些广告商会添加一些cookie到浏览器,但是这些cookie不属于你访问的网页的,而是这个广告商的,当你再去访问其它网页,一样能读取到广告商的cookie,然后又可以推荐广告了

缓存控制

基本流程

  • 浏览器发现缓存无数据,于是发送请求,向服务器获取资源;
  • 服务器响应请求,返回资源,同时标记资源的有效期;
  • 浏览器缓存资源,等待下次重用。

服务端缓存控制

Cache-Control

可以有以下的值存在

  1. max-age:生存时间,单位是s,响应报文的创建时刻(即 Date 字段,也就是离开服务器的时刻),而不是客户端收到报文的时刻
  2. no_store:不允许缓存
  3. no_cache:可以缓存,使用前必须要去服务器验证是否过期,是否有最新的版本
  4. must-revalidate:缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证

image.png

客户端缓存控制

浏览器也可以发Cache-Control

  • 刷新按钮的时候,浏览器会在请求头里加一个Cache-Control: max-age=0
  • Ctrl+F5 的“强制刷新,发了一个Cache-Control: no-cache

条件请求

流程

第一次的响应报文预先提供Last-modifiedETag,第二次请求时就可以带上缓存里的原值,验证资源是否是最新的,如果资源没有变,服务器就回应一个304 Not Modified,表示缓存依然有效

image.png

  1. Last-modifiedif-Modified-Since

Last-modified是文件的最后修改时间, 这种方式存在两个问题

  • 一个文件在一秒内修改了多次,但因为修改时间是秒级,所以这一秒内的新版本无法区分
  • 一个文件定期更新,但有时会是同样的内容,实际上没有变化,用修改时间就会误以为发生了变化,传送给浏览器就会浪费带宽
  1. ETagIf-None-Match

ETag是资源的一个唯一标识,可以精确地识别资源的变动情况,让浏览器能够更有效地利用缓存

  • ETag 有强``弱的区分
  • 强 ETag 要求资源在字节级别必须完全相符
  • 弱 ETag 在值前有个W/标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变

HTTP代理

代理相关头字段

Via

image.png

  • 请求头或响应头里都可以出现,每当报文经过一个代理节点,代理服务器就会把自身的代理主机名(或者域名)追加到字段的末尾
  • Via 字段只解决了客户端和源服务器判断是否存在代理的问题,不能知道对方的真实信息

X-Forwarded-For

  • 每经过一个代理节点就会在字段里追加请求方的 IP 地址。在字段里最左边的 IP 地址就是客户端的地址

X-Real-IP

  • 记录客户端 IP 地址
  • 如果客户端和源服务器之间只有一个代理,X-Forwarded-For的值等于X-Real-IP

缓存代理

HTTP 传输链路上,不只是客户端有缓存,服务器也有缓存,所以代理服务器也有缓存

缓存代理服务

  • 在没有缓存的时候,代理服务器每次都是直接转发客户端和服务器的报文,中间不会存储任何数据,只有最简单的中转功能
  • 在有缓存的时候,代理服务器把报文转发给客户端,然后把报文存入自己的 Cache 里,下一次再有相同的请求,代理服务器就可以直接发送 304 或者缓存数据,不必再从源服务器那里获取。降低了客户端的等待时间,节约了源服务器的网络带宽

image.png

源服务器的缓存控制相关头部字段

  1. privatepublic
  • 客户端的缓存只是用户自己使用,而代理的缓存可能会为非常多的客户端提供服务
  • private表示缓存只能在客户端保存, 不能放在代理上与别人共享
  • public的意思就是缓存完全开放,谁都可以存,谁都可以用
  1. proxy-revalidate
  • 要求代理的缓存过期后必须验证,客户端不必到源服务器获取缓存信息,只需到代理获取缓存信息
  1. s-maxage
  • 缓存生效时间,类似于max-age,只针对代理服务器
  1. no-transform
  • 禁止代理对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式

image.png

注意:源服务器在设置完Cache-Control后必须要为报文加上Last-modifiedETag字段。否则,客户端和代理后面就无法使用条件请求来验证缓存是否有效,也就不会有 304 缓存重定向

客户端的缓存控制相关头部字段

max-ageno_storeno_cache 同之前

  1. max-stale

如果代理上的缓存过期了也可以接受,但不能超过max-stale的值

  1. min-fresh

缓存必须有效,而且必须在min-fresh的值后依然有效

  1. only-if-cached

表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504(Gateway Timeout)