更新:(2020年5月18日)解决方案在本文末尾!
我正在尝试将大型 CSV 文件(30MB - 2GB)从浏览器上传到运行 Python 3.7 + Flask 的 GCP App Engine,然后将这些文件推送到 GCP 存储。这在使用大文件进行本地测试时效果很好,但如果文件大于大约 20MB,则 GCP 上会立即出错,并显示“413 - 您的客户端发出了太大的请求”。 此错误在上传时立即发生,甚至在到达我的自定义 Python 逻辑之前(我怀疑 App Engine 正在检查 Content-Length
header )。经过大量 SO/博客研究后,我尝试了许多解决方案,但均无济于事。请注意,我正在使用基本/免费的 App Engine 设置以及运行 Gunicorn 服务器的 F1 实例。
首先,我尝试设置 app.config['MAX_CONTENT_LENGTH'] = 2147483648
但这并没有改变任何内容 ( SO post )。我的应用程序在到达我的 Python 代码之前仍然抛出错误:
# main.py
app.config['MAX_CONTENT_LENGTH'] = 2147483648 # 2GB limit
@app.route('/', methods=['POST', 'GET'])
def upload():
# COULDN'T GET THIS FAR WITH A LARGE UPLOAD!!!
if flask.request.method == 'POST':
uploaded_file = flask.request.files.get('file')
storage_client = storage.Client()
storage_bucket = storage_client.get_bucket('my_uploads')
blob = storage_bucket.blob(uploaded_file.filename)
blob.upload_from_string(uploaded_file.read())
<!-- index.html -->
<form method="POST" action='/upload' enctype="multipart/form-data">
<input type="file" name="file">
</form>
经过进一步研究,我改用 Flask-Dropzone
进行分块上传,希望能够批量上传数据,然后将 CSV 文件附加/构建为存储 Blob:
# main.py
app = flask.Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 2147483648 # 2GB limit
dropzone = Dropzone(app)
@app.route('/', methods=['POST', 'GET'])
def upload():
if flask.request.method == 'POST':
uploaded_file = flask.request.files.get('file')
storage_client = storage.Client()
storage_bucket = storage_client.get_bucket('my_uploads')
CHUNK_SIZE = 10485760 # 10MB
blob = storage_bucket.blob(uploaded_file.filename, chunk_size=self.CHUNK_SIZE)
# hoping for a create-if-not-exists then append thereafter
blob.upload_from_string(uploaded_file.read())
JS/HTML 直接来 self 在网上找到的一些示例:
<script>
Dropzone.options.myDropzone = {
timeout: 300000,
chunking: true,
chunkSize: 10485760 };
</script>
....
<form method="POST" action='/upload' class="dropzone dz-clickable"
id="dropper" enctype="multipart/form-data">
</form>
上面确实分块上传(我可以看到对 POST/upload 的重复调用),但是,对 blob.upload_from_string(uploaded_file.read()) 的调用
只是不断用最后上传的 block 替换 blob 内容,而不是追加。即使我删除 chunk_size=self.CHUNK_SIZE
参数,这也不起作用。
接下来我查看了写入 /tmp
然后写入存储,但文档说写入 /tmp
占用了我所拥有的少量内存,并且读取了其他地方的文件系统-only,所以这些都不起作用。
是否有附加 API 或经批准的方法来将大文件上传到 GCP App Engine 并推送/流式传输到存储?鉴于代码在我的本地服务器上运行(并且很高兴上传到 GCP 存储),我假设这是 App Engine 中需要解决的内置限制。
解决方案(2020 年 5 月 18 日) 我能够使用 Flask-Dropzone 让 JavaScript 将上传内容分割成许多 10MB 的 block ,并将这些 block 一次一个地发送到 Python 服务器。在 Python 方面,我们会不断附加到/tmp 中的文件以“构建”内容,直到所有 block 都进入。最后,在最后一个 block 上,我们将上传到 GCP Storage,然后删除/tmp 文件。
@app.route('/upload', methods=['POST'])
def upload():
uploaded_file = flask.request.files.get('file')
tmp_file_path = '/tmp/' + uploaded_file.filename
with open(tmp_file_path, 'a') as f:
f.write(uploaded_file.read().decode("UTF8"))
chunk_index = int(flask.request.form.get('dzchunkindex')) if (flask.request.form.get('dzchunkindex') is not None) else 0
chunk_count = int(flask.request.form.get('dztotalchunkcount')) if (flask.request.form.get('dztotalchunkcount') is not None) else 1
if (chunk_index == (chunk_count - 1)):
print('Saving file to storage')
storage_bucket = storage_client.get_bucket('prairi_uploads')
blob = storage_bucket.blob(uploaded_file.filename) #CHUNK??
blob.upload_from_filename(tmp_file_path, client=storage_client)
print('Saved to Storage')
print('Deleting temp file')
os.remove(tmp_file_path)
<!-- index.html -->
<script>
Dropzone.options.myDropzone = {
... // configs
timeout: 300000,
chunking: true,
chunkSize: 1000000
};
</script>
请注意,/tmp 与 RAM 共享资源,因此您至少需要与上传文件大小一样多的 RAM,再加上 Python 本身的更多 RAM(我必须使用 F4 实例)。我想有一个更好的解决方案可以写入 block 存储而不是/tmp,但我还没有做到这一点。
最佳答案
答案是您无法在单个 HTTP 请求中上传或下载大于 32 MB 的文件。 Source
您需要重新设计服务以在多个 HTTP 请求中传输数据、使用预签名 URL 将数据直接传输到 Cloud Storage,或者选择不使用全局前端 (GFE) 的其他服务,例如 Compute Engine。这不包括 Cloud Functions、Cloud Run、App EngineFlexible 等服务。
如果您使用多个 HTTP 请求,则需要管理内存,因为所有临时文件都存储在内存中。这意味着当您接近 2 GB 的最大实例大小时,您将会遇到问题。
关于python-3.x - 无法将大文件上传到 GCP App Engine 中的 Python + Flask,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61858949/