您的位置 首页 java

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

我们知道很多大型项目都或多或少的采用跨域的模式开发, 以达到服务和资源的解耦和高效利用. 在 大前端 盛行的今天更为如此, 前端工程师可以通过 nodejs 或者 Nginx 轻松搭建起 web服务器 .这个时候我们只需要请求后端服务器的接口即可实现系统的业务功能开发.这个过程中会涉及到 web页面 API服务器 的跨域访问(由于受到浏览器的 同源策略 ,但是业界已有很多解决方案,接下来会介绍).通过这种开发模式使得我们真正的实现了前后端完全分离.

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

采用这种前后端单独开发部署的模式好处有如下几点:

  • 减少后端服务器的并发/负载压力
  • 前端项目和后端项目完全分离, 一定程度上提高了自动化部署的灵活性, 并且代码更易管理和维护
  • 提高前后端开发团队的工作效率, 各司其职, 出现bug更容易定位问题
  • 在大并发情况下可以同水平扩展前后端服务器,利用多台前端服务器做集群来抗住日均千万级的pv
  • 提高应用容错, 即使是API服务器挂了, 前端页面依然能正常访问
  • API服务器能同时为多个应用平台提供服务, 大量复用接口,提升效率。(比如说 微服务 )

虽然好处有很多, 但是为了实现以上的架构模式, 我们首先要解决的就是 跨域问题 .

浏览器的同源策略

如果两个URL的 protocol (协议,比如http协议,https协议)、 port (端口号,如80)和 host (主机,如developer.mozilla.org) 都相同的话,则这两个 URL 是 同源 。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。也就是说如果 不满足以上3个条件中的任意一个,则被视为跨域 .

解决跨域问题的几种方式

业界解决浏跨域问题的方案很多, 笔者在这里粗略介绍一下:

  • JSONP实现跨域 通过script标签和url回调来实现跨域, 缺点是只支持get请求
  • CORS CORS需要浏览器和后端同时支持, 后端设置 Access-Control-Allow-Origin 就可以开启 CORS
  • postMessage 可以实现跨文本档、多窗口、跨域消息传递(笔者之前写可插拔式聊天机器人就是采用该方案)
  • websocket websocket是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,也是跨域的一种解决方案
  • nginx反向代理
  • document.domain + iframe 比较传统的跨域解决方案

目前作为大规模跨域开发使用最多的模式还是 CORS方案 ,所以笔者接下来将具体介绍采用 cors模式 搭建前后端跨域访问通用解决方案, 为了方便,笔者后端将采用 nodejs+koa , ( java/php 开发类似), 前端采用 axios 作为请求库来配合实现完整的 cors模式 .

跨域开发的后端配置(node/koa版)

要想彻底了解 cors 的跨域模式, 我们还是要深入实践中来, 笔者将采用 nodejs koa中间件 来实现 cors 模式的搭建.这里笔者先简单介绍一下cors:

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头 来告诉浏览器 让运行在一个域上的Web应用被准许访问来自不同源服务器上指定的资源。

基本场景如下:

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

对于简单的跨域场景,我们只需要设置请求头的 Access-Control-Allow-Origin 字段即可, 比如设置为*号表示允许任何域名的访问.

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

这里我们使用 koa2-cors 这个中间件来实现一下, 代码如下:

 import koa from 'koa';
import cors from 'koa2-cors';
const app = new koa();
// 设置跨域
app.use(cors({
    origin: function (ctx) {
        return '*'
    }
}))
复制代码  

通过这样的配置, 我们就能轻松实现 cors 跨域, 不过现实开发中我们一般不会这么设置, 因为这样设置意味着任何人都能访问我们的服务,安全性无法保证. 作为小型的开放服务,可以采用这样的配置加上访问限流来实现免费图床类应用.(开放图床实现可以参考笔者之前写的文章使用nodeJs开发自己的图床应用)

在实际开发中, 我们会将origin的返回值设置为指定域名, 这样就只允许该域名下的请求访问, 所以正确的姿势如下:

 import koa from 'koa';
import cors from 'koa2-cors';
const app = new koa();
const isDev = process.env.NODE_ENV === 'development';
// 设置跨域
app.use(cors({
    origin: function (ctx) {
        return isDev ? '*' : '#39;
    }
}))
复制代码  

通过这种方式, 我们在开发环境中, 可以让前端同事自由访问我们的API接口, 提高联调效率, 而在生产环境中只允许我们的WEB服务器所在域名访问.

更进一步

对于简单请求和简单的开发模式, 以上的设计就基本满足要求了, 但是对于复杂的业务场景, 我们的请求模式往往会涉及到更多的要求, 比如说需要携带cookie, 用户凭证或者自定义的请求头信息等(比如典型的 JWT 认证的 token 一般会存放到自定义的头信息中), 此时往往会发送 预检请求 (要求必须先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响).

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

这里我们需要了解以下几个响应头部的字段:

  • Access-Control-Allow-Methods 表明服务器允许客户端使用的请求方法
  • Access-Control-Allow-Headers 表明服务器允许请求中携带的头部字段
  • Access-Control-Max-Age 表明响应的有效时间。在有效时间内,浏览器无须为同一请求再次发起预检请求
  • Access-Control-Expose-Headers 服务器允许浏览器访问的头信息白名单
  • Access-Control-Allow-Credentials 指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容

以上这5个响应头部字段非常重要,这也是我们解决复杂跨域场景的关键配置. 具体配置案例如下:

 // 设置跨域
app.use(cors({
    origin: function (ctx) {
        if (ctx.url.indexOf(config.API_VERSION_PATH) > -1) {
          return isDev ? '#39; : '#39;; // 允许来自指定域名请求, 如果设置为*,前端将获取不到错误的响应头
        }
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization', 'x-show-msg'],
    maxAge: 5,  //  该字段可选,用来指定本次预检请求的有效期,单位为秒
    credentials: true,  // 允许携带用户凭证
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的请求方法
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Requested-With'] // 允许接收的头部字段
}))
复制代码  

以上是采用koa2-cors实现的方案, 通过设置 exposeHeaders , 我们就可以在浏览器端拿到服务器响应的头部字段’WWW-Authenticate’, ‘Server-Authorization’, ‘x-show-msg’, 进而根据这些字段的值来实现定制化的消息机制.

需要注意的是, 我们服务器在设置 credentials 后,需要前端请求库配置设置,比如我们需要在 axios 中设置withCredentials为true, 代码如下:

 import axios from 'axios'
const isDev = process.env.NODE_ENV === 'development'
const instance = axios.create({
    baseURL: isDev ? '#39; : '#39;,
    withCredentials: true
});
复制代码  

这样我们就能成功携带用户凭证并被跨域的后端服务器获取了.以上就实现了我们 cors 模式的后端配置, 对于 nodeJS 为主的后端选手, 基本任务已经完成, 对于 java/PHP 选手, 也可以参考类似的配置和库来实现. 接下来我们来实现前端请求库的封装.

跨域开发的前端请求库封装(axios版)

作为一名前端工程师, 没有一个上手的请求库是万万不行的, 目前业界比较好的轮子有 axios , umi-request 等, 但是后者在使用过程中有一些坑(毕竟基于fetch实现), 所以这里笔者将基于 axios 来简单实现一个跨域请求库的封装.方便大家集成在自己的 vue 或者 react 项目中. 接下来看看请求库封装的简单模型:

当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

笔者将基于http规范的错误类型进行基本的消息系统设计, 代码如下:

 import axios from 'axios'
import { message } from 'antd'

const isDev = process.env.NODE_ENV === 'development'

const instance = axios.create({
    baseURL: isDev ? '#39; : '#39;,
    timeout: 10000,
    withCredentials: true
});

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    config.headers = {
        'x-requested-with': localStorage.getItem('user') || '',
        'authorization': localStorage.getItem('token') || ''
    }
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    if(response.headers['x-show-msg'] === 'zxzk_msg_200') {
        message.success(response.data.msg);
    }
    return response.data.result;
  }, function (error) {
    // 对响应错误做点什么
    const { response } = error;
    if(response.status === 404) {
        message.error('请求资源未发现');
    }else if(response.status === 403) {
        message.error(response.data.msg, () => {
            window.location.href = '/login'
        });
    }else {
        message.error(response.data.msg);
    }
    return Promise.reject(error);
  });

export default instance
复制代码  

以上笔者结合 antd 的message作为消息反馈UI,利用 axios 的请求和响应拦截来实现消息系统的设计, 以上只是基本的框架, 大家可以基于以上设计进行更加自定义的封装.

讲到这里, 大家是不是对跨域下的服务端和前端配置有了更进一步的了解了呢?

最后

如果想学习更多 H5游戏 , webpack node gulp css3 javascript nodeJS canvas数据可视化 等前端知识和实战,欢迎在《趣谈前端》学习讨论,共同探索前端的边界。

文章来源:智云一二三科技

文章标题:当遇到跨域开发时,我们如何处理好前后端配置和请求库封装

文章地址:https://www.zhihuclub.com/181869.shtml

关于作者: 智云科技

热门文章

网站地图