Создайте Zipfile в Python в памяти на веб-сервере

Я работаю над редактором HTML WYSIWYG, и в настоящее время я работаю над функцией «Загрузить», где пользователь может нажать кнопку «Загрузить», чтобы загрузить zip-файл своей темы. Я использую скрипт Python CGI для реализации этой функции. В настоящее время мой скрипт создает zip-файл и предлагает пользователю загрузить его, но когда я пытаюсь распаковать zip-файл, он создает только другой zip-файл с расширением «.cpgz». Я считаю, что мой скрипт неправильно создал zip.

Я использую модуль «zipfile» для создания объекта zipfile в памяти, а не на диске, модуль «StringIO» для создания файлового объекта в памяти и модуль «cgi» для приема данных POST из запроса Ajax.

Моя проблема в моем цикле for. ZIP-файл «zf» не добавляет файлы и подкаталоги из параметра «layoutDir», который я передал в os.walk(). Сценарий предложит браузеру загрузить zip-файл, но я не могу его распаковать.

#!/usr/bin/python

import sys
import os
import zipfile
import StringIO
import cgitb

cgitb.enable()

layoutDir = 'http://localhost:8888/funWYSIWYG/public/views/layouts/Marketing'

tmpZip = StringIO.StringIO() 
zf = zipfile.ZipFile(tmpZip, 'w', zipfile.ZIP_DEFLATED)

for root, dirs, files in os.walk(layoutDir):
    for name in files:
        absfn = os.path.join(root, name)
        relfn = absfn[len(layoutDir) + len(os.sep):]
        zf.write(absfn, relfn)

zf.close()

sys.stdout.write("Content-Type: application/octet-stream\r\n")
sys.stdout.write("Content-Disposition: attachment; filename=\"funWYSIWYG-Marketing.zip\"\r\n\r\n")

sys.stdout.write(tmpZip.getvalue())

# Close opened file
tmpZip.close()

ОБНОВЛЕНИЕ 1. Я избавился от некоторых ненужных вещей, которые были в моем коде. Я также исправляю опечатку с помощью «absfn» и «adsfn». Приведенный выше код теперь представляет именно то, что у меня есть в моем локальном редакторе кода. У меня все та же проблема: я не могу распаковать созданный zip-файл.

ОБНОВЛЕНИЕ 2. Вот как выглядит каталог «Маркетинг» на моем компьютере.

|---- Marketing
      |---- css
      |    |---- default.css
      | 
      |---- img
      |
      |---- index.html

person Rashad    schedule 06.01.2014    source источник
comment
В качестве примечания: os.path.relpath — это гораздо лучший способ получить относительный путь, чем пытаться определить длину префикса и отрезать его вручную.   -  person abarnert    schedule 06.01.2014
comment
Кроме того, \r\n\n почти никогда не является тем, что вам нужно в любом контексте. Если вам нужно отправить CRLF, вам нужно \r\n\r\n. Если вы этого не сделаете, вы просто хотите \n\n. Я не могу вспомнить ни одного случая, когда вам нужны какие-то CRLF и какие-то простые LF.   -  person abarnert    schedule 06.01.2014
comment
Наконец, это ваш настоящий код? Если это так, ваша проблема заключается в простой опечатке: adsfn вызовет NameError, потому что такой переменной нет. Если нет, предоставьте нам SSCCE, который действительно работает и демонстрирует проблему. (Другая часть SSCCE заключается в удалении ненужного материала. Если вы не думаете, что материал CGI связан с проблемой, дайте нам более простую программу, которая просто создает zip-файл и, скажем, сохраняет его в локальный файл.)   -  person abarnert    schedule 06.01.2014


Ответы (1)


Если это ваш реальный код, ваша проблема - простая опечатка:

zf.write(adsfn, relfn)

У вас нет переменной с именем adsfn, у вас есть переменная с именем absfn. Так что это поднимет NameError и ничего не вернет.

Если я это исправлю, то запустите этот код с layoutDir, установленным на разумный относительный путь с некоторой иерархией в нем, и сохраните полученный zip-файл в памяти на диск следующим образом:

with open('foo.zip', 'wb') as f:
    f.write(tmpZip.getvalue())

… тогда я получаю zip-файл со всеми файлами, хранящимися в нем правильно, что означает, что проблем нет вообще.

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


Похоже, ваша реальная проблема заключается в том, что в вашем реальном коде вы пытаетесь использовать абсолютный или относительный URL, например http://localhost:8888/path-to/Marketing, в качестве layoutDir. URL-адреса и пути — это не одно и то же. Если вы попытаетесь использовать его как путь, он будет фактически таким же, как ./http:/localhost:8888/path-to-Marketing. У вас почти наверняка нет каталога с именем http: в текущем рабочем каталоге, поэтому os.walk просто ничего не даст, а это означает, что вы в конечном итоге создадите пустой zip-файл.

Если файлы, которые вы пытаетесь добавить, на самом деле доступны по некоторому (относительному или абсолютному) пути, используйте этот путь вместо URL-адреса, и ваша проблема исчезнет.

Если они доступны только через HTTP, то то, что вы пытаетесь сделать, невозможно; нет возможности обойти «подкаталоги» URL-адреса HTTP; концепция даже не имеет смысла. Во многих случаях, конечно, веб-серверы сопоставляют части пути URL с некоторым путем файловой системы и предоставляют некоторый способ косвенной навигации по этой файловой системе (например, имея возможность автоматически генерировать страницу index.html, полную ссылок), но чтобы воспользоваться этим, вам нужно точно знать, как рассматриваемый сервер предоставляет эту информацию, а затем написать код очистки, чтобы воспользоваться этим. И затем, даже если у вас собраны все ссылки, вы не сможете передать URL-адрес zipfile.write, только файл. Это означает, что вы должны загрузить каждый URL-адрес (например, прочитать его в память, а затем writestr результат).

person abarnert    schedule 06.01.2014
comment
Не могли бы вы показать мне, какой относительный путь вы использовали? Я добавил код, который вы только что написали, в конец моего скрипта, но он создал zip-файл на сервере, который я до сих пор не могу распаковать. - person Rashad; 06.01.2014
comment
Относительный путь, который я использовал, был foo. Я создал каталог с именем foo вместе со сценарием, добавил в него каталог с именем bar и добавил однострочные текстовые файлы с именами spam и eggs к foo и foo/bar. Полученный foo.zip представляет собой zip-файл, который воспроизводит эту структуру (при расширении с помощью Python или стандартного InfoZip unzip 5.52). - person abarnert; 06.01.2014
comment
Когда я экспериментировал с вашим методом, я думаю, что обнаружил, в чем настоящая проблема. Я изменил layoutDir на относительный путь, отражающий то, что находится на моем локальном диске, и скрипт работает. Он создает zip-файл содержимого папки Marketing, и я могу его распаковать. Однако я пытаюсь имитировать это же действие на веб-сервере. Когда я меняю layoutDir на относительный путь на моем веб-сервере или на абсолютный путь в http://localhost:8888/path-to/Marketing, это не работает. - person Rashad; 07.01.2014
comment
@Rashad: http://localhost:8888/path-to/Marketing - это не путь, это URL. Так что, если это то, что вы делаете, это ваша проблема. Позвольте мне отредактировать ответ. - person abarnert; 07.01.2014
comment
Я не думаю, что zipfile.write(absfn, relfn) работает, если absfn является абсолютным путем HTTP или относительным путем на веб-сервере к каталогу Marketing. - person Rashad; 07.01.2014
comment
Относительный путь от моего сценария к каталогу Marketing на моем веб-сервере: ../funWYSIWYG/public/views/layouts/Marketing/. На моем локальном диске это: ../htdocs/funWYSIWYG/public/views/layouts/Marketing/. - person Rashad; 07.01.2014
comment
@Rashad: Это именно то, что я только что объяснил. zipfile.write не будет работать с URL-адресом, как и os.walk в первую очередь. Вам нужны пути файловой системы, а не URL-адреса HTTP. - person abarnert; 07.01.2014
comment
Ты прав. Я начал экспериментировать с urllib и теперь собираюсь получить файлы с моего веб-сервера. Я использую zipfile.writestr() вместо zipfile.write, чтобы записать содержимое ответа. Это решило проблему. Мой WYSIWYIG теперь может редактировать тему HTML и предлагать браузеру загрузить ее в zip-архив с помощью заголовков HTTP. Хотя я еще не закончил, мне все еще нужно выяснить, как перемещаться по каталогам на моем веб-сервере так же, как я делал это в своей файловой системе os.walk(). Возможно, мне понадобится что-то вроде wget или BeautifulSoup. Спасибо за помощь. - person Rashad; 07.01.2014
comment
@Rashad: Как я объяснил в своем ответе, обычно нет возможности просматривать каталоги на веб-сервере. На некоторых веб-сайтах может быть способ сделать это, но он будет специфичен для этого сайта, и вам придется выяснить, как его очистить. И если это ваш веб-сайт, которым вы управляете, это глупо — просто раскрывайте информацию напрямую, чтобы вам не приходилось ее очищать. - person abarnert; 07.01.2014