尝试使用 gcs json api、axios、vue3、quasar 和 node14 执行单个块可恢复上传到谷歌云存储时出现 cors 错误
Posted
技术标签:
【中文标题】尝试使用 gcs json api、axios、vue3、quasar 和 node14 执行单个块可恢复上传到谷歌云存储时出现 cors 错误【英文标题】:Getting cors errors when trying to perform a single chunk resumable upload to google cloud storage using gcs json api, axios, vue3, quasar and node14 【发布时间】:2022-01-17 07:46:49 【问题描述】:我正在尝试通过 gcs、node、vue per https://cloud.google.com/storage/docs/performing-resumable-uploads 执行单个块可恢复上传。我能够生成signedUrl,但是当我尝试通过客户端将signedUrl 放入或发布到signedUrl 时会出现错误。
如果导航到signedUrl,浏览器会显示以下错误:
<Error>
<Code>MalformedSecurityHeader</Code>
<Message>Your request has a malformed header.</Message>
<ParameterName>content-type</ParameterName>
<Details>Header was included in signedheaders, but not in the request.</Details>
</Error>
在控制台中,我收到以下错误(所有这些中的 domain.appspot 和 domain.iam 都设置为我的存储桶名称。我只是在此处替换/隐藏它以防万一):
在 'https://storage.googleapis.com/domain.appspot.com/media/20161017_213931.mp4?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcs-server% 访问 XMLHttpRequest 40domain.iam.gserviceaccount.com%2F20211213%2Fauto%2Fstorage%2Fgoog4_request&X-goog-日期= 20211213T202005Z&X-goog-过期= 900&X-goog-SignedHeaders =内容类型%3Bhost%3BX-goog-可恢复&X-goog签名= 920390253e558265309a73ceda3ac981c56d40580c8103d41dd191478bb7186b3aea742891f0fc50ad8c766ff1e262c1f012f021f7687699873f98cf244799539ec86f3f600eb9b2e849f869de677ae8bc75a0343eb474f50e12dd4bebc9594c0d4b309bf94b55a1a9c1e3971004c62ed11ebdb328813d8c860d70714feade4b940b7f14c015d45eaa87c816c83d3ba2a1b41783dcda9a9f9ffe09de6ccd47a0c1d292ee0e4c0e1fa0a61d1109207f8a9b9c67d41ae8797bcacb8102a5b3e09a4c108d07d29697bddbe638b32117c078ec180f50c021b5094a163034f3c3d799295f6dd78279a4fb4f0a03d3037333fdf3a03bacc2bd8edb02c2f63974707561'来自原点“http://localhost:8081”已被 CORS 策略阻止:没有“Access-Control-Allow-Origin”标头为 p重新发送请求的资源。
PUT https://storage.googleapis.com/domain.appspot.com/media/20161017_213931.mp4?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcs-server%40domain.iam.gserviceaccount.com%2F20211213%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20211213T214015Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-resumable&X-Goog-Signature=93eda476fe4444b7aa7b51054b56ad5124249fc77c2a268e3a6e4967f2cf037da4981325922a2b646d6254e8b9b64d4f317ad3a63c3c40820a55e51f4520e5b29ca3b92b85644909159201f01004d3a3c087364ab0bacff9db651026fd87401f1aeb567ad3ee663a25f2cd94a65e6bb6e1882bba14cfc2a238d5c725000d0ae6637f9c665e42688372ef1118418bb548254cf9868a8a7d773861295b0299f8caae59525232d9059920b302210b30740dca8ccc2c581264674f627a49f87f10d38ef2e806c39e4ccab712abc6ab4a5ca4905cb1d4e8272f6d030985ebaa72b1f9ca69259670d56af50d099a2b700b0a81c7cf61b2b2468e3f381bf3750a4d0d12 net::ERR_FAILED 400
// 节点/后端
import Storage from '@google-cloud/storage'
const storage = new Storage(
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS
)
const bucketName = 'xxxx.appspot.com'
async function createResumableUrl (file)
const blob = storage.bucket(bucketName).file(`media/$file.originalname`)
const options =
version: 'v4',
action: 'resumable',
contentType: 'application/octet-stream',
expires: Date.now() + 15 * 60 * 1000 // 15 minutes
const [signedUrl] = await blob.getSignedUrl(options)
return signedUrl
// vue/quasar前端表单
<q-form
enctype="multipart/form-data"
>
<q-file
v-if="!initFile"
label="Select a file for Upload"
dense
outlined
rounded
no-error-icon
hide-bottom-space
bg-color="grey-1"
class="q-pt-sm q-mb-md"
@input="mediaAction"
>
<template v-if="media.location" v-slot:append>
<q-icon name="mdi-autorenew" @click.stop="" class="cursor-pointer" />
</template>
<template v-else v-slot:append>
<q-icon name="mdi-plus" @click.stop="" class="cursor-pointer" />
</template>
<template v-if="uploaderHint" v-slot:hint>
uploaderHint
</template>
</q-file>
<q-btn
unelevated
dense
size="0.6rem"
color="primary"
class="q-py-xs q-px-sm q-mr-md"
@click="onOKClick"
>
<span>
<q-icon name="mdi-upload-outline" class="q-mr-xs" />
<span v-if="initFile">Update</span>
<span v-else>Upload</span>
</span>
</q-btn>
</q-form>
//vue/quasar方法
methods:
async onOKClick ()
const formData = new FormData()
formData.append('file', this.selectedMedia)
const signedUrl = await this.$store.dispatch('media/createResumableUrl', formData)
// signedUrl is present
// console.log(signedUrl.data)
const upload = await this.$api.put(signedUrl.data, formData,
withCredentials: false,
headers:
'Content-Type': 'application/octet-stream',
'Access-Control-Allow-Origin': 'http://localhost:8081'
)
// gcs 存储桶配置
[
"origin": [
"*"
],
"responseHeader": [
"Content-Type",
"x-goog-resumable",
"Access-Control-Allow-Origin"
],
"method": ["PUT", "GET", "HEAD", "DELETE", "POST", "OPTIONS"],
"maxAgeSeconds": 15
]
我尝试过的事情:
-
我尝试使用 post 而不是 put 来发出客户端请求
我已尝试将 'Content-Range': 'bytes *' 添加到所有请求中
我已经尝试将源添加到节点请求的 signedUrl 并且我尝试将客户端请求和 cors 存储桶配置匹配为相同。我用端口试过这个,没有端口和'*':
// 节点
const options =
version: 'v4',
origin: 'http://localhost',
action: 'resumable',
contentType: 'application/octet-stream',
expires: Date.now() + 15 * 60 * 1000 // 15 minutes
// 客户端
const upload = await this.$api.put(signedUrl.data, formData,
withCredentials: false,
headers:
'Content-Type': 'application/octet-stream',
'Access-Control-Allow-Origin': 'http://localhost'
)
// cors 存储桶配置
[
"origin": [
"http://localhost"
],
"responseHeader": [
"Content-Type",
"x-goog-resumable",
"Access-Control-Allow-Origin"
],
"method": ["PUT", "GET", "HEAD", "DELETE", "POST", "OPTIONS"],
"maxAgeSeconds": 1
]
// 预检信息
响应标头:
access-control-allow-headers:内容类型、x-goog-resumable、Access-Control-Allow-Origin 访问控制允许方法:PUT、GET、HEAD、DELETE、POST、OPTIONS 访问控制允许来源:* 访问控制最大年龄:15 alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443";马=2592000; v="46,43" 缓存控制:私有,max-age=0 内容长度:0 内容类型:文本/html;字符集=UTF-8 日期:格林威治标准时间 2021 年 12 月 14 日星期二 11:03:32 到期:格林威治标准时间 2021 年 12 月 14 日星期二 11:03:32 服务器:上传服务器 x-guploader-uploadid: ADPycdsNLbraMoNCtWBN2etY5999RcEAzpzumosHd6kQb-O00g53MwwY1JciRK7UauOU4mbLo84Tmasvl-57QCo41NoP8s-8Pg
请求标头
:authority: storage.googleapis.com :method: 选项 :path: /domain.appspot.com/media/20161017_213931.mp4?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcs-server%40origin-sports.iam.gserviceaccount.com%2F20211214%2Fauto% 2Fstorage%2Fgoog4_request&X-goog-日期= 20211214T110332Z&X-goog-过期= 900&X-goog-SignedHeaders =内容类型%3Bhost%3BX-goog-可恢复&X-goog签名= 40d4d9bcfd6b62786144968bf3e7fe3a2820d7a42ab6a510832ddbea3aac65bf31ca59f85694125ac98da6c13d1ba740302f88164d5c53ecdd1e40eb4bdb431f34d103c2f5f2f7d0018a9ef0e4ff15978d834b4b3a2b17699a0dc7f8fabc49f99129d7d9b8de4341c0f6883c03a5ce16303811b278ca72f080167d0f4a1e7cc98076e473b7a65043976ddcf87532f52e9d2efefd48fae38bd3742e3e21ef86702b00cfe71b8b08fa506b886183146c94d61b747150ad2b5ae6ea668a5750dce27c5f212e9b60002e5bd09af0fee43a3566606f9063113a1f14d51d22af65eaf6503270f696e9bf50c9015c2af65ef8ec994f1949a4081d5872b2be09cb070e68结果 :方案:https 接受:/ 接受编码:gzip、deflate、br 接受语言:en-US,en;q=0.9 访问控制请求标头:访问控制允许来源、内容类型、x-goog-resumable 访问控制请求方法:PUT 缓存控制:无缓存 来源:http://localhost:8081 杂注:无缓存 推荐人:http://localhost:8081/ sec-fetch-dest:空 秒取模式:cors sec-fetch-site:跨站点 用户代理:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
我不经常在这里发帖,所以如果您需要任何其他内容或者我是否需要重组这个问题,请告诉我。
// 我发现没有用的相关事情
-
XMLHttpRequest CORS to Google Cloud Storage only working in preflight request
MalformedSecurityHeader error in signed url - Header was included in signedheaders, but not in the request
https://github.com/googleapis/nodejs-storage/issues/347
【问题讨论】:
你检查过Canonical Headers的格式是否正确吗? @RobertG 我相信是这样,但我不确定我是否正确理解了文档。我已经为客户端、服务器和存储桶的 cors 配置中的所有标头设置了相同的大小写。我相信它们在 signedUrl 中看起来像这样:X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-resumable 是我在其他任何地方设置它们的方式。 【参考方案1】:我可以看到您的问题存在多个问题,但我想重点关注您希望使用签名 URL 进行可恢复上传的部分。
据我了解,您正在通过向签名 URL 发送 PUT 请求来进行可恢复上传。也没有提到对您的问题使用会话 URL,因此已经缺少一些东西。致clarify:
使用可恢复上传时,您只需为启动上传的
POST
请求创建和使用签名 URL。此初始请求会返回一个会话 URI,您可以在后续的PUT
请求中使用它来上传数据。
可恢复上传需要一个签名 URL 用于启动上传的 POST
请求。此初始请求将返回一个会话 URI,您将使用它与 PUT
对象请求一起上传数据。
首先,创建一个接受 POST
方法、content-type
和 x-goog-resumable
标头的签名 URL。
这将返回一个会话 URI,它是 Location
响应标头的值,将用于单块上传。
我建议您重组流程。这个Github link 应该有助于理解流程。虽然 Gist 是用 Ruby 编写的,但您仍然应该能够看到可恢复上传需要一个会话 URL。我建议也遵循相同的流程。
【讨论】:
以上是关于尝试使用 gcs json api、axios、vue3、quasar 和 node14 执行单个块可恢复上传到谷歌云存储时出现 cors 错误的主要内容,如果未能解决你的问题,请参考以下文章
使用 Axios 在 Vuetify v-data-table 中未显示 JSON 数据
如何使用 axios 将文件和 JSON 数据一起发送到 API 端点?
在 Vue JS 中使用 Axios 将 json 数据发布到 API 的问题