整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

详解网络请求Axios

xios是什么?

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。我们知道 Promise 是 js 异步的一种解决方案,它最大的特性就是可以通过 .then 的方式来进行链式调用。

其实说白了axios是对ajax的封装,axios有的ajax都有,ajax有的axios不一定有,总结一句话就是axios是ajax,ajax不止axios。

为什么选择axios?

  1. vue的作者尤雨溪推荐使用axios.
  2. 符合前后端分离的趋势,及前端的MVVM的浪潮

功能特点:

  • 在浏览器中发送XMLHttpRequests请求
  • 在node.js中发送http请求
  • 支持Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF
  • 支持多种请求方式:

  • axios(config)
  • axios.request(config)
  • axios.get(url, [, config])
  • axios.delete(url, [, config])
  • axios.head(url, [, config])
  • axios.post(url, [,data[,config] ])
  • axios.put(url, [,data[,config] ])
  • axios.patch(url, [,data[,config] ])
  • Axios的基本使用

    axios的使用比较简单,文档讲得也非常清晰,你应该先阅读axios的官方文档:axios文档。

    在html页面中直接引入使用:

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

    Html页面基本使用

    以下案例中的get请求地址为crmeb相关演示站地址,可用于测试获取!

    1. 获取一个get请求
    <script>
      const url = 'https://store.crmeb.net/api/pc/get_category_product'
      axios({
        url: url,
        method: 'get',  // 这里可以省略,默认为get
      }).then(res => {
        // 返回请求到的数据
        console.log(res)
      }).catch(err => {
        // 返回错误信息
        console.log(err)
      })  
    </script>
    1. 在get请求的url中传参,只需要定义一个params:{}即可!
    <script>
      const url = 'https://store.crmeb.net/api/pc/get_category_product'
      axios({
        url: url,
        method: 'get',  // 这里可以省略,默认为get
        // 这里的键值对会拼接成这样url?page=1&limit=3
        params: {
          page: 1,
          limit: 3
        }
      }).then(res => {
        // 返回请求到的数据
        console.log(res)
      }).catch(err => {
        // 返回错误信息
        console.log(err)
      })  
    </script>
    1. 发送一个post请求,与get请求类似,只需要将method改为post,定义一个data:{}即可,data中的数据则是服务器需要接收的字段数据!
    <script>
    axios({
      method: 'post',
      url: '/user/12345',
      data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
      }
    }).then(res => {
        // 返回请求到的数据
        console.log(res)
      }).catch(err => {
        // 返回错误信息
        console.log(err)
      });
    </script>
    1. 发送一个并发请求

    如果在开发中需要等到多个接口的数据同时请求到后才能继续后边的逻辑,那么即可使用并发请求,axios并发请求,使用all方法,all方法的参数为一个数组,数组的每个值可以为一次请求,请求完成后直接.then即可合并两次请求的数据,返回结果为一个数组!

    <script>
    axios.all([
        axios({
            url: 'https://store.crmeb.net/api/pc/get_products',
            params: {
                page: 1,
                limit: 20,
                cid: 57,
                sid: 0,
                priceOrder: '', 
                news: 0,
            }
        }),
        axios({
            url: 'https://store.crmeb.net/api/pc/get_company_info',
        })
    ]).then(results => {
        console.log(results)
    })
    </script>

    如果你想自动把这个数组展开的话在then()方法中传入axios.spread()方法即可,如下所示:

    <script>
    axios.all([
        axios({
            url: 'https://store.crmeb.net/api/pc/get_products',
            params: {
                page: 1,
                limit: 20,
                cid: 57,
                sid: 0,
                priceOrder: '', 
                news: 0,
            }
        }),
        axios({
            url: 'https://store.crmeb.net/api/pc/get_company_info',
        })
    ]).then(axios.spread((res1, res2) => {
        console.log(res1);
        console.log(res2);
    }))
    </script>

    但在使用vue组件化开发的时候一般我们会通过npm安装,引入项目!

    组件化开发中使用

    1. 使用npm进行安装
    npm install axios --save

    一般在实际项目中我们并不会像上边这样直接去使用axios请求数据,而是将axios封装在一个单独的文件,这样做的目的主要是用来抽取公共逻辑到一个配置文件里,对这些公共逻辑做一个封装,即使某一天这个axios框架不维护了,或者出现了重大bug也不再修复的时候,我们可以只修改配置文件即可达到全局修改的目的,如果把每次请求逻辑都写到对应的组件中,那修改起来简直就是一个噩梦!

    1. 封装一个axios的请求文件request.js

    在项目的src目录下创建一个network文件夹,再在其中创建一个request.js文件,路径为:src/network/request.js

    // src/network/request.js
    
    // 引入axios
    import axios from 'axios'
    
    // 这里未使用default导出,是为了以后的扩展,便于导出多个方法
    export function request(config){
        // 创建axios实例
        const instance = axios.create({
            // 这里定义每次请求的公共数据,例如全局请求头,api根地址,过期时间等
            // 具体参数可参考axios的官方文档
            baseURL: 'http://demo26.crmeb.net/api',
            timeout: 5000
        })
        
        // 拦截请求,如果获取某个请求需要携带一些额外数据
        instance.interceptors.request.use(
            config => {
                console.log(config);
                return config;
            }, err => {
                console.log(err);
            })
            
        // 拦截响应
        instance.interceptors.response.use(
            res => {
                console.log(res)
                return res.data
            }, err => {
                console.log(err)
            }
        )
        
        // 发送请求
        return instance(config)  
    1. 使用我们封装的request请求

    一般我们会将所有的请求放在一个api.js文件中,将每次请求封装为一个方法,比如我这里会在request.js的同目录创建一个api.js文件封装我们所有的请求。

    import { request } from '../api/request'
    
    // 获取分类
    export const getHomeCategory = () => {
        return request({
            url: '/category'
        })
    }
    
    // 获取banner图
    export const getHomeBanner = () => {
        return request({
            url: '/pc/get_banner'
        })
    }

    之后再在组件中引入调用导出的相关接口方法即可,如:

    import { getHomeBanner } from "../network/api"
    
    getHomeBanner().then(res => {
    	console.log(res)
    })

    以上就是一个简单的封装,其中有个拦截请求和拦截响应,可能很多初学的人理解起来有点吃力,我在这里以个人浅见阐述,希望能带给你些许启发!

    何为拦截器?

    还是发挥阅读理解能力,拦截拦截其实就是此路是我开,此树是我栽,要想过此路,留下买路钱,拦截请求就是比如某些请求需要携带一些额外的信息才能访问,实际项目中最常见的就是需要登录后才能查看的信息,请求中就必须携带token才能访问,就可以在这里处理,还有拦截响应,比如请求到数据之后,发现不符合要求,先拦下来处理一下,再返回给前端,这就是一个拦截器的基本工作流程!

  • axios有一个全局拦截的方式:axios.interceptors()
  • 拦截成功后必须return返回,否则数据无法请求到
  • 如下所示:

      // 拦截请求,如果获取某个请求需要携带一些额外数据
        instance.interceptors.request.use(
            config => {
                console.log(config);
                return config;
            }, err => {
                console.log(err);
            })
            
        // 拦截响应
        instance.interceptors.response.use(
            res => {
                console.log(res)
                return res.data
            }, err => {
                console.log(err)
            }
        )

    axios还为我们提供了一些全局配置,如下:

    axios.defaults.baseURL = 'https://api.example.com';
    axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

    当然也可以将其配置在我们之前创建的axios实例中,使其只作用于某个实例!

    然后来看一下 axios 的所有配置信息:

    数据来自axios中文文档

    ,一般现在流传的HTTP请求:GET和POST的比较是这样的:

    GET和POST是HTTP的两个常用方法。

    什么是HTTP?

    超文本传输协议(HyperText Transfer Protocol -- HTTP)是一个设计来使客户端和服务器顺利进行通讯的协议。

    HTTP在客户端和服务器之间以request-responseprotocol(请求-回复协议)工作。

    GET- 从指定的服务器中获取数据

    POST- 提交数据给指定的服务器处理

    GET方法:

    使用GET方法时,查询字符串(键值对)被附加在URL地址后面一起发送到服务器:

    /test/demo_form.jsp?name1=value1&name2=value2

    特点:

    · GET请求能够被缓存

    · GET请求会保存在浏览器的浏览记录中

    · 以GET请求的URL能够保存为浏览器书签

    · GET请求有长度限制

    · GET请求主要用以获取数据

    POST方法:

    使用POST方法时,查询字符串在POST信息中单独存在,和HTTP请求一起发送到服务器:

    POST/test/demo_form.jsp HTTP/1.1

    Host:w3schools.com

    name1=value1&name2=value2

    特点:

    · POST请求不能被缓存下来

    · POST请求不会保存在浏览器浏览记录中

    · 以POST请求的URL无法保存为浏览器书签

    · POST请求没有长度限制

    GET和POST的区别:

    其他HTTP请求方式

    二,本质上,这些并不是HTTP的GET和POST两者请求的区别,这些区别是建立在HTML标准对于HTTP协议的用法的约定之上的。

    1. GET和POST与数据如何传递没有关系

    GET和POST是由HTTP协议定义的。在HTTP协议中,Method和Data(URL, Body, Header)是正交的两个概念,也就是说,使用哪个Method与应用层的数据如何传输是没有相互关系的。

    HTTP没有要求,如果Method是POST数据就要放在BODY中。也没有要求,如果Method是GET,数据(参数)就一定要放在URL中而不能放在BODY中。

    那么,网上流传甚广的这个说法是从何而来的呢?我在HTML标准中,找到了相似的描述。这和网上流传的说法一致。但是这只是HTML标准对HTTP协议的用法的约定。怎么能当成GET和POST的区别呢?

    而且,现代的Web Server都是支持GET中包含BODY这样的请求。虽然这种请求不可能从浏览器发出,但是现在的Web Server又不是只给浏览器用,已经完全地超出了HTML服务器的范畴了。

    2. HTTP协议对GET和POST都没有对长度的限制

    HTTP协议明确地指出了,HTTP头和Body都没有长度的要求。而对于URL长度上的限制,有两方面的原因造成:

    1. 浏览器。据说早期的浏览器会对URL长度做限制。据说IE对URL长度会限制在2048个字符内(流传很广,而且无数同事都表示认同)。但我自己试了一下,我构造了90K的URL通过IE9访问live.com,是正常的。网上的东西,哪怕是Wikipedia上的,也不能信。

    2. 服务器。URL长了,对服务器处理也是一种负担。原本一个会话就没有多少数据,现在如果有人恶意地构造几个几M大小的URL,并不停地访问你的服务器。服务器的最大并发数显然会下降。另一种攻击方式是,把告诉服务器Content-Length是一个很大的数,然后只给服务器发一点儿数据,嘿嘿,服务器你就傻等着去吧。哪怕你有超时设置,这种故意的次次访问超时也能让服务器吃不了兜着走。有鉴于此,多数服务器出于安全啦、稳定啦方面的考虑,会给URL长度加限制。但是这个限制是针对所有HTTP请求的,与GET、POST没有关系。

    备知识

    前文没有描述到传输和协议直接的层级对应关系,大概补充下网络通信中数据传输对应的协议,首先了解下OSI(开放式系统互联:Open System InterConnection)七层 模式,及其对应不同层次的协议。

    OSI体系结构TCP/IP相关协议结构应用层HTTP,Telnet,FTP等表示层会话层传输层TCP,UDP网络层IP数据链路层物理层

    了解到HTTP协议是建立在TCP连接基础之上的。HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由浏览器发起请求,用来获取不同类型的文件, 例如 HTML 文件、CSS 文件、JavaScript 文件、图片、视频等。此外,HTTP 也是浏览器使用最广的协议。

    我们对HTTP不太了解的话都会存在这样的疑惑,为什么再次访问同一站点会比第一次快,登录过一次后的网站再次访问就处于登录状态等,我们 通过对HTTP请求过程的剖析来解开这些谜团。

    浏览器端发起 HTTP 请求流程

    浏览器输入网址:http://time.geekbang.org/index.html,之后会完成什么步骤呢?

    1、构建请求

    首先,浏览器构建请求行信息,构建好后,浏览器准备发起网络请求。

    GET /index.html HTTP1.1

    2、查找缓存

    在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术

    当浏览器发现请求资源已经存在浏览器缓存中存有副本,则会拦截请求并返回该资源副本结束请求。如果查找缓存失败,则会进入网络请求。所以会有利于:

    • 缓解服务器端压力,提升性能
    • 对于网站来说,缓存是实现快速资源加载的重要组成部分,减少了获取资源的时间。

    3、准备IP地址和端口

    我们通过开头预备知识和前文也大概了解到了HTTP和TCP的关系。浏览器使用 HTTP 协议作为应用层协议,用来封装请求的文本信息;并使用 TCP/IP 作传输层协议将它发到网络上,所以在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接。也就是说 HTTP 的内容是通过 TCP 的传输数据阶段来实现的。

    TCP和HTTP的关系示意图:

    据此,我们可以知道建立HTTP网络请求就是,通过URL地址来解析获取IP和端口信息,建立服务器和TCP连接。我们通过前文《TCP协议》 说到了数据包都是通过IP地址传输给接收方的。而我们网站一般的地址都是域名,所以需要把域名和IP地址做映射关系,即解析IP地址的系统“域名系统(DNS)”解析出 IP地址,并获取对应端口号获得建立连接的前置条件。换句话说,即浏览器请求DNS返回域名对应的IP,而请求DNS时也会查询DNS数据缓存服务,判断是否域名已解析过, 如果解析过则查询直接使用,拿到IP后则判断URL是否指明端口号,没有则HTTP协议默认时80端口。

    4、等待TCP队列

    Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。当然,如果当前请求数量少于 6,会直接进入下一步,建立 TCP 连接。

    5、建立TCP连接

    队列等待结束后,TCP和服务器实现“三次握手”(前文TCP协议有描述),即客户端和服务器发送三个数据包以确认连接,实现浏览器和服务的连接。

    6、发送HTTP请求

    一旦建立了 TCP 连接,浏览器就可以和服务器进行通信了。而 HTTP 中的数据正是在这个通信过程中传输的。

    HTTP请求数据格式:

    首先浏览器会向服务器发送请求行,它包括了请求方法、请求 URI(Uniform Resource Identifier)和 HTTP 版本协议

    其中请求方式有GET,POST,PUT,Delete等,其中常用的POST会用于发送一些数据给服务器,比如登录网站把用户信息发送给服务器,一般 这些数据会通过请求体发送。

    在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、Cookie等。

    服务器端处理 HTTP 请求流程

    1、返回请求

    curl -i https://time.geekbang.org/

    通过curl工具(或network面板)我们可以了解到服务器返回的数据格式:

    首先服务器会返回响应行,包括协议版本和状态码。

    如果出现错误,服务器会通过请求行的状态码来返回对应的处理结果,例如:

    • 最常用的状态码是 200,表示处理成功;
    • 404,表示没有找到页面
    • 500,表示服务器错误

    正如浏览器会随同请求发送请求头一样,服务器也会随同响应向浏览器发送响应头。响应头包含了服务器自身的一些信息, 比如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的 Cookie 等信息。

    响应头之后,服务器会发送响应体数据,通常包含了HTML的实际内容。以上为服务器响应浏览器的过程。

    2、断开连接

    一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了:

    Connection:Keep-Alive

    则TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接发送请求。保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。 如果一个页面内嵌的图片都来自同一web站点,则初始化一个持久连接则可复用减少TCP的连接。

    3、重定向

    重定向返回响应行和响应头:

    状态 301 就是告诉浏览器,我需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的 Location 字段中,接下来,浏览器获取 Location 字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。

    总结

    通过http请求的完整过程,我们就知道,请求过程中DNS缓缓和页面资源缓存会被浏览器缓存起来,以减少向服务器请求的资源,所以会再次请求站点时速度会快。

    浏览器资源缓存处理过程:

    从上图的第一次请求可以看出,当服务器返回 HTTP 响应头给浏览器时,浏览器是通过响应头中的 Cache-Control 字段来设置是否缓存该资源。通常,我们还需要为这个资源设置一个缓存过期时长,而这个时长是通过 Cache-Control 中的 Max-age 参数来设置的。

    因此在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。

    如果缓存过期了,浏览器则会继续发起网络请求,并且在 HTTP 请求头中带上If-None-Match,服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新。

    • 如果没有更新,就返回 304 状态码,相当于服务器告诉浏览器,这个缓存可以继续使用。
    • 如果资源有更新,服务器就直接返回最新资源给浏览器。

    登录网站,通过POST方式提交信息给服务器,服务器接收到浏览器提交的信息之后,查询验证信息正确则会生成表面用户身份的字符串写入响应头的Set-Cookie字段里返回浏览器。

    浏览器解析响应头,如有Set-Cookie字段则保存在本地,当用户再次访问时,发起HTTP请求前浏览器会读取Cookie数据并写入请求头发送到服务器,服务器再次判断信息,如果 正确则展示用户登录状态及用户信息。

    最后总结出浏览器中的HTTP请求从发起到结束一共经历了八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接

    详细HTTP请求流程: