Потоковый файл в ответ HTTP в Pylons - PullRequest
7 голосов
/ 10 марта 2010

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

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

Это работает, но, очевидно, неэффективно для больших файлов. Какой лучший способ сделать это? Мне не удалось найти какие-либо удобные методы в Pylons для потоковой передачи содержимого файла. Неужели мне действительно нужно написать код для одновременного чтения фрагмента с нуля?

Ответы [ 3 ]

8 голосов
/ 10 марта 2010

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

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

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

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

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

5 голосов
/ 06 мая 2010

Я наконец заставил его работать, используя класс 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)
1 голос
/ 06 мая 2010

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

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)

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

...