用的好好的vue.config.js代理,突然报308, 怎么回事?🤔

背景

上周有个vue项目, package.json中显示的vue-cli-service版本是 "@vue/cli-service": "~4.5.0",本地开发代理配置突然不能使用了, 报308永久重定向错误

查看了一下vue.config.js中的代理转发配置,没发现明显问题,

js 复制代码
  devServer: {
    proxy: {
      '/api': {
        target: 'https://test.xxx.com',
        changeOrigin: true,
        pathRewrite: {
          '^/api': '',
        },
      },
    },
  },

又看了一下公共网络请求方法设置的基础路径, 没发现问题

js 复制代码
const instance = axios.create({
  // ...
  baseURL:process.env.NODE_ENV == 'development' ? '/api' : env.VUE_APP_BASE_URL,
})

最后又查看了一下api定义处接口的url, 也看不出来问题。

js 复制代码
  // 店铺详情
  storeInfo: (id) => {
    return get(`/项目名/api-v2/ticketStore/noAuth/store/${id}`)
  },

当时开发任务时间紧张,只能先迂回过去, 根据api文档定义,盲写业务逻辑, 写完之后,部署到线上环境去调试。今天相对空闲一些, 想查找一下引发问题的原因,毕竟本地开发,不可能不使用接口代理转发功能,问题迟早都要解决,绕不过去。

问题排查

以我过去排查疑难问题的经验, 一个比较有效的做法,就是把执行流程中的每个步骤的详细信息打印出来,问题一般就会水落石出。按照这个思路, 我给代理转发配置中添加了许多执行步骤日志, 看看转发的目标url是否正确,是从哪个环节开始不正常了。

在请求转发事件中,打印请求转发目标url

js 复制代码
onProxyReq: (proxyReq, req, res) => {
    console.log('=== 代理请求详情 ===')
    console.log('URL:',`${proxyReq.protocol}//${proxyReq.getHeader('host')}${proxyReq.path}`)
}

在请求转发响应事件中, 打印响应数据

js 复制代码
onProxyRes: (proxyRes, req, res) => {
    console.log('=== 代理响应详情 ===')
    console.log('状态码:', proxyRes.statusCode)

    proxyRes.on('data', (chunk) => {
      body += chunk.toString()
    })
    
    proxyRes.on('end', () => {
      console.log('=== 错误详细信息 ===')
      console.log(body)
    })
},

重启服务,刷新了一下页面, 终端控制台输出如下:

js 复制代码
=== 代理请求详情 ===
URL: https://test.xxx.com/xxx/api-v2/ticketStore/noAuth/store/578518674433958631?t=1756195546614
 
=== 代理响应详情 ===
状态码: 308
=== 错误详细信息 ===
<html>
<head><title>308 Permanent Redirect</title></head>
<body>

打印出来的目标url没问题, 但是响应不对。308和常见的301/302 重定向 类似, 表示目标地址已经迁移, 区别在于:308 要求客户端在重定向时必须保持原有的 HTTP 方法和请求体(比如 POST 还是 POST,不能变成 GET), 308 响应里通常会带一个 Location 头,指向新的地址, 那么这个新的地址是什么呢? 打印出来看看

vue-cli-service比较坑爹, 不会像vite一样, 每次修改了配置文件,重新启动服务,每次修改了vue.config.js的配置,都得手动重启服务,刷新页面。

js 复制代码
onProxyRes: (proxyRes, req, res) => {
  let body = ''

  console.log('=== 代理响应详情 ===')
  console.log('状态码:', proxyRes.statusCode)

  // 打印所有响应头
  console.log('响应头:', proxyRes.headers)

  // 单独打印 Location
  if (proxyRes.headers.location) {
    console.log('Location:', proxyRes.headers.location)
  }
}

打印出来的内容如下:很奇怪,这不是没转发之前的接口地址吗, 为什么要原路返回? 此刻心态平稳,头脑清晰的我发现, 协议头发生了变化, 为什么发出去的时候协议是http, 返回的响应中变成了https。

js 复制代码
=== 代理响应详情 ===
状态码: 308
响应头: {
  server: 'nginx/1.19.0',
  date: 'Tue, 26 Aug 2025 08:22:09 GMT',
  'content-type': 'text/html',
  'content-length': '171',
  connection: 'close',
  location: 'https://localhost/xxx/api-v2/ticketStore/noAuth/store/578518674433958631?t=1756196529903',
  'strict-transport-security': 'max-age=15724800; includeSubDomains',
  'access-control-allow-origin': '*',
  'access-control-allow-credentials': 'true',
  'access-control-allow-methods': 'PUT, GET, POST, OPTIONS,DELETE',
  'access-control-allow-headers': 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,token,Cookie'
}
Location: https://localhost/xxx/api-v2/ticketStore/noAuth/store/578518674433958631?t=1756196529903

查了一下为什么协议会从 http 变成 https的原因, 常见原因有:

  1. 后端应用 / 框架本身做了强制跳转

    比如 Spring Boot、Django、Rails 等框架里,可以开启"强制 HTTPS",请求 HTTP 就自动 301/308 跳转到 HTTPS。

  2. 反向代理(Nginx/Traefik/Ingress)配置了 HTTPS 跳转

    在代理里通常会有类似:

    perl 复制代码
    if ($scheme = http) {
        return 308 https://$host$request_uri;
    }

    或者

    kotlin 复制代码
    return 308 https://$host$request_uri;

    这会把所有 http 请求强制跳到 https。

  3. 应用识别到 X-Forwarded-Proto: http 和安全策略

    有的服务会检测请求头,如果发现是 http 协议,就直接重定向到 https。 (比如某些 API 网关、Kubernetes Ingress 默认行为)

  4. 浏览器/客户端 HSTS 策略

    如果你之前访问过 https://localhost 并且服务端设置了 HSTS 头,浏览器可能会强制所有后续请求走 HTTPS(不过你这里是服务端返回 308,更可能是前两种原因)。

逐条看了一下, 第三种情况的可能性最大。我先让运维查了一下最近Kubernetes Ingress有没有改动, 运维说最近半年都没有修改过,那么就得查请求头了,把请求头打印出来看一下

js 复制代码
 onProxyReq: (proxyReq, req, res) => {
    // 追加打印
    console.log('Headers:', proxyReq.getHeaders())
 }

果然打印出来的请求头中有'x-forwarded-proto': 'http'

js 复制代码
=== 代理请求详情 ===
// ...
Headers: [Object: null prototype] {
  'x-forwarded-host': 'localhost:8080',
  'x-forwarded-proto': 'http',
  'x-forwarded-port': '8080',
  'x-forwarded-for': '127.0.0.1',

为什么 x-forwarded-proto: http 会触发 308 重定向?

1. 后端/网关判断请求协议

  • 大部分后端框架、API 网关、Ingress Controller(nginx-ingress、Traefik 等)都会根据 X-Forwarded-Proto 来识别"原始请求协议"。
  • 如果它看到 x-forwarded-proto: http,而站点要求强制 HTTPS,就会返回 301/302/308 跳转到 https://

2. 典型 Nginx Ingress 配置

Nginx ingress 默认就有一个选项 force-ssl-redirect: true。 它的逻辑就是:

js 复制代码
    if ($http_x_forwarded_proto = "http") {
        return 308 https://$host$request_uri;
    }

所以一旦代理传了 x-forwarded-proto: http,Ingress 就会强制跳转。至此,问题已经水落石出。

修复问题

既然Nginx Ingress遇到x-forwarded-proto: http,就会执行308重定向, 那么只需在vue-service-cli代理配置中, 每次转发请求时,移除x-forwarded-proto: http请求头设置就可以了。

js 复制代码
  devServer: {
    proxy: {
      '/api': {
        // 添加这句
        xfwd: false,
      }
    }
  }

果然,提交之后,打印的请求头中所有以x-forwarded-开头的请求头都看不见了,代理响应也正常了。可是,为什么突然变成这样了,在没改项目配置的情况下。是不是@vue/cli-service的版本最近有升级,查看了一下依赖链,发现与半年前相比,并无改变。

js 复制代码
@vue/cli-service@4.5.19
   ↓
webpack-dev-server@3.11.3 (依赖 webpack@4.47.0)
   ↓
http-proxy-middleware@0.19.1
   ↓
http-proxy@1.18.1
   ↓
follow-redirects@1.0.0

我将项目的git版本进行了回退, 回退到半年前, 添加了请求头打印, 发现也会输出x-forwarded-相关的请求头, 浏览器上显示请求响应代码是308, 所以这个问题不是前端的改动引起的,如果后端的话可信的话,可能是浏览器的安全策略升级导致的。

最后

我又找了一个vite项目对比了一下,发现vite.config.ts配置的server.proxy, 不会引发308重定向问题,打印了一下请求头,没有输出x-forwarded-xxx, 那就奇怪了。难道vite的代理请求使用的不是http-proxy, 查看了vite的官方源码,发现使用的代理工具果然不同, 是http-proxy-3,它是对经典 http-proxy 的 TypeScript 重写版本。目标是解决原版 http-proxy 中的 socket 泄漏、安全漏洞和老旧 API。已用于生产环境。看了一下 http-proxy最新的版本是v1.8.1, 5年之前发布的, 现在还使用它的话,本地代理转发默认的配置会引发308重定向问题, 难怪vite不使用它了。通过对这个问题的排查,让我觉得,开发工具得与时俱进,不定期升级才行,否则就会出现莫名其妙的幺蛾子。

相关推荐
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte4 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc