如何解决使用 `GenerateSignedPostPolicyV4` 在 Google Cloud Storage 中上传文件时出现 Cors 错误
我正致力于在 Cloud Storage 中使用 signed URL 以允许客户将文件直接上传到 Cloud Storage。
因为我想限制用户可以上传到 GCS 的最大文件大小,我在考虑使用 policy document 通过使用 content-length-range
条件来控制上传行为。我正在使用 GenerateSignedPostPolicyV4 生成发布策略文档。
如果我基于由 GenerateSignedPostPolicyV4
生成的发布策略文档构建 HTML 表单,那么我的设置工作得非常好。
以下是对我来说很好用的 sample form 副本。
<form action="https://storage.googleapis.com/[MY_BUCKET_NAME]/" method="POST" enctype="multipart/form-data">
<input name="content-type" value="application/octet-stream" type="hidden">
<input name="key" value="test" type="hidden">
<input name="policy" value="[MY_POLICY]" type="hidden">
<input name="x-goog-algorithm" value="GOOG4-RSA-SHA256" type="hidden">
<input name="x-goog-credential" value="[MY_CREDENTIAL]" type="hidden">
<input name="x-goog-date" value="20210624T194049Z" type="hidden">
<input name="x-goog-signature" value="[MY_SIGNATURE]" type="hidden">
<input type="file" name="file"><br>
<input type="submit" value="Upload File" name="submit"><br>
</form>
现在,我有一个单页应用程序,如果可能的话,我希望在不使用 HTML 表单的情况下以 JavaScript/TypeScript 的方式以编程方式完成上传。例如,我想使用 fetch
或 xhr
而不是使用标准 HTML 表单上传文件。
奇怪的是,当我使用 xhr
或 fetch
发出 POST
请求时,我遇到了 CORS
错误。我确实在我的存储桶中正确设置了 CORS
,因为如果我使用标准 SignedUrl 生成 URL 并使用 PUT
方法上传文件,上传通过 {{1} 工作正常} 或 xhr
,这证明 CORS 已在我的存储桶中正确设置。
(我的 cors 如下所示)
fetch
但是……由于您无法通过 [{"maxAgeSeconds": 3600,"method": ["PUT","POST"],"origin": ["*"],"responseHeader": ["Content-Type","Access-Control-Allow-Origin"]}]
上传来强制执行文件大小限制,因此使用 PUT
PUT
不是我的选择。
所以我的问题是,如果我使用基于 xhr/fetch
的上传,是否需要使用 html 表单将数据上传到 GCS? GCS 是否有任何理由决定对此类提交强制执行 CORS?
解决方法
使用 Signed Url 将对象(文件)上传到 Google Cloud Storage 时,PUT
文件大小限制确实可以强制执行。
这是通过设置 HTTP 标头 x-goog-content-length-range
(查找文档 here)并指定您希望签名 URL 允许的字节范围来实现的。例如:
"x-goog-content-length-range":"0,24117249"
这指定将接受上传到该 URL 的文件,大小从 0B(字节)到 23MB(24117249 字节)。
您必须在创建签名 URL 以及访问该 URL(即上传文件)时使用此标头。
编辑:
为了回应 Martin Zeitler 的评论,我对该主题进行了更多研究,并设法使用带可恢复上传的签名 URL 获得了一个有点工作的脚本。
它是如何工作的?首先,我们创建带有标头的 POST
方法签名 URL,指示存储桶启动可恢复的上传操作,作为交换,该标头响应 Location
标头,其中包含我们必须将文件发送到的 URI PUT
请求。
您希望在启动服务器之前设置您的凭据。详细了解如何执行此操作here。
但是,为了获得调用签名 URL 和将文件上传到存储桶所需的权限,我们需要一个访问令牌。你可以得到它here。你也可以learn more about OAuth2 Authentication。获取上传 URI 和上传时,此访问令牌不必相同;然而,为了简单起见,我决定保持不变。
脚本本身您不想在制作中使用:它仅用于说明目的。
(您需要 flask
和 google-cloud-storage
Python 库才能工作)
main.py:
from flask import Flask,render_template
import datetime,requests
from google.cloud import storage
#----------------------------------------------------------------------
#----------------------------------------------------------------------
def generate_upload_signed_url_v4(bucket_name,blob_name):
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name) #Sets name of the target bucket
blob = bucket.blob(blob_name) #Sets the filename our object will have once uploaded to the bucket
headers = {
"x-goog-resumable":"start",#Needed for creating a resumable upload: https://cloud.google.com/storage/docs/xml-api/reference-headers#xgoogresumable
}
url = blob.generate_signed_url(
version="v4",expiration=datetime.timedelta(minutes=15),headers=headers,method="POST",)
return url
#----------------------------------------------------------------------
#----------------------------------------------------------------------
bucket_name = 'sample-bucket' #INSERT YOUR BUCKET NAME HERE
blob_name = 'your-desired-filename' #INSERT THE NAME OF THE FILE HARE
url = generate_upload_signed_url_v4(bucket_name,blob_name) #Instantiates the Signed URL to get the Session ID to upload the file
app = Flask(__name__) #Flask
token = "access-token" #Insert access token here
headers = { #Must have the same headers used in the generation of the Signed URL + the Authorization header
"Authorization":f"Bearer {token}","x-goog-resumable":"start",}
#Get Session ID from the `Location` response header and store it in the `session_url` variable
r = requests.post(url,data="",headers=headers)
if r.status_code == requests.codes.created:
session_url = r.headers["Location"]
else:
session_url = "None"
#----------------------------------------------------------------------
#----------------------------------------------------------------------
@app.route("/gcs",methods=["PUT","GET","POST"])
def main():
return render_template("index.html",token=token,url=session_url) # Sends token and session_url to the template
if __name__ == "__main__":
app.run(debug=True,port=8080,host="0.0.0.0") #Starts the server on port 8080 and sets the host to 0.0.0.0 (available to the internet)
templates/index.html(了解有关 Flask 模板的更多here):
<html>
<head>
</head>
<body>
<input type="file" id="fileinput" />
<script>
// Select your input type file and store it in a variable
const input = document.getElementById('fileinput');
// This will upload the file after having read it
const upload = (file) => {
fetch('{{ url }}',{ // Your PUT endpoint -> On this case,the Session ID URL retrieved by the Signed URL
method: 'PUT',body: file,headers: {
"Authorization": "Bearer {{ token }}",//I don't think it's a good idea to have this publicly available.
"x-goog-content-length-range":"0,24117249" //Having this on the front-end may allow users to tamper with your system.
}
}).then(
response => response.text()
).then(str => (new window.DOMParser()).parseFromString(str,"text/xml")
).then(data => console.log(data) //Prints response sent from server in an XML format
).then(success => console.log(success) // Handle the success response object
).catch(
error => console.log(error) // Handle the error response object
);
};
const onSelectFile = () => upload(input.files[0]);
input.addEventListener('change',onSelectFile,false); //Whenever a file is selected,the EventListener is triggered and executes the `onSelectFile` function
</script>
</body>
</html>
现在我们必须为我们的存储桶配置 CORS 设置。我们必须通过更改 origin
值来允许我们的服务器。然后,我们必须明确说明我们想要允许的 HTTP 标头和方法。 如果未正确设置,将引发 CORS 错误。
cors.json:
[
{
"origin": ["http://<your-ip-here>:<yourport-here>"],"responseHeader": [
"Content-Type","Authorization","Access-Control-Allow-Origin","X-Upload-Content-Length","X-Goog-Resumable","x-goog-content-length-range"
],"method": ["PUT","OPTIONS","POST"],"maxAgeSeconds": 3600
}
]
正确配置后,我们可以使用命令将此配置应用到我们的存储桶
gsutil cors set <name-of-configfile> gs://<name-of-bucket>
要尝试此操作,请转到您的浏览器并输入以下网址:http://<your-ip>:<your-port>/gcs
。
选择您选择的文件(小于 23MB 或您可以设置的上限),然后观察它实际上如何上传到您的存储桶。
现在您可能想尝试上传大于 x-goog-content-length-range
标头上设置的上限的文件,并观察上传如何失败并出现 EntityTooLarge
错误。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。