阅读 491

万字长文,掌握必备网络知识(下篇)来了解一下HTTP协议的相关知识

目录

一、HTTP/1.1
  URI与URL
  报文结构
  状态码
  Cookie
  常用方法
二、HTTP/2.0
三、HTTPS
四、跨域
  同源策略
  解决方案
五、攻击
  XSS
  CSRF
六 、缓存
  强缓存
  协商缓存
七、其它
  fetch与ajax
  性能优化
附录
复制代码

本文为原创文章,万字长文,建议先码后读。

一、HTTP/1.1

HTTP 于 1990 年问世,直到 1996 年 5 月才被正式作为标准公布,HTTP/1.0 就此诞生。1999 年,HTTP/1.1 公布,这个也是目前主流的 HTTP 协议版本。

HTTP 协议工作在应用层,请求从客户端发出,最后服务器响应请求并返回。

URI 与 URL

URI 是 Uniform Resource Identifier 的缩写,译为 “统一资源标识符”,用来标示抽象或物理资源。

URI

URI 可以分为 URL、URN 或 同时具备 locators 和 names 特性的一个紧凑字符串。

  • URN 好比是名字,确定了它的身份。
  • URL 就好比是地址,提供了找到它的方式

HTTP 报文结构

HTTP 协议交互的信息称为 HTTP 报文。

⇩ HTTP 报文结构

http 报文结构

HTTP 报文由三个部分组成

  • 报文首部:服务端或客户端需要处理的请求或响应的内容及属性
  • 空行(CR+LF):CR-回车符,LF-换行符
  • 报文主体:被发送的数据

客户端发起的 HTTP 报文称为 请求报文,服务端返回的 HTTP 报文称为 响应报文;主要差异体现在 报文首部。

请求报文首部自上而下分别是:

  • 请求行:包含用于请求的方法,请求 URI 和 HTTP 版本
  • 首部字段
    • 请求首部字段
    • 通用首部字段
    • 实体首部字段
  • 其它字段

响应报文首部自上而下分别是:

  • 状态行:表明响应结果的状态码,原因短语和 HTTP 版本
  • 首部字段
    • 响应首部字段
    • 通用首部字段
    • 实体首部字段
  • 其它字段

HTTP/1.1 规范定义了 47 种首部字段,见文末附录。这里先讲一下常用的 Content-Type,后面小节还会讲到其它常用的首部字段。

Content-Type:说明了实体主体内对象的媒体类型

  • text/html:HTML 文档标记
  • text/plain:普通 ASCII 文档标记
  • text/xml:忽略 xml 头所指定编码格式而默认采用 us-ascii 编码
  • image/jpeg:JPEG 图片标记
  • image/gif:GIF 图片标记
  • application/javascript:js 文档标记
  • application/xml:xml 文件标记;使用 xml 格式的数据
  • application/octet-stream:任意二进制数据
  • application/x-www-form-urlencoded:用于表单提交,将请求参数用 key1=val1&key2=val2 的方式进行组织,并放到请求实体里
  • application/json:以“键-值”对的方式组织的数据。这个使用这个类型,需要参数本身就是 json 格式的数据,参数会被直接放到请求实体里,不进行任何处理
  • multipart/form-data:多部分多媒体类型;首先生成了一个 boundary 用于分割不同的字段,在请求实体里每个参数以------boundary 开始,然后是附加信息和参数名,然后是空行,最后是参数内容。多个参数将会有多个 boundary 块。如果参数是文件会有特别的文件域。最后以------boundary–为结束标识。multipart/form-data 支持文件上传的格式,一般需要上传文件的表单则用该类型。

只列出部分常用可选值,完整版请跳转到这里 MIME 参考手册

常见状态码

状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了错误。

1xx:Informational(信息性状态码),接收的请求正在处理

  • 100:请求已被部分处理
  • 101:切换协议

2xx:Success(成功状态码),请求正常处理完毕

  • 200:请求成功
  • 204:请求处理成功,但是没有内容返回
  • 206:返回指定范围的内容

3xx:Redirection(重定向状态码),需要进行附加操作以完成请求

  • 301:永久重定向
  • 302:临时重定向
  • 303:临时重定向,要求使用 GET 方法
  • 304:请求资源未更改,直接使用缓存

4xx:Client Error(客户端错误状态码),服务器无法处理请求

  • 400:请求出错
  • 401:未授权
  • 403:被服务器拒绝访问
  • 404:服务器上没有请求的资源

5xx:Server Error(服务器错误状态码),服务器处理请求出错

  • 500:服务器内部错误
  • 503:服务器暂时无法处理请求
  • 504: 服务器作为网关或代理,没有及时从上游服务器收到请求,请求超时

Cookie

HTTP 是一种 无状态(stateless) 协议。看到这有的同学可能要说了,它不是有状态码吗?怎么无状态了?

这话没错,状态码是指通信状态,而 无状态 指的是 不保存状态。每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这么设计主要是为了能处理大量事务,确保协议的可伸缩性。

假设是需要登录的网页,但是 HTTP 本身又不能记录之前的状态,所以每次跳转新页面都要再次登录。

保留无状态协议这个特征的同时又要解决类似的矛盾问题,于是引入了 Cookie。

Cookie 的工作机制是用户识别及状态管理。Web 网站为了管理用户的状态会通过 Web 浏览器,把一些数据临时写入用户的计算机内。接着当用户访问该 Web 网站时,可通过通信方式取回之前发放的 Cookie。

Cookie 由以下字段组成

  • name:名称
  • value:值
  • domain:允许访问此 cookie 的域名
  • path:允许访问此 cookie 的页面路径
  • expires/Max-Age:超时时间,不设置则于 session 一起失效(关闭浏览器)
  • size:cookie 的大小
  • httponly:若为 true,则只有在 http 请求头中会带有此 cookie 的信息,而不能通过 document.cookie 来访问此 cookie(可以一定程度上地防止信息盗取)
  • secure:是否只能通过 https 来传递此条 cookie

示例:username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT

常用方法

HTTP/1.1 提供了 GET、POST、PUT、HEAD、DELETE、OPTIONS、TRACE、CONNECT 八个可使用的方法

  • GET:获取资源。参数直接拼接在 url,对长度有限制,不同浏览器、服务器限制长度各不相同。
  • POST:传输实体主体。参数(实体主体)作为请求的有效载荷数据(补充项)被传输
  • PUT:用来传输文件。自身不带验证机制,因此任何人都可以上传文件,不推荐
  • HEAD:获取报文首部。不返回报文主体部分,多用于确认 URI 的信息
  • DELETE:删除文件。与 PUT 一样不带验证机制,一般不用
  • OPTIONS:查询针对请求 URI 指定的资源所能支持的方法。
  • TRACE:追踪路径。可以查询发送出去的请求是怎样被加工修改/ 篡改的。容易造成 XST(跨站追踪)攻击,通常不使用。
  • CONNECT:要求在与代理服务器通信时建立隧道,实现用隧道协议进行 TCP 通信。主要使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。

网上还有说 post 请求会发出两个数据包,先发 header 再发实体;get 只发一个数据包。
这个说法其实是没错的,发几个包取决于客户端如何实现,目前已知的只有 ruby 会这里处理,大部分都是发一个数据包

注:实体主体指作为请求或响应的有效载荷数据(补充项)被传输,其内容由实体首部和实体主体组成

二、HTTP/2.0

2015 年 5 月,HTTP/2.0 协议正式版发布。相比于 HTTP 1.x ,大幅度的提升了 web 性能。在与 HTTP/1.1 完全语义兼容的基础上,进一步减少了网络延迟。

先借助 Chrome Developer Tools 来看看 HTTP/2.0 与 HTTP/1.1 性能差多少

http/1.1

http/2.0

通过对比可以发现,同一资源使用 http/2.0 比使用 http/1.1 加载时所花的时间要少 46% ~ 95% 左右(对,我真的去算了 -_-)

为什么能这么 “快” 呢?

这还要从 HTTP/2.0 的出身讲起了。HTTP/2.0 协议主要基于 Google 开发的 SPDY 协议,通过 压缩、多路复用 和 优先级 等技术,缩短网页的加载时间并提高安全性。SPDY 协议的核心思想是尽量减少 TCP 连接数。

HTTP/2.0 所做的改进或者说特点包括:

  • 二进制分帧
    • 在 应用层 和 运输层 之间增加一个二进制分帧层
    • 在二进制分帧层中,将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码
  • 多路复用
    • 在一个 TCP 连接中存在多个流,即可以同时发送多个请求
    • 在客户端帧乱序发送,到对端后再根据每个帧首部的流标识符重新组装。
  • 首部压缩
    • 使用 HPACK(HTTP2 头部压缩算法)压缩格式对传输的 header 进行编码
    • 并在两端维护了索引表,用于记录出现过的 header,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值
  • 服务端推送
    • 服务端可以对客户端的一个请求发送多个响应

三、HTTPS

HTTP 协议虽好,但是依旧不够安全;主要有以下三个方面:

  1. 因为使用明文通信,内容可能被 “窃听”
  2. 不会验证通信方的身份,因此可能遭遇身份伪装
  3. 也无法验证报文是否完整,可能会被篡改

因此,我们需要一个可以加密、能够验证通信方身份、并且能保护报文完整的协议,于是 HTTPS 诞生了。

HTTP+ 加密 + 认证 + 完整性保护 = HTTPS

我们把添加了加密及认证机制的 HTTP 称为 HTTPS(HTTP Secure)

需要注意的是,HTTPS 并不是 应用层 的协议,更像是 SSL 与 HTTP 的统称。
(这里说的 SSL 是对 SSL3.0 与 TSL1.0 协议的统称,毕竟 TSL 也是以 SSL 为原型开发的协议)


http+ssl

如上图所示,通常 HTTP 是直接与 TCP 进行通信;当使用了 SSL 时,HTTP 会先与 SSL 通信,SSL 再与 TCP 通信。

针对上面列出的 HTTP 存在的 3 个问题,HTTPS 通过以下方法完善

  1. 采用 对称加密非对称加密 混合的加密方式,确保内容不被“窃听”。具体做法如下:

    • 客户端向服务端请求获取 公钥
    • 客户端生成 随机密码串 使用 公钥 加密后发送给服务端
    • 服务端使用私钥解密报文得到 随机密码串
    • 后续通信种使用 随机密码串 加密通信
  2. HTTPS 身份验证有两种:

  • 对公钥验证
    • 服务端将自己的 公钥 登录至 数字证书认证机构
    • 认证机构用自己的 私钥 对服务端的 公钥 签署数字签名并颁发证书
    • 客户端拿到服务端的证书后,使用认证机构的公钥向认证机构校验签名,确认 公钥 的真实性
  • 客户端身份认证
    • 事先将客户端证书分发给客户端,且客户端必须安装此证书
    • 服务端发送 Certificate Request 报文,要求客户端提供客户端证书
    • 客户端把客户端证书信息以 Client Certificate 报文方式发送给服务端
    • 服务端验证通过后,领取证书内客户端的 公钥,开始加密通信
  1. 发送数据时会附加一种叫做 MAC(Message Authentication Code)的报文摘要。MAC 能够查知报文是否遭到篡改,从而保护报文的完整性。

最后来看一下完整的 HTTPS 通信过程

来源:《图解HTTP》

  1. 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含 SSL 版本以及 加密组件

  2. 服务端可进行 SSL 通信时,会以 Server Hello 报文作为应答。报文包含 SSL 版本,及从客户端发来的加密组件中筛选后得到的加密组件内容

  3. 服务端发送 Certificate 报文。报文中包含 公开密钥证书。

  4. 服务端发送 Server Hello Done 报文通知客户端。SSL 第一次握手结束

  5. 客户端发送 Client Key Exchange 报文。包含经步骤 3 获取的 公钥 加密后的 随机密码串(Pre-master secret)

  6. 客户端继续发送 Change Cipher Spec 报文。告知服务端,此后的通信采用步骤 5 的 随机密码串 加密。

  7. 客户端发送 Finished 报文。包含连接至今全部报文的整体校验值

  8. 服务端同样发送 Change Cipher Spec 报文

  9. 服务端同样发送 Finished 报文

  10. Finished 报文交换完毕之后,SSL 连接建立完成

  11. 应用层协议通信,即发送 HTTP 响应

  12. 最后由客户端断开连接。断开时,发送 close_notify 报文

  13. 客户端再发送 TCP FIN 报文来关闭与 TCP 的通信

四、跨域

基于同源策略,当协议域名端口至少有一个不一致时就会发生跨域

出现跨域时,请求已正确发出,服务端也返回了数据,只是被浏览器拦截了。

同源策略

同源策略是一种安全策略,限制一个源的文档或者脚本与另一个源的资源进行交互。能够阻隔恶意文档,减少可能被攻击的媒介。

限制的内容包括:

  • cookie、localStorage、sessionStorage、indexedDB 等存储型内容
  • DOM 节点
  • ajax 请求

允许跨域加载资源的标签:

  • <img src=xxx>
  • <script src=xxx>
  • <link href=xxx>

注:CORB 可能会拦截从<script><img>发出且响应数据为 json、html 或 xml 的请求

解决方案

常见的方式有以下 5 种:

  1. JSONP:本质上就是利用 <script> 标签发起 get 请求
  2. CORS:服务端需要设置响应头 Access-Control-Allow-Origin。如需传 cookie 还需设置 Access-Control-Allow-Credentials 为 true,客户端设置请求头 withCredentials 为 true
  3. 代理服务器转发:使用同源服务器转发跨域请求
  4. websocket
  5. postMessage()方法:跨文档、跨域、多窗口、嵌套的 iframe 通信都是 ok 的

发起 CORS 请求时,若请求为复杂请求,在正式发起前会发出一次 OPTIONS 方法的“预检”请求,检查服务端是否允许跨域请求。
(复杂请求 即 非简单请求)

简单请求需满足以下两个条件:

  1. 方法为 GET/HEAD/POST
  2. Content-Type 为 text/plain/multipart/form-data/application/x-www-form-urlencoded

五、攻击

web 应用中的安全问题包括但不限于:跨站脚本(xss)、SQL 注入、OS 命令注入、HTTP 首部注入、邮件首部注入、强制浏览漏洞、强制浏览、不正确的错误消息处理、开放重定向、会话劫持、会话固定攻击、跨站点请求伪造(csrf)

有点多,这里主要讲一下常见的两种常见的攻击

XSS

XSS 即跨站脚本攻击。

指通过存在安全漏洞的 Web 网站注册用户的浏览器内运行非法的 HTML 标签或 JavaScript 进行的一种攻击

常见攻击形式有:

  • 使用伪造的表单骗取用户的账号密码等信息
  • 使用脚本获取用户的 Cookie 值
  • 在页面上展示攻击者伪造的文章或者图片等内容

防范 xss 攻击可以从两个方面:

  • 设置过滤器,对用户输入的数据提前过滤
  • 使用 Cookie 的 HttpOnly 字段避免恶意脚本读取 Cookie

CSRF

CSRF 跨站点请求伪造攻击。

指攻击者通过设置好的陷阱,强制对已完成认证的用户进行非预期的个人信息或设定信息等某些状态更新,属于被动攻击

结合案例讲解一下

  • 攻击者登录网站 M,并在网站留言板上发表含恶意代码的留言 <img src='http://exampleM.com/gift/send?up=xxx'>
  • 用户 A 登录网站 M 后,浏览留言板时触发了攻击者留下的恶意代码
  • 浏览器中的 Cookie 已有用户 A 的登录信息,触发恶意代码后以 A 的身份向攻击者送出了礼物

防范 CSRF 攻击主要从以下几个方面入手

  • 不要使用 GET 请求对数据做修改
  • 阻止第三方获取用户 Cookie(Cookie 的 SameSite 属性)
  • 阻止第三方请求接口
  • 发起请求时带上验证信息,如 Token、验证码等

Cookie 的 SameSite 属性三个值可选:

  • Strict:完全禁止第三方 Cookie
  • Lax(Chrome 默认):只有导航到目标网址的 GET 请求才发送 Cookie,这只包括三种情况:链接,预加载请求,GET 表单
  • None:关闭该属性。不过,前提是必须同时设置 Secure 属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

六、缓存

我们这里讲的缓存主要指的是 HTTP 的缓存机制

HTTP 缓存机制就是,配置服务器响应头来告诉浏览器是否应该缓存资源、是否强制校验缓存、缓存多长时间;浏览器非首次请求根据响应头是否应该取缓存、缓存过期发送请求头验证缓存是否可用还是重新获取资源的过程

强缓存

强缓存 在首次访问服务端拿到数据后,在过期时间内不会再发重复的请求,而是直接从缓存中读取。强缓存的核心便是如何判断缓存是否过期了。

强缓存通过 Cache-Control 来实现。

注:HTTP/1.0 通过设置 Expires 实现,Expires 与 Cache-Control 都设置了的话优先使用 Cache-Control。

Cache-Control 作为请求头字段有如下可选指令:

指令参数说明
no-cache强制向源服务器再次验证
no-store不缓存请求或响应的任何内容
max-age = [ 秒]必需响应的最大 Age 值
max-stale( = [ 秒])可省略接收已过期的响应
min-fresh = [ 秒]必需期望在指定时间内的响应仍有效
no-transform代理不可更改媒体类型
only-if-cached从缓存获取资源
cache-extension-新指令标记(token)

Cache-Control 作为响应头字段有如下可选指令:

指令参数说明
public可向任意方提供响应的缓存
private可省略仅向特定用户返回响应
no-cache可省略缓存前必须先确认其有效性
no-store不缓存请求或响应的任何内容
no-transform代理不可更改媒体类型
must-revalidate可缓存但必须再向源服务器进行确认
proxy-revalidate要求中间缓存服务器对缓存的响应有效性再进行确认
max-age = [ 秒]必需响应的最大 Age 值
s-maxage = [ 秒]必需公共缓存服务器响应的最大 Age 值
cache-extension-新指令标记(token)

协商缓存

协商缓存每次读取数据时都需要跟服务器通信,并且会增加缓存标识

  • 第一次请求服务端数据时,服务端会返回数据以及缓存标识
  • 第二次请求相同资源时,浏览器会先发送缓存标识给服务端
  • 服务端收到标识后判断标识是否匹配
    • 若不匹配,说明资源有更新,服务端返回新数据以及新的缓存标识
    • 若匹配,说明资源没有更新,返回 304 状态码,浏览器直接读取缓存中的数据

(HTTP/1.0 的缓存标识为 Last-Modified,HTTP/1.1 的缓存标识为 Etag)

Etag 值存在强弱之分

  • 强 Etag:不论资源发生多么细微的变化都会改变其值
  • 弱 Etag:在字段值前附加 “W/”,如:ETag: W/"usagi-1234",只有资源发生根本改变时才会改变其值

借助一张图完整了解一下 HTTP 的缓存机制

http缓存

在 强制缓存 失效后开始走 协商缓存。
浏览器读取缓存优先读取内存中的缓存(memory cache),若没有再读取硬盘上的缓存(disk cache)

七、其它

fetch

先来康康 fetch 是啥

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

即 官方出品的替代 XMLHttpRequest 的跨网络异步获取资源的方式

fetch 的特点:

  • 基于 promise 实现的,也可以结合 async/await
  • fetch 请求默认是不带 cookie 的,需要设置 fetch(URL,{ credentials: 'include' })
    Credentials 的三种参数:omit(从不发送 cookie),same-origin(同源发送),include(始终发送)
  • 服务器返回 400、500 状态码时并不会触发 reject,仅当网络出错导致请求不能完成时,fetch 才会被 reject
  • 所有版本的 IE 均不支持原生 Fetch
  • fetch 不支持取消一个请求 使用 XMLHttpRequest 可以用 xhr.abort() 方法取消一个请求(虽然这个方法也不是那么靠谱,同时是否真的取消了还取决于服务端实现)
  • fetch 无法查看请求的进度 使用 XMLHttpRequest 你可以通过 xhr.onprogress 回调来动态更新请求的进度,而这一点目前 fetch 还没有原生支持

性能优化

这里讲一下前端在网络上应该如何优化

1. DNS 优化 DNS 解析也是需要耗时的,解决方案无外乎就两点:

  • 减少 DNS 请求次数
  • 使用 DNS 预获取 即 dns-prefetch

可以在 head 中添加如下配置(域名记得更换)

<meta http-equiv="x-dns-prefetch-control" content="on" />
<link rel="dns-prefetch" href="//picture.example.com" />
<link rel="dns-prefetch" href="//static.example.com" />
<link rel="dns-prefetch" href="//example.tool.com" />
复制代码

2. 减少请求次数和资源大小

  • 使用 http 编码压缩技术,如常用的 gzip
  • 静态资源走 CDN,不了解 CDN 的建议看一下上篇

3. 缓存

  • 前面提到的 强缓存、协商缓存
  • 使用 Service Worker 开辟独立线程进行缓存
  • 使用推送缓存,Push Cache(HTTP/2.0)

4. 直接上 HTTP/2.0

附录:

通用首部字段

字段名说明
Cache-Control控制缓存的行为
Connection逐跳首部、连接的管理
Date创建报文的日期时间
Pragma报文指令
Trailer报文末端的首部一览
Transfer-Encoding指定报文主体的传输编码方式
Upgrade升级为其他协议
Via代理服务器的相关信息
Warning错误通知

请求首部字段

字段名说明
Accept用户代理可处理的媒体类型
Accept-Charset优先的字符集
Accept-Encoding优先的内容编码
Accept-Language优先的语言(自然语言)
AuthorizationWeb 认证信息
Expect期待服务器的特定行为
From用户的电子邮箱地址
Host请求资源所在服务器
If-Match比较实体标记(ETag)
If-Modified-Since比较资源的更新时间
If-None-Match比较实体标记(与 If-Match 相反)
If-Range资源未更新时发送实体 Byte 的范围请求
If-Unmodified-Since比较资源的更新时间(与 If-Modified-Since 相反)
Max-Forwards最大传输逐跳数
Proxy-Authorization代理服务器要求客户端的认证信息
Range实体的字节范围请求
Referer对请求中 URI 的原始获取方
TE传输编码的优先级
User-AgentHTTP 客户端程序的信息

响应首部字段

字段名说明
Accept-Ranges是否接受字节范围请求
Age推算资源创建经过时间
ETag资源的匹配信息
Location令客户端重定向至指定 URI
Proxy-Authenticate代理服务器对客户端的认证信息
Retry-After对再次发起请求的时机要求
ServerHTTP 服务器的安装信息
Vary代理服务器缓存的管理信息
WWW-Authenticate服务器对客户端的认证信息

实体首部字段

字段名说明
Allow资源可支持的 HTTP 方法
Content-Encoding实体主体适用的编码方式
Content-Language实体主体的自然语言
Content-Length实体主体的大小(单位:字节)
Content-Location替代对应资源的 URI
Content-MD5实体主体的报文摘要
Content-Range实体主体的位置范围
Content-Type实体主体的媒体类型
Expires实体主体过期的日期时间
Last-Modified资源的最后修改日期时间

碍于本人不是 网工 出身,文中若有表述不当之处还请各位大佬指正。文章同时发在个人公众号 MelonField,欢迎关注。

参考:

文章分类
前端
文章标签