【问题标题】:How do I use Signed URLs to upload to Google Storage bucket in Python?如何在 Python 中使用签名 URL 上传到 Google 存储桶?
【发布时间】:2021-01-27 17:27:13
【问题描述】:

我能够创建签名的 URL,只需要知道创建后如何处理它们。

有几个使用 Javascript 通过签名 URL 上传的示例,但我在 Python 中找不到任何示例。我正在尝试使用签名 URL 作为 Google App Engine 为我的 Flask 应用程序施加的 32 MB 限制的解决方法。

这是我的 python app.py 脚本(这里不是我的应用程序的全部功能,只是尝试成功上传到我的存储桶):

from flask import Flask, request, render_template
from google.cloud import storage
import pandas as pd
import os
import gcsfs

bucket_name = "my-bucket"

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/file.json'

app = Flask(__name__)

def upload_blob(bucket_name, source_file_name, destination_blob_name):
    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(destination_blob_name)
    blob.upload_from_file(source_file_name)

    print("success")

@app.route('/')
def homepage():
    return render_template('home.html')

@app.route('/', methods = ['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file1 = request.files['file1'] 
        file2 = request.files['file2']
        upload_blob(bucket_name, file1, 'file-1')
        upload_blob(bucket_name, file2, 'file-2')
        df = pd.read_csv('gs://' + bucket_name + '/' + 'file-1')
        print(df.shape)
        return "done"


if __name__ == "__main__":
  app.run(debug=True)

这是我用来创建签名 URL 的函数:

def generate_upload_signed_url_v4(bucket_name, blob_name):

    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(blob_name)

    url = blob.generate_signed_url(
        version="v4",
        # This URL is valid for 15 minutes
        expiration=datetime.timedelta(minutes=15),
        # Allow GET requests using this URL.
        method="PUT",
        content_type="application/octet-stream",
    )
    print(url)
    return url

generate_upload_signed_url_v4(bucket_name, 'file.csv')

下面是我的home.html:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>test upload</title>
</head>
<body>
    <h3> test upload </h3>

    <form method="POST" action="/" enctype="multipart/form-data">
        <p>Upload file1 below</p>
        <input type="file" name="file1"> 
        <br>
        <br>
        <p>Upload file2 below</p>
        <input type="file" name="file2">
        <br>
        <br>
        <input type="submit" value="upload">
    </form>


</body>
</html>

根据我在这里研究的是我尝试上传到的存储桶的 CORS 配置:


[
{"maxAgeSeconds": 3600, 
"method": ["GET", "PUT", "POST"], 
"origin": ["https://my-app.uc.r.appspot.com", "http://local.machine.XXXX/"], 
"responseHeader": ["Content-Type"]}
]

生成的签名 URL 是否采用 html 格式?它需要进入我的upload_file函数吗?

最后,当我将签名的 URL 粘贴到浏览器中时,它会显示以下错误:


<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>

这是我的第一个 SO 问题,因此如果构造不当,我深表歉意。我对 GCP 非常迷茫和陌生。我已经搜索了一段时间,但没有找到 Python/Flask 的用例,我可以在其中看到签名的 URL 是如何合并到文件上传过程中的。

再次,我正在 Google App Engine flex 上构建一个 webapp,并且需要签名 URL 来解决 32 MB 文件上传限制。

更新

在意识到我需要简单地向签名 URL 发出请求后,我得到了签名 URL 组件。

下面是我在 App Engine 中加载的新脚本(导入和“如果 name = ma​​in...”为下面的 sn-p 删除)。


os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/file.json'

EXPIRATION = datetime.timedelta(minutes=15)
FILE_TYPE = 'text/csv'
BUCKET = 'my-bucket'

def upload_via_signed(bucket_name, blob_name, filename, expiration, file_type):
    bucket = storage.Client().get_bucket(bucket_name)

    blob = bucket.blob(blob_name)

    signed_url = blob.generate_signed_url(method='PUT', expiration=expiration, content_type=file_type)

    requests.put(signed_url, open(filename.filename, 'rb'), headers={'Content-Type': file_type})

app = Flask(__name__)

app.config['UPLOAD_FOLDER'] = '/tmp'

@app.route('/')
def homepage():
    return render_template('home.html')

@app.route('/', methods = ['GET', 'POST'])
def upload_file():
    if request.method == 'POST':

        diag = request.files['file']
        filename_1 = secure_filename(diag.filename)
        filepath_1 = os.path.join(app.config['UPLOAD_FOLDER'], filename_1)
        diag.save(filepath_1)

        person = request.files['person']
        filename_2 = secure_filename(person.filename)
        filepath_2 = os.path.join(app.config['UPLOAD_FOLDER'], filename_2)
        person.save(filepath_2)

        upload_via_signed(BUCKET, 'diag.csv', diag, EXPIRATION, FILE_TYPE)

        upload_via_signed(BUCKET, 'person.csv', person, EXPIRATION, FILE_TYPE)

        df_diag = pd.read_csv('gs://' + BUCKET + '/' + 'diag.csv')
        print(df_diag.shape)
        return "done"

上面的代码仍然抛出 413 entity too large 错误。我认为这是因为即使我正在创建签名 URL,我也通过 App Engine 获得了“POST”。我需要如何重新安排/我做错了什么?如何构建代码以让用户通过签名 URL 直接上传到 Google Cloud Storage 并避免触发 413 entity too large 错误?

【问题讨论】:

  • 您可以尝试 2 件事吗:1. 生成签名 url generate_upload_signed_url_v4(bucket_name, '/uploaded/') 时使用路径作为 blob。 2 你能把方法 signed_url 方法从 PUT 更改为 POST 吗?如果其中一个或两个都解决了您的问题,请告诉我。
  • 在发现我只需要向我创建的签名 URL 发出请求后,我确实让它工作了。但是,它只能在我的本地机器上运行,并且在 App Engine 上仍然出现 413 错误,因为我测试的文件大于 32 MB。根据我所阅读的内容,签名的 URL 应该直接上传到 Google Cloud Storage,然后在上传后它们通过 App Engine 触发应用逻辑。但是,我没有发现这实际上是如何完成的。当我尝试时,我仍在通过 App Engine 发出 POST 请求,从而触发 413 错误。我做错了什么?
  • 您不必通过 App Engine。您的前端(用户浏览器)要求一个签名的 URL,您的后端生成它并将其发送到前端。然后浏览器使用签名的 URL 将文件上传到 Cloud Storage。您还可以想象,在上传结束时,如果您需要跟踪后端中的文件,则从前端调用后端以提供上传的文件(名称和位置)。
  • 这是有道理的。我将如何实现让浏览器请求签名 URL 以从访问我的应用程序的用户上传文件?用户选择要上传的文件并通过应用程序单击“提交”会触发它吗?实现这一点的代码需要在“home.html”中还是在“app.py”中?我用我用来通过 App Engine 部署应用程序的新代码更新了我上面的问题,这样你就可以看到我在哪里关闭/我做错了什么。非常感谢你在这方面的帮助,我想我已经接近解决了。
  • 不,您的代码需要将文件发送到 App Engine。签名的网址在这里没用。浏览器需要直接将文件发送到 Storage,无需 App Engine 中介。我会尝试制作一些原型给你看,但是我在前端/javascript方面很糟糕!

标签: python google-app-engine flask google-cloud-storage signed-url


【解决方案1】:

在服务器上生成签名 url 后,您只需将其发送回客户端并使用它来上传文件。例如,您可以使用普通的 fetch put 请求或我更喜欢始终使用 axios 发送文件数据:

await axios.put(url, file);

这里的url是签名的url。您可能希望将文件发送为formData

【讨论】:

  • 您好,感谢您的帮助,但我对异步函数不熟悉。在收到“未等待”错误后,我做了一些研究。然后我无法安装 axios,并且仍在对其进行故障排除。同时,我可以通过类似于 axios.put 方法的简单 requests.put 来使用 url,但是我仍然通过 Google App Engine 收到 413 错误。
猜你喜欢
  • 2021-09-09
  • 1970-01-01
  • 2019-01-30
  • 2019-09-09
  • 2019-04-18
  • 2022-10-05
  • 1970-01-01
  • 2020-08-31
  • 2015-08-26
相关资源
最近更新 更多