目标
我们希望用户能够将图片上传到Google云端存储。
问题
我们可以通过我们的服务器作为中间人间接实现这一点 - 首先,用户上传到我们的服务器,然后我们的特权服务器可以上传到云存储。
但是,我们认为这是不必要的慢,而是希望用户直接上传到云存储。
提议的解决方案
要实现直接上传,我们会在服务器上生成Signed URL。签名URL指定到期时间,并且只能与HTTP PUT动词一起使用。用户可以请求签名URL,然后 - 仅在有限时间内 - 将图像上载到签名URL指定的路径。
解决方案问题
有没有办法强制实施最大文件上传大小?显然我们希望避免用户在我们预期< 1MB文件时尝试上传20GB文件。
这似乎是一个明显的漏洞,但我仍然不知道在使用SignedURL时如何解决它。
使用政策文件(Stack Overflow answer)似乎有办法实现这一目标,但问题已超过2年了。
答案 0 :(得分:4)
政策文件仍然是正确的答案。它们记录在此处:https://cloud.google.com/storage/docs/xml-api/post-object#policydocument
您需要的政策文件的重要部分是:
["content-length-range", <min_range>, <max_range>].
答案 1 :(得分:1)
签署内容长度应该可以解决问题。
即使 content-length 设置为较低的值,Google Cloud 也不允许上传较大的文件。
签名的 url 选项应如下所示:
const writeOptions: GetSignedUrlConfig = {
version: 'v4',
action: 'write',
expires: Date.now() + 900000, // 15 minutes
extensionHeaders: {
"content-length": length // desired length in bytes
}
}
答案 2 :(得分:0)
您可以使用X-Upload-Content-Length
代替Content-Length
。 See blog post here。
在服务器端(Java):
Map<String, String> extensionHeaders = new HashMap<>();
extensionHeaders.put("X-Upload-Content-Length", "" + contentLength);
extensionHeaders.put("Content-Type", "application/octet-stream");
var url =
storage.signUrl(
blobInfo,
15,
TimeUnit.MINUTES,
Storage.SignUrlOption.httpMethod(HttpMethod.PUT),
Storage.SignUrlOption.withExtHeaders(extensionHeaders),
Storage.SignUrlOption.withV4Signature()
);
在客户端(打字稿):
const response = await fetch(url, {
method: 'PUT',
headers: {
'X-Upload-Content-Length': `${file.size}`,
'Content-Type': 'application/octet-stream',
},
body: file,
});
您将需要在存储桶上设置cors策略:
[
{
"origin": ["https://your-website.com"],
"responseHeader": [
"Content-Type",
"Access-Control-Allow-Origin",
"X-Upload-Content-Length",
"x-goog-resumable"
],
"method": ["PUT", "OPTIONS"],
"maxAgeSeconds": 3600
}
]
答案 3 :(得分:0)
对于今天所有观看遮阳篷的人,请注意x-goog-content-length-range:0,25000
是将PUT
的上传大小限制在0到25000字节之间的有效方法
X-Upload-Content-Length
不起作用,您仍然可以上传较大的文件
答案 4 :(得分:0)
我在 NodeJS 中工作的代码遵循 https://blog.koliseo.com/limit-the-size-of-uploaded-files-with-signed-urls-on-google-cloud-storage/。您必须使用版本 v4
public async getPreSignedUrlForUpload(
fileName: string,
contentType: string,
size: number,
bucketName: string = this.configService.get('DEFAULT_BUCKET_NAME'),
): Promise<string> {
const bucket = this.storage.bucket(bucketName);
const file = bucket.file(fileName);
const response = await file.getSignedUrl({
action: 'write',
contentType,
extensionHeaders: {
'X-Upload-Content-Length': size,
},
expires: Date.now() + 60 * 1000, // 1 minute
version: 'v4',
});
const signedUrl = this.maskSignedUrl(response[0], bucketName);
return signedUrl;
}
在前端,我们必须在头X-Upload-Content-Length
中设置相同数量的Size
export async function uploadFileToGCP(
signedUrl: string,
file: any
): Promise<any> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.withCredentials = process.env.NODE_ENV === 'production';
xhr.addEventListener('readystatechange', function () {
if (this.readyState === 4) {
resolve(this.responseText);
}
});
xhr.open('PUT', signedUrl, true);
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('X-Upload-Content-Length', file.size);
xhr.send(file);
});
}
并且不要忘记在 responseHeader
GS CORS
gsutil cors get gs://asia-item-images
[{"maxAgeSeconds": 3600, "method": ["GET", "OPTIONS", "PUT"], "origin": ["*"], "responseHeader": ["Content-Type", "Access-Control-Allow-Origin", "X-Upload-Content-Length", "X-Goog-Resumable"]}]