尝试使用 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-typex-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 的问题

如何使用 Nativescript Vue 和 Axios 显示 API 数据

使用 Axios 和 Vue.js 加载 JSON 数据

如何使用@nestjs/axios 发出 api Get 请求