cgi — Поддержка Common Gateway Interface
Исходный код: Lib/cgi.py
Модуль поддержки скриптов Common Gateway Interface (CGI).
Этот модуль определяет ряд утилит для использования скриптами CGI, написанными на Python.
1. Введение
Скрипт CGI вызывается сервером HTTP, обычно для обработки пользовательского ввода, отправленного через элемент HTML <FORM> или <ISINDEX>.
Чаще всего скрипты CGI находятся в специальном cgi-bin каталоге сервера. Сервер HTTP размещает всю информацию о запросе (например, имя хоста клиента, запрашиваемый URL, строку запроса и многое другое) в среде выполнения скрипта, выполняет скрипт и отправляет результат скрипта обратно клиенту.
Входные данные скрипта также связаны с клиентом, и иногда данные формы считываются таким образом; в других случаях данные формы передаются через часть URL-адреса «строка запроса». Этот модуль призван обрабатывать различные случаи и предоставить более простой интерфейс для скрипта Python. Он также предоставляет ряд утилит, которые помогают в отладке скриптов, и последним дополнением является поддержка загрузки файлов из формы (если ваш браузер ее поддерживает).
Вывод скрипта CGI должен состоять из двух разделов, разделенных пустой строкой. Первый раздел содержит ряд заголовков, сообщая клиенту, какой тип данных следует за ними. Python-код для генерации минимального раздела заголовков выглядит так:
print "Content-Type: text/html" # HTML is following print # blank line, end of headers
Второй раздел, как правило, содержит HTML, который позволяет программному обеспечению клиента красиво отображать отформатированный текст с заголовками, встроенными изображениями и т. д. Вот Python-код, который печатает простой фрагмент HTML:
print "<TITLE>CGI script output</TITLE>" print "<H1>This is my first CGI script</H1>" print "Hello, world!"
2. Использование модуля cgi
Начните с написания import cgi. Не используйте from cgi import * — модуль определяет множество имён для собственного использования или обратной совместимости, которых вы не хотите в своём пространстве имён.
При написании нового скрипта рассмотрите возможность добавления этих строк:
import cgitb cgitb.enable()
Это активирует специальный обработчик исключений, который отобразит подробные отчеты в веб-браузере, если возникнут какие-либо ошибки. Если вы не хотите показывать содержимое вашей программы пользователям вашего скрипта, вы можете сохранить отчеты в файлы вместо этого, используя код подобный этому:
import cgitb cgitb.enable(display=0, logdir="/path/to/logdir")
Это очень полезно во время разработки скрипта. Отчеты, созданные cgitb, предоставляют информацию, которая может сэкономить вам много времени при поиске ошибок. Вы всегда можете удалить строку cgitb позже, когда протестируете свой скрипт и убедитесь, что он работает правильно.
Для доступа к отправленным данным формы лучше всего использовать класс FieldStorage. Другие классы, определённые в этом модуле, предоставляются в основном для обеспечения обратной совместимости. Инициализируйте его ровно один раз без аргументов. Это считывает содержимое формы из стандартного ввода или из среды (в зависимости от значения различных переменных среды, установленных в соответствии со стандартом CGI). Поскольку он может использовать стандартный ввод, его следует инициализировать только один раз.
Объект FieldStorage может быть индексирован как словарь Python. Он допускает проверку принадлежности с оператором in, а также поддерживает стандартные методы словаря keys() и встроенную функцию len(). Поля формы, содержащие пустые строки, игнорируются и не отображаются в словаре; чтобы сохранить такие значения, при создании экземпляра FieldStorage укажите истинное значение для необязательного ключевого параметра keep_blank_values.
Например, следующий код (который предполагает, что заголовок Content-Type и пустая строка уже напечатаны) проверяет, что поля name и addr оба установлены в непустую строку:
form = cgi.FieldStorage()
if "name" not in form or "addr" not in form:
print "<H1>Error</H1>"
print "Please fill in the name and addr fields."
return
print "<p>name:", form["name"].value
print "<p>addr:", form["addr"].value
...further form processing here...
Здесь поля, к которым обращаются через form[key], сами являются экземплярами FieldStorage (или MiniFieldStorage, в зависимости от кодировки формы). Атрибут value экземпляра возвращает строковое значение поля. Метод getvalue() возвращает это строковое значение напрямую; он также принимает необязательный второй аргумент в качестве значения по умолчанию для возврата, если запрашиваемый ключ отсутствует.
Если отправленные данные формы содержат более одного поля с тем же именем, объект, полученный через form[key], не является экземпляром FieldStorage или MiniFieldStorage, а списком таких экземпляров. Аналогично, в этой ситуации form.getvalue(key) вернёт список строк. Если вы ожидаете эту возможность (когда ваша HTML-форма содержит несколько полей с одинаковым именем), используйте метод getlist(), который всегда возвращает список значений (чтобы вам не пришлось обрабатывать случай с одним элементом отдельно). Например, этот код конкатенирует любое количество полей username, разделенных запятыми:
value = form.getlist("username")
usernames = ",".join(value)
Если поле представляет загруженный файл, доступ к значению через атрибут value или метод getvalue() считывает весь файл в память как строку. Это может быть не то, что вам нужно. Вы можете проверить загруженный файл, проверив атрибут filename или атрибут file. Затем вы можете в удобное время читать данные из атрибута file.
fileitem = form["userfile"]
if fileitem.file:
# It's an uploaded file; count lines
linecount = 0
while 1:
line = fileitem.file.readline()
if not line: break
linecount = linecount + 1
Если при получении содержимого загруженного файла возникнет ошибка (например, когда пользователь прерывает отправку формы, нажав кнопку «Назад» или «Отмена»), атрибут done объекта для поля будет установлен в значение -1.
Черновик стандарта загрузки файлов допускает возможность загрузки нескольких файлов из одного поля (используя рекурсивное кодирование multipart/*). В этом случае элемент будет похож на словарь FieldStorage . Это можно определить, проверив его атрибут type, который должен быть multipart/form-data (или, возможно, другой MIME-тип, соответствующий multipart/*). В этом случае им можно рекурсивно итерироваться, как и по верхнему объекту формы.
Когда форма отправляется в «старом» формате (как строка запроса или как отдельный элемент данных типа application/x-www-form-urlencoded), элементы фактически являются экземплярами класса MiniFieldStorage. В этом случае атрибуты list, file, и filename всегда None.
Форма, отправленная через POST, которая также содержит строку запроса, будет содержать как FieldStorage , так и MiniFieldStorage элементы.
3. Интерфейс более высокого уровня
Новое в версии 2.2.
Предыдущий раздел объясняет, как читать данные CGI-формы с использованием класса FieldStorage . Этот раздел описывает интерфейс более высокого уровня, который был добавлен к этому классу, чтобы позволить делать это более читаемым и интуитивно понятным способом. Интерфейс не делает методы предыдущих разделов устаревшими — они по-прежнему полезны для эффективной обработки загрузок файлов, например.
Интерфейс состоит из двух простых методов. С помощью этих методов вы можете обрабатывать данные формы обобщённым образом, не беспокоясь о том, было отправлено одно или несколько значений под одним именем.
В предыдущем разделе вы узнали, как писать следующий код всякий раз, когда ожидаете, что пользователь отправит более одного значения под одним именем:
item = form.getvalue("item")
if isinstance(item, list):
# The user is requesting more than one item.
else:
# The user is requesting only one item.
Эта ситуация распространена, например, когда форма содержит группу нескольких флажков с одинаковым именем:
<input type="checkbox" name="item" value="1" /> <input type="checkbox" name="item" value="2" />
Однако в большинстве случаев в форме имеется только один элемент управления с определенным именем, и вы ожидаете и нуждаетесь только в одном значении, связанном с этим именем. Поэтому вы пишете скрипт, содержащий, например, этот код:
user = form.getvalue("user").upper()
Проблема с кодом заключается в том, что вы никогда не должны ожидать, что клиент предоставит валидный ввод для ваших скриптов. Например, если любопытный пользователь добавит ещё одну пару user=foo в строку запроса, скрипт выйдет из строя, потому что в этой ситуации вызов метода getvalue("user") возвращает список вместо строки. Вызов метода upper() над списком недействителен (поскольку у списков нет метода с таким именем) и приводит к исключению AttributeError.
Поэтому правильный способ чтения значений данных формы — всегда использовать код, который проверяет, является ли полученное значение единственным значением или списком значений. Это утомительно и приводит к менее удобочитаемым скриптам.
Более удобный подход — использовать методы getfirst() и getlist(), предоставленные этим интерфейсом более высокого уровня.
-
FieldStorage.getfirst(name[, default]) -
Этот метод всегда возвращает только одно значение, связанное с полем формы name. Метод возвращает только первое значение в случае, если было отправлено несколько значений под таким именем. Обратите внимание, что порядок получения значений может отличаться в разных браузерах и не должен учитываться. 1 Если такое поле формы или значение не существует, метод возвращает значение, указанное в необязательном параметре default. Этот параметр по умолчанию равен
None, если не указан.
-
FieldStorage.getlist(name) -
Этот метод всегда возвращает список значений, связанных с полем формы name. Метод возвращает пустой список, если для name не существует такого поля формы или значения. Он возвращает список, состоящий из одного элемента, если существует только одно такое значение.
С помощью этих методов вы можете написать компактный и понятный код:
import cgi
form = cgi.FieldStorage()
user = form.getfirst("user", "").upper() # This way it's safe.
for item in form.getlist("item"):
do_something(item)
4. Старые классы
Устарело начиная с версии 2.6: Эти классы, присутствующие в более ранних версиях модуля cgi, всё ещё поддерживаются для обратной совместимости. Новые приложения должны использовать класс FieldStorage.
SvFormContentDict хранит содержимое формы с одним значением в виде словаря; предполагается, что каждое имя поля встречается в форме только один раз.
FormContentDict хранит содержимое формы с несколькими значениями в виде словаря (элементы формы — списки значений). Полезно, если ваша форма содержит несколько полей с одинаковым именем.
Другие классы (FormContent, InterpFormContentDict) присутствуют только для обратной совместимости с очень старыми приложениями.
5. Функции
Эти функции полезны, если вы хотите иметь больший контроль или использовать некоторые алгоритмы, реализованные в этом модуле, в других обстоятельствах.
-
cgi.parse(fp[, environ[, keep_blank_values[, strict_parsing]]]) -
Разбирает запрос из среды или файла (по умолчанию файл —
sys.stdin, а среда —os.environ). Параметры keep_blank_values и strict_parsing передаются вurlparse.parse_qs()без изменений.
-
cgi.parse_qs(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]]) -
Эта функция устарела в этом модуле. Используйте
urlparse.parse_qs()вместо неё. Она поддерживается только для обратной совместимости.
-
cgi.parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]]) -
Эта функция устарела в этом модуле. Используйте
urlparse.parse_qsl()вместо неё. Она поддерживается только для обратной совместимости.
-
cgi.parse_multipart(fp, pdict) -
Разбирает входные данные типа multipart/form-data (для загрузки файлов). Аргументы — fp для входного файла и pdict для словаря, содержащего другие параметры в заголовке Content-Type.
Возвращает словарь, аналогичный
urlparse.parse_qs(), где ключи — имена полей, а каждое значение — список значений для этого поля. Это легко использовать, но не очень хорошо, если вы ожидаете загрузки мегабайтов — в этом случае используйте классFieldStorage, который намного гибче.Обратите внимание, что это не разбирает вложенные части multipart — используйте
FieldStorageдля этого.
-
cgi.parse_header(string) -
Разбирает MIME-заголовок (например, Content-Type) в основное значение и словарь параметров.
-
cgi.test() -
Надежный тестовый скрипт CGI, который можно использовать в качестве основной программы. Записывает минимальные HTTP-заголовки и форматирует всю информацию, предоставленную скрипту, в форме HTML.
-
cgi.print_environ() -
Форматирует среду оболочки в HTML.
-
cgi.print_form(form) -
Форматирует форму в HTML.
-
cgi.print_directory() -
Форматирует текущую директорию в HTML.
-
cgi.print_environ_usage() -
Выводит список полезных (используемых CGI) переменных среды в HTML.
-
cgi.escape(s[, quote]) -
Преобразует символы
'&','<'и'>'в строке s в безопасные для HTML последовательности. Используйте эту функцию, если вам нужно отобразить текст, который может содержать такие символы в HTML. Если необязательный флаг quote имеет значение True, символ кавычки (") также преобразуется; это полезно для включения в значение атрибута HTML, ограниченного двойными кавычками, как в<a href="...">. Обратите внимание, что одинарные кавычки никогда не переводятся.Если нужно закодировать значение, которое может содержать одинарные или двойные кавычки, или оба, используйте функцию
quoteattr()из модуляxml.sax.saxutilsвместо этого.
6. Обеспечение безопасности
Существует одно важное правило: если вы вызываете внешнюю программу (через функции os.system() или os.popen(), или другие с аналогичной функциональностью), убедитесь, что вы не передаёте произвольные строки, полученные от клиента, в оболочку. Это известная уязвимость безопасности, с помощью которой хакеры могут эксплуатировать уязвимый скрипт CGI для вызова произвольных команд оболочки. Даже части URL или имён полей не заслуживают доверия, так как запрос не обязательно исходит от вашей формы!
Для большей безопасности, если вам нужно передать строку, полученную из формы, в команду оболочки, убедитесь, что эта строка содержит только буквенно-цифровые символы, дефисы, нижние подчёркивания и точки.
7. Установка скрипта CGI на системе Unix
Прочитайте документацию вашего HTTP-сервера и проконсультируйтесь с местным системным администратором, чтобы найти директорию, в которой должны быть установлены скрипты CGI; обычно это директория cgi-bin в дереве сервера.
Убедитесь, что ваш скрипт читаем и исполняем для «других»; режим файла Unix должен быть 0755 восьмеричный (используйте chmod 0755 filename). Убедитесь, что первая строка скрипта содержит #! начиная с первого столбца, за которой следует путь к интерпретатору Python, например:
#!/usr/local/bin/python
Убедитесь, что интерпретатор Python существует и исполняем для «других».
Убедитесь, что любые файлы, которые нужен вашему скрипту для чтения или записи, читаемы или записываемы соответственно для «других» — их режим должен быть 0644 для чтения и 0666 для записи. Это связано с тем, что по соображениям безопасности HTTP-сервер выполняет ваш скрипт как пользователь «nobody», без каких-либо специальных привилегий. Он может только читать (писать, выполнять) файлы, которые может читать (писать, выполнять) любой пользователь. Текущая директория во время выполнения также отличается (обычно это директория cgi-bin сервера), а набор переменных среды также отличается от того, что вы получаете при входе в систему. В частности, не полагайтесь на путь поиска оболочки для исполняемых файлов (PATH) или путь поиска модулей Python (PYTHONPATH), чтобы он был установлен на что-то интересное.
Если вам нужно загрузить модули из директории, которая не находится в стандартном пути поиска модулей Python, вы можете изменить этот путь в вашем скрипте перед импортом других модулей. Например:
import sys sys.path.insert(0, "/usr/home/joe/lib/python") sys.path.insert(0, "/usr/local/lib/python")
(Таким образом, последняя вставленная директория будет проверяться первой!)
Инструкции для систем, не являющихся Unix, будут отличаться; обратитесь к документации вашего HTTP-сервера (обычно в ней есть раздел о скриптах CGI).
8. Тестирование скрипта CGI
К сожалению, скрипт CGI обычно не будет работать, когда вы попробуете его запустить из командной строки, и скрипт, который работает безупречно из командной строки, может загадочно выйти из строя при запуске с сервера. Есть одна причина, по которой вы всё равно должны тестировать свой скрипт из командной строки: если он содержит синтаксическую ошибку, интерпретатор Python не выполнит его вообще, а HTTP-сервер, скорее всего, отправит клиенту загадочную ошибку.
Если ваш скрипт не содержит синтаксических ошибок, но он не работает, вам не остаётся ничего другого, как прочитать следующий раздел.
9. Отладка скриптов CGI
Прежде всего, проверьте на наличие тривиальных ошибок установки — внимательное прочтение раздела об установке скрипта CGI может сэкономить вам много времени. Если вы сомневаетесь, правильно ли вы поняли процедуру установки, попробуйте установить копию этого файла модуля (cgi.py) как скрипт CGI. При вызове как скрипта этот файл выведет свою среду и содержимое формы в форме HTML. Придайте ему нужный режим и т. д., и отправьте ему запрос. Если он установлен в стандартной директории cgi-bin, его можно вызвать, введя URL в браузер в формате:
http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home
Если это даёт ошибку типа 404, сервер не может найти скрипт — возможно, вам нужно установить его в другую директорию. Если он даёт другую ошибку, есть проблема с установкой, которую вы должны исправить, прежде чем пытаться идти дальше. Если вы получите красиво отформатированный список среды и содержимого формы (в этом примере поля должны быть перечислены как «addr» со значением «Дома» и «name» со значением «Иван Иванов»), скрипт cgi.py был установлен правильно. Если вы выполните ту же процедуру для собственного скрипта, вы теперь сможете отладить его.
Следующим шагом может быть вызов функции cgi модуля test() из вашего скрипта: замените его основной код одной строкой
cgi.test()
Это должно дать те же результаты, что и при установке самого файла cgi.py.
Когда обычный скрипт Python вызывает необработанное исключение (по любой причине: из-за опечатки в имени модуля, файла, который не может быть открыт и т. д.), интерпретатор Python выводит удобный трассировку стека и завершается. Хотя интерпретатор Python по-прежнему будет делать это, когда ваш скрипт CGI вызывает исключение, скорее всего, трассировка стека попадет в один из журналов HTTP-сервера или будет вообще отброшена.
К счастью, как только вы сумеете заставить ваш скрипт выполнить некоторый код, вы можете легко отправлять трассировки стека в веб-браузер, используя модуль cgitb. Если вы ещё этого не сделали, добавьте следующие строки:
import cgitb cgitb.enable()
в начало вашего скрипта. Затем попробуйте запустить его снова; при возникновении проблемы вы увидите подробный отчёт, который, вероятно, покажет причину сбоя.
Если вы подозреваете, что может быть проблема с импортом модуля cgitb, вы можете использовать ещё более надёжный подход (который использует только встроенные модули):
import sys sys.stderr = sys.stdout print "Content-Type: text/plain" print ...your code here...
Это зависит от интерпретатора Python для вывода трассировки. Тип содержимого вывода задан как обычный текст, что отключает всю обработку HTML. Если ваш скрипт работает, ваш клиент отобразит исходный HTML. Если он вызывает исключение, скорее всего, после печати первых двух строк, будет отображена трассировка. Поскольку никакой интерпретации HTML не происходит, трассировка будет читаемой.
10. Распространённые проблемы и решения
- Большинство HTTP-серверов буферизуют вывод скриптов CGI до завершения выполнения скрипта. Это означает, что невозможно отобразить отчёт о прогрессе на экране клиента во время выполнения скрипта.
- Проверьте инструкции по установке выше.
- Проверьте файлы журналов HTTP-сервера. (
tail -f logfileв отдельном окне может оказаться полезным!) - Всегда сначала проверяйте скрипт на синтаксические ошибки, например, выполняя
python script.py. - Если в вашем скрипте нет синтаксических ошибок, попробуйте добавить
import cgitb; cgitb.enable()в начало скрипта. - При вызове внешних программ убедитесь, что они доступны. Обычно это означает использование абсолютных путей —
PATHобычно не имеет полезного значения в скрипте CGI. - При чтении или записи внешних файлов убедитесь, что они доступны для чтения/записи пользователем, под которым будет выполняться ваш скрипт CGI: обычно это пользователь, под которым работает веб-сервер, или какой-либо явно указанный пользователь для функции
suexecвеб-сервера. - Не пытайтесь назначить скрипту CGI режим set-uid. Это не работает на большинстве систем и представляет собой также угрозу безопасности.
Примечания
-
1 -
Обратите внимание, что в некоторых последних версиях спецификации HTML указан порядок предоставления значений полей, но определить, получен ли запрос от совместимого браузера или вообще от браузера, утомительно и подвержено ошибкам.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/2.7/library/cgi.html