首页 > 其他分享 >跨域问题排查实战:一个困扰两天的线上问题

跨域问题排查实战:一个困扰两天的线上问题

时间:2024-12-11 13:31:47浏览次数:7  
标签:实战 跨域 headers response 排查 api https com example

"老师,我们的新功能上线后接口突然调不通了!"周一早上,实习生小李急匆匆地跑来找我。我打开监控面板,发现生产环境的错误日志突然暴增,全是 CORS 相关的报错。作为技术导师,我立即和小李一起开始排查这个问题。

说实话,跨域问题在本地开发时很常见,但在生产环境突然出现还是第一次。更让人困惑的是,这些接口在上周五还是好好的,周末发版后就出问题了。带着这些疑问,我们开始了一场"破案"之旅。

问题的表象

首先,我让小李演示了一下具体的错误场景。在浏览器控制台里,我们看到了这样的报错:

Access to XMLHttpRequest at 'https://api.example.com/data'
from origin 'https://www.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

"奇怪了,我们的 CORS 配置明明一直都在啊。"小李一脸困惑。我们开始仔细梳理上周的发版内容:

// 上周的发版记录
const deployChanges = {
  frontend: {
    'feat: 新增数据分析页面': {
      api: 'https://api-analysis.example.com/v1',
      changes: ['新增数据大屏', '接入实时数据接口', '对接新的认证服务']
    }
  },
  backend: {
    'refactor: 服务架构调整': {
      changes: ['网关服务升级', '认证服务重构', '引入服务网格']
    }
  }
}

深入排查

通过对比测试环境和生产环境的请求,我们发现了一些线索:

// 测试环境的请求(正常)
fetch('https://api-test.example.com/data', {
  headers: {
    Authorization: 'Bearer token123',
    'Content-Type': 'application/json'
  }
}).then(response => {
  console.log('响应头:', response.headers)
  // Access-Control-Allow-Origin: https://www.example.com
  // Access-Control-Allow-Methods: GET, POST, OPTIONS
  // Access-Control-Allow-Headers: Content-Type, Authorization
})

// 生产环境的请求(异常)
fetch('https://api.example.com/data', {
  headers: {
    Authorization: 'Bearer token123',
    'Content-Type': 'application/json'
  }
}).then(response => {
  console.log('响应头:', response.headers)
  // 缺少 CORS 相关的响应头
})

通过进一步分析,我们发现问题出在新引入的服务网格配置上:

# 原来的网关配置
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: api-gateway
spec:
  hosts:
    - 'api.example.com'
  gateways:
    - api-gateway
  http:
    - route:
        - destination:
            host: api-service
            port:
              number: 80
      # 这里缺少了 CORS 策略配置

问题的根源

原来是这样!在服务架构调整时,我们将原来网关层的 CORS 配置迁移到了服务网格,但是漏掉了一些细节:

  1. 预检请求(OPTIONS)没有正确配置
  2. 多级域名的跨域配置缺失
  3. 认证头信息没有加入允许列表

解决方案

知道问题后,解决方案就比较清晰了:

# 修复后的配置
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: api-gateway
spec:
  hosts:
    - 'api.example.com'
    - 'api-analysis.example.com'
  gateways:
    - api-gateway
  http:
    - corsPolicy:
        allowOrigins:
          - exact: 'https://www.example.com'
          - regex: 'https://*.example.com'
        allowMethods:
          - GET
          - POST
          - OPTIONS
        allowHeaders:
          - Authorization
          - Content-Type
        maxAge: '24h'
      route:
        - destination:
            host: api-service
            port:
              number: 80

同时,我们在前端也增加了错误处理机制:

// utils/request.ts
class APIClient {
  private async request(url: string, options: RequestOptions) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Content-Type': 'application/json'
        }
      })

      if (!response.ok) {
        // 处理 HTTP 错误
        throw new HTTPError(response.status, response.statusText)
      }

      return await response.json()
    } catch (error) {
      if (error instanceof HTTPError) {
        // 处理 HTTP 错误
        if (error.status === 0) {
          console.error('可能是跨域问题:', error)
          // 显示友好的错误提示
          notification.error({
            message: '网络请求失败',
            description: '请检查网络连接或联系技术支持'
          })
        }
      }
      throw error
    }
  }

  // 提供重试机制
  async requestWithRetry(url: string, options: RequestOptions, retries = 3) {
    for (let i = 0; i < retries; i++) {
      try {
        return await this.request(url, options)
      } catch (error) {
        if (i === retries - 1) throw error
        // 延迟重试
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)))
      }
    }
  }
}

预防措施

为了防止类似问题再次发生,我们建立了一套完整的测试机制:

// tests/cors.spec.ts
describe('CORS Configuration Tests', () => {
  const origins = ['https://www.example.com', 'https://admin.example.com', 'https://data.example.com']

  const endpoints = ['/api/v1/data', '/api/v1/analysis', '/api/v1/auth']

  origins.forEach(origin => {
    endpoints.forEach(endpoint => {
      it(`should allow CORS from ${origin} to ${endpoint}`, async () => {
        const response = await fetch(`https://api.example.com${endpoint}`, {
          method: 'OPTIONS',
          headers: {
            Origin: origin,
            'Access-Control-Request-Method': 'POST',
            'Access-Control-Request-Headers': 'Content-Type,Authorization'
          }
        })

        expect(response.headers.get('Access-Control-Allow-Origin')).to.include(origin)
        expect(response.headers.get('Access-Control-Allow-Methods')).to.include('POST')
        expect(response.headers.get('Access-Control-Allow-Headers')).to.include('Authorization')
      })
    })
  })
})

经验总结

这次问题排查让我们学到了很多:

  1. 架构 设计 更要特别注意配置迁移的完整性
  2. 跨域配置要考虑全面,包括预检请求和各种场景
  3. 要建立完善的测试机制,及早发现问题
  4. 前端要有合适的错误处理机制

就像搬家时要仔细检查有没有遗漏重要物品一样,系统架构调整时也要特别注意配置的完整性。而且要像检查清单一样,把所有可能的场景都测试一遍。

写在最后

跨域问题虽然常见,但解决起来并不简单,特别是在复杂的微服务架构中。关键是要理解背后的原理,建立完善的测试机制,这样才能及时发现和解决问题。

有什么问题欢迎在评论区讨论,让我们一起提高技术水平!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

标签:实战,跨域,headers,response,排查,api,https,com,example
From: https://www.cnblogs.com/yuanyanglu/p/18599301

相关文章

  • shodan(3)命令进阶及VNC空密码实战
    本篇文章旨在为网络安全初学者介绍渗透测试行业信息收集的引擎。通过阅读本文,读者将能够对shodan引擎工具的使用有一个初步的了解一、命令进阶1、查询平台漏洞数量shodancount'"\x03\x00\x00\x0b\x06\xd0\x00\x00\x124\x00"'2、过滤IP地址(vuln需要shodan高级账号......
  • linux 服务器被当作肉鸽后的一次排查
    查询服务器启动时间who-b显示网络连接、路由表、接口统计信息netstat:网络统计(networkstatistics)的缩写,是一个用于显示网络连接、监听端口、路由表等信息的命令行工具。-a:显示所有连接和监听的端口。-n:以数字形式显示地址和端口号,而不是尝试解析主机名和服务名。-p:显示与......
  • 如何找到企业瓶颈,定位企业关键问题?-中小企实战运营和营销工作室博客
    如何找到企业瓶颈,定位企业关键问题?1,财务数据分析(1)利润指标:分析企业的净利润率、毛利率等指标。如果净利润率持续下降,可能是成本过高或者售价过低。例如,一家制造企业发现其净利润率从过去的15%下降到现在的8%,通过进一步分析成本结构,发现原材料成本上涨了30%,这可......
  • k8s-服务网格实战-配置 Mesh(灰度发布)
    k8s-服务网格实战-配置Mesh(灰度发布)程序那点事2023-11-0722:09湖南 在上一篇 k8s-服务网格实战-入门Istio中分享了如何安装部署 Istio,同时可以利用 Istio 实现 gRPC 的负载均衡。今天我们更进一步,深入了解使用Istio的功能。从Istio的流量模型中......
  • SSM项目实战哈米音乐03---专辑模块、文件上传
    目录专辑开发1.1分页条件组合查询1.2专辑的添加1.2.1文件上传1.2.2文件上传的bug1.2.3表单的校验和提交1.3专辑修改isSameName1方法是修改的防重名1.4专辑删除专辑开发先做逆向工程1.1分页条件组合查询1.在映射文件中提供相应的查询的sql<selectid="selec......
  • 按键精灵实战处理业务需求
    Call注册大漠()Function注册大漠()  //首先打包dm.dll和RegDll.dll到附件,当然如果你还有其它资源(字库,图片等)也可以一并打包  //这个need_ver作为本脚本需要使用的插件版本.如果要换插件时,记得更改这个值.  need_ver="3.1233"  //插件需要用到a......
  • 运维实战:K8s 上的 Doris 高可用集群最佳实践
    首发:运维有术今天我们将深入探讨::如何在K8s集群上部署Computestoragecoupled(存算耦合)模式的Doris高可用集群?本文,我将为您提供一份全面的实战指南,逐步引导您完成以下关键任务:配置DorisConfigMap:实现自定义配置文件配置DorisSecret:管理特殊密码配置DorisService:......
  • JeecgBoot 与分布式事务 Seata v1.7.0 集成实战
    准备环境一、创建四个数据库,如下jeecg_order(订单数据库)jeecg_account(账户数据库)jeecg_product(商品数据库)seata(seata数据库)以上数据库脚本已存放至jeecg-cloud-test-seata示例中,文件位置如下图所示二、准备调试代码1.示例代码提供如下jeecg-cloud-test-seata-order......
  • k8s 实战 4----副本集
    副本集是什么?我们在前文中讲过什么是pod,简单来说pod就是k8s直接操作的基本单位。不了解的同学可以参考前文:k8s实战1----初识(https://www.cnblogs.com/jilodream/p/18245222)k8s实战2----pod基础(https://www.cnblogs.com/jilodream/p/18284282)k8s实战3----标签(htt......
  • 【机器学习与数据挖掘实战案例01】基于支持向量回归的市财政收入分析
    【作者主页】FrancekChen【专栏介绍】⌈⌈⌈机器学习与数据挖掘实战⌋......