axios的使用与跨域问题的解决

Posted lovoo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了axios的使用与跨域问题的解决相关的知识,希望对你有一定的参考价值。

一、axios入门

1、axios的作用

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
官方网站:http://www.axios-js.com

特性:
1、从浏览器中创建 XMLHttpRequests
2、从 node.js 创建 http 请求
3、支持 Promise API
4、拦截请求和响应
5、转换请求数据和响应数据
6、取消请求
7、自动转换 JSON 数据
8、客户端支持防御 XSRF

2、在前端项目中安装axios

2.1) 用npm安装

npm install axios -g

cnpm install axios -g

参数说明:
-g:表示全局安装,将会安装在你配置的:C:\\Users\\XinLiu\\nodejs\\node_global目录下。如果不指定则为当前文件夹所在目录(局部);

2.2)无需安装,直接使用cdn

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

二、axios应用案例

1、前端实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
      //基于promise
      axios(
        method: 'get',
        url: 'http://localhost:8080/user/list',
      )
        .then((response) => 
          console.log('获取数据成功', response)
        )
        .catch((error) => 
          console.log('获取数据失败', error)
        )

      //另一种写法
      axios
        .get('http://localhost:8080/user/list')
        .then((response) => 
          console.log('获取数据成功1', response)
        )
        .catch((error) => 
          console.log('获取数据失败1', error)
        )
    </script>
  </body>
</html>

2、后端实现

1)在项目中添加mybatis-plus依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2)创建用户表和实体类

CREATE TABLE `user`  (
  `id` int(11) NOT NULL,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
@Data
public class User 

    private String id;

    private String username;

    private String password;

    private String address;

    private Integer age;

    private Date craeteTime;

    private Date modifyTime;

3)创建控制层,及列表展示方法list

@RestController
@RequestMapping("/user")
public class UserController 

    @Resource
    private UserService userService;

    @GetMapping("/list")
    public List<User> list()
        return userService.list();
    

4)启动主类

访问:http://localhost:8080/user/list

3、解决跨域

3.1、为什么会出现跨域问题?
出于浏览器的同源策略限制。
所谓同源(即指在同一个域)就是两个地址具有相同的协议(protocol)、主机(host)和端口号(port)
以下情况都属于跨域:

跨域原因说明示例
域名不同www.jd.com 与 www.taobao.com
域名相同,端口不同www.jd.com:8080 与 www.jd.com:8081
二级域名不同item.jd.com 与 miaosha.jd.com
安全级别不同http和https也属于跨域。

http和https也属于跨域。
如果域名和端口都相同,但是请求路径不同,不属于跨域,如:www.jd.com/item 和 www.jd.com/goods
同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。
而我们刚才是从localhost:5500端口去访问localhost:8080端口,这属于端口不同,跨域了。

3.2、单个Controller解决跨域问题
Spring早就给我们提供了解决方案,我们只需要在对应controller上添加一个注解就可以了
我们在 UserController 类上添加跨域标签@CrossOrigin,再进行测试,则测试成功!

@CrossOrigin //解决跨域问题
@RestController
@RequestMapping("/user")
public class UserController 

    @Resource
    private UserService userService;

    @GetMapping("/list")
    public List<User> list()
        return userService.list();
    

3.3、全局解决跨域

@Configuration
public class GlobalCorsConfig 

    @Bean
    public CorsWebFilter corsFilter() 
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        //2.是否发送Cookie信息
        config.setAllowCredentials(true);
        //3.有效时间
        config.setMaxAge(3600L);
        //2.添加映射路径,我们拦截一切请求
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);
        //4.返回新的CorsFilter.
        return new CorsWebFilter(configSource);
    

三、自定义配置

1、配置axios实例

可以对axios进行配置,简化代码的编写

//使用自定义配置
const request = axios.create(
    baseURL: 'http://localhost:8080', //url前缀
    timeout: 1000, //超时时间
    headers: 'token': 'helen123456' //携带令牌
)

2、配置请求参数

这样,远程接口的url地址就可以修改成相对路径了

//基于promise
//注意:这里使用了前面定义的request
request(
    method:'get',
    url:'/user/list'
).then(response => 
    console.log('获取数据成功', response)
).catch(error => 
    console.log('获取数据失败', error)
)

四、拦截器

1、请求拦截器

在发送axios请求前,可以拦截请求,对请求做一些处理

// 请求拦截器
request.interceptors.request.use(
  function (config) 
    // 在发送请求之前做些什么,例如:在请求头中携带一个令牌
    config.headers.token = 'helen123456'
    return config
  ,
  function (error) 
    // 对请求错误做些什么
    return Promise.reject(error)
  
)

2、响应拦截器

在发送完请求,获取到响应后,可以对响应做一些处理,再返回给前端用户

// 添加响应拦截器
request.interceptors.response.use(
  function (response) 
    // 对响应数据做点什么,例如:使用response.data替代response,简化前端拿到的数据结果
    return response.data
  ,
  function (error) 
    // 对响应错误做点什么
    return Promise.reject(error)
  
)

3、完整的request.js文件

import axios from 'axios'
import Cookies from 'js-cookie'
import router from '@/router'
import qs from 'qs'
import  clearLoginInfo  from '@/utils'
import isPlainObject from 'lodash/isPlainObject'

const http = axios.create(
  baseURL: window.SITE_CONFIG['apiURL'],
  timeout: 1000 * 180,
  withCredentials: true
)

/**
 * 请求拦截
 */
http.interceptors.request.use(config => 
  config.headers['Accept-Language'] = Cookies.get('language') || 'zh-CN'
  config.headers['token'] = Cookies.get('token') || ''
  // 默认参数
  var defaults = 
  // 防止缓存,GET请求默认带_t参数
  if (config.method === 'get') 
    config.params = 
      ...config.params,
      ... '_t': new Date().getTime() 
    
  
  if (isPlainObject(config.params)) 
    config.params = 
      ...defaults,
      ...config.params
    
  
  if (isPlainObject(config.data)) 
    config.data = 
      ...defaults,
      ...config.data
    
    if (/^application\\/x-www-form-urlencoded/.test(config.headers['content-type'])) 
      config.data = qs.stringify(config.data)
    
  
  return config
, error => 
  return Promise.reject(error)
)

/**
 * 响应拦截
 */
http.interceptors.response.use(response => 
  if (response.data.code === 401 || response.data.code === 10001) 
    clearLoginInfo()
    router.replace( name: 'login' )
    return Promise.reject(response.data.msg)
  
  return response
, error => 
  console.error(error)
  return Promise.reject(error)
)

export default http

AJAX 与跨域通信:AJAX 与同源策略

1.AJAX 解决了什么问题?

在远古时代,如果浏览器需要从服务器请求资源,其交互模式为 “客户端发出请求 -> 服务端接收请求并返回相应 HTML 文档 -> 页面刷新,客户端加载新的 HTML文档”,很显然,在这种情况下,即使只是为了更新部分数据,我们也不得不重新加载整个重绘的页面。而 AJAX 的出现解决了这个问题。

AJAX 即异步 JavaScript 和 XML,它可以在不重新加载整个网页的情况下,对网页的某部分进行异步更新。

2.XMLHttpRequest 对象

AJAX 的核心实现依靠的是浏览器提供的 XMLHttpRequest 对象。可以看作是一个构造函数,由此我们可以通过 const xhr = new XMLHttpRequest() 创建一个 XML 对象的实例,该实例有以下方法:

  • open():准备启动一个 AJAX 请求;
  • setRequestHeader():设置请求头部信息;
  • send():发送 AJAX 请求;
  • getResponseHeader(): 获得响应头部信息;
  • getAllResponseHeader():获得一个包含所有头部信息的长字符串;
  • abort():取消异步请求;

以及以下属性:

  • responseText:包含响应主体返回文本;
  • responseXML:如果响应的内容类型是 text/xml 或 application/xml,该属性将保存包含着相应数据的 XML DOM文档;
  • status:响应的 HTTP 状态;
  • statusText:HTTP 状态的说明;
  • readyState:表示“请求”/“响应”过程的当前活动阶段

3.AJAX 请求

3.1 创建 XML 对象的实例:

const xhr = new XMLHttpRequest();

3.2 准备请求

xhr.open('get','demo.php?name=Sam&job=coder');
  • 请求方式:有 GET 和 POST 两种,GET 请求用于向服务器拿取数据,我们可以像示例代码中那样给 URL 加上查询参数,即 ?name=Sam&job=coder,表示要查询的特定资源;POST 请求用于向服务器发送要保存的数据,数据存放的位置通过 send() 方法的参数来指定。那么,对于 GET 请求, send() 方法是否可以不传递参数呢?——不可以,应该传递 null
  • 请求 URL:可以是相对路径和绝对路径
  • 是否为异步请求:true 为异步,false 为同步。

3.3 设置请求头

xhr.setRequestHeader('Header','Value')

每个 HTTP 请求和响应都会带有相应的头部信息,包含一些与数据、收发者网络环境与状态等相关信息。默认情况下,当发送 AJAX 请求时,会附带以下头部信息:

  • Accept:浏览器能够处理的内容类型;
  • Accept-Charset: 浏览器能够显示的字符集;
  • Accept-Encoding:浏览器能够处理的压缩编码;
  • Accept-Language:浏览器当前设置的语言;
  • Connection:浏览器与服务器之间连接的类型;
  • Cookie:当前页面设置的任何Cookie;
  • Host:发出请求的页面所在的域;
  • Referer:发出请求的页面URI;
  • User-Agent:浏览器的用户代理字符串;

另外,我们还可以通过 setRequestHeader() 方法来设置请求头信息。该函数接受两个参数:头部字段(部分默认的或者自定义的)的名称和头部字段的值。

这个方法要在 open()send() 之间调用

3.4 发送请求

xhr.send(null)
// 或者
xhr.send(data_holder)

3.5 处理响应

目前为止,我们只是发送了请求,还没有针对服务器的响应结果做出一些处理。比方说,响应成功了怎么怎么样,响应失败了怎么怎么样。但是怎么知道是成功还是失败呢?这里就用到前面讲过的 xhr.status 属性,状态码可分为五大类:

状态码 范围 分类
1XX 100-101 信息提示
2XX 200-206 成功
3XX 300-305 重定向
4XX 400-415 客户端错误
5XX 500-505 服务器错误

当然还有具体的分类,这里不展开讲。那么,根据 xhr.status 这个响应结果,我们就可以进行相应处理了:

...
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText)
} else {
alert("Request was unsuccessful: " + xhr.status)
}

这么写对于同步请求(我们前面设置 open() 时第三个参数是 false)来说当然没问题 —— 因为是同步的,所以一定是 send 之后,服务器那边响应结果了才会继续执行后面判断 status 的代码,那么不管请求成功还是失败,这个判断一定是可以被正常执行的。但是如果是异步请求呢?对于异步请求,不需要等待服务器响应结果我们就可以执行后面的判断了,甚至可能出现一种情况是:服务器还没来得及响应结果,判断已经先执行了。那么这时候,请求一定会失败。

也就是说,我们需要加一层判断,确保收到服务器的响应结果之后,再去判断请求成功还是失败。这里就用到前面讲过的 xhr.readyState 属性,readyState 会随着 AJAX 的进程而不断变化,我们可以通过 onreadystatechange() 去监听它的变化,进而判断何时收到服务器的响应结果。

readyState 可取值有:

状态值 含义 说明
0 未初始化 尚未调用 open() 方法
1 启动 已经调用 open() 方法,但尚未调用 send() 方法
2 发送 已经调用 send() 方法,但尚未接收到响应
3 接受 已经接收到部分响应数据
4 完成 已经接收到全部响应数据,而且已经可以在客户端使用了

那么,前面的代码就变成了:

xhr.onreadystatechange = function(){
if (xhr.readystate == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText)
} else {
alert("Request was unsuccessful: " + xhr.status)
}
}
}

3.6 取消异步请求

设想这么一种情况:我们正在上传一张图片(也就是发送一个 AJAX 请求),由于耗时过长,我们决定取消上传,那么取消上传其实就是取消 AJAX 请求,这是通过 abort() 方法实现的。一旦调用这个方法,xhr 就会停止触发事件,而且也不再允许访问任何与响应相关的对象属性。在终止请求之后,不要忘了对 xhr 对象解引用。

正常上传:

取消上传:

4. XMLHttpRequest 2 级

4.1 FormData

通常提交表单数据的时候,这些数据需要经过序列化,虽然 $('#form').serialize() 可以实现序列化,但对于文件流无能为力。而 FormData 不仅可以做到表单序列化,而且支持异步上传二进制文件。

var data = new FormData();
data.append('name','Sam');
// or
var data = new FormData(document.forms[0]);

4.2 超时设定

xhr.timeout 指定一个毫秒为单位的时间,一旦浏览器在这个规定的时间内没有收到响应,就会触发 timeout 事件,执行回调函数。

xhr.onreadystatechange = function () {
if(xhr.readyState == 4){
try{
if((xhr.status >= 200 && xhr.status <300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful:" + xhr.status);
}
} catch(ex){
//......
}
}
}
xhr.open('get','timeout.php',true);
xhr.timeout = 1000;
xhr.ontimeout = function () {
alert("Request did not return in a second");
};
xhr.send(null);

注意:这时候很可能出现一种情况,就是超过1秒后浏览器没收到响应,因此终止了请求,而这时候恰好 xhr.status 为4,因此又调用函数进行判断,这个判断需要访问 xhr.status 属性,而请求已经被终止,这个属性是无法访问的,此时要用 try...catch... 捕获这个错误。

4.3 overrideMimeType() 方法

服务器返回的响应头中有一个是 Content-Type,用以告诉客户端返回的资源类型(MIME)以及应该用什么编码去解码。例如 Content-Type:text/html;charset=UTF-8,那么客户端就会通过 UTF-8 对资源进行解码,然后对资源进行 HTML 解析。但可能存在一种情况:虽然服务器返回数据是 XML,但 MIME 类型指定为 text/plain,那么这时候客户端就会当作纯文本去处理了,这显然不对,所以我们可以利用 overrideMineType() 方法重写响应的 MIME 类型,这样,客户端就可以将其当作 XML 去处理了。

var xhr = new XMLHttpRequest();
xhr.open('get','text.php',true);
xhr.overrideMineType('text/xml');
xhr.send(null);

注意,必须在 send 调用之前重写。

4.4 进度事件

Progress Events规范规范定义了与客户端与服务器通信相关的一系列事件,这些事件监听了通信进程中的各个关键节点,使我们能够以更细的颗粒度掌控数据传输过程中的细节。有以下6个进度事件:

  • loadstart:在接受到响应数据的第一个字节时触发
  • progress:在接受响应期间持续不断地触发
  • error:在请求错误时触发
  • abort:在因为调用 abort() 方法而终止连接时触发
  • load:在接收到完整的响应数据时触发
  • loadend:在通信完成或触发 error、abort、load 事件后触发

每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,然后触发 error、abort 或 load 中的一个,最后以触发 loadend 事件结束。

有没有发现,前面的 xhr.readyState == 4 以及这里的 load 事件都可以判断是否接受到完整响应?load 事件实际上简化了这个过程,它不需要像前者那样,既绑定一个监听函数又做一次 readyState 的判断,而只需要绑定监听函数即可。

xhr.onreadystatechange = function () {
if(xhr.readyState == 4){
//....
}
}
// 变成
xhr.onload = function () {
//....
}

5. 同源策略

浏览器的同源策略即  Same-Origin Policy (SOP),它限制了不同源之间执行特定操作。

5.1 源

一个源由协议、端口、域名组成,只要有一个不同,就认为是不同源。

以 http://test.com/dist/demo.html 为例,不同源以及同源可能有以下情况:

类型 URL 结果
不同协议 https://test.com/dist/demo.html 失败
不同端口 http://test.com:80/dist/demo.html 失败
不同域名 http://test.cn/dist/demo.html 或者 http://www.test.com/dist/demo.html 失败
不同路径 http://test.com/dist2/demo.html 成功

5.2 特定操作:

特定操作指的是:

  • 读取 Cookie、LocalStorage、IndexDB
  • 获取 DOM 元素
  • 发送 AJAX 请求

为什么同源策略要禁止不同源之间进行这些操作呢?我们不妨假设一下,不存在同源策略、且不同源之间这些操作是允许的,看看可能会发生什么事。

  • 我A源可以读取B源的 Cookie、LocalStorage、IndexDB,那么等于B源存储的信息都暴露了,所以同源策略禁止不同源之间读取 Cookie、LocalStorage、IndexDB;

  • A源可以获取B源的 DOM 元素。那么假定用户访问了我在A源中用 iframe 引入的B源网页,他的所有操作都会在我们的掌握之中,因为我们可以在A源操作B源的 DOM 元素;

  • A 源可以自由发送 AJAX 请求给B源。假定现在有一个用户首先登录了 Bank.com,那么本地客户端的 Cookie 就会记录用户在该网站的身份信息,之后用户不小心点进了危险网站 Evil.com,这个网站做了一些设置,一旦用户进入,就自动发送 AJAX 请求给 Bank.com,由于发送请求的时候,浏览器会自动在本地检索目标网站的 Cookie ,并添加到请求报文中,所以此时目标网站的 Cookie 被请求携带着发送过去了,而 Bank.com 的响应头又是携带着 Cookie 返回的,那么这时候等于 Evil.com 已经拿到了这个 Cookie。也就是说,发送请求前它确实拿不到这个 Cookie(是浏览器给请求报文加上的,不是我们),但接受到响应后它的的确确拿到了,于是事情一发不可收拾......

6. 跨域通信

这样看来,同源策略确实很有存在的必要,不然网络安全无从谈起。等等,既然有同源策略的限制,那我A域怎么去请求B域中的资源呢?也就是说,要怎么解决跨域通信的问题呢?

参考:

  • 《JavaScript 高级程序设计》第三版
  • 再也不学AJAX了!(二)使用AJAX


以上是关于axios的使用与跨域问题的解决的主要内容,如果未能解决你的问题,请参考以下文章

axios可以解决跨域访问的问题吗

vue 使用 axios 时怎么解决跨域问题

CORS跨域问题

vue+axios跨域解决方法

Vue之Axios跨域问题解决方案

解决react项目中跨域和axios封装使用