Потоковая передача файлов WSGI с помощью генератора

У меня есть следующий код:

def application(env, start_response):
    path = process(env)
    fh = open(path,'r')
    start_response('200 OK', [('Content-Type','application/octet-stream')])
    return fbuffer(fh,10000)


def fbuffer(f, chunk_size):
    '''Generator to buffer file chunks'''  
    while True:
        chunk = f.read(chunk_size)      
        if not chunk: break
        yield chunk

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

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

Третий вопрос: как лучше всего закрыть файл после его вывода? В коде, который я разместил, я все равно не вижу, чтобы на самом деле закрыть файл.

(Я использую Python 3.2.3 с uWSGI 1.2.4)


person Mediocre Gopher    schedule 04.08.2012    source источник
comment
python.org/dev/peps/pep-0333/#id36   -  person SingleNegationElimination    schedule 04.08.2012
comment
Или более прямая ссылка. python.org/dev/peps/pep -0333/   -  person Graham Dumpleton    schedule 05.08.2012


Ответы (1)


Без особой осторожности uwsgi старается не допустить утечки ошибок, но если вы запустите свое приложение в более строгой реализации, скажем, в той, которая поставляется с python как wsgiref.simple_server, вам будет легче увидеть проблему.

Serving <function application at 0xb65848> http://0.0.0.0:8000
Traceback (most recent call last):
  File "/usr/lib64/python3.2/wsgiref/handlers.py", line 138, in run
    self.finish_response()
  File "/usr/lib64/python3.2/wsgiref/handlers.py", line 179, in finish_response
    self.write(data)
  File "/usr/lib64/python3.2/wsgiref/handlers.py", line 264, in write
    "write() argument must be a bytes instance"
AssertionError: write() argument must be a bytes instance
localhost.localdomain - - [04/Aug/2012 16:27:08] "GET / HTTP/1.1" 500 59

Проблема в том, что wsgi требует, чтобы данные, передаваемые на HTTP-шлюз и из него, обслуживались как bytes, но когда вы используете open(path, 'r'), python 3 удобно конвертирует прочитанные данные в unicode, что в python 3 — str, используя кодировку по умолчанию.

изменение

fh = open(path, 'r')

to

fh = open(path, 'rb')
#                 ^

исправляет это.

person SingleNegationElimination    schedule 04.08.2012
comment
Ах, спасибо! Это имеет смысл... Не могли бы вы прокомментировать мой вопрос о закрытии файла? Будет ли работать команда close непосредственно перед оператором break в генераторе? Или есть лучший способ сделать это? - person Mediocre Gopher; 05.08.2012
comment
да, это разумный поступок. Также обратите внимание на функцию environ['wsgi.file_wrapper'], которая может позволить вам использовать sendfile() на платформах, которые ее поддерживают, для повышения эффективности. - person SingleNegationElimination; 05.08.2012