Как загрузить большой файл с двоичным режимом в python?

Я кодирую функцию загрузки в python. Размер файла >1 ГБ. Сервер Linux, HTTP-сервер Karrigell. Клиент просматривает, Firefox или IE. Я встречаю большую беду.

Сначала я использую sys.stdout() для отправки содержимого файла.

file = open(path, 'rb')
size = os.path.getsize(path)

RESPONSE['Pragma'] = 'public'
RESPONSE['Expires'] = '0'
RESPONSE['Cache-Control'] = 'must-revalidate, pre-check=0'
RESPONSE['Content-Disposition'] = 'attachment; filename="' + os.path.basename(path) + '"'
RESPONSE['Content-type'] = "application/octet-stream"
RESPONSE['Content-Transfer-Encoding'] = 'binary'
RESPONSE['Content-length'] = str(os.path.getsize(path))

sys.stdout.flush()
chunk_size = 10000
handle = open(path, "rb")
while True:
    buffer = handle.read(chunk_size)
    if buffer:
        STDOUT(buffer)
    else:
        break
sys.stdout.flush()

Проблема в том, что серверу не хватает памяти! Я знаю, что стандартный вывод сначала записывает содержимое в память, а затем память отправляет в сокет.

Итак, я модифицирую функцию. Отправить содержимое в сокет напрямую. Я использую модуль py-sendfile. http://code.google.com/p/py-sendfile/

file = open(path, 'rb')
size = os.path.getsize(path)

sock = REQUEST_HANDLER.sock
sock.sendall("""HTTP/1.1 200 OK\r\nPragma: no-cache\r\nExpires: 0\r\nCache-Control: no-cache, no-store\r\nContent-Disposition: attachment; filename="%s"\r\nContent-Type: application/octet-stream\r\nContent-Length: %u\r\nContent-Range: bytes 0-4096/%u\r\nLocation: "%s"\r\n\r\n""" % (os.path.basename(path), size, size, os.path.basename(path)))

offset = 0
nbytes = 4096
while 1:
    try:
        sent = sendfile.sendfile(sock.fileno(), file.fileno(), offset, nbytes)
    except OSError, err:
        if err.errno in (errno.EAGAIN, errno.EBUSY):  # retry
            continue
        raise
    else:
        if sent == 0:
            break    # done
        offset += sent

На этот раз с памятью сервера все в порядке, но просмотр невозможен! Память просмотра увеличивается быстро! Не бесплатно, пока сокет не примет все содержимое файла.

Я не знаю, как бороться с этими проблемами. Я думаю, что вторая идея верна, отправлять контент в сокет напрямую. Но почему просмотр не может освободить память при приеме данных?


person Bobby    schedule 21.02.2011    source источник
comment
Мне любопытно, почему ваша вторая попытка отправляет Content-Range< /a> заголовок?   -  person sarnold    schedule 21.02.2011


Ответы (1)


Вы должны попытаться загрузить файл кусками. Это пример, который работает для меня с использованием urllib2

import os
import urllib2
import math

def downloadChunks(url):
    """Helper to download large files
        the only arg is a url
       this file will go to a temp directory
       the file will also be downloaded
       in chunks and print out how much remains
    """

    baseFile = os.path.basename(url)

    #move the file to a more uniq path
    os.umask(0002)
    temp_path = "/tmp/"
    try:
        file = os.path.join(temp_path,baseFile)

        req = urllib2.urlopen(url)
        total_size = int(req.info().getheader('Content-Length').strip())
        downloaded = 0
        CHUNK = 256 * 10240
        with open(file, 'wb') as fp:
            while True:
                chunk = req.read(CHUNK)
                downloaded += len(chunk)
                print math.floor( (downloaded / total_size) * 100 )
                if not chunk: break
                fp.write(chunk)
    except urllib2.HTTPError, e:
        print "HTTP Error:",e.code , url
        return False
    except urllib2.URLError, e:
        print "URL Error:",e.reason , url
        return False

    return file
person Gourneau    schedule 04.12.2011