likes
comments
collection
share

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

作者站长头像
站长
· 阅读数 22

HTTP 前世今生

  • HTTP 0.9:
    • 出现原因:用来在网络之间传递 HTML 超文本 的内容。
    • 实现:采用了基于请求响应的模式,从客户端发出请求,服务器返回数据。
    • 特点:只有一个请求行、服务器也没有返回头信息、返回的文件内容是以 ASCII 字符流传输的。
  • HTTP 1.0:
    • 出现原因:需要支持 JavaScript、CSS、图片、音频、视频等多种类型的文件下载。
    • 实现:在之前的基础上,每次与服务器交互,需要新开一个连接。
    • 特点:更好的多类型文件支持、引入状态码、提供缓存机制、加入 User-Agent。
  • HTTP 1.1:
    • 出现原因:HTTP 1.0 每次请求都需要创建连接、关闭连接的形式造成了性能上的缺陷。
    • 实现:在之前的基础上,默认支持长连接(Connection: keep-alive,即在一个TCP连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。
      • 建立一次连接,多次请求均由这个连接完成。
      • 允许客户端不用等待上一次请求结果返回,就可以发出下一次请求。
    • 特点:改进持久连接、不成熟的 HTTP 管线化、提供虚拟主机的支持、对动态生成的内容提供了完美支持、引入了客户端 Cookie 和安全机制。
  • HTTP 2.0:
    • 出现原因:TCP 慢启动、网络拥塞、HTTP 1.1 队头阻塞。
    • 实现:一个域名只使用一个 TCP 长连接来传输数据;实现资源的并行请求。
    • 特点:引入二进制分帧层,实现 HTTP 多路复用技术,并行处理请求;设置请求优先级,优先处理高优先级请求;提前向客户端推送数据;压缩头部,提高传输效率;采用二进制格式而非文本格式。

HTTP 常见状态码

在 HTTP 协议中,状态码可主要分为以下五类:

类别原因描述
1xx信息性状态码接受的请求正在处理
2xx成功状态码请求正常处理完毕
3xx重定向状态码需要附加操作来完成请求
4xx客户端错误状态码客户端处理请求出错
5xx服务端错误状态码服务器处理请求出错
  1. 1xx 信息性状态码: 代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。
    • 100:服务器收到了请求的初始部分,客户端可继续请求

      常用于大文件上传时,客户端询问服务器是否可接收

    • 101:服务器应客户端升级协议的请求,切换协议
  2. 2xx 成功状态码: 代表请求被正常处理了。
    • 200:客户端的请求被服务器正常处理了
    • 204:客户端发送的请求被服务器正常处理了,但是没有返回的内容,响应报文中不包含实体的主体部分

      常用于客户端单向发送请求给客户端的情况

    • 206:客户端进行了范围请求,服务器成功执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容

      HTTP 下载工具常使用此类响应实现 断点续传 或者 将一个大文档分解为多个下载段同时下载

  3. 3xx 重定向状态码: 代表浏览器需要执行某些特殊的处理以正确处理请求。
    • 301:永久重定向。被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一

      常用于更换域名

    • 302:临时性重定向,请求的资源被分配到了新的 URI,希望用户(本次)能使用新的 URI 访问资源

      常用于 未登录用户访问内容时重定向到登录页面、访问 404 页面重定向到首页 等

    • 303:由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源
    • 304:表示协商缓存。状态码 304 并不是一种错误,而是告诉客户端有缓存,直接使用缓存中的数据。返回页面的只有头部信息,是没有内容部分的,这样在一定程度上提高了网页的性能
    • 307:临时性重定向,和状态码 302 具有相同的含义,区别在于 307 会遵从浏览器标准,不允许浏览器将原本为 POST 的请求重定向到 GET 请求上
  4. 4xx 客户端错误状态码: 表明客户端是发生错误的原因所在。
    • 400:请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求
    • 401:发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。若之前已进行过一次请求,则表示用户认证失败

      以下情况会出现 401 状态码:

      • 登录失败
      • 由于 ACL 对资源的限制而未获得授权
      • 筛选器授权失败
      • ISAPI/CGI 应用程序授权失败
      • 访问被 Web 服务器上的 URL 授权策略拒绝
    • 403:请求资源的访问被服务器拒绝了

      通常是访问权限问题

    • 404:服务器上无法找到请求的资源
    • 405:服务器已知请求方法,但已被禁用且无法使用
  5. 5xx 服务器错误状态码: 表明服务器是发生错误的原因所在。
    • 500:服务器端在执行请求时发生了错误

      有可能是 Web 应用存在的 bug 或某些临时的故障

    • 502:扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的
    • 503:服务器暂时处于超负载或正在停机维护,无法处理请求

      这个状况是临时的,并且将在一段时间以后恢复

    • 504:网关或者代理的服务器无法在规定的时间内获得想要的响应

      常见于代码执行时间超时,或者发生了死循环

❓ Question 1️⃣:同样是重定向,301、302、303、307、308 有什么区别?

301 和 308 都是永久性重定向,不同的是 308 遵循浏览器规范不允许将 POST 请求重定向为 GET。

302,303,307 都是临时重定向,但是 303 不管原请求是什么方法,重定向请求的方法都是 GET(或 HEAD,不常用),而 307 不允许将 POST 重定向为 GET。

❓ Question 2️⃣:HTTP 状态码 304 出现次数多比较好还是少比较好?

服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回 304 ,此时客户端调用缓存内容,不必进行二次下载。

状态码 304 不应该认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。

搜索引擎蜘蛛会更加青睐内容源更新频繁的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在一定时间内一直处于 304 的状态,那么蜘蛛可能会降低对网站的抓取次数。相反,若网站变化的频率非常之快,每次抓取都能获取新内容,那么日积月累,的回访率也会提高。

产生较多 304 状态码的原因:

  • 页面更新周期长或不更新
  • 纯静态页面或强制生成静态 html

304 状态码出现过多会造成以下问题:

  • 网站快照停止
  • 收录减少
  • 权重下降

HTTP 报文

HTTP 报文分为 请求报文响应报文

(一)请求报文

请求报文由三部分组成,分别是:请求行、请求头部、实体(请求的数据)

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

1. 请求行

请求行是 http 请求报文的第一个行。请求行主要由 方法字段URL 字段 和 HTTP 协议版本字段 构成。

  • 方法字段:方法字段严格区分大小写,当前 HTTP 协议中的方法都是大写,常见的请求方法包括:GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT 等。
  • URL 字段:一个完整的包括类型、主机名和可选路径名的统一资源引用名。

请求行例子:GET /index.html HTTP/1.1

2. 请求头部

请求头部由关键字/值对组成,每⾏⼀对,关键字和值⽤英⽂冒号 “:” 分隔

  • User-Agent:产⽣请求的浏览器类型。
  • Accept:客户端可识别的内容类型列表。
  • Host:请求的主机名,允许多个域名同处⼀个IP地址,即虚拟主机。

Request Headers 请求报文中常见的首部:

Accept 首部:

  • Accept:指明服务器能发送的媒体类型。(eg: application/json, text/plain,  / )。使用它并通过 content-type 响应头通知客户端它的选择。
  • Accept-Charset:支持使用的字符集。
  • Accept-Encoding:支持使用的编码方式。(eg: gzip, deflate, br)
  • Accept-Language:支持使用语言。(eg: en,zh;q=0.9)。使用它并通过 content-language 响应头通知客户它的选择。

条件首部:

  • Expect:告诉服务器能够发送来哪些媒体类型
  • If-Modified-Since:是否在指定时间以来修改过此资源
  • If-None-Match:如果提供的实体标记与当前文档的实体标记不符,就获取此文档

安全相关首部:

  • Authorization:客户端提交给服务端的认证数据。(eg: 帐号和密码,token)。用于向服务器认证用户代理的凭证,通常在服务器响应 401 Unauthorized 状态和 WWW-Authenticate 标题后。
  • Cookie:客户端发送给服务器端身份标识。

其他首部:

  • Host:请求的主机名和端口号,虚拟主机环境下用于不同的虚拟主机。(eg: github.com:8080)
  • Range:请求实体的字节范围。(eg: 存在,浏览器可能会尝试恢复中断的下载,而不是从头再次开始。)
  • Referer:指明了请求当前资源的原始资源的URL。
  • User-Agent:用户代理,使用什么工具发出的请求。(eg: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36)

预检请求

  • Origin:<origin> 表明预检请求或实际请求的源站。在所有访问控制请求(Access control request)中,Origin 首部字段总是被发送。
  • Access-Control-Request-Method:<method> 用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。
  • Access-Control-Request-Headers:<field-name>[, <field-name>]* 用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。

通用首部 Request Headers 与 Response Headers 中都有的首部:

  • Connetion:连接管理。(eg: keep-alive、close)
  • Cache-Control:缓存指示。
  • Content-Length 标头:实体的长度。
  • Content-Type 标头:实体的媒体类型。
  • Via:显示报文经过的中间节点。

3. 请求数据

即 POST、PUT 等请求携带的数据。

(二)响应报文

响应报文也由三部分组成,分别是 响应状态行、响应首部行 和 响应实体。

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

1. 响应状态行

状态行包括三个字段:协议版本、状态码 与 原因短语。

响应状态行例子:HTTP/1.1 200 OK

2. 响应首部行

  • Access-Control-Allow-Credentials:true 是否允许浏览器读取 response 的内容当用在对preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials。
  • Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header 在跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。(浏览器访问头的白名单)
  • Access-Control-Allow-Methods: <method>[, <method>]* 用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
  • Access-Control-Allow-Origin: <origin> | * origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。如果服务端指定了具体的域名而非“ * ”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。
  • Access-Control-Allow-Headers: <field-name>[, <field-name>]* 用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
  • Access-Control-Max-Age: <delta-seconds> 指定了 preflight 请求的结果能够被缓存多久(秒)
  • Age:(从最初创建开始)响应持续时间。包含以秒计的对象一直在代理缓存的时间。
  • Allow:允许对此资源使用的请求方法。如果服务器响应 405 Method Not Allowed 状态码来指示可以使用哪种请求方法,则必须发送此标头。例如,空白 Allow 标题表示该资源不允许任何请求方法,例如可能暂时针对给定资源发生。

内容首部:

  • Content-Disposition:作为网页或作为网页的一部分或作为附件下载并且本地保存。(eg:attachment;filename=index.doc)
  • Content-Encoding:支持的编码。
  • Content-Language:支持的自然语言。
  • Content-Length:文本长度。
  • Content-Location:资源所在位置。
  • Content-Range:在整个资源中此实体表示的字节范围。
  • Content-Type:主体的对象类型。

协商首部:

  • Accept-Ranges: 对当前资源来讲,服务器所能够接受的范围类型
  • Vary: 首部列表,服务器会根据列表中的内容挑选出最适合的版本发送给客户端 跟安全相关的响应首部:
  • Set-Cookie: 服务器端在某客户端第一次请求时发给令牌
  • WWW-Authentication: 质询,即要求客户提供帐号和密码

缓存首部:

  • ETag: 协商缓存中的强校验器。如果给定 URL 处的资源发生更改,则Etag必须生成新值
  • Expires: http1.0 的产物,资源过期时间,绝对时间,强缓存控制字段。Cache-Control 优先级高于 - Expires。
  • Last-Modified: 上一次的修改时间

其他首部:

  • Date:消息产生的时间。

3. 响应实体

实体包含了 Web 客户端请求的对象。

Content-Length 标头及 Content-Type 标头用于计算实体的位置、数据类型和数据长度。

当 Web 服务器接收到 Web 客户端的请求报文后,对 HTTP 请求报文进行解析,并将 Web 客户端的请求的对象取出打包,通过 HTTP 响应报文将数据传回给 Web 客户端,如果出现错误则返回包含对应错误的错误代码和错误原因的 HTTP 响应报文。

HTTP 请求

在不同版本的 HTTP 中,请求方式如下:

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

  • GET:请求指定资源的表示。通常用于向服务器获取数据
  • POST:将实体提交到指定的资源,即将数据发送到服务器。请求主体的类型由 Content-Type 标头指示
  • PUT:创建新的资源或替换请求负载目标资源的表示。即上传文件,更新数据

POST 与 PUT 区别:

PUT 是幂等的:调用它一次或多次连续具有相同的效果(也就是没有侧面的效果)。

PUT 是将客户端的资源放在请求 URI 上。对于服务器到底是创建还是更新,由服务器返回的 HTTP Code 来区别。

如果用 PUT 来达到更改资源,需要客户端提交资源全部信息,如果只有部分信息,不应该使用 PUT(因为服务器使用客户端提交的对象整体替换服务器的资源)

  • DELETE:删除指定的资源。即删除服务器上的对象
  • HEAD:获取报文首部,与 GET 相比,不返回报文主体部分
  • OPTIONS:用于描述目标资源的通讯选择。客户端可以为 OPTIONS 方法指定一个特定的 URL,或者指定一个星号( * )来引用整个服务器。可以使用 OPTIONS 方法对服务器发起请求,以检测服务器支持哪些 HTTP 方法。
  • CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行 TCP 通信
  • TRACE: 回显服务器收到的请求,主要⽤于测试或诊断

OPTIONS 请求方法及使用场景

OPTIONS 是除了 GET 和 POST 之外的其中一种 HTTP 请求方法。

OPTIONS 方法是用于请求获得由 Request-URI 标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。该请求方法的响应不能缓存。

OPTIONS 请求方法的主要用途有两个:

  • 获取服务器支持的所有 HTTP 请求方法;
  • 用来检查访问权限。例如:在进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。

简单请求与复杂请求

简单请求 就是会直接发送请求,而复杂请求则会在发送真正的请求之前发一次 OPTIONS 预检请求,来试探服务器是否能够接收真正的请求,MDN 对 OPTIONS 预检请求的释义为检测服务器所支持的请求方法。

简单请求 符合如下两个条件:

  • 请求方式:GET、POST、HEAD,即HTTP1.0提供的三种请求方法
  • HTTP 头部信息不超过一下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

复杂请求 则符合如下几个条件之一:

  • 请求方式:PUT、DELETE
  • 自定义头部字段
  • 发送 json 格式数据

GET 和 POST 的使用小贴士

HTTP 的底层是 TCP/IP。所以 GET 和 POST 的底层也是 TCP/IP,也就是说,GET/POST 本质上都是 TCP 链接。

但是由于 HTTP 的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。GET 方式在使用时,传输长度有限制,通过地址栏传输,相对不安全,只能 URL 编码,只接受 ASCII 字符的参数类型等。所以,GET 与 POST 都有自己的语义,不能随便混用。

RESTful 风格

❓ Question:什么是 RESTful 架构:

  • 每一个 URI 代表一种资源
  • 客户端和服务器之间,传递这种资源的某种表现层
  • 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"

HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

在 RESTful 架构中,每个网址代表一种资源,所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合",所以 API 中的名词也应该使用复数。

GraphQL

GraphQL 是一种面向数据的 API 查询风格。GraphQL 规范,让前端自己描述自己希望的数据形式,服务端则返回前端所描述的数据结构。

GraphQL 是一种用于 API 的查询语言,为用户的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

  • 多终端的出现,APP、小程序、PC 端都需要相同的接口,但是又略有差异,常规接口需要提供几套,GraphQL 的话只需写好查询语句即可
  • 天生的聚合接口,当一个页面需要请求不同的数据时,通过 GraphQL 后可以实现用户自己聚合想要的数据
  • 不用被版本困扰,通过配置 GraphQL,更新接口时,无需关心版本问题,改变接口逻辑语句即可
  • 迁移简单,服务端在之前的接口上稍加改造就好,前端写查询语句

HTTP 与 HTTPS 的区别

概括来说,HTTPS = HTTP + 加密 + 完整性 + 身份认证。

两者之间的具体区别如下:

  • HTTPS 协议需要 CA 证书,费用较高;而 HTTP 协议不需要;
  • HTTP 协议是超文本传输协议,信息是明文传输的,HTTPS 则是具有安全性的 SSL 加密传输协议;
  • 使用不同的连接方式,端口也不同,HTTP 协议端口是 80,HTTPS 协议端口是 443;
  • HTTP 协议连接很简单,是无状态的;HTTPS 协议是有 SSL 和 HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加安全。

跨域

同源策略

什么是同源:

页面的协议,域名和端口都相同,则属于同一源,否则就是非同源

同源策略是对浏览器的安全保护机制。

同源策略是浏览器的行为,是为了保护本地数据不被 JavaScript 代码获取回来的数据污染,因此拦截的是客户端发出请求后响应回来的数据。即请求发送了,服务器响应了,但是无法被浏览器接收。

这是一个用于隔离潜在恶意文件的重要安全机制。(请求发送了,服务器也响应了,但是浏览器检测到如果是不同源,就会根据同源策略拦截响应后返回来的数据,无法接收)。

缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。

(一)同源策略的限制内容

  • 允许跨域写操作(Cross-origin writes)。例如:链接,重定向,表单提交,特定少数的 http请求 需要添加 preflight
  • 允许跨域资源嵌入(Cross-origin embedding),例如:img,script 等 标签。
  • 不允许跨域读操作(Cross-origin reads),但可以通过内嵌资源来巧妙的进行读取访问。

(二)浏览器进行同源策略限制的方面

  • Cookie、LocalStorage 和 IndexDB 同源策略

禁止读取存储在不同源下的 Cookie,LocalStorage 和 IndexDB。

  • DOM 同源策略

禁止对不同源页面的 DOM 进行操作,DOM 和 JS 对象无法获得。主要包括 iframe、canvas 之类的。

  • XMLHttpRequest 同源策略

禁止不同源的 AJAX 请求,主要用来防止 CSRF 攻击。 浏览器中,script,img、link 等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了 JavaScript 的权限使其不能读、写加载的内容。 因为 script 标签引入的文件不能够被客户端的 js 获取到, 不会影响到原页面的安全,所以 script 不受同源限制。

跨域

非同源请求,均是跨域。只要协议、域名、端口号其中任意一者不同,均属跨域。

跨域请求是浏览器根据同源策略对请求的限制,但服务器仍然能收到客户端请求,服务器之间的通信不存在跨域问题。跨域是浏览器行为。

(一)跨域的时机

  • XMLHttpRequest 或 Fetch 发起的跨源 HTTP 请求。
  • Web 字体 (CSS 中通过 @font-face 使用跨源字体资源)。
  • WebGL 贴图。
  • 使用 drawImage 将 images/video 绘制到 canvas。
  • 此外有样式表,脚本等。

(二)跨域问题的出现

由于浏览器同源策略的限制,浏览器会拒绝跨域请求(拒绝跨域读操作)

(二)跨域问题的解决方案

1. document.domain + iframe 跨域

此方案仅限主域相同,子域不同的跨域应用场景。

  • 实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。document.domain 的设置是有限制的,只能设置成自身或者更高级的父域,且主域必须相同。
// 父窗口为 https://www.baidu.com
// 跨域请求子窗口数据
<iframe id="iframe" src=" https://www.doc.baidu.com/b.html"></iframe>
<script>
	// 设置为基础域名
    document.domain = baidu.com';
    var user = 'admin';
</script>
// 子窗口为https://www.doc.baidu.com
<script>
    document.domain = baidu.com';
    // 获取父窗口中变量
    alert(‘get js data from parent --->’+window.parent.user);
</script>
2. location.hash + iframe 跨域
  • 实现原理:改变 URL 的 hash 部分来进行双向通信。每个 window 通过改变其他 window 的 location 来发送消息(由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值,所以要借助于父窗口域名下的一个代理 iframe),并通过监听自己的 URL 的变化来接收消息。
  • 这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持 onhashchange 事件,需要轮询来获知URL的改变。
  • 同时存在诸如数据直接暴露在了url中,数据容量和类型都有限等缺点。
/**
 * A页面:
 * a.html 传送数据到 b.html
 * a.html 下修改 iframe 的 src 为 google.com/b.html#paco
 * b.html 监听到url发生变化,触发相应操作
 */
 /** 
 * B页面:b.html 传送数据到 a.html
 * 由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值,
 * 所以要借助于父窗口域名下的一个代理iframe。
 * b.html 下创建一个隐藏的 iframe,此 iframe 的 src 是 baidu.com 域下的,
 * 并挂上要传送的 hash 数据,如 src="www.baidu.com/proxy.html# 传输的数据"
 * b.html页面的关键代码如下:
 */
try {  
    parent.location.hash = 'data';  
} catch (e) {  
    // ie、chrome的安全机制无法修改parent.location.hash,  
    var ifrproxy = document.createElement('iframe');  
    ifrproxy.style.display = 'none';  
    ifrproxy.src = "http://www.baidu.com/proxy.html#data";  
    document.body.appendChild(ifrproxy);  
}
/** 
 * C页面:
 * proxy.html监听
 * proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,
 * 所以proxy.html可修改a.html的url hash)a.html监听到url发生变化,触发相操作。
 * proxy.html页面的关键代码如下 :
 */

//因为parent.parent(即baidu.com/a.html)和baidu.com/proxy.html属于同一个域,所以可以改变其location.hash的值  
parent.parent.location.hash = self.location.hash.substring(1);
3. window.name + iframe 跨域

window 对象 name 属性的特征:在一个窗口 (window) 的生命周期内,窗口载入的所有的页面都共享一个window.name,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

  • 实现原理:基于这个 window.name 属性的特性,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。

location.hash + iframe 跨域和 window.name + iframe 跨域都是通过中间代理页实现。

4. postMessage 跨域
  • 实现原理:postMessage(data,origin) 方法接受两个参数
    • data: html5 规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用 JSON.stringify() 序列化。
    • origin: 协议+主机+端口号,也可以设置为 " * ",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
  • 解决的问题场景:
    • 页面和其打开的新窗口的数据传递
    • 多窗口之间消息传递
    • 页面与嵌套的iframe消息传递
    • 上面三个场景的跨域数据传递
5. 通过 jsonp 跨域
  • 实现原理:JSONP 是一种非正式传输协议,允许用户传递一个 callback 给服务端,然后服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。当 GET 请求从后台页面返回时,可以返回一段 JavaScript 代码,这段代码会自动执行,可以用来负责调用后台页面中的一个 callback 函数。
  • 使用方式:基于 JSONP 的原理,我们可以通过动态创建 script,再请求一个带参网址实现跨域通信。
const jsonp = ({ url, params, callbackName }) => {
  const generateUrl = () => {
    let dataSrc = "";
    for (let key in params) {
      if (Object.prototype.hasOwnProperty.call(params, key)) {
        dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  };
  return new Promise((resolve, reject) => {
    const scriptEle = document.createElement("script");
    scriptEle.src = generateUrl();
    document.body.appendChild(scriptEle);
    window[callbackName] = (data) => {
      resolve(data);
      document.removeChild(scriptEle);
    };
  });
};

this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback' // 自定义回调函数
}).then((res) => {
    console.log(res); 
})
6. 跨域资源共享(CORS)
  • 实现原理:CORS 允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。基本思想就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
  • 使用方式:
    • 普通跨域请求:在服务端设置 Access-Control-Allow-Origin 即可,前端无须设置。
    • 带 cookie 请求:前后端都需要设置。

CORS 请求分两类: 简单请求(simple request)和非简单请求(not-so-simple request)。

简单请求需满足以下条件,否则都是非简单请求:

  • 请求方法是以下三种方法之一:

    • HEAD
    • GET
    • POST
  • HTTP的头信息不超出以下几种字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

注意:CORS 请求默认不发送 Cookie 和 HTTP 认证信息。

如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段。

另一方面需要在 AJAX 中打开 withCredentials 属性。

如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。

同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie。

7. WebSocket 协议跨域

WebSocket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现。

  • 实现原理:WebSocket 不是基于 http 的,而是与 http 处于同层(应用层),但 WebSocket 与 TCP/IP 建立连接是通过 http 协议传输的,建立连接后,真正的数据传送不再需要 http。所以,WebSocket 没有同源限制,允许跨域通信。全双工通信,客户端和服务端均可主动推送消息。实时性和灵活性高。
8. nginx 代理跨域
  • 实现原理:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。
9. nodejs 中间件代理跨域
  • 实现原理:node 中间件实现跨域代理,是通过启动一个代理服务器,实现数据的转发,也可以通过设 cookieDomainRewrite 参数修改响应头中 cookie 中域名,实现当前域的 cookie 写入,方便接口登录认证。

浏览器缓存

浏览器缓存,也称客户端缓存http(协议层)缓存

http 缓存是保存资源副本的技术,复用资源,提高网站性能,减少等待时间和网络流量。

浏览器发现请求的资源已被存储,拦截请求,返回缓存数据,不会取服务器重新下载,缓解服务器压力,提高性能。

浏览器缓存机制

在浏览器缓存中,强缓存优先级高于协商缓存。

而在强缓存中, Cache-Control 优先级高于 Expires。

浏览器缓存流程图如下:

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

(一)强制缓存

强缓存命中不会发送请求到服务端,直接从本地缓存中获取资源。此时返回状态码 200 (from memory cache/from disk cache)

强缓存利用Cache-ControlExpires两个字段实现,表示资源在客户端的缓存有效期。

1. 利用 Expires 字段的强缓存
  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Expires 字段。
  2. 浏览器接受此资源,会将此资源以及响应头一起缓存(注意: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的 header)。
  3. 浏览器再次请求此资源时,会先从缓存中找,找到后,拿 Expires 和当前的请求时间比较,如果请求时间小于 Expires,就能命中缓存,否则没命中。
  4. 如果没命中,浏览器直接从服务器加载资源时,Expires 重新加载的时候会被更新。

由于在服务器时间与客户端时间相差较大时,缓存管理容易出现问题。(比如:随意修改下客户端时间,就能影响缓存命中的结果)。

所以在 HTTP 1.1 的时候,提出了一个新的 header,就是 Cache-Control,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示。

2. 利用 Cache-Control 字段的强缓存
  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Cache-Control 字段。
  2. 浏览器接受此资源,会将此资源以及响应头一起缓存(注意: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的 header)。
  3. 浏览器再请求此资源时,会先从缓存中找,找到后,根据它第一次的请求时间和 Cache-Control 设定的有效期,计算出一个资源过期时间,再用这个过期时间和当前请求时间比较,如果请求时间小于资源过期时间,就能命中缓存,否则没命中。
  4. 如果没命中,浏览器直接从服务器加载资源时,Cache-Control 重新加载的时候会被更新。

Cache-Control 描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较 Expires,Cache-Control 的缓存管理更有效,安全一些。

Cache-Control 优先级高于 Expires

强缓存过程中,如果没有命中,强缓存就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的 http 状态为 304 并且会显示一个 Not Modified 的字符串。

(二)协商缓存

协商缓存会发送请求到服务端,服务端通过请求头的头部字段验证是否命中协商缓存。如果命中,服务器会将这个请求返回,但是不会返回这个资源的实体,而是返回状态码304(not modified) ,通知浏览器从缓存中获取资源。

协商缓存是利用的是Last-Modified, If-Modified-Since 和ETag, If-None-Match这两对字段来管理的。

1. Last-Modified & If-Modified-Since 控制的协商缓存
  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Last-Modified 字段,表示这个资源在服务器上最后的修改时间。
  2. 浏览器再次请求此资源时,会在请求头加 If-Modified-Since 字段,值等于上次请求时的 Last-Modified 的值。
  3. 服务器再次收到资源请求时,会根据请求头的 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化返回 304(Not Nodified),但不会返回资源内容;如果有变化,则正常返回资源。当服务器返回 304 的响应时,响应头不会再添加 Last-Modified ,因为资源没有变化,那么 Last-Modified 也不会变化,即不更新 Last-Modified.
  4. 浏览器收到 304 响应后,就会从缓存中加载资源。
  5. 如果协商缓存没有命中,浏览器直接从服务器加载,Last-Modified 会被更新,下次请求时,If-Modified-Since 会使用上次返回的 Last-Modified 。

Last-Modified 存在的问题:

  • 如果本地打开缓存文件,即使没有对文件进行修改(文件内容没有变化),但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
  • 有些服务器无法精准获取文件的最后修改时间
2. ETag & If-None-Match 控制的协商缓存
  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 ETag 字段,ETag 是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就会不同,跟最后修改时间没有关系,所以能很好的补充 Last-Modified 的问题
  2. 浏览器再次请求此资源时,会在请求头加 If-None-Match 字段,值等于上次请求时的 ETag 的值。
  3. 服务器再次收到资源请求时,会根据请求头的 If-None-Match 和 服务端再根据资源生成的新的 ETag 比较,如果没有变化返回 304(Not modified),但是不会返回资源的内容;如果有变化,就正常返回资源。与 Last-Modified 不一样的是,当服务器返回 304 的响应时,由于 ETag 重新生成过,响应头中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化
  4. 浏览器收到 304 响应后,就会从缓存中加载资源。

Etag 和 Last-Modified 非常相似,都是用来判断一个参数,从而决定是否启用缓存。

但是 ETag 相对于Last-Modified 也有其优势,可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。

如果两个都支持的话,服务器会优先选择 ETag 。

具体使用

浏览器具体运行时,会先试图命中强缓存,然后再试图命中协商缓存。

  • 强缓存与协商缓存的共同点是: 如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;

  • 强缓存与协商缓存的区别是: 强缓存不发请求到服务器,协商缓存会发请求到服务器。当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。

协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。

大部分 web 服务器都默认开启协商缓存,而且是同时启用Last-Modified & If-Modified-Since 和 ETag & If-None-Match,同时开启是为了处理 Last-Modified 不可靠的问题。

缓存实践

缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效

❓ Question:在更新版本之后,如何让用户第一时间使用最新的资源文件呢?

✅ Answer:在更新版本的时候,可以顺便修改静态资源的路径。即,可以利用 webpack 的文件指纹策略。这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。

一个较为合理的缓存方案:

  • HTML:使用协商缓存。

    • 假设 html 使用了强缓存,当我们重新上线 css、js 等文件,html 文件(因为 css 改变了,所以 html 要重新引入 css 文件,因此 html 也要改并且重新上线),但是 html 使用的是强缓存,在缓存期间浏览器直接从强缓存中读取 html 文件,以至于用户重新上线了但是还是原来的 html 内容,必须要用户强制刷新才会有新内容出现,这就不合理。所以不能使用强缓存。
  • CSS、JS、图片:使用强缓存,且文件命名带上 hash 值。

    • 更新资源发布路径以实现非覆盖式发布,使强缓存失效。
    • 利用 Webpack 合理分包加上文件指纹做到客户端的持久化缓存,不去解析 DNS,不去请求服务器,减小服务端压力的同时提高用户体验
    • 若发布了最新资源,由于 html 中引用的资源的路径变了,会拉取最新资源,做无覆盖式更新。

❓ Question:利用版本号更新策略可行吗?

✅ Answer:不可行。

虽然在 <link rel="stylesheet" href="/index.css?v=1.0.2"> 在后面加个版本号,每次 html 都会发送请求询问是否过期,可以达到缓存更新效果。

但是开发不可能就引用一个 css 文件,往往是多个的。每次修改一个 css 文件,其余的版本也要跟着升级,没有必要。

浏览器缓存位置

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。

  • service worker
  • memory cache
  • disk cache
  • push cache
1. service worker

Service Worker 借鉴了 Web Worker 的 思路,即让 JS 运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问 DOM。

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

2. memory cache

Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

内存缓存中有一块重要的缓存资源是 preloader 相关指令(例如<link rel="prefetch">)下载的资源。 preloader 的相关指令已经是页面优化的常见手段之一,它可以一边解析 js/css 文件,一边网络请求下一个资源。

需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。

3. disk cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度较慢,但是包容性强,比之 Memory Cache 胜在容量和存储时效性上。

它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。

4. push cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

❓ Question:状态码为 200 from cache 和 304 Not modified 的区别?

✅ Answer:

  • 请求状态码为 200 from cache: 表示该资源已经被缓存过,并且在有效期内,所以不再向浏览器发出请求,直接使用本地缓存。
  • 状态码为 304 Not modified: 表示浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用(即还未更新),那么便会返回 304,继续使用本地的缓存。

其他相关知识

ajax 异步请求

(一)ajax 简介

AJAX 全称 Async Javascript and XML,即异步的 JavaScript 和 XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页。

Ajax 的原理,简单来说就是通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面。

流程图如下:

详解 HTTP 网络协议讲解了 HTTP 协议的历史、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的

浏览器可以发送 HTTP 请求后,接着做其他事情,等收到 XHR 返回来的数据再进行操作。

(二)ajax 具体实现

实现 Ajax 异步交互需要服务器逻辑进行配合,需要完成以下步骤:

  1. 创建 Ajax 的核心对象 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
  1. 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
xhr.open(method, url, [async][, user][, password])

参数说明:

  • method:表示当前的请求方式,常见的有GETPOST
  • url:服务端地址
  • async:布尔值,表示是否异步执行操作,默认为true
  • user: 可选的用户名用于认证用途;默认为null
  • password: 可选的密码用于认证用途,默认为null
  1. 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
xhr.send([body])

body: 在 XHR 请求中要发送的数据体,如果不传递数据则为 null

如果使用GET请求发送数据的时候,需要注意如下:

  • 将请求数据添加到open()方法中的url地址中
  • 发送请求数据中的send()方法中参数设置为null
  1. 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
  • onreadystatechange 事件用于监听服务器端的通信状态,主要监听的属性为XMLHttpRequest.readyState,这个属性有五种状态,分别是:UNSENTOPENEDHEADERS_RECEIVEDLOADINGDONE
  • 只要 readyState 属性值一变化,就会触发一次 readystatechange 事件。
  • XMLHttpRequest.responseText属性用于接收服务器端的响应结果。

举个栗子🍘:

const request = new XMLHttpRequest()
request.onreadystatechange = function(e){
    if(request.readyState === 4){ // 整个请求过程完毕
        if(request.status >= 200 && request.status <= 300){
            console.log(request.responseText) // 服务端返回的结果
        }else if(request.status >=400){
            console.log("错误信息:" + request.status)
        }
    }
}
request.open('POST','http://xxxx')
request.send()
  1. 接受并处理服务端向客户端响应的数据结果
  2. 将处理结果更新到 HTML 页面中

(三)ajax 封装

我们可以通过以下方式来封装一个简单的 ajax 请求:

//封装一个ajax请求
function ajax(options) {
    //创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()


    //初始化参数的内容
    options = options || {}
    options.type = (options.type || 'GET').toUpperCase()
    options.dataType = options.dataType || 'json'
    const params = options.data

    //发送请求
    if (options.type === 'GET') {
        xhr.open('GET', options.url + '?' + params, true)
        xhr.send(null)
    } else if (options.type === 'POST') {
        xhr.open('POST', options.url, true)
        xhr.send(params)

    //接收请求
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            let status = xhr.status
            if (status >= 200 && status < 300) {
                options.success && options.success(xhr.responseText, xhr.responseXML)
            } else {
                options.fail && options.fail(status)
            }
        }
    }
}

使用方式:

ajax({
    type: 'post',
    dataType: 'json',
    data: {},
    url: 'https://xxxx',
    success: function(text,xml){//请求成功后的回调函数
        console.log(text)
    },
    fail: function(status){////请求失败后的回调函数
        console.log(status)
    }
})

axios 请求库使用

(一)axios 简介

axios 是一个轻量的 HTTP客户端,基于 XMLHttpRequest 服务来执行 HTTP 请求,支持丰富的配置,支持 Promise,支持浏览器端和 Node.js 端。

主要特性如下:

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御XSRF

(二)axios 基本使用

  • 发送请求:
axios({        
  url:'xxx',    // 设置请求的地址
  method:"GET", // 设置请求方法
  params:{      // get请求使用params进行参数凭借,如果是post请求用data
    type: '',
    page: 1
  }
}).then(res => {  
  // res为后端返回的数据
  console.log(res);   
})
  • 并发请求 axios.all([])
function getUserAccount() {
    return axios.get('/user/12345');
}

function getUserPermissions() {
    return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
    .then(axios.spread(function (res1, res2) { 
    // res1第一个请求的返回的内容,res2第二个请求返回的内容
    // 两个请求都执行完成才会执行
}));

(三)axios 二次封装

axios 封装的内容主要包括:

  1. 设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分。
if (process.env.NODE_ENV === 'development') {
  axios.defaults.baseURL = 'http://dev.xxx.com'
} else if (process.env.NODE_ENV === 'production') {
  axios.defaults.baseURL = 'http://prod.xxx.com'
}

在本地调试的时候,还需要在vue.config.js文件中配置devServer实现代理转发,从而实现跨域

devServer: {
    proxy: {
      '/proxyApi': {
        target: 'http://dev.xxx.com',
        changeOrigin: true,
        pathRewrite: {
          '/proxyApi': ''
        }
      }
    }
  }
  1. 设置请求头超时时间:大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,这里将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置。
const service = axios.create({
    ...
    timeout: 30000,  // 请求 30s 超时
	  headers: {
        get: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
          // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
        },
        post: {
          'Content-Type': 'application/json;charset=utf-8'
          // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
        }
  },
})
  1. 封装请求方法:先引入封装好的方法,在要调用的接口重新封装成一个方法暴露出去。
// get 请求
export function httpGet({
  url,
  params = {}
}) {
  return new Promise((resolve, reject) => {
    axios.get(url, {
      params
    }).then((res) => {
      resolve(res.data)
    }).catch(err => {
      reject(err)
    })
  })
}

// post
// post请求
export function httpPost({
  url,
  data = {},
  params = {}
}) {
  return new Promise((resolve, reject) => {
    axios({
      url,
      method: 'post',
      transformRequest: [function (data) {
        let ret = ''
        for (let it in data) {
          ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
        }
        return ret
      }],
      // 发送的数据
      data,
      // url参数
      params

    }).then(res => {
      resolve(res.data)
    })
  })
}

把封装的方法放在一个api.js文件中

import { httpGet, httpPost } from './http'
export const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })

然后就可以在页面中直接调用

// .vue
import { getorglist } from '@/assets/js/api'

getorglist({ id: 200 }).then(res => {
  console.log(res)
})
  1. 设置请求拦截器:请求拦截器可以在每个请求里加上token,做了统一处理后维护起来也方便。
// 请求拦截器
axios.interceptors.request.use(
  config => {
    // 每次发送请求之前判断是否存在token
    // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况,此处token一般是用户完成登录后储存到localstorage里的
    token && (config.headers.Authorization = token)
    return config
  },
  error => {
    return Promise.error(error)
  })
  1. 设置响应拦截器:响应拦截器可以在接收到响应后先做一层操作,如根据状态码判断登录状态、授权。
// 响应拦截器
axios.interceptors.response.use(response => {
  // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
  // 否则的话抛出错误
  if (response.status === 200) {
    if (response.data.code === 511) {
      // 未授权调取授权接口
    } else if (response.data.code === 510) {
      // 未登录跳转登录页
    } else {
      return Promise.resolve(response)
    }
  } else {
    return Promise.reject(response)
  }
}, error => {
  // 我们可以在这里对异常状态作统一处理
  if (error.response.status) {
    // 处理请求失败的情况
    // 对不同返回码对相应处理
    return Promise.reject(error.response)
  }
})

总结

本文通过讲解 HTTP 协议的历史发展、状态码内容、报文内容、请求内容,以及 HTTP 与 HTTPS 的区别,系统梳理了 HTTP 网络协议的相关知识,同时也对跨域、浏览器缓存、ajax 异步请求、axios 请求库使用等要点做了系统性的剖析,希望能为你的学习带来些许帮助,感谢观看!

转载自:https://juejin.cn/post/7403910440608989247
评论
请登录