Как загрузить большие (~ 100Mb) файлы? - PullRequest
2 голосов
/ 21 февраля 2020

У меня есть flask app

fileform. html:

<html>
    <head>
        <title>Simple file upload using Python Flask</title>
    </head>
    <body>
        <form action="/getSignature" method="post" enctype="multipart/form-data">
          Choose the file: <input type="file" name="photo"/><BR>
              <input type="submit" value="Upload"/>
        </form>
    </body>
</html>

app.py:

import os
from flask import Flask, request, render_template, url_for, redirect


app = Flask(__name__)


@app.route("/")
def fileFrontPage():
    return render_template('fileform.html')

@app.route("/getSignature", methods=['POST'])
def handleFileUpload():
    if 'photo' in request.files:
        photo = request.files['photo']
        if photo.filename != '':
            filepath = os.path.join('/flask/files', photo.filename)
            photo.save(filepath)
    return render_template('result.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Это работает для небольших файлов, но не удается для файла большего размера. После нажатия кнопки Upload браузер показывает (Uploading 13%..), а затем время ожидания браузера с ERR_CONNECTION_RESET. Не вижу никакой ошибки в приложении flask.

Я обслуживаю это через Nginx. Когда я проверяю nginx журналы, я вижу

2020/02/20 22:38:47 [error] 6#6: *170614 client intended to send too large body: 80762097 bytes, client: 10.2.16.178, server: localhost, request: "POST /getSignature

Есть ли какая-либо конфигурация Nginx, которую мне нужно добавить для этого?

Я хочу загрузить файлы размером до 100 МБ.

Ответы [ 3 ]

1 голос
/ 21 февраля 2020

Вы смотрели на куски? Это позволяет разбивать файлы и данные на отдельные части для отправки по проводам. И у вас будет две основные части: интерфейс и серверная часть. Для внешнего интерфейса вы можете использовать что-то вроде Dropzone. js. Однако вам нужно будет включить режим чанкинга, так как по умолчанию он не включен. К счастью, это действительно легко включить.

Это может быть выполнено следующим образом:

<html>
    <meta charset="UTF-8">
    <link rel="stylesheet"
     href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.4.0/min/dropzone.min.css"/>

    <link rel="stylesheet"
     href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.4.0/min/basic.min.css"/>

    <script type="application/javascript"
     src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.4.0/min/dropzone.min.js">
    </script>
    <head>
        <title>Simple file upload using Python Flask</title>
    </head>
        <form method="POST" action='/upload' class="dropzone dz-clickable"
                    id="dropper" enctype="multipart/form-data">
        </form>

        <script type="application/javascript">
                Dropzone.options.dropper = {
                        paramName: 'file',
                        chunking: true,
                        forceChunking: true,
                        url: '/upload',
                        maxFilesize: 1025, // megabytes
                        chunkSize: 1000000 // bytes
                }
        </script>
</html>

И следующий пример flask будет обрабатывать серверную часть:

import logging
import os

from flask import render_template, Blueprint, request, make_response
from werkzeug.utils import secure_filename

from pydrop.config import config

blueprint = Blueprint('templated', __name__, template_folder='templates')

log = logging.getLogger('pydrop')


@blueprint.route('/')
@blueprint.route('/index')
def index():
    # Route to serve the upload form
    return render_template('index.html',
                           page_name='Main',
                           project_name="pydrop")


@blueprint.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']

    save_path = os.path.join(config.data_dir, secure_filename(file.filename))
    current_chunk = int(request.form['dzchunkindex'])

    # If the file already exists it's ok if we are appending to it,
    # but not if it's new file that would overwrite the existing one
    if os.path.exists(save_path) and current_chunk == 0:
        # 400 and 500s will tell dropzone that an error occurred and show an error
        return make_response(('File already exists', 400))

    try:
        with open(save_path, 'ab') as f:
            f.seek(int(request.form['dzchunkbyteoffset']))
            f.write(file.stream.read())
    except OSError:
        # log.exception will include the traceback so we can see what's wrong 
        log.exception('Could not write to file')
        return make_response(("Not sure why,"
                              " but we couldn't write the file to disk", 500))

    total_chunks = int(request.form['dztotalchunkcount'])

    if current_chunk + 1 == total_chunks:
        # This was the last chunk, the file should be complete and the size we expect
        if os.path.getsize(save_path) != int(request.form['dztotalfilesize']):
            log.error(f"File {file.filename} was completed, "
                      f"but has a size mismatch."
                      f"Was {os.path.getsize(save_path)} but we"
                      f" expected {request.form['dztotalfilesize']} ")
            return make_response(('Size mismatch', 500))
        else:
            log.info(f'File {file.filename} has been uploaded successfully')
    else:
        log.debug(f'Chunk {current_chunk + 1} of {total_chunks} '
                  f'for file {file.filename} complete')

    return make_response(("Chunk upload successful", 200))
1 голос
/ 21 февраля 2020

Возможно, длина контента ограничена значением по умолчанию.

Поскольку вы используете nginx, возможно, тайм-аут в восходящем направлении, когда он играет в свою игру (по умолчанию для upstream keepalive_timeout 60s), добавьте следующую настройку в nginx:

keepalive_timeout 900s;

#extra
proxy_read_timeout 900s;
uwsgi_read_timeout 900s;

Больше настроек здесь

И если вы используете python wsgi-сервер, такой как uwsgi, установите также keep alive там:

http-keepalive 900;
0 голосов
/ 21 февраля 2020

С https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/:

Проблема сброса соединения

При использовании локального сервера разработки вы можете получить ошибку сброса соединения вместо 413 ответ. Вы получите правильный ответ о состоянии при запуске приложения с рабочим сервером WSGI.

Решение: не используйте сервер разработки, настройте настоящий сервер WSGI

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...