http-生产环境有跨域吗?

生产环境有跨域吗?

这个问题是作者在一次解决完本地跨域之后产生的一个疑问。问题的答案是:有。但是是一定有吗,这个就要看公司的项目架构了。一起来看看吧。

1. 什么是跨域

这个问题是有标准答案的: 由于浏览器的同源协议,当请求的URL的协议、域名(ip)、端口三者之间任意一个与当前页面URL不同时,即被视为跨域

注意上述的描述,跨域其实是一种现象。在作者早期的认知中,大概浏览器报了类似如下的错才算是跨域

上述的报错,实际上只能算是跨域导致的结果。无论报不报错,只要请求的url不满足同源条件就已经产生了跨域。

基于此,只要是前后端分离的项目,那就一定会产生跨域。前后端分离的项目中,前端和后端分别会启动两个服务,不说协议和域名,单是端口就不可能一样,所以跨域是必然的。

所以生产上一定有跨域吗? 这并不一定。

上述说的是前后端分离的项目,但是在一些老项目中,前后端其实并不分离。例如javaWeb项目,前端和后端的代码都在同一个项目中,而最终也只会运行一个服务。也就是说你打开的页面和你要发的请求是同源的,针对这种的项目压根就不会出现跨域。

而本文主要就是针对前后端的分离的项目去探讨生产环境下的跨域问题怎么解决

2. 开发环境解决跨域问题

相信针对开发环境的跨域解决方案,大家应该是信手沾来的, 毕竟这面试题太经典了。这里我举一个proxy代理解决跨域的例子。虽然我知道大家一定会配置,但是我更想让大家了解的是这种使用proxy代理解决跨域的思想,这是值的学习和借鉴的。

以vue为例,在一个vue(基于webpack)项目中怎么配置proxy代理呢,大概是在vue.config.js中进行如下的配置

js 复制代码
  // vue.config.js
  devServer: {
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8001', // 真实的后端地址
        changeOrigin: true,
        pathRewrite: {
          '/api': ''
        }
      }
    },
  }

如上所示,只要进行简单的几行配置就能解决跨域问题了。那为啥加上这几行配置就可以解决呢。实现思路其实也很简单。

我们运行vue项目,webpack工具会帮我启动一个服务,这个服务中包含两部分功能:

  1. 帮我们编译前段代码,生成前端页面
  2. 代理请求

而我们的上述配置其实就相当于开启了 代理请求 功能。默认情况下,我们发送的请求会被后端直接接收(如下图1所示)。但当我们开启这个功能之后,webpack会拦截请求url中以/api(上述配置的代理标识是/api)开头的所有请求,之后webpack会帮我们把拦截到的请求发送到后端真实地址,也就是配置中的target。 整体流程如下图2所示。

如上图所示,我们发现展示到网站的url 和 发送接口的url 协议、ip和端口是完全相同的,换而言之它们是同源的,所以压根就不会存在跨域。而代理服务将请求转发到后端也不会存在跨域,因为压根就没走浏览器。这也是为啥配置完代理之后就可以解决跨域问题。

之所以花不少篇幅阐述proxy代理,是因为有一种解决生产跨域的方案和上述方案非常类似,一起看看生产跨域的几种方案吧!

3. 生产环境解决跨域问题

基于可能很多小伙伴不是很清楚前端项目的生产发布流程。我说下发布过程大致经历的步骤。

  1. 项目打包, 执行类似于npm run build 的命令,生成一个dist文件夹
  2. 将该dist目录,放置到一个服务器中,通常是nginx服务器
  3. 进行ginx配置确保项目能正确运行。比如指定服务启动之后默认打开的页面,一般都是dist中的index.html
  4. 启动服务,之后前端项目就可以正常访问了。

上述步骤是可以人为操作成功的,但实际生产中基本都会由自动化程序(cicd)自动完成

而接下说的第一种的跨域解决方案就和上述nginx配置有关

1. 配置nginx代理解决跨域问题

下面是nginx.conf文件中的一段配置, 不了解nginx也没关系,主要了解其解决思路

nginx 复制代码
 server {
        listen 8223;
        location / { # 代理前端项目
            root /usr/share/nginx/html/dist; # 指定dist目录
            try_files $uri $uri/ /index.html; # 解决路由刷新404
            index  index.html; # 指定其实页是dist中的index.html
        }
        location ^~ /api { # 配置代理
            proxy_pass http://127.0.0.1:8001/;
        }
    }

上述配置中的有个字眼是不是特别熟悉, 没错就是 /api,只要加上第二个location及之后的配置就可以解决跨域问题。它底层又是怎么实现的呢?

其实它和开发环境中的webpack是非常类似的。nginx会运行我们的项目, webpack也会运行我们的项目,只不过webpack会多一个步骤就是帮我们编译。并且webpack提供了proxy代理,而nginx换成了location 进行代理,其中的proxy_pass就相当于proxy代理中的target,指向的是后端地址。整体运行流程如下:

上图是不是很眼熟,是的,它和proxy代理的思路可以说是一模一样的。是不是很简单。

不过有细心同学会发现,proxy代理配置中会去掉/api这个标识,但是nginx代理中好像没有去掉哎!其实是去掉了的!这就涉及nginx配置代理时的两个注意点了

  1. 代理的后端地址一定要以 / 结尾 ,比如proxy_pass http://127.0.0.1:8001/ 中的8001后面一定要就加 /。 加上之后会自动替换掉/api这个标识。
  2. 代理标识的前面的一定要加 ^~ ,这表示使用了正则,只要url是以/api开头 就会走这个代理。如果去掉^~ 就表示精准匹配,url必须等于/api才会走代理

nginx配置代理解决跨域问题,虽然常见,但却并不是一种很好的编码实践。nginx中使用了/api作为代理标识,对应前端项目中发送请求的时候也要使用/api ,换而言之,你压根就不知道后端服务地址是多少。这并利于项目的维护。

使用nginx配置跨域代理,更多的时候是因为,在发送一些第三方接口的时候,第三方的接口没有进行跨域处理(比如调用第三方的天气信息之类的接口), 不得不使用nginx进行接口代理。

而更好的跨域实践一定是让后端配置cors

2. 配置CORS解决跨域问题

CORS是啥? 来看看MND上是怎么解释的。

跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。

上面这段话是有点难以理解的。通俗的解释就是:CORS是一种机制,该机制允许我们在请求头或响应头上加上一些特殊的字段,让浏览器可以加载跨域资源。

先说结论:CORS的配置基本都是由后端配置,后端会在后端服务中配置跨域相关的响应头,从而解决跨域问题。

不知道同学们有没有这样一个疑问:跨域不是浏览器的同源协议导致的吗,和后端有啥关系?如果你有这样的疑问,那代表你依然没有理解CORS。

回顾一下跨域是什么:是一种现象,浏览器的同源协议,会禁止不同源的url之间共享数据,但与此同时它也提供了一种机制,让就算不同源的url之间也可以数据共享,这种机制就是CORS

代理解决跨域其实一句话总结就是:让不同源的url(页面url和接口url)变成同源的。 而CORS解决跨域:是通过添加特殊的http头的方式 让 依旧不同源的两个url能够资源共享

如果只是一个简单的请求,后端大概只需要配置一个响应头就可以解决跨域问题了。下面使用node的express框架模拟了一个后端接口。

js 复制代码
import express from "express";

const exApp = express();

exApp.get("/getUserList", (req: Request, resp: Response) => { // 定义一个get请求接口
    resp.setHeader("Access-Control-Allow-Origin", "*"); // 在响应体中配置跨域响应头
    resp.send({userName: 'lsm'}); // 返回给前端数据
});

exApp.listen(8001, () => { // express 服务启动之后监听8001端口
  console.log("express starting at port 8001...");
});

如上述代码所示,只要给接口添加一个Access-Control-Allow-Origin响应头就可以解决跨域问题。同样可以解决文章开头提到的报错。

这个响应头的作用可以简单理解为 告诉浏览器允许哪些来源(页面url)可以访问该接口,上述代码该响应头的值是 "*" ,代表任意来源的url都可以访问该接口。如果想更精细的控制, 可以把这个值改成对应的前端服务的地址,比如作者实验时前端的服务是:http://127.0.0.1:8000, 就可以写成: resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8000");

其实除了上述说那个响应头,还有另外五个和跨域相关的响应头

js 复制代码
exApp.get("/getUserList", (req: Request, resp: Response) => {

    resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8000"); // 配置允许的来源页面的url
    resp.setHeader("Access-Control-Allow-Headers", "*"); // 配置允许前端发送请求时能携带的请求头
    resp.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); // 配置允许前端能发送的接口
    resp.setHeader("Access-Control-Max-Age", "3600"); // 配置预检请求的缓存时间
    resp.setHeader('Access-Control-Expose-Headers', '*') // 配置前端能够拿到响应头
    resp.setHeader('Access-Control-Allow-Credentials', true) // 配置是否允许前端请求时携带凭证

    resp.send({userName: 'lsm'}); // 返回给前端数据
});

其余的响应头,总结来说只做了一件事,就是细化对于跨域请求的处理。 针对上述的响应头就不挨个讲述了,其中涉及跨域响应头的配置规则涉及的东西非常多,包括像是 简单和复杂请求,预检请求等。作者会专门写一篇文章详述这些。

其实关于CORS说了这么多,只想让同学们了解两件事:

  1. 什么是CORS 及 CORS是怎么工作的
  2. 跨域相关的六个响应头。

有同学可能就要吐槽了,那个响应头是后端设置的,和我前端有啥关系, 我知道它有啥用。这就不得不说到最后一种解决生产跨域的方案了

3. node中间件解决跨域问题

nginx代理的方案不好维护,配置CORS的方案又是后端去配,那有啥折中的方案没有,答案就是node中间件。

实现思路:既然我们改不了后端的代码,那我添加一个中间服务,前端把接口发送到这个中间服务,中间服务再转发给后端服务。之后我们可以在中间服务配置CORS,不就可以解决跨域问题了吗。

大部分公司这个中间件都是使用node开发的,下面我依然用node加express模拟一个中间件。

js 复制代码
import express from "express";
import proxy from 'express-http-proxy'; // 新增:用于接口转发的

const exApp = express();

// 修改: 这会拦截所有经过express的请求和响应,后端的响应会经过这里,在这里给响应添加CORS响应头
exApp.use((req: Request, resp: Response) => {

    resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8000"); 
    resp.setHeader("Access-Control-Allow-Headers", "*"); 
    resp.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); 
    resp.setHeader("Access-Control-Max-Age", "3600"); 
    resp.setHeader('Access-Control-Expose-Headers', '*') 
    resp.setHeader('Access-Control-Allow-Credentials', true) 

    next(); // 新增: 继续向后执行请求和响应
});

// 做接口转发, 将前端请求转发到后端:http://127.0.0.1:8001
exApp.use('/', proxy('http://127.0.0.1:8001', {
    proxyReqPathResolver: (req) => {
        return req.path
    },
}))

exApp.listen(8001, () => { // express 服务启动之后监听8001端口
  console.log("express starting at port 8001...");
});

是不是和我上述用node模拟的后端服务很像,但其实两者之间没有啥关系。这个node服务做了两件事:

  1. 使用express-http-proxy 插件转发前端的请求到真实的后端。
  2. 在后端响应的时候,我给后端的响应头上加上 CORS响应头

大致流程如下

上图就是整体解决跨域的流程啦。但, 貌似,有点别扭。

按照上述这个流程来说,还不如直接走nignx 代理,这还多整一个node服务,完全就是脱裤子**

其实如果只是单纯为了解决跨域,完全没必要使用该方案。但是有一种情况使用该方案是非常合适的:

在有些大型的前端项目中,可能会使用到很多后端服务,这个时候使用nginx对多个后端服务进行代理将会非常繁琐,而把所有请求都转发到一个node服务器,由node服务进行统一的接口分发、统一配置CORS,将会是一个很好的解决方案。

以上就是今天的全部内容啦,谢谢各位看官老爷的观看。不好的地方,还请包涵。不对的地方,还请指正。

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd4 小时前
前端知识汇总(持续更新)
前端
加油,旭杏5 小时前
【中间件学习】fastCG介绍和使用
学习·nginx·fastcgi