跨域的产生与 3 种有效解决方案

刚开始做 vue 项目时,在本地开发阶段遇到跨域的报错,查网上的资料去设置 proxy,照葫芦画瓢,一知半解导致有的项目能解决,有的却行不通。本文就来好好说说啥是跨域,并且尽可能讲明白 3 种亲测可用的解决跨域方案。

跨域问题的产生

跨域问题的产生是因为浏览器的同源策略,根据 MDN 的解释,所谓同源策略:

是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。只有当两个 URL 的协议、端口(如果有指定的话)和主机都相同,这两个 URL 才是同源的。

比如现在有个运行在 http://127.0.0.1:5500 的页面,使用 Fetch API 发送了个服务器地址为 http://localhost:3000 的 GET 请求,因为端口号不同(localhost 的 ip 地址即为 127.0.0.1,所以主机是相同的 ),会产生跨域问题:

跨域问题的解决

如果可以让网页和接口都部署到同个服务器,那么就不会产生跨域问题。但我们在平常开发项目时,多半都是前端通过运行在本地的服务器来查看网站的效果,而接口都是后端部署在一些云服务器上的,所以有必要掌握一些切实可行的解决跨域问题的方法。

方法一: CORS

上文中的跨域报错信息里提示的请求 "been blocked by CORS policy",即被 CORS 策略阻止,而 CORS 就是 Cross-Origin Resource Sharing 跨源资源共享,下面引用一段 MDN 的解释:

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

报错信息里还说 "No 'Access-Control-Allow-Origin' header is present on the requested resource",所以对于简单请求,我们只需要给接口设置响应头 Access-Control-Allow-Origin 为需要请求资源的域名即可,比如在 koa 搭建的项目里就可以通过 ctx.set 设置:

javascript 复制代码
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
  await next()
})

可以用 * 号替换具体的域名,代表任何源都可以访问:

javascript 复制代码
ctx.set('Access-Control-Allow-Origin', '*')

如果请求是非简单请求,则还需要添加一些设置:

javascript 复制代码
app.use(async (ctx, next) => {
  // 简单和非简单请求都需要设置
  ctx.set('Access-Control-Allow-Origin', '*')
  // 非简单请求可能需要以下额外设置
  ctx.set(
    'Access-Control-Allow-headers',
    'Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent'
  )
  ctx.set(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, PATCH, DELETE, OPTIONS'
  )
  ctx.set('Access-Control-Allow-Credentials', true) // 允许携带 cookie
  if (ctx.method == 'OPTIONS') {
    ctx.status = 204
  } else {
    await next()
  }
})

不是每个非简单请求都需要以上的全部设置,比如 PUT 请求,就只是需要额外设置 Access-Control-Allow-headersAccess-Control-Allow-Methods

简单请求与非简单请求

下面说一下简单请求和非简单请求的区分,只有同时满足以下两方面条件的才是简单请求:

  1. 请求方法是 HEAD、GET 或 POST;
  2. HTTP headers 不超出以下字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type 并且值是 application/x-www-form-urlencoded,multipart/form-data, 或者 text/plain之一的(忽略参数)。

方法二:Node 代理服务器

在之前使用 vue-cli 创建的 vue2 项目里,解决跨域问题我们通常会去 vue.config 设置 devServer.proxy,其本质上是使用 node 创建一个代理服务器,然后我们向该代理服务器发送请求,它会使用 http-proxy-middleware 这个库,将我们的请求代理转发给接口服务器,服务器向服务器发送请求,就不存在浏览器同源策略导致的跨域问题了。

下面我们自己实现上述过程,先安装依赖:

powershell 复制代码
npm i http-proxy-middleware 

然后创建并启动代理服务器:

javascript 复制代码
const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware')

const app = express()

// 解决请求代理服务器跨域的问题
app.use(express.static('./client'))

app.use(
  '/api',
  createProxyMiddleware({
    target: 'http://localhost:3000',
    pathRewrite: {
      '^/api': ''
    }
  })
)

app.listen(4396, () => {
  console.log('代理服务器启动')
})

代理服务器跑在 http://localhost:4396,我们的页面如果依旧使用 Live Server 启动,运行在 http://127.0.0.1:5500,那么我们向代理服务器发送请求仍会有跨域问题,所以我们使用 app.use(express.static('./client')) 将页面所在的目录设置为静态资源(关于 express 的使用可参见《结合源码了解 Express 的基本使用》)。

我们使用一个中间件,来处理以 /api 开头的请求,在 createProxyMiddleware 的配置中,target 指向的即为接口服务器;因为原本的接口地址是没有 /api 的,所以在 pathRewrite 中要将 /api去除。

现在,我们在页面中,假设原本的请求地址为 http://localhost:3000/test,就应该改为http://localhost:4396/api/test ,即向代理服务器请求。此时通过控制台查看网络请求,可以看到请求地址指向的是代理服务器,但可以正确接收到预期的数据:

方法三:Nginx 反向代理

该方法的本质和方法二其实是一样的,无非是使用 Nginx 服务器来替换方法二中我们自己创建的服务器作为代理。先去官网下载 Nginx,如果是 windows 电脑就下载下图所示的稳定版:

下载后解压,双击 nginx.exe 即可开启 nginx 服务器:

此时如果去访问 http://localhost,可以看到 nginx 的欢迎页面(默认为 80 端口):

既然 nginx 监听的是 localhost:80,所以我们只需要将页面中请求代码改为 fetch('http://localhost/test'),然后让 nginx 去请求真正的接口服务器即可。配置方式为修改 conf/nginx.conf 文件,设置 proxy_pass 为接口服务器,并且同样需要添加 header 设置 Access-Control-Allow-Origin*

ini 复制代码
server {
  # 省略其它代码
  location / {
    add_header Access-Control-Allow-Origin *;
    proxy_pass http://localhost:3000;
  }
}

之后重启 nginx 让修改生效,重启方法为在 nginx 目录下打开 Git Bash,然后输入命令 ./nginx.exe -s reload(或者直接去任务管理器关闭 nginx 进程后再次双击 nginx.exe):

现在就可以请求成功了:

相关推荐
Rhi63713 分钟前
从零搭建项目:React 19 + Vite 8 + Tailwind CSS v4 实战配置
前端
Vane118 分钟前
从零开发一个AI插件,经历了什么?
人工智能·后端
竹林81820 分钟前
用Viem替代ethers.js:从一次签名失败到完整迁移的实战记录
前端·javascript
之歆25 分钟前
DAY08_CSS浮动与行内块布局实战指南(上)
前端·css
9523640 分钟前
SpringBoot统一功能处理
java·spring boot·后端
light blue bird1 小时前
主子端台二分法任务汇总组件
前端·数据库·.net·桌面端winform
rleS IONS1 小时前
SpringBoot中自定义Starter
java·spring boot·后端
DevilSeagull1 小时前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
jeffwang2 小时前
我做了个让 AI 看屏幕跑测试的工具,因为 Playwright 测不了我的 Flutter Web
前端
HSunR2 小时前
dify 搭建ai作业批改流
开发语言·前端·javascript