You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Axios中预检请求(Preflight Request)失败的处理问题——服务端限流导致预检请求失败时无法触发响应拦截器

Axios中预检请求(Preflight Request)失败的处理问题——服务端限流导致预检请求失败时无法触发响应拦截器

碰到过好几个开发者问这个问题,服务端加了限流后,预检请求被直接打回来,Axios的响应拦截器完全没动静,代码直接卡壳连错误提示都出不来,确实挺闹心的。我来一步步给你拆解原因和解决方案:

为什么预检请求失败不会触发Axios响应拦截器?

首先得搞懂浏览器的CORS预检机制:

当你发送跨域且非简单请求时,浏览器会自动先发起一个OPTIONS方法的预检请求,验证服务端是否允许当前域名的请求。这个过程完全由浏览器主导,Axios根本没有参与到预检请求的发送和响应处理中。

如果预检请求被服务端的限流规则拦截(返回429或其他错误),浏览器会直接抛出一个网络层面的错误,而不是把这个错误响应交给Axios处理。这时候Axios拿到的错误对象里err.responseundefined,如果你的拦截器只处理有err.response的情况,自然就捕获不到。

解决方案:从前端+服务端双向处理

1. 前端Axios拦截器补全网络错误处理

修改你的响应拦截器,专门处理err.response不存在的情况(也就是网络错误,包含预检失败):

api.interceptors.response.use(
  res => res,
  err => {
    // 处理网络错误(含预检请求失败、断网、请求被浏览器拦截等场景)
    if (!err.response) {
      // 这里可以统一做提示、日志上报等操作
      console.error('请求被拦截或发生网络异常:', err.message);
      // 抛出自定义错误,方便业务代码捕获
      return Promise.reject(new Error('请求过于频繁,请稍后重试'));
    }

    // 处理正常的HTTP错误响应(比如实际请求被限流返回429)
    const { status } = err.response;
    if (status === 429) {
      console.error('请求触发限流,请稍后再试');
      // 可以在这里加倒计时提示、禁用按钮等交互
    } else {
      console.error(`请求失败,状态码:${status}`);
    }

    return Promise.reject(err);
  }
);

2. 服务端优化:不对预检请求做限流

这是从根源解决问题的方案——因为预检请求只是浏览器的验证请求,不携带任何业务数据,完全没必要对它限流。你可以在服务端的限流规则里排除OPTIONS请求:

举几个常见服务端的配置示例:

  • Nginx

    # 先匹配OPTIONS请求,直接返回204(成功无内容),跳过限流
    if ($request_method = OPTIONS) {
      add_header Access-Control-Allow-Origin $http_origin;
      add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
      add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
      return 204;
    }
    
    # 只对非OPTIONS请求应用限流规则
    limit_req zone=api_requests burst=10 nodelay;
    
  • Node.js + Express

    // 先注册OPTIONS请求的全局处理,跳过限流
    app.options('*', (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
      res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      res.sendStatus(204);
    });
    
    // 给业务路由单独加限流中间件
    const rateLimit = require('express-rate-limit');
    const apiLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15分钟
      max: 100 // 每个IP最多100次请求
    });
    
    app.use('/api/*', apiLimiter);
    

这样调整后,预检请求会直接通过,实际的业务请求才会触发限流,这时候Axios就能拿到429的响应,你的拦截器也能正常处理了。

额外注意点

  • 浏览器会缓存预检请求的结果(默认缓存24小时),如果之前服务端允许过OPTIONS请求,修改配置后可能需要清空浏览器缓存测试。
  • 如果你在本地开发,可以用代理工具(比如Vue CLI的devServer、Webpack Dev Server)绕开跨域,这样浏览器不会发预检请求,也能快速验证限流逻辑。

优先推荐服务端排除OPTIONS请求的方案,前端拦截器做兜底,双管齐下最稳妥~

火山引擎 最新活动