2023 年 10 月底,阿里云栖大会发布了阿里云函数 3.0 版,这是一个大版本升级。
我最近也把部分函数升级到了 3.0,其中一个函数用作 API 供网站调用,绑定了自定义域名,但升级后发现网页无法像之前那样成功发起网络请求。
简单调查后发现是浏览器的 CORS 机制导致的,但根据 FC 3.0 的文档,阿里云函数默认是允许跨域请求的,函数的 HTTP 触发器确实支持浏览器发起跨域请求,只是自定义域名不支持。可能是 FC 3.0 对自定义域名的处理机制和之前版本不同的原因。
所谓跨域请求,是指向不同于主域发起的请求。浏览器不会阻止和主域相同的请求,但对于跨域请求,浏览器会先发起预检请求,即 preflight request,目的是问服务器:大哥,你同意我发起跨域请求啊?服务器需要返回合适的 headers 以表示同意或拒绝。如果同意,浏览器才会发起正式请求,如果不同意,浏览器就不会发起正式请求。
我的这个 API 升级到 3.0 之后,由于绑定了自定义域名,就拒绝了浏览器发起的 preflight 请求,如果绑定的是 FC 内置的 HTTP 触发器就不会拒绝。
要解决自定义域名的这个问题也很简单,根据阿里云函数的文档,开发者可以在函数的代码中自定义函数对跨域请求的处理行为。具体来说,就是当请求 method 为 OPTIONS
时(因为 preflight 请求的方法就是 OPTIONS
),返回一个值为 *
的 Access-Control-Allow-Origin
响应头。
下面的代码来自 vercel 的文档,和阿里云函数不完全相同,但思路一样,可供参考:
const allowCors = (fn) => async (req, res) => {
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Access-Control-Allow-Origin", "*");
// another common pattern
// res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader(
"Access-Control-Allow-Methods",
"GET,OPTIONS,PATCH,DELETE,POST,PUT"
);
res.setHeader(
"Access-Control-Allow-Headers",
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
);
if (req.method === "OPTIONS") {
res.status(200).end();
return;
}
return await fn(req, res);
};
const handler = (req, res) => {
const d = new Date();
res.end(d.toString());
};
module.exports = allowCors(handler);
CORS 是浏览器的一种机制,其他客户端通常没有这个机制,这些客户端不会发起 preflight 请求,即不会先征求 API 是否支持跨域请求,而是直接发起正式请求。