Как развернуть zip-файлы (или другие двоичные файлы) через cgi в Python?

Я кодирую небольшой веб-сайт с Python и CGI, где пользователи могут загружать zip-файлы и загружать файлы, загруженные другими пользователями. В настоящее время я могу правильно загружать zip-файлы, но у меня возникают проблемы с правильной отправкой файлов пользователю. Мой первый подход был:

file = open('../../data/code/' + filename + '.zip','rb')

print("Content-type: application/octet-stream")
print("Content-Disposition: filename=%s.zip" %(filename))
print(file.read())

file.close()

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

print("Content-type: application/octet-stream")
print("Content-Disposition: filename=%s.zip" %(filename))
print('Content-transfer-encoding: base64\r')
print( base64.b64encode(file.read()).decode(encoding='UTF-8') )

И разные его варианты. Это просто не работает; Apache вызывает ошибку «неверный заголовок из сценария», поэтому я думаю, что мне следует кодировать файл каким-то другим способом.


person dvilela    schedule 20.08.2013    source источник


Ответы (3)


Вам нужно напечатать пустую строку после заголовков, а в заголовке Content-disposition отсутствует тип (attachment):

print("Content-type: application/octet-stream")
print("Content-Disposition: attachment; filename=%s.zip" %(filename))
print()

Вы также можете использовать более эффективный метод загрузки результирующего файла; используйте shutil.copyfileobj(), чтобы скопировать данные в sys.stdout.buffer:

from shutil import copyfileobj
import sys

print("Content-type: application/octet-stream")
print("Content-Disposition: attachment; filename=%s.zip" %(filename))
print()

with open('../../data/code/' + filename + '.zip','rb') as zipfile:
    copyfileobj(zipfile, sys.stdout.buffer)

Вы ни в коем случае не должны использовать print() для двоичных данных; все, что вы получаете, это b'...' байтовый литеральный синтаксис. Объект sys.stdout.buffer является базовым буфером двоичного ввода-вывода, скопируйте двоичные данные непосредственно в него.

person Martijn Pieters    schedule 20.08.2013
comment
Я использую Python 2.7, что может иметь значение, но чтобы заставить этот код работать, мне пришлось изменить последнюю строку copyfileobj(zipfile, sys.stdout). - person Daniel Griscom; 19.07.2015
comment
@DanielGriscom: да, это совершенно правильно; мой ответ был написан для Python 3, где библиотека io заботится обо всех операциях ввода-вывода, а sys.stdout.buffer предоставляет доступ к базовому io.BufferedIOBase реализация, а не (кодировка) io.TextIOBase; в Python 2 файловый объект sys.stdout напрямую принимает закодированные байты. - person Martijn Pieters; 20.07.2015
comment
Работая в Python 2, мне пришлось удалить print() в третьей строке, а также изменить последнюю строку на copyfileobj(zipfile, sys.stdout), как предложил @DanielGriscom выше. - person eeScott; 19.01.2020
comment
@eeScott да, потому что этот вопрос специально помечен python-3.x. В Python 2 sys.stdout — это простой файловый объект Python 2, а не объект io.TextIOWrapper(). - person Martijn Pieters; 19.01.2020
comment
Согласен, я просто хотел задокументировать это для следующего человека, потому что не так много людей с этим вопросом, адаптированным к Python 2... - person eeScott; 19.01.2020

Заголовок имеет неправильный формат, потому что по какой-то причине Python отправляет его после отправки файла.

Что вам нужно сделать, это очистить стандартный вывод сразу после заголовка:

sys.stdout.flush()

Затем поместите копию файла

person Alecz    schedule 16.06.2014
comment
Не уверен, что это хороший ответ, но может быть полезно объяснить немного больше. - person Aaron Hall; 16.06.2014

Это то, что сработало для меня, я запускаю Apache2 и загружаю этот скрипт через cgi. Python 3 — мой язык.

Возможно, вам придется заменить первую строку на путь к bin python 3.

#!/usr/bin/python3
import cgitb
import cgi
from zipfile import ZipFile
import sys

# Files to include in archive
source_file = ["/tmp/file1.txt", "/tmp/file2.txt"]

# Name and Path to our zip file.
zip_name = "zipfiles.zip"
zip_path = "/tmp/{}".format(zip_name)

with ZipFile( zip_path,'w' ) as zipFile:
    for f in source_file:
        zipFile.write(f);

# Closing File.
zipFile.close()

# Setting Proper Header.
print ( 'Content-Type:application/octet-stream; name="{}"'.format(zip_name) );
print ( 'Content-Disposition:attachment; filename="{}"\r\n'.format(zip_name) );

# Flushing Out stdout.
sys.stdout.flush()

bstdout = open(sys.stdout.fileno(), 'wb', closefd=False)
file = open(zip_path,'rb')
bstdout.write(file.read())
bstdout.flush()
person Community    schedule 21.11.2016