如何借助Ngrok复现并排查生产环境CORS问题?
问题描述
生产环境中遇到CORS问题,为复现该问题,通过Ngrok暴露本地实例:
- 首次测试出现预期的CORS错误,但后续不再出现
- 已尝试清空浏览器缓存、使用无痕模式测试
- 执行Ngrok命令强制设置CORS未生效:
ngrok http --domain=domain-name.ngrok-free.app --response-header-add="Access-Control-Allow-Origin:zero" 8080
附服务端GraphQL代码:
const requestHandler = handlers.createAPIGatewayProxyEventRequestHandler<APIGatewayProxyWithCognitoAuthorizerEvent>(); const corsMiddleware: middleware.MiddlewareFn<typeof requestHandler> = async () => { return async (result) => { result.headers = { ...result.headers, 'Access-Control-Allow-Origin': '*' }; // 理论上应避免CORS拦截,但生产环境仍出现问题 }; }; const server = createPrivateServer(); const lambdaHandler = startServerAndCreateLambdaHandler(server, requestHandler, { middleware: [corsMiddleware], context: async ({ event }) => { // 上下文处理逻辑 }, }); export const handler = middy(lambdaHandler) .use(doNotWaitForEmptyEventLoop({ runOnBefore: true })) .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger, { clearState: true }))
分析与解决方案
一、为什么后续不再触发CORS错误?
- 预检请求缓存:浏览器会缓存OPTIONS预检请求的响应结果(默认由
Access-Control-Max-Age头控制缓存时长),即使清空普通缓存,无痕模式下可能仍残留预检缓存。 - 请求类型为简单请求:如果后续测试用的是简单请求(比如GET请求、POST请求Content-Type为
application/x-www-form-urlencoded等),浏览器不会触发OPTIONS预检,也就不会抛出CORS错误。
二、Ngrok配置无效的原因
你执行的命令中Access-Control-Allow-Origin:zero是无效值,正确格式应为具体允许的源(如https://your-frontend-domain.com)或通配符*,且注意冒号后需加空格:
ngrok http --domain=domain-name.ngrok-free.app --response-header-add="Access-Control-Allow-Origin: *" 8080
另外,如果服务端本身已返回Access-Control-Allow-Origin头,Ngrok添加的头可能被覆盖或冲突,优先以服务端返回的头为准。
三、服务端代码的优化点
当前自定义的corsMiddleware仅在请求结果阶段添加响应头,可能无法正确处理OPTIONS预检请求(部分GraphQL服务器对OPTIONS请求的处理逻辑特殊)。建议直接使用Middy官方的CORS中间件,确保覆盖所有请求场景:
import cors from '@middy/http-cors'; export const handler = middy(lambdaHandler) // 优先添加CORS中间件 .use(cors({ origin: '*', // 生产环境需替换为具体允许的域名,避免使用通配符(若需携带凭证) credentials: true, // 若前端请求需携带Cookie等凭证,需开启此项 headers: '*', // 允许所有自定义请求头 methods: 'OPTIONS,POST,GET' // 明确允许的请求方法 })) .use(doNotWaitForEmptyEventLoop({ runOnBefore: true })) .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger, { clearState: true }))
四、正确复现CORS错误的步骤
- 确保跨域场景:前端页面所在域名(如
localhost:3000)与Ngrok暴露的服务域名(domain-name.ngrok-free.app)完全不同。 - 发送非简单请求:构造触发预检的请求,例如带自定义头的POST请求:
fetch('https://domain-name.ngrok-free.app/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'test' // 自定义头强制触发OPTIONS预检 }, body: JSON.stringify({ query: '{ yourGraphqlQuery }' }) })
- 查看Network面板:在浏览器开发者工具的Network标签中,确认OPTIONS请求的响应头是否包含正确的
Access-Control-Allow-Origin、Access-Control-Allow-Headers等字段。
内容的提问来源于stack exchange,提问作者Duveral
相关产品推荐
相关产品推荐

