【问题标题】:Generate presigned url for uploading file to google storage using python使用python生成用于将文件上传到谷歌存储的预签名url
【发布时间】:2019-12-13 14:02:47
【问题描述】:

我想使用 javascript ajax 功能将图像从前端上传到谷歌存储。我需要一个服务器将生成的预签名 url,它将向我的前端提供身份验证以上传 blob。 使用本地机器时如何生成预签名 url。

以前对于 aws s3 我会这样做:

pp = s3.generate_presigned_post(
            Bucket=settings.S3_BUCKET_NAME,
            Key='folder1/' + file_name,  
            ExpiresIn=20  # seconds
        )

当为用户生成签名 url 以查看存储在谷歌存储上的文件时,我会这样做:

    bucket = settings.CLIENT.bucket(settings.BUCKET_NAME)
    blob_name = 'folder/img1.jpg'
    blob = bucket.blob(blob_name)
    url = blob.generate_signed_url(
        version='v4',
        expiration=datetime.timedelta(minutes=1),
        method='GET')

【问题讨论】:

  • 您的问题是关于如何在本地生成 url?然后,您应该在运行脚本时参考authentication guide 并在本地设置 GOOGLE_APPLICATION_CREDENTIALS 环境变量。
  • 不,不是本地生成。它是关于我的服务器如何生成一个 url,前端用户可以使用该 url 直接从浏览器将文件上传到存储。
  • 那么您只需将代码 sn-p 的方法从 GET 更改为 PUT 或 POST。该功能的文档令人困惑,因此还要注意这一点:1.您需要instantiate the client from a credentials json,2.凭据应使用有权在目标存储桶中创建对象的服务帐户,3.您需要configure CORS with gsutil允许前端调用——如果出现问题,尝试将responseHeaders 设置为'*'
  • 是否有特定的字典/Json 格式用于将帖子数据发送到 GCP 存储?你有一个工作 Ajax 最好是 Axios 前端示例吗?例如:{键:'',数据:''}
  • 应该像axios.put(presignedUrl, file)一样简单

标签: python google-cloud-platform google-cloud-storage


【解决方案1】:

编辑我的答案,因为我不明白您面临的问题。

看看问题中的 cmets 线程,正如@Nick Shebanov 所说,在将 GAE 与 flex 环境一起使用时,有一种可能性可以完成您尝试做的事情。

到目前为止,我一直在尝试对 GAE 标准环境做同样的事情,但没有成功。在这一点上,我建议在public issue tracker 上打开一个功能请求,以便以某种方式实现。

【讨论】:

  • 这没有回答原始问题,显然@Aseem 已经在使用文档中的示例
【解决方案2】:

在谷歌支持上花了 100 美元,我花了 2 周的时间终于找到了解决方案。

client = storage.Client() # works on app engine standard without any credentials requirements

但如果你想使用generate_signed_url() 功能,那么你需要服务帐户的Json key。

每个应用引擎标准都有一个默认服务帐号。 (您可以在 IAM/服务帐户中找到它)。为该默认 sv 帐户创建一个密钥并以 json 格式下载密钥 ('sv_key.json')。将该密钥存储在 app.yaml 文件旁边的 Django 项目中。然后执行以下操作:

from google.cloud import storage
CLIENT = storage.Client.from_service_account_json('sv_key.json')
bucket = CLIENT.bucket('bucket_name_1')
blob = bucket.blob('img1.jpg') # name of file to be saved/uploaded to storage
pp = blob.generate_signed_url(
    version='v4',
    expiration=datetime.timedelta(minutes=1),
    method='POST') 

这将适用于您的本地计算机和 GAE 标准。当您将应用程序部署到 GAE 时,sv_key.json 也会与 Django 项目一起部署,因此它可以工作。

希望对你有帮助。

【讨论】:

  • 在这种情况下,将 json 添加到 .gitignore 很重要
  • 如果我们将 json 放在 .gitignore 中,GAE 将如何访问私有 json 密钥?我可以将它存储在秘密管理器中,但 func storage.client.from_service_account_json() 需要一个 json 路径。它不需要json内容
  • 没有使用 GAE,但是使用 Heroku 解决这个问题的方法是将访问密钥 JSON 内容作为环境变量中的字符串。然后在应用程序启动中,有一个从该环境变量创建 gcreds.json 文件的函数。这样存储库中不包含访问密钥,您可以使用 ENV 变量更改访问密钥,并在不执行提交/部署的情况下重新启动应用程序
【解决方案3】:
  • 创建服务帐户私钥并将其存储在 SecretManager (SM) 中。
  • 在 settings.py 从 SecretManager 中检索该密钥并将其存储在一个常量中 - SV_ACCOUNT_KEY
  • 覆盖 Client() 类函数 from_service_account_json() 以获取 json 密钥内容而不是 json 文件的路径。这样,我们的文件系统(本地、cloudbuild 或 GAE)中就不必有 json 文件。我们可以随时随地从SM获取私钥内容。

settings.py

secret = SecretManager()
SV_ACCOUNT_KEY = secret.access_secret_data('SV_ACCOUNT_KEY')

signed_url_mixin.py

import datetime
import json

from django.conf import settings
from google.cloud.storage.client import Client
from google.oauth2 import service_account


class CustomClient(Client):
    @classmethod
    def from_service_account_json(cls, sv_account_key, *args, **kwargs):
        """
        Copying everything from base func (from_service_account_json). 
        Instead of passing json file for private key, we pass 
            sv_account_key (json content) to sign a url. 
        Since its not properly written, we cannot just overwrite a class or a 
        func, we have to write the entire func.
        """
        if "credentials" in kwargs:
            raise TypeError("credentials must not be in keyword arguments")
        credentials_info = json.loads(sv_account_key)
        credentials = service_account.Credentials.from_service_account_info(
            credentials_info
        )
        if cls._SET_PROJECT:
            if "project" not in kwargs:
                kwargs["project"] = credentials_info.get("project_id")

        kwargs["credentials"] = credentials
        return cls(*args, **kwargs)


class _SignedUrlMixin:
    bucket_name = settings.BUCKET_NAME
    CLIENT = CustomClient.from_service_account_json(settings.SV_ACCOUNT_KEY)
    exp_min = 4  # expire minutes

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.bucket = self.CLIENT.bucket(self.bucket_name)

    def _signed_url(self, file_name, method):
        blob = self.bucket.blob(file_name)
        signed_url = blob.generate_signed_url(
            version='v4',
            expiration=datetime.timedelta(minutes=self.exp_min),
            method=method
        )
        return signed_url


class GetSignedUrlMixin(_SignedUrlMixin):
    """
    A GET url to view file on CS
    """

    def get_signed_url(self, file_name):
        """
        :param file_name: name of file to be retrieved from CS.
            xyz/f1.pdf
        :return: GET signed url
        """
        method = 'GET'
        return self._signed_url(file_name, method)


class PutSignedUrlMixin(_SignedUrlMixin):
    """
    A PUT url to make a put req to upload a file to CS
    """

    def put_signed_url(self, file_name):
        """
        :file_name: xyz/f1.pdf
        """
        method = 'PUT'
        return self._signed_url(file_name, method)

【讨论】:

    猜你喜欢
    • 2020-06-28
    • 2019-09-09
    • 1970-01-01
    • 2016-06-30
    • 2019-01-30
    • 2018-08-30
    • 2015-08-26
    • 2017-12-07
    • 1970-01-01
    相关资源
    最近更新 更多