Потоковая загрузка файла с использованием бутылки (или фляжки или аналогичной)

У меня есть интерфейс REST, написанный с использованием Python/Bottle, который обрабатывает файлы, обычно большие. API будет вибрировать таким образом, чтобы:

Клиент отправляет PUT с файлом в качестве полезной нагрузки. Помимо прочего, он отправляет заголовки даты и авторизации. Это мера безопасности против повторных атак - запрос сменяется временным ключом, используя целевой URL, дату и несколько других вещей.

Теперь проблема. Сервер принимает запрос, если предоставленная дата указана в окне даты и времени в 15 минут. Если загрузка занимает достаточно много времени, она будет длиннее, чем разрешенная временная дельта. Теперь обработка авторизации запроса выполняется с использованием декоратора на методе просмотра бутылки. Тем не менее, бутылка не начнет процесс отправки, если загрузка не будет завершена, поэтому проверка не удастся при более длительной загрузке.

Мой вопрос: есть ли способ объяснить бутылке или WSGI для немедленного обращения с запросом и потоковой загрузкой по мере ее поступления? Это было бы полезно для меня и по другим причинам. Или любые другие решения? Поскольку я пишу это, связующее ПО WSGI приходит на ум, но тем не менее я бы хотел получить внешнее понимание.

Я бы хотел переключиться на Flask или даже на другие структуры Python, так как интерфейс REST довольно легкий.

Спасибо

Ответ 1

Я рекомендую разбивать входящий файл на куски меньшего размера на интерфейсе. Я делаю это, чтобы реализовать функцию приостановки/возобновления для больших загрузок файлов в приложении Flask.

Используя плагин Sequistian Tschan jquery, вы можете реализовать chunking, указав maxChunkSize при инициализации плагина, как в:

$('#file-select').fileupload({
    url: '/uploads/',
    sequentialUploads: true,
    done: function (e, data) {
        console.log("uploaded: " + data.files[0].name)
    },
    maxChunkSize: 1000000 // 1 MB
});

Теперь клиент отправляет несколько запросов при загрузке больших файлов. И ваш серверный код может использовать заголовок Content-Range для исправления исходного большого файла вместе. Для приложения Flask вид может выглядеть примерно так:

# Upload files
@app.route('/uploads/', methods=['POST'])
def results():

    files = request.files

    # assuming only one file is passed in the request
    key = files.keys()[0]
    value = files[key] # this is a Werkzeug FileStorage object
    filename = value.filename

    if 'Content-Range' in request.headers:
        # extract starting byte from Content-Range header string
        range_str = request.headers['Content-Range']
        start_bytes = int(range_str.split(' ')[1].split('-')[0])

        # append chunk to the file on disk, or create new
        with open(filename, 'a') as f:
            f.seek(start_bytes)
            f.write(value.stream.read())

    else:
        # this is not a chunked request, so just save the whole file
        value.save(filename)

    # send response with appropriate mime type header
    return jsonify({"name": value.filename,
                    "size": os.path.getsize(filename),
                    "url": 'uploads/' + value.filename,
                    "thumbnail_url": None,
                    "delete_url": None,
                    "delete_type": None,})

Для вашего конкретного приложения вам просто нужно убедиться, что правильные заголовки заголовков по-прежнему отправляются с каждым запросом.

Надеюсь, это поможет! Я некоторое время боролся с этой проблемой;)

Ответ 2

При использовании решения plupload возможно следующее:

$("#uploader").plupload({
    // General settings
    runtimes : 'html5,flash,silverlight,html4',
    url : "/uploads/",

    // Maximum file size
    max_file_size : '20mb',

    chunk_size: '128kb',

    // Specify what files to browse for
    filters : [
        {title : "Image files", extensions : "jpg,gif,png"},
    ],

    // Enable ability to drag'n'drop files onto the widget (currently only HTML5 supports that)
    dragdrop: true,

    // Views to activate
    views: {
        list: true,
        thumbs: true, // Show thumbs
        active: 'thumbs'
    },

    // Flash settings
    flash_swf_url : '/static/js/plupload-2.1.2/js/plupload/js/Moxie.swf',

    // Silverlight settings
    silverlight_xap_url : '/static/js/plupload-2.1.2/js/plupload/js/Moxie.xap'
});

И ваш код флип-питона в таком случае будет похож на это:

from werkzeug import secure_filename

# Upload files
@app.route('/uploads/', methods=['POST'])
def results():
    content = request.files['file'].read()
    filename = secure_filename(request.values['name'])

    with open(filename, 'ab+') as fp:
        fp.write(content)

    # send response with appropriate mime type header
    return jsonify({
        "name": filename,
        "size": os.path.getsize(filename),
        "url": 'uploads/' + filename,})

Plupload всегда отправляет куски в точно таком же порядке, от первого до последнего, так что вам не нужно беспокоиться о поиске или что-то в этом роде.