I'm trying to perform a single chunk resumable upload via gcs, node, vue per https://cloud.google.com/storage/docs/performing-resumable-uploads . I'm able to produce the signedUrl but get errors when I try to put or post to the signedUrl via the client.
If navigate to the signedUrl, the browser displays the following error:
<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>
In the console, I get the following error (domain.appspot and domain.iam in all of these is set to my bucket name. i just replaced/hid it here just in case):
Access to XMLHttpRequest at '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=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.
// node/backend
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 frontend form
<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 method
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 bucket config
[
{
"origin": [
"*"
],
"responseHeader": [
"Content-Type",
"x-goog-resumable",
"Access-Control-Allow-Origin"
],
"method": ["PUT", "GET", "HEAD", "DELETE", "POST", "OPTIONS"],
"maxAgeSeconds": 15
}
]
things i've tried:
// node
const options = {
version: 'v4',
origin: 'http://localhost',
action: 'resumable',
contentType: 'application/octet-stream',
expires: Date.now() + 15 * 60 * 1000 // 15 minutes
}
// client
const upload = await this.$api.put(signedUrl.data, formData, {
withCredentials: false,
headers: {
'Content-Type': 'application/octet-stream',
'Access-Control-Allow-Origin': 'http://localhost'
}
})
// cors bucket config
[
{
"origin": [
"http://localhost"
],
"responseHeader": [
"Content-Type",
"x-goog-resumable",
"Access-Control-Allow-Origin"
],
"method": ["PUT", "GET", "HEAD", "DELETE", "POST", "OPTIONS"],
"maxAgeSeconds": 1
}
]
// Preflight Info
Response Headers:
access-control-allow-headers: content-type,x-goog-resumable,Access-Control-Allow-Origin access-control-allow-methods: PUT,GET,HEAD,DELETE,POST,OPTIONS
access-control-allow-origin: * access-control-max-age: 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"; ma=2592000; v="46,43" cache-control: private, max-age=0 content-length: 0 content-type: text/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
Request Headers
:authority: storage.googleapis.com
:method: OPTIONS
: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 accept: / accept-encoding: gzip, deflate, br accept-language: 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
I don't post here too often so let me know if you need anything else or if I need to restructure this question.
// related things i've found that haven't worked
I can see that there are multiple problems to your question, but I wanted to focus on a part where you wanted to do a resumable upload using Signed URLs.
From what I understand, you're doing a resumable upload by sending a PUT request to a Signed URL. There's also no mention of using a Session URL to your question so there's already something missing. To clarify :
When working with resumable uploads, you only create and use a signed URL for the
POST
request that initiates the upload. This initial request returns a session URI that you use in subsequentPUT
requests to upload the data.
Resumable uploads need a signed URL for the POST
requests that would initiate the upload. This initial request will return a Session URI that you will use with PUT
object request to upload data.
First, create a signed URL that accepts POST
method, content-type
and x-goog-resumable
headers.
This would return a Session URI, which is the value of Location
response header and will be used for single chunk uploads.
I suggest that you restructure your flow. This Github link should be useful to understand the flow. While the Gist is written in Ruby, you should still be able to see that the resumable upload requires a session URL. I would suggest to follow the same flow as well.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.