使用 BlobStore 上传到 GCS(Google Cloud Storage)时创建上传URL https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/blobstore/BlobstoreService#createUploadUrl-java.lang.String-函数中,我可以提供一个回调以及将被 POST 到回调 URL 的标头数据。
GCS 似乎没有办法做到这一点签名的 URL https://cloud.google.com/storage/docs/access-control#Signed-URLs
我知道有对象变更通知 https://cloud.google.com/storage/docs/object-change-notification但这不允许用户在 POST 标头中提供上传特定信息,而 createUploadURL 的回调可以采用这种方式。
我的感觉是,如果 createUploadURL 可以做到这一点,那么一定有一种方法可以使用签名 URL 来做到这一点,但我找不到任何相关文档。我想知道是否有人知道 createUploadURL 如何实现回调调用行为。
PS:我正在尝试放弃 createUploadURL 因为__BlobInfo__
它创建的实体,对于我的特定用例我不需要,并且不知何故似乎是不可消除的并且浪费存储空间。
Update:有效!具体方法如下:
简短回答:这是不能用PUT https://cloud.google.com/storage/docs/access-control?hl=en#Signed-URLs,但可以用POST https://cloud.google.com/storage/docs/xml-api/post-object
长答案:
如果你看一下签名 URL https://cloud.google.com/storage/docs/access-control?hl=en#Signed-URLs页,在 HTTP_Verb 前面,在描述,有一个微妙的说明,这个页面只与 GET、HEAD、PUT 和 DELETE 相关,但 POST 是一个完全不同的游戏。我错过了这一点,但事实证明它非常重要。
有一整页HTTP 标头 https://cloud.google.com/storage/docs/reference-headers没有列出可与 POST 一起使用的重要标头;该标头是 success_action_redirect,正如 voscausa 正确回答的那样。
在 POST 页面中,Google“强烈建议”使用 PUT,除非处理表单数据。然而,POST 有一些 PUT 所没有的好功能。他们可能担心 POST 给我们带来了太多的束缚。
但我想说,完全值得放弃 createUploadURL,并编写自己的代码来重定向到回调。具体方法如下:
Code:
如果您正在使用 Python voscausacode https://github.com/voscausa/appengine-gcs-upload/blob/master/gcs_upload.py非常有帮助。
我在用着apejs在 Java 应用程序中编写 javascript,所以我的代码如下所示:
var exp = new Date()
exp.setTime(exp.getTime() + 1000 * 60 * 100); //100 minutes
json['GoogleAccessId'] = String(appIdentity.getServiceAccountName())
json['key'] = keyGenerator()
json['bucket'] = bucket
json['Expires'] = exp.toISOString();
json['success_action_redirect'] = "https://" + request.getServerName() + "/test2/";
json['uri'] = 'https://' + bucket + '.storage.googleapis.com/';
var policy = {'expiration': json.Expires
, 'conditions': [
["starts-with", "$key", json.key],
{'Expires': json.Expires},
{'bucket': json.bucket},
{"success_action_redirect": json.success_action_redirect}
]
};
var plain = StringToBytes(JSON.stringify(policy))
json['policy'] = String(Base64.encodeBase64String(plain))
var result = appIdentity.signForApp(Base64.encodeBase64(plain, false));
json['signature'] = String(Base64.encodeBase64String(result.getSignature()))
上面的代码首先提供了相关字段。
然后创建一个策略对象。然后它对对象进行字符串化并将其转换为字节数组(您可以在Java中使用.getBytes。我必须为javascript编写一个函数)。
该数组的 Base64 编码版本填充policy场地。
然后使用附身性 https://cloud.google.com/appengine/docs/java/appidentity/包裹。最后签名是base64编码的,我们就完成了。
在客户端,json对象的所有成员都会被添加到Form中,除了uri这是表格的地址。
var formData = new FormData(document.forms.namedItem('upload'));
var blob = new Blob([thedata], {type: 'application/json'})
var keys = ['GoogleAccessId', 'key', 'bucket', 'Expires', 'success_action_redirect', 'policy', 'signature']
for(field in keys)
formData.append(keys[field], url[keys[field]])
formData.append('file', blob)
var rest = new XMLHttpRequest();
rest.open('POST', url.uri)
rest.onload = callback_function
rest.send(formData)
如果您不提供重定向,则响应状态将为 204 表示成功。但如果您进行重定向,状态将为 200。如果您收到 403 或 400,则有关签名或策略的信息可能有误。查看响应文本。如果经常有帮助。
有几点需要注意:
- POST 和 PUT 都有一个签名字段,但它们的含义略有不同。对于 POST,这是签名policy.
- PUT 有一个包含 key(对象名称)的 baseurl,但用于 POST 的 URL 可能只包含存储桶名称
- PUT 要求过期时间为 UNIX 纪元的秒数,但 POST 希望它为 ISO 字符串。
- PUT 签名应该是 URL 编码的(Java:通过使用 URLEncoder.encode 调用包装它)。但对于 POST,Base64 编码就足够了。
- 通过扩展,对于 POST 执行 Base64.encodeBase64String(result.getSignature()),并且不要使用 Base64.encodeBase64URLSafeString 函数
- 您不能通过 POST 传递额外的标头;仅那些列在发布页面 https://cloud.google.com/storage/docs/xml-api/post-object被允许。
- 如果您为 success_action_redirect 提供 URL,它将收到带有以下内容的 GETkey, bucket and eTag.
- 使用 POST 的另一个好处是您可以提供大小限制。然而,使用 PUT 时,如果文件违反了大小限制,您只能在完全上传后将其删除,即使它有多个 TB 字节。
createUploadURL 有什么问题吗?
上面的方法是手动的创建上传URL.
But:
- 你不明白这些
__BlobInfo__
创建许多索引并且不可删除的对象。这让我很恼火,因为它浪费了很多空间(这让我想起了一个单独的问题:问题 4231 https://code.google.com/p/googleappengine/issues/detail?id=4231。请去给它一颗星)
- 您可以提供自己的对象名称,这有助于在存储桶中创建文件夹。
- 您可以为每个链接提供不同的到期日期。
对于极少数 javascript 应用工程师:
function StringToBytes(sz) {
map = function(x) {return x.charCodeAt(0)}
return sz.split('').map(map)
}