Выгрузка файлов клиента на сервер

vygruzka fajlov klienta na server Сценарии на стороне сервера

Сценарий getfile позволяет клиенту просматривать файлы, находящиеся на сервере, но в некотором смысле он может считаться инструментом общего назначения для загрузки файлов с сервера. Хотя и не так прямо, как при получении файлов по FTP или непосредственно через сокеты, но он служит аналогичным целям. Пользователи сценария могут копировать отображаемый код с веб-страницы или через пункт меню броузера View Source (Исходный код страницы или Просмотр HTML-кода).

Как уже описывалось выше, сценарии, контактируя со сценарием get- file с помощью модуля urllib, также способны извлекать содержимое файлов с помощью модуля Python, реализующего синтаксический анализ HTML.

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

В главе 13 мы видели, что выгрузка достаточно просто осуществляется сценарием, выполняемым на стороне клиента и использующим модуль Python поддержки FTP. Однако такое решение не применимо в контексте веб-броузера — вряд ли вы станете предлагать всем клиентам программы запустить сценарий Python для FTP в другом окне, чтобы отправить файл. Кроме того, нет простого способа явно запросить отправку файла в сценарии на стороне сервера, если только на компьютере клиента не работает сервер FTP (что случается отнюдь не часто). Пользователи могут посылать файлы по почте отдельно, но это может быть неудобно, особенно в случае вложений в электронные письма.

Так есть ли способ написать веб-приложение, которое разрешает своим пользователям отправлять файлы на общий сервер? На самом деле есть, хотя он имеет большее отношение к HTML, чем собственно к Python. Теги HTML <input> поддерживают тип type=file, с помощью которого создается поле ввода вместе с кнопкой, щелчок на которой выводит диалог выбора файла. Имя файла на компьютере клиента, который нужно выгрузить на сервер, можно ввести в это поле или выбрать с помощью диалога. Чтобы продемонстрировать это, в примере 15.29 приводится файл страницы HTML, которая позволяет выбрать любой файл на стороне клиента и выгрузить его с помощью серверного сценария, имя которого указано в параметре action формы.

Пример 15.29. PP4E\Internet\Web\putfile.html

<html><title>Putfile: upload page</title>

<body>

<form enctype="multipart/form-data" method=post action="cgi-bin/putfile.py"> <h1>Select client file to be uploaded</h1> <p><input type=file size=50 name=clientfile> <p><input type=submit value=Upload>

</form>

<hr><a href="cgi-bin/getfile.py?filename=cgi-bin\putfile.py">View script code</a>

</body></html>

Обратите внимание на одно ограничение: формы, использующие поля ввода с типом file, должны указывать тип кодировки multipart/form-da- ta и метод передачи post, как и сделано в этом файле. Адреса URL в стиле get не годятся для выгрузки файлов на сервер. При обращении к этому файлу HTML отображается страница, изображенная на рис. 15.33. Нажатие кнопки Browse (Обзор) открывает диалог выбора файла, а нажатие кнопки Upload (Выгрузить) — отсылает выбранный файл.

ЛГ Putfile: upload page Windows Internet Explorer i_ | И |м^|

@©’l g] http://localho5Vputfile.html | Ц | | X |1 ?*1| Google P |

Favorites | @ Putfile: upload page

Select client file to be uploaded

C:\Python31\LibtagLpy [ Browse ]

Upload |

View script code

0 Internet | Protected Mode: On 125% r /jl

Рис. 15.33. Страница выбора файла для отправки

Когда на стороне клиента на этой странице нажимается кнопка Upload (Выгрузить), броузер открывает выбранный файл, читает его и упаковывает его содержимое вместе с остальными полями ввода формы (если они есть). Когда эти данные попадают на сервер, выполняется сценарий Python, указанный в параметре action формы и представленный в примере 15.30.

Пример 15.30. PP4E\Internet\Web\cgi-bin\putfile.py

#!/usr/bin/python

############################################################################ извлекает файл, выгруженный веб-броузером по протоколу HTTP; пользователи открывают страницу putfile.html, чтобы получить страницу с формой выгрузки, которая затем запускает этот сценарий на сервере; способ очень мощный

и очень опасный: обычно желательно проверять имя файла и так далее; выгрузка возможна, только если файл или каталог доступен для записи: команды Unixchmod 777 uploadsможет оказаться достаточно; путь к файлу поступает в формате пути на стороне клиента: его требуется обработать здесь;

предупреждение: выходной файл можно было бы открыть в текстовом режиме, чтобы подставить символы конца строки, используемые на принимающей платформе, поскольку содержимое файла всегда возвращается модулем cgi

в виде строки str, но это временное решение — модуль cgi в 3.1 вообще не поддерживает выгрузку двоичных файлов;

############################################################################

подпись: import cgi, os, sys
import posixpath, ntpath, macpath
debugmode = false loadtextauto = false uploaddir = './uploads'
sys.stderr = sys.stdout
form = cgi.fieldstorage()
print("content-type: text/html\n") if debugmode: cgi.print_form(form)
подпись: # для обработки клиентских путей
# тгие=вывод данных формы
# тгие=читать файл целиком
# каталог для сохранения файлов
# для вывода ошибок
# выполнить анализ данных формы
# с пустой строкой
# вывести поля формы


<p>Your file, '%s', has been saved on the server as '%s'.

<p>An echo of the file's contents received and saved appears below.

</p><hr>

<p><pre>%s</pre>

</p><hr>

подпись: # шаблоны html
подпись: html = """
<html><title>putfile response page</title>
<body>
<h1>putfile response page</h1> %s
</body></html>"""
goodhtml = html % """
<p>your file, '%s', has been saved on the server as '%s'.
<p>an echo of the file's contents received and saved appears below.
</p><hr>
<p><pre>%s</pre>
</p><hr>
подпись: # обработать данные формы
подпись: def splitpath(origpath): # получить имя файла без пути
for pathmodule in [posixpath, ntpath, macpath]: # проверить все типы basename = pathmodule.split(origpath)[1] # сервер может быть любой if basename != origpath:
return basename # пробелы допустимы
return origpath # неудача или нет каталога
def saveonserver(fileinfo): # поле с именем файла data
basename = splitpath(fileinfo.filename) # имя без пути
srvrname = os.path.join(uploaddir, basename) # в каталог, если указан
srvrfile = open(srvrname, 'wb') # всегда в двоичном режиме
if loadtextauto:
filetext = fileinfo.value # прочитать текст в строку
if isinstance(filetext, str): # прием для python 3.1
filedata = filetext.encode()




def main():

if not ‘clientfile’ in form:

print(html % ‘Error: no file was received’)

elif not form[‘clientfile’].filename:

print(html % ‘Error: filename is missing’) else:

fileinfo = form[‘clientfile’] try:

filetext, srvrname = saveonserver(fileinfo) except:

errmsg = ‘<h2>Error</h2><p>%s<p>%s’ % tuple(sys.exc_info()[:2]) print(html % errmsg)

else:

print(goodhtml % (cgi.escape(fileinfo.filename), cgi.escape(srvrname), cgi.escape(filetext)))

main()

В этом сценарии для обработки выгружаемых на сервер файлов применены интерфейсы, свойственные Python. На самом деле в этом нет ничего нового: файл поступает сценарию в виде записи в объекте, возвращаемом конструктором cgi.FieldStorage в результате анализа формы, как обычно; ключом является clientfile, определяемый значением атрибута name поля ввода в странице HTML.

Однако на этот раз запись имеет дополнительные атрибуты в виде имени этого файла на стороне клиента. Кроме того, при обращении к атрибуту value входного объекта выгруженного файла содержимое файла автоматически целиком считывается в строку на сервере. Если файл очень большой, его можно читать построчно (или блоками байтов заданного размера). Внутренняя реализация модуля Python cgi автоматически сохраняет выгруженный файл во временном файле — наш сценарий просто выполняет чтение этого временного файла. Однако, если файл слишком велик, он может не уместиться целиком в памяти.

Для иллюстрации сценарий реализует обе схемы: в зависимости от значения глобальной переменной loadtextauto он либо запрашивает содержимое файла как строку, либо читает его построчно. Вообще говоря, для полей выгружаемых файлов модуль CGI возвращает объекты со следующими атрибутами:

filename

Имя файла, как оно указано на стороне клиента

file

Объект файла, из которого может быть прочитано содержимое файла, выгруженного на сервер

value

Содержимое выгруженного файла (читаемое из атрибута file)

Существуют дополнительные атрибуты, не используемые в нашем сценарии. Файлы представляют собой третий тип объектов полей ввода. Мы уже видели, что атрибут value является стро кой для простых полей ввода, а для элементов с множественным выбором может быть получен спи сок объектов.

Для сохранения выгруженных файлов на сервере сценарии CGI (выполняемые с привилегиями пользователя «nobody») должны иметь право записи в каталог, если файл еще не существует, или в сам файл, если он уже имеется. Чтобы изолировать выгружаемые файлы, сценарий записывает их в тот каталог, который указан в глобальной переменной uploaddir. На сервере Linux моего сайта я должен был с помощью команды chmod установить для этого каталога биты разрешений 777 (все права чтения/записи/выполнения), чтобы заставить загрузку работать вообще. Для локального веб-сервера, используемого в этой главе, это не является проблемой, но у вас может быть иная ситуация, поэтому, если данный сценарий откажется работать, проверьте права доступа.

В этом сценарии также вызывается функция os.chmod, чтобы установить для файла на сервере права доступа, в соответствии с которыми его чтение и запись могут осуществлять все пользователи. Если при выгрузке файл создается заново, его владельцем станет пользователь «no- body», что означает возможность просмотра файла и загрузки на сервер для всех пользователей в киберпространстве. Однако на моем сервере Linux этот файл будет также по умолчанию доступен для записи только пользователю «nobody», что может оказаться неудобным, когда потребуется изменить этот файл непосредственно на сервере (степень неудобства может различаться в зависимости от операций).

Изоляция файлов, выгружаемых клиентами, путем помещения их в отдельный каталог на сервере способствует уменьшению угрозы безопасности: исключается возможность произвольного затирания существующих файлов. Но при этом может потребоваться копирование файлов на сервере после их выгрузки и не устраняются все угрозы безопасности — злонамеренные клиенты сохраняют возможность выгрузки очень больших файлов; для отлавливания этого нужна дополнительная логика, отсутствующая в данном сценарии. В целом, такие ловушки могут понадобиться только в сценариях, открытых для Интернета.

Если клиент и сервер оба выполнят свои роли, то сценарий CGI, после того как сохранит содержимое файла клиента в новом или существующем уже файле на сервере, вернет страницу, изображенную на рис. 15.34. Для контроля в странице ответа указываются пути к файлу на стороне клиента и сервера, а также повторяется содержимое выгруженного файла вместе со счетчиком строк, если чтение производилось в построчном режиме.

Рис. 15.34. Ответная страница сценария putfile

 

Обратите внимание, что вывод файла в странице ответа предполагает, что его содержимым является текст. Как оказывается, это вполне безопасное предположение, потому что модуль cgi всегда возвращает содержимое файла в виде строки str, а не bytes. Это проистекает из несколько неприятного факта, что в версии 3.1 модуль cgi вообще не поддерживает выгрузку двоичных файлов (подробнее об этом ограничении рассказывается во врезке ниже).

Этот файл, выгруженный и сохраненный в отдельном каталоге, идентичен оригинальному файлу (выполните команду fc в Windows, чтобы убедиться в этом). Кстати, проверить выгруженный файл можно также с помощью программы getfile, написанной в предыдущем разделе.

Просто зайдите на страницу выбора файла и введите путь и имя файла на сервере, как показано на рис. 15.35.

Рис. 15.35. Проверка результатов работы putfile с помощью getfile — выбор файла

 

Если выгрузка файла на сервер прошла успешно, то будет получена результирующая страница, изображенная на рис. 15.36. Поскольку пользователь «nobody» (сценарии CGI) смог записать файл, «nobody» должен быть в состоянии и прочесть его.

Рис. 15.36. Проверка результатов работы putfile с помощью getfile — ответ

Обратите внимание на адрес URL в адресной строке броузера для этой страницы: броузер преобразовал символ /, введенный на странице выбора файла, в шестнадцатеричную экранированную последовательность %2F перед тем, как поместить его в конец URL в качестве параметра. С экранированными последовательностями URL мы познакомились выше в этой главе. В данном случае преобразование осуществил броузер, но конечный результат получился таким же, как если бы мы вручную применили к строке пути одну из функций экранирования из модуля urllib.parse.

Технически экранированная последовательность %2F представляет здесь стандартное преобразование URL для символов, не входящих в диапазон ASCII, в стандартной схеме кодирования, используемой броузерами. Пробелы обычно также транслируются в символы +. Часто можно обойтись без преобразования вручную большинства символов, не входящих в диапазон ASCII, при явной отправке путей (во вводимых адресах URL). Но как мы видели раньше, иногда нужно следить за тем, чтобы экранировать символы, имеющие в строках URL особое значение (например, &), с помощью инструментов в модуле urllib.parse.

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий