0

我正在尝试通过 gcs、node、vue 每个https://cloud.google.com/storage/docs/performing-resumable-uploads执行单个块可恢复上传。我能够生成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%40domain.iam 访问 XMLHttpRequest .gserviceaccount.com%2F20211213%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20211213T202005Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-resumable&X-Goog-Signature=920390253e558265309a73ceda3ac981c56d40580c8103d41dd191478bb7186b3aea742891f0fc50ad8c766ff1e262c1f012f021f7687699873f98cf244799539ec86f3f600eb9b2e849f869de677ae8bc75a0343eb474f50e12dd4bebc9594c0d4b309bf94b55a1a9c1e3971004c62ed11ebdb328813d8c860d70714feade4b940b7f14c015d45eaa87c816c83d3ba2a1b41783dcda9a9f9ffe09de6ccd47a0c1d292ee0e4c0e1fa0a61d1109207f8a9b9c67d41ae8797bcacb8102a5b3e09a4c108d07d29697bddbe638b32117c078ec180f50c021b5094a163034f3c3d799295f6dd78279a4fb4f0a03d3037333fdf3a03bacc2bd8edb02c2f63974707561' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Access-Control-Allow-Origin' 标头出现在请求的资源上。Access-Control-Allow-Origin' 标头出现在请求的资源上。

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净::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
  }
]

我尝试过的事情:

  1. 我试图用帖子而不是 put 来发出客户请求
  2. 我尝试将 'Content-Range': 'bytes *' 添加到所有请求
  3. 我已经尝试将源添加到节点请求的 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
  }
]

// 预检信息

响应标头:

访问控制允许标头:内容类型、x-goog-resumable、访问控制允许来源访问控制允许方法: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;charset=UTF-8 date: Tue, 14 Dec 2021 11:03:32 GMT expires: Tue, 14 Dec 2021 11:03:32 GMT server: UploadServer 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-Date=20211214T110332Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost%3Bx-goog-resumable&X-Goog-Signature=40d4d9bcfd6b62786144968bf3e7fe3a2820d7a42ab6a510832ddbea3aac65bf31ca59f85694125ac98da6c13d1ba740302f88164d5c53ecdd1e40eb4bdb431f34d103c2f5f2f7d0018a9ef0e4ff15978d834b4b3a2b17699a0dc7f8fabc49f99129d7d9b8de4341c0f6883c03a5ce16303811b278ca72f080167d0f4a1e7cc98076e473b7a65043976ddcf87532f52e9d2efefd48fae38bd3742e3e21ef86702b00cfe71b8b08fa506b886183146c94d61b747150ad2b5ae6ea668a5750dce27c5f212e9b60002e5bd09af0fee43a3566606f9063113a1f14d51d22af65eaf6503270f696e9bf50c9015c2af65ef8ec994f1949a4081d5872b2be09cb070e68
:scheme: https 接受: / 接受编码: gzip, deflate, br 接受语言: en-US,en;q=0.9 access-control-request-headers: access-control-allow-origin,content-type,x -goog-resumable access-control-request-method: PUT cache-control: no-cache origin: http://localhost:8081 pragma: no-cache referer: http://localhost:8081/sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36

我不经常在这里发帖,所以如果您需要其他任何内容或者我是否需要重组这个问题,请告诉我。

// 我发现没有用的相关事情

  1. XMLHttpRequest CORS 到 Google Cloud Storage 仅在预检请求中工作
  2. 签名 URL 中的 MalformedSecurityHeader 错误 - 标头包含在签名标头中,但不在请求中
  3. https://github.com/googleapis/nodejs-storage/issues/347
4

1 回答 1

0

我可以看到您的问题存在多个问题,但我想重点关注您希望使用签名 URL 进行可恢复上传的部分。

据我了解,您正在通过向签名 URL 发送 PUT 请求来进行可恢复上传。也没有提到对您的问题使用会话 URL,因此已经缺少一些东西。澄清:_

POST使用可恢复上传时,您只需为启动上传的请求创建和使用签名 URL 。此初始请求会返回一个会话 URI,您可以在后续PUT请求中使用该会话 URI 来上传数据。

可恢复的上传需要一个签名的 URL 用于POST启动上传的请求。此初始请求将返回一个会话 URI,您将使用它与PUT对象请求一起上传数据。

  1. 首先,创建一个接受POST方法content-typex-goog-resumable标头的签名 URL。

  2. 这将返回一个会话 URI,它是Location响应头的值,将用于单个块上传。

我建议你重组你的流程。这个Github 链接应该有助于理解流程。虽然 Gist 是用 Ruby 编写的,但您仍然应该能够看到可恢复上传需要一个会话 URL。我建议也遵循相同的流程。

于 2021-12-16T08:10:55.920 回答