用的好好的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不使用它了。通过对这个问题的排查,让我觉得,开发工具得与时俱进,不定期升级才行,否则就会出现莫名其妙的幺蛾子。

相关推荐
moyu847 分钟前
Vue3 作用域插槽:后台管理系统表格的灵动引擎
前端
好好好明天会更好7 分钟前
Vue中this.$options.data()是什么东西?
前端·vue.js
lovepenny9 分钟前
告别重复加载:掌握浏览器强缓存与协商缓存策略
前端·面试
moyu8415 分钟前
从打包到按需编译:深入理解 Webpack 和 Vite 的差异化实现路径
前端
ze_juejin19 分钟前
基于Angular的高内聚、低耦合、可扩展模块设计参考
前端
大舔牛26 分钟前
浏览器访问网页全流程:小白友好版详解
前端·面试
前端小木屋29 分钟前
浅谈vue3响应式原理
前端·vue.js
蒙奇·D·路飞-32 分钟前
2025改版:npm 新淘宝镜像域名地址
前端·npm·node.js
Dolphin_海豚34 分钟前
augment + figma mcp,让你的 vibe coding 更加得心应手
前端·ai编程·mcp
前端老鹰1 小时前
CSS outline-offset:让焦点样式不再 “紧贴” 元素的实用属性
前端·css