Передайте файл потоком к ответу HTTP в Опорах

У меня есть действие контроллера Опор, которое должно возвратить файл клиенту. (Файл вне веб-корня, таким образом, я не могу просто связаться непосредственно с ним.) Самый простой путь - конечно, это:

    with open(filepath, 'rb') as f:
        response.write(f.read())

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

7
задан EMP 6 May 2010 в 00:59
поделиться

3 ответа

Я наконец-то заставил его работать, используя класс FileApp, благодаря Chris AtLee и THC4k (из этого ответа). Этот метод также позволил мне установить заголовок Content-Length, с чем у Pylons большие проблемы, что позволяет браузеру показывать оценку оставшегося времени.

Вот полный код:

def _send_file_response(self, filepath):
    user_filename = '_'.join(filepath.split('/')[-2:])
    file_size = os.path.getsize(filepath)

    headers = [('Content-Disposition', 'attachment; filename=\"' + user_filename + '\"'),
               ('Content-Type', 'text/plain'),
               ('Content-Length', str(file_size))]

    from paste.fileapp import FileApp
    fapp = FileApp(filepath, headers=headers)

    return fapp(request.environ, self.start_response)
5
ответ дан 6 December 2019 в 15:20
поделиться

Правильный инструмент - shutil.copyfileobj, который копирует из одного блока в другой по частям.

Пример использования:

import shutil
with open(filepath, 'r') as f:
    shutil.copyfileobj(f, response)

Это не приведет к очень большому использованию памяти и не требует самостоятельной реализации кода.

Следует проявлять обычную осторожность с исключениями - если вы обрабатываете сигналы (такие как SIGCHLD), вы должны обрабатывать EINTR, потому что запись в ответ может быть прервана, а IOError / OSError может возникать по разным причинам при выполнении ввода-вывода.

8
ответ дан 6 December 2019 в 15:20
поделиться

Ключевым моментом здесь является то, что WSGI и, соответственно, pylons работают с итерабельными ответами. Поэтому вы должны быть в состоянии написать код типа (предупреждение, непроверенный код ниже!):

def file_streamer():
    with open(filepath, 'rb') as f:
        while True:
            block = f.read(4096)
            if not block:
                break
            yield block
response.app_iter = file_streamer()

Также, paste.fileapp.FileApp предназначен для возврата данных файла, поэтому вы также можете попробовать:

return FileApp(filepath)

в вашем методе контроллера.

1
ответ дан 6 December 2019 в 15:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: