XMLHttpRequest CORS 到 Google Cloud Storage 仅在预检请求中工作

Posted

技术标签:

【中文标题】XMLHttpRequest CORS 到 Google Cloud Storage 仅在预检请求中工作【英文标题】:XMLHttpRequest CORS to Google Cloud Storage only working in preflight request 【发布时间】:2014-10-30 13:38:29 【问题描述】:

我使用 XMLHttpRequest 发送到服务器端创建的可恢复上传 URL,实现了基于浏览器的可恢复上传到 Google 的云存储。当我在开发过程中禁用网络安全时,这完全可以正常工作。

但现在在现实世界中,CORS 一直在制造麻烦。我也在其他浏览器上尝试过(没有成功),但坚持使用 chrome 进行进一步测试。

注意:/etc/HOSTS 中的 fake.host 条目用于欺骗 chrome 以避开 localhost 限制。尽管如此,我们在线测试服务器的“真实”域也会发生同样的情况。

使用普通的 XMLHttpRequest 调用启动请求:

var xhr = this.newXMLHttpRequest();
xhr.open('PUT', url, true);

xhr.setRequestHeader('Content-Type', this.currentInputFile.type);
xhr.setRequestHeader('Content-Range', 'bytes ' + startByte + '-' + (this.currentInputFile.size - 1) + '/' + this.currentInputFile.size);

xhr.onload = function(e) 
   ...
;

...

if (startByte > 0) 
    xhr.send(this.currentInputFile.slice(startByte));
 else 
    xhr.send(this.currentInputFile);

浏览器随后成功发起预检请求:

Remote Address:173.194.71.95:443
Request URL:https://www.googleapis.com/upload/storage/v1/b/my-bucket-name/o?uploadType=resumable&name=aa%20spacetestSMALL_512kb.mp4&upload_id=XXXXXXXXX
Request Method:OPTIONS
Status Code:200 OK

请求标头:

:host:www.googleapis.com
:method:OPTIONS
:path:/upload/storage/v1/b/my-bucket-name/o?uploadType=resumable&name=aa%20spacetestSMALL_512kb.mp4&upload_id=XXXXXXXXX
:scheme:https
:version:HTTP/1.1
accept:*/*
accept-encoding:gzip,deflate
accept-language:en-US,en;q=0.8,de;q=0.6
access-control-request-headers:content-range, content-type
access-control-request-method:PUT
origin:https://fake.host
referer:https://fake.host/upload.xhtml
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 Safari/537.36
x-client-data:YYYYYY

查询字符串参数

uploadType:resumable
name:aa spacetestSMALL_512kb.mp4
upload_id:XXXXXXXXX

响应标头

access-control-allow-credentials:true
access-control-allow-headers:content-range, content-type
access-control-allow-methods:PUT
access-control-allow-origin:https://fake.host
alternate-protocol:443:quic
content-length:0
content-type:text/html; charset=UTF-8
date:Fri, 05 Sep 2014 14:11:21 GMT
server:UploadServer ("Built on Aug 18 2014 11:58:36 (1408388316)")
status:200 OK
version:HTTP/1.1

... 并启动 PUT 请求,直到所有数据都传输完毕。但是之后 chrome 会默默地记录一个错误而没有完成/结束请求:

XMLHttpRequest 无法加载 https://www.googleapis.com/upload/storage/v1/b/my-bucket-name…XXXXXXXX。请求的资源上不存在“Access-Control-Allow-Origin”标头。 Origin 'https://fake.host' 因此不允许访问。

这是 chrome 关于 PUT 请求的记录:

Request URL:https://www.googleapis.com/upload/storage/v1/b/my-bucket-name/o?uploadType=resumable&name=aa%20spacetestSMALL_512kb.mp4&upload_id=XXXXXXXXX

请求标头

Provisional headers are shown
Content-Range:bytes 0-3355302/3355303
Content-Type:video/mp4
Origin:https://fake.host
Referer:https://fake.host/upload.xhtml
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 Safari/537.36
X-DevTools-Emulate-Network-Conditions-Client-Id:YYYYYYY

查询字符串

uploadType:resumable
name:aa spacetestSMALL_512kb.mp4
upload_id:XXXXXXXXX

值得注意的是,当在 http://client.cors-api.appspot.com/client 中添加相同的 url 并发出任何请求时,除了 OPTIONS 之外的所有请求类型也会失败。似乎云存储 api 只为 OPTION 请求发出正确的响应标头,而不是 PUT/POST/GET/... 请求。

那我是在做不可能的事吗?有什么东西坏了吗?这是云存储api中的错误吗?我花了几个小时在谷歌上搜索和阅读 SO 答案,到目前为止没有任何运气。

目前,我可以定期检查下载是否传输了 100% 的数据,而忽略 http 请求结果,因为文件实际上已完全上传到存储桶。但这是一个相当丑陋的解决方法,如果可以解决真正的问题,我真的不想使用它。

【问题讨论】:

您是否在 GCS 上设置了 CORS? cloud.google.com/storage/docs/… 是的,当然。我猜如果没有它,预检请求会失败,但事实并非如此。只有上传文件后的最终响应无效。 你能显示你使用的json吗?感觉就像你忘记了“方法”:[“PUT”,“POST”,“GET”], 我正在使用 java API:pastebin 看起来这可能是一个已知问题:***.com/a/25536248 【参考方案1】:

当请求一个可恢复的上传 url 时,您必须包含浏览器在尝试使用该上传 url 时将发送的来源,否则后续上传将失败,就像问题中发生的那样(OPTIONS 调用看起来不错,但 PUT 不会)。

它必须与浏览器的来源完全匹配(您可以在 javascript 中获取 location.origin)。

这是本文档中“启动可恢复上传会话”的步骤: https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload

如果您在服务器端请求可恢复的上传 url,您可能需要客户端(浏览器)向您传递其来源(例如:location.origin)。

fwiw 我在这一步使用 Google 的 Cloud Storage library for python,需要像这样添加源:

myblob.create_resumable_upload_session(mycontenttype, origin=browserorigin)

请注意,您绝对不需要为您的存储桶设置 CORS。

【讨论】:

我想这在 3 年前是不可能的,但现在看来是正确的解决方案。从 2016 年开始就有关于这个问题的 git 问题(即github.com/GoogleCloudPlatform/google-cloud-node/issues/1099)。谢谢! 花了这么长时间。【参考方案2】:

由于这个问题仍未得到解答,并且仍有相当多的观点,我将尝试在此处发布一些确定的内容。

在对上传数据的任何 PUT 请求的响应中返回的 'Access-Control-Allow-Origin' 标头始终设置为 the initial POST request 中给出的用于启动上传的 origin,根据 current docs:

当使用可续传协议时,Origin 从第一个 (开始上传)请求总是用来决定 响应中的 Access-Control-Allow-Origin 标头,即使您使用 后续请求的不同来源。因此,您应该 对第一个和后续请求使用相同的来源,或者如果 第一个请求与后续请求的来源不同,请使用 将 CORS 配置设置为 * 的 XML API。

这意味着您必须在任何发送数据的 PUT 请求之前发送初始 POST 请求,并且任何后续 PUT 请求必须与初始 POST 具有相同的“来源”。

关于 GCS 中设置的 CORS 配置,这仅适用于从 current docs 调用 XML API:

注意:CORS 配置仅适用于 XML API 请求。对于 JSON API 请求,Cloud Storage 始终返回 带有请求来源的 Access-Control-Allow-Origin 标头。

【讨论】:

以上是关于XMLHttpRequest CORS 到 Google Cloud Storage 仅在预检请求中工作的主要内容,如果未能解决你的问题,请参考以下文章

XMLHttpRequest CORS 到 Google Cloud Storage 仅在预检请求中工作

从本地 html 文件到服务器的 JSON 请求引发 CORS 错误:CORS 策略已阻止从源“null”访问 <URL> 处的 XMLHttpRequest

XMLHttprequest 无法加载 [link]。从 [link] 重定向到已被 laravel 5 中的 CORS 策略阻止

了解页面响应

从 Angular 前端到 json-server 的 GET 调用访问被 CORS 策略阻止的 XMLHttpRequest

CORS 策略已阻止从源 URL 访问 URL 的 XMLHttpRequest [重复]