实现 CORS 的服务器是不是应该始终使用 2xx 代码回复 OPTIONS 方法?
Posted
技术标签:
【中文标题】实现 CORS 的服务器是不是应该始终使用 2xx 代码回复 OPTIONS 方法?【英文标题】:Should a server implementing CORS always reply with a 2xx code for OPTIONS method?实现 CORS 的服务器是否应该始终使用 2xx 代码回复 OPTIONS 方法? 【发布时间】:2021-01-28 19:46:03 【问题描述】:我正在服务器上实现 CORS,并且在请求不存在的路由时在 chrome 控制台中出现以下错误:
Access to fetch at 'https://localhost:5000/unknown_route'
from origin 'https://localhost:3000' has been blocked by
CORS policy: Response to preflight request doesn't pass
access control check: It does not have HTTP ok status.
实现 CORS 的服务器是否应该始终为 OPTIONS 方法返回 200(或 204),而不管之后的潜在错误如何?
我找到了this question,但它只是说明应该存在 CORS 标头(这是我的情况)。 这个other question的回答说状态应该是200,但是没有特别提到404错误。
【问题讨论】:
在flask-cors 上链接一个有趣的问题,要求:github.com/corydolphin/flask-cors/issues/262 你正在实现这个服务器,所以我相信这是你想要什么行为的问题,而不是规范要求的问题。您是否希望浏览器继续发出该请求并收到 404,或者不? 我想在这里了解规范所倡导的内容。正如上面评论中的链接,flask-cors 没有实现这一点,我想了解标准应该是什么。如果 status 应该始终为 200,那么我可以主张对 flask-cors 项目进行更改并提交 PR。 规范不提倡对此有任何特别之处。 fetch.spec.whatwg.org/#ref-for-ok-status 是规范中关于状态代码的全部内容——它只是说如果响应具有 2xx 状态代码,则预检成功。 【参考方案1】:我不相信CORS protocol 在这里指定了行为,尽管它implies 具有:
CORS 预检请求是一个 CORS 请求,用于检查是否理解 CORS 协议。
(我强调)这是对 CORS 是否被理解的检查,不是强制验证的地方。
通常,由服务器实现者决定是允许请求继续进行,并返回适当的状态代码和响应,还是阻止请求。
在the linked issue:
当找不到请求的路径 (404) 时,我想向我的 webapp 的用户显示“找不到资源”消息
如果这是您想要的,您应该允许 CORS,因此这会显示为 404 而不是被阻止的请求。
【讨论】:
当然。 CORS 限制的整个想法是不向未经授权的消费者显示信息。现在,这可能方便也可能不方便,但混合这些担忧似乎不是一个好主意。【参考方案2】:Spec 不在乎——实际上也不应该在乎。您的服务器也不应该关心 - 尽快解决其响应的 CORS 部分。
看,当用户代理发出 Preflight 请求时,它(按规范)应该只关心它是否成功。
如果成功,通常会将结果放入所谓的CORS-preflight Cache 中,以用于具有匹配方法、url 和标头的后续请求。
但如果不是,这些缓存条目将被清除——不管是什么原因。哦,当然,主请求也不会被处理。
现在,规范变为into much details,描述了究竟什么是成功:
回复通过CORS Check 响应状态为ok status(200 到 299 范围内的任何状态,含)Access-Control-Allow-Methods
和 Access-Control-Allow-Headers
标头在那里,
他们的价值观符合预期...
...还有更多。但关键是,任何步骤的失败总是会导致“...然后返回网络错误”结果。
这就是网络错误的样子:
网络错误是状态始终为 0 的响应,状态消息 总是空字节序列,头列表总是空的,并且 body 始终为空。
我是说规范无关紧要吗?
看看 Chromium implements that spec:
std::unique_ptr<PreflightResult> CreatePreflightResult(/* args skipped */)
const int response_code = head.headers ? head.headers->response_code() : 0;
*detected_error_status = CheckPreflightAccess(
final_url, response_code,
GetHeaderString(head.headers, header_names::kAccessControlAllowOrigin),
GetHeaderString(head.headers,
header_names::kAccessControlAllowCredentials),
original_request.credentials_mode,
tainted ? url::Origin() : *original_request.request_initiator);
if (*detected_error_status)
return nullptr;
base::Optional<mojom::CorsError> error;
error = CheckPreflight(response_code);
if (error)
*detected_error_status = CorsErrorStatus(*error);
return nullptr;
if (original_request.is_external_request)
*detected_error_status = CheckExternalPreflight(GetHeaderString(
head.headers, header_names::kAccessControlAllowExternal));
if (*detected_error_status)
return nullptr;
auto result = PreflightResult::Create(
original_request.credentials_mode,
GetHeaderString(head.headers, header_names::kAccessControlAllowMethods),
GetHeaderString(head.headers, header_names::kAccessControlAllowHeaders),
GetHeaderString(head.headers, header_names::kAccessControlMaxAge),
&error);
if (error)
*detected_error_status = CorsErrorStatus(*error);
return result;
如你所见,这...
return nullptr;
... 是函数的主旨:每当检查失败时,函数立即返回空指针。没错,detected_error_status
也已填充,但它似乎主要用于调试目的(即查看哪一步检查失败 - 并相应地设计控制台警告)。
【讨论】:
“规范不关心——实际上也不应该关心这一点。你的服务器也不应该关心——只要尽快解决其响应的 CORS 部分。”好的,如果路由不存在,它不应该关心,解析 CORS 标头并返回 200 对吗? 对。除非您的 API 是分段的,并且 CORS 仅对某些分段开放,否则 OPTIONS 请求应在最顶层解析。事实上,有时基于 nginx 的配置就是这样做的:nginx 完全负责处理预检。比如here。 至于具体代码, 204 No Content 似乎更语义化。以上是关于实现 CORS 的服务器是不是应该始终使用 2xx 代码回复 OPTIONS 方法?的主要内容,如果未能解决你的问题,请参考以下文章
允许任何 CORS 来源 (*) 时是不是应该设置“Vary: Origin”?
enablecors-webapi跨域 为何不能实现?都有哪些注意事项
“operator !=”是不是应该在 C++ 中始终通过“operator ==”来实现?
GrpahQL 的 DataFetcher 中是不是应该始终使用多线程?