在Google云端存储签名网址

时间:2016-01-03 03:33:56

标签: google-app-engine upload callback google-cloud-storage blobstore

使用BlobStore的createUploadURL功能上传到GCS(Google云端存储)时,我可以提供回调以及将张贴到回调网址的标头数据。

使用GCS的signed URL's

似乎无法做到这一点

我知道有Object Change Notification但是不允许用户在POST的标题中提供上传特定信息,就像使用createUploadURL的回调一样。

我的感觉是,如果createUploadURL能够做到这一点,必须有办法用签名的URL来做,但我找不到任何文档。我想知道是否有人可能知道createUploadURL如何实现回调调用行为。

PS:我正在尝试离开createUploadURL,因为它创建了__BlobInfo__个实体,这对于我不需要的特定用例,并且似乎不可思议并且浪费存储空间。< / p>

更新: 有效!方法如下:

简答:使用PUT无法完成,但可以使用POST

完成

长答案:

如果你看看signed-URL页面,在HTTP_Verb前面,在 Description 下面,有一个微妙的说明,这个页面只与GET,HEAD,PUT和DELETE相关,但POST是一个完全不同的游戏。我错过了这个,但事实证明这非常重要。

整页HTTP Headers没有列出可以与POST一起使用的重要标题;该标题是success_action_redirect,因为voscausa正确回答。

在POST页面中,Google“强烈建议”使用PUT,除非处理表单数据。但是,POST有一些很好的功能,PUT没有。他们可能会担心POST给我们带来了太多的字符串,无法自拔。

但我要说完全值得删除createUploadURL,并编写自己的代码来重定向到回调。方法如下:

代码:

如果你在Python voscausa工作code非常有用。

我正在使用 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编码版本填充​​策略字段。 然后使用appidentity包进行签名。最后,签名是base64编码的,我们就完成了。

在客户端,json对象的所有成员都将添加到表单中,但 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有关签名或政策可能是错误的。看看responseText。如果通常有帮助。

需要注意的一些事项:

  • POST和PUT都有一个签名字段,但这些意味着略有不同。如果是POST,则这是策略的签名
  • PUT有一个包含密钥(对象名称)的baseurl,但用于POST的URL可能只包含存储桶名称
  • PUT要求从UNIX纪元开始到期为止,但POST要将其作为ISO字符串。
  • PUT签名应该是URL编码的(Java:通过URLEncoder.encode调用包装它)。但对于POST,Base64编码就足够了。
  • 通过扩展,对于POST执行Base64.encodeBase64String(result.getSignature()),并且不使用Base64.encodeBase64URLSafeString函数
  • 您无法通过POST传递额外的标头;只允许POST page中列出的那些。
  • 如果您提供success_action_redirect的网址,则会收到包含密钥存储桶 eTag 的GET。
  • 使用POST的另一个好处是您可以提供大小限制。但是,使用PUT,如果文件超出了您的大小限制,您只能在完全上传后删除它,即使它是多个tera-bytes。

createUploadURL有什么问题?

上述方法是手动 createUploadURL 。 但是:

  • 您没有获得那些创建许多索引并且不可磨灭的__BlobInfo__个对象。这让我感到恼火,因为它浪费了很多空间(这让我想起了一个单独的问题:issue 4231。请给它一个明星)
  • 您可以提供自己的对象名称,这有助于在您的存储桶中创建文件夹。
  • 您可以为每个链接提供不同的到期日期。

对于极少数的javascript app-engineers:

function StringToBytes(sz) {
  map = function(x) {return x.charCodeAt(0)}
  return sz.split('').map(map)
}

3 个答案:

答案 0 :(得分:2)

使用GCS post对象时,可以在策略文档中包含succes_action_redirect。

文档:文档:https://cloud.google.com/storage/docs/xml-api/post-object
Python示例:https://github.com/voscausa/appengine-gcs-upload

示例回调结果:

def ok(self):
    """ GCS upload success callback """

    logging.debug('GCS upload result : %s' % self.request.query_string)
    bucket = self.request.get('bucket', default_value='')
    key = self.request.get('key', default_value='')
    key_parts = key.rsplit('/', 1)
    folder = key_parts[0] if len(key_parts) > 1 else None

答案 1 :(得分:0)

您可以在签名网址中包含以下标题:

HTTP Headers and Query String Parameters

例如,请参阅访问签名URL时如何请求返回特定的Content-Disposition标头:

public static String getSignedUrl(String httpVerb, String fileName, String gsKey) {
    try {
        long expiration = new Date().getTime()/1000 + 5 * 60 * 60;
        String unsigned = stringToSign(expiration, gsKey, httpVerb);
        String signature = sign(unsigned);
        return new StringBuilder(BASE_URL).append("/")
                .append(BUCKET)
                .append("/")
                .append(gsKey)
                .append("?GoogleAccessId=")
                .append(identityService.getServiceAccountName())
                .append("&Expires=")
                .append(expiration)
                .append("&response-content-disposition=")
                .append(URLEncoder.encode("attachment;filename=" + "\"" + fileName + "\"", "UTF-8"))
                .append("&Signature=")
                .append(URLEncoder.encode(signature, "UTF-8")).toString();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return null;
}

我希望它有所帮助 - 我想看看你是如何离开createUploadUrl的。

答案 2 :(得分:0)

我正在使用的解决方案是打开Object Changed Notifications。每次添加一个对象时,Post都会发送到一个URL - 在我的例子中 - 是我项目中的一个servlet。

doPost()中,我将所有被反对的信息添加到GCS中并从那里开始,我可以做任何事情。

这在我的App Engine项目中运行良好。