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

徐小夕 等级 427 0 1

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

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

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

浏览器的同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

如果两个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的跨域模式, 我们还是要深入实践中来, 笔者将采用nodejskoa中间件来实现cors模式的搭建.这里笔者先简单介绍一下cors:

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

基本场景如下: 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版) 对于简单的跨域场景,我们只需要设置请求头的Access-Control-Allow-Origin字段即可, 比如设置为号表示允许任何域名的访问. 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版) 这里我们使用*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 ? '*' : 'http://qutanqianduan.com'
    }
}))

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

更进一步

对于简单请求和简单的开发模式, 以上的设计就基本满足要求了, 但是对于复杂的业务场景, 我们的请求模式往往会涉及到更多的要求, 比如说需要携带cookie, 用户凭证或者自定义的请求头信息等(比如典型的JWT认证的token一般会存放到自定义的头信息中), 此时往往会发送预检请求(要求必须先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响). 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版) 这里我们需要了解以下几个响应头部的字段:

  • 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 ? 'http://192.xxx.1.3:8000' : 'http://qutanqianduan.cn'; // 允许来自指定域名请求, 如果设置为*,前端将获取不到错误的响应头
        }
    },
    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 ? 'http://localhost:3000/api/xxx' : 'http://localhost/api/xxx',
    withCredentials: true
});

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

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

作为一名前端工程师, 没有一个上手的请求库是万万不行的, 目前业界比较好的轮子有axios, umi-request等, 但是后者在使用过程中有一些坑(毕竟基于fetch实现), 所以这里笔者将基于axios来简单实现一个跨域请求库的封装.方便大家集成在自己的vue或者react项目中. 接下来看看请求库封装的简单模型: 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版) 笔者将基于http规范的错误类型进行基本的消息系统设计, 代码如下:

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

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

const instance = axios.create({
    baseURL: isDev ? 'http://localhost:3000/api/xxx' : 'http://qutanqianduan/api/xxx',
    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游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入我们的技术群一起学习讨论,共同探索前端的边界。 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版)

更多推荐

收藏
评论区

相关推荐

教你用200行代码写一个爱豆拼拼乐H5小游戏(附源码)
前言 本文将带大家一步步实现一个H5拼图小游戏,考虑到H5游戏的轻量级和代码体积,我没有使用react或vue这些框架,而采用我自己写的dom库和原生javascript来实现业务功能,具体库代码可见我的文章如何用不到200行代码写一款属于自己的js类库(https://juejin.im/post/6844903880707293198),构建工具我采
《前端实战总结》之使用解释器模式实现获取元素Xpath路径的算法
前端领域里基于javascript的设计模式和算法有很多,在很多复杂应用中也扮演着很重要的角色,接下来就介绍一下javascript设计模式中的解释器模式,并用它来实现一个获取元素Xpath路径的算法。 上期回顾 《前端实战总结》之迭代器模式的N1种应用场景(https://juejin.im/post/6844904008616771591)
当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版)
我们知道很多大型项目都或多或少的采用跨域的模式开发, 以达到服务和资源的解耦和高效利用. 在大前端盛行的今天更为如此, 前端工程师可以通过nodejs或者Nginx轻松搭建起web服务器.这个时候我们只需要请求后端服务器的接口即可实现系统的业务功能开发.这个过程中会涉及到web页面向API服务器的跨域访问(由于受到浏览器的同源策略,但是业界已有很多解决方案,
Cors跨域解决
一、浏览器跨域问题产生 1、跨源资源共享(CORS)中文文档: https://developer.mozilla.org/zhCN/docs/Web/HTTP/Access_control_CORS 2、什么是浏览器跨域问题 指的是浏览器不能执行其他网站的脚本。JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象,即同源策略。就好比我
vue实现世界疫情地图(点击进入子地图)
点击进入子地图目前只实现了中国模块 数据来源,腾讯实时疫情(https://news.qq.com/zt2020/page/feiyan.htm/global),中国疫情网(https://www.ncovchina.com/data.html) 原本只想做中国模块,后来想了想,做个世界的吧 使用axios和echarts,elementui的
Vue跨域解决方法
vue项目中,前端与后台进行数据请求或者提交的时候,如果后台没有设置跨域,前端本地调试代码的时候就会报“No 'AccessControlAllowOrigin' header is present on the requested resource.” 这种跨域错误。 要想本地正常的调试,解决的办法有三个:
Chrome 中 Set-Cookie SameSite 问题
关于 “Chrome 修改对未设置 SameSite 的 cookie,视作 SameSite:Lax 处理的变更” 的问题,目前看,最妥善的解决方案还是按照规矩办事儿,目前 Chrome 是行动最快的,而 FireFox 和 Edge 也在积极跟进,持支持态度。 影响范围: 如果你的项目中有如下跨域场景: 1. 跨域的 ajax
问题 first path segment in URL cannot contain colon 的解决方案
目录问题解决 问题使用Golang开发流媒体服务器处理Post请求时,遇到了这个报错信息:2020/12/14 07:21:01 callback post failed2020/12/14 07:21:01 error::8080/api/callback: first path segment in URL cannot contain col
前后端分离的情况下,vue保存cookie时碰到的坑! (axios.defaults.withCredentials = true;)
文章目录 一号坑问题描述当我们的项目是前后端分离的模式时,vue不会自动帮我们保存后端传来的cookie!解决方案我们需要在main中添加axios.defaults.withCredentials true 二号坑问题描述如果你之前处理过跨域方面的问题,应该会记得你曾经在后端请求头添加
Git基础命令教程
Git学习 git 之前,我们需要先明白一个概念,版本控制! 版本控制 什么是版本控制版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。 实现跨区域多人协同开发 追踪和记载一个或者多个文件的历史记录 组织和保护你的源代码和文档 统计工作量
如何在React Native和Expo中掩盖Text和TextInput组件
在本文中,我将向您展示如何在React Native和Expo中使用自定义蒙版,可用于iOS,Android和Web!我们将使用一个名为库,这是一个没有本机代码的完整javascript库,然后您可以在React Native环境的所有CLI中使用。](https://res.cloudinary.com/practicaldev/image/fetch/s
JavaScript预解析处理过程原来是这回事
讲解一般来说,Javascript代码的执行包括两个过程:预解析处理过程 和 逐行解读过程。在代码逐行解读前,Javasript引擎需要进行代码的预处理过程。预解析处理的工作主要是变量提升和给变量分配内存,具体过程是在每个作用域中查找var声明的变量、函数定义和命名函数(函数参数),找到它们后,在当前作用域中给他们分配内存,并给他们设置初始值。预解析设置的初
重学JavaScript第1集|变量提升
变量提升就好比JavaScript引擎用一个很小的代码起重机将所有var声明和function函数声明都举起到所属作用域(所谓作用域,指的是可访问变量和函数的区域)的最高处。这句话的意思是:如果在函数体外定义函数或使用var声明变量。则变量和函数的作用域会提升到整个代码的最高处,此时任何地方访问这个变量和调用这个函数都不会报错;而在函数体内定义函数或使用va
Webpack学习整理集锦【从最基础的demo入手,自己实现一个脚手架 】
前言本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。开源网址https://github.com/maomi
前端容易理解错的跨域原理
前言关于跨域这件事,自从我遇到后,了解一下,这事就过去了。我也一直认为这是个小问题,大家应该都懂。直到我要教妹妹前端时遇上这个问题才发现,这个问题得整个逻辑讲出来其实还挺绕的。知道问题怎么解决很简单,但是要讲清楚问题为什么出现就十分复杂了。那么我突然就好奇了,大家是都懂这个逻辑了嘛。所以我在几个交流群里问了一个问题 为什么很多人都出现本地环境会跨域而线上环境