Spec-Zone.ru › Python 3.5

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.

При написании нового скрипта рассмотрите добавление этих строк:

import cgitb
cgitb.enable()

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

import cgitb
cgitb.enable(display=0, logdir="/path/to/logdir")

Это очень полезно при разработке скриптов. Отчеты, созданные cgitb, предоставляют информацию, которая может сэкономить вам много времени при поиске ошибок. Вы всегда можете удалить строку cgitb позже, когда протестируете свой скрипт и будете уверены, что он работает правильно.

Чтобы получить данные отправленной формы, используйте класс FieldStorage. Если форма содержит символы, не входящие в ASCII, используйте ключевой параметр encoding, заданный значением кодировки, определенной для документа. Обычно он содержится в теге META в разделе HEAD HTML-документа или в заголовке Content-Type). Это считывает содержимое формы из стандартного ввода или среды (в зависимости от значения различных переменных среды, установленных в соответствии со стандартом CGI). Поскольку он может использовать стандартный ввод, его следует создавать только один раз.

Экземпляр FieldStorage может быть индексирован как словарь Python. Он позволяет проверять принадлежность с оператором in, а также поддерживает стандартный метод словаря keys() и встроенную функцию len(). Поля формы, содержащие пустые строки, игнорируются и не отображаются в словаре; чтобы сохранить такие значения, укажите значение истины для необязательного ключевого параметра keep_blank_values при создании экземпляра FieldStorage.

Например, следующий код (который предполагает, что заголовок 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 перед тем, как он автоматически закроется в рамках сборки мусора экземпляра FieldStorage (методы read() и readline() вернут байты):

fileitem = form["userfile"]
if fileitem.file:
    # It's an uploaded file; count lines
    linecount = 0
    while True:
        line = fileitem.file.readline()
        if not line: break
        linecount = linecount + 1

Объекты FieldStorage также поддерживают использование в операторе with, который автоматически закроет их по окончании.

Если при получении содержимого загруженного файла возникает ошибка (например, когда пользователь прерывает отправку формы, нажав кнопки «Назад» или «Отмена»), атрибут done объекта для поля будет установлен в значение -1.

Черновик стандарта загрузки файлов допускает возможность загрузки нескольких файлов из одного поля (с использованием рекурсивной кодировки multipart/*). Когда это происходит, элемент будет похожим на словарь FieldStorage элементом. Это можно определить, проверив его атрибут type, который должен быть multipart/form-data (или, возможно, другой тип MIME, соответствующий multipart/*). В этом случае по нему можно итерироваться рекурсивно, как и по верхнеуровневому объекту формы.

Когда форма отправляется в «старом» формате (как строка запроса или как единственная часть данных типа application/x-www-form-urlencoded), элементы фактически будут экземплярами класса MiniFieldStorage. В этом случае атрибуты list, file, и filename всегда None.

Форма, отправленная через POST, которая также имеет строку запроса, будет содержать как FieldStorage, так и MiniFieldStorage элементы.

Изменено в версии 3.4: Атрибут file автоматически закрывается при сборке мусора создающего его экземпляра FieldStorage.

Изменено в версии 3.5: Добавлена поддержка протокола управления контекстом для класса FieldStorage.

3. Интерфейс более высокого уровня

Предыдущий раздел объясняет, как читать данные 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=None)

Этот метод всегда возвращает только одно значение, связанное с полем формы 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. Функции

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

cgi.parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False)

Разбирает запрос из среды или из файла (по умолчанию файл sys.stdin). Параметры keep_blank_values и strict_parsing передаются в urllib.parse.parse_qs() без изменений.

cgi.parse_qs(qs, keep_blank_values=False, strict_parsing=False)

Эта функция устарела в этом модуле. Используйте urllib.parse.parse_qs() вместо неё. Она поддерживается только для обратной совместимости.

cgi.parse_qsl(qs, keep_blank_values=False, strict_parsing=False)

Эта функция устарела в этом модуле. Используйте urllib.parse.parse_qsl() вместо неё. Она поддерживается только для обратной совместимости.

cgi.parse_multipart(fp, pdict)

Разбирает входные данные типа multipart/form-data (для загрузки файлов). Аргументы fp для входного файла и pdict для словаря, содержащего другие параметры в заголовке Content-Type.

Возвращает словарь, аналогичный urllib.parse.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=False)

Преобразует символы '&', '<' и '>' в строке s в безопасные для HTML последовательности. Используйте это, если вам нужно отобразить текст, который может содержать такие символы в HTML. Если опциональный флаг quote имеет значение true, также преобразуется символ кавычек ("); это полезно для включения в значение атрибута HTML, ограниченного двойными кавычками, как в <a href="...">. Обратите внимание, что одинарные кавычки никогда не преобразуются.

Устарело начиная с версии 3.2: Эта функция небезопасна, потому что quote по умолчанию ложно, и поэтому устарела. Используйте html.escape() вместо неё.

5. Безопасность

Существует важное правило: если вы вызываете внешнюю программу (через функции os.system() или os.popen(), или другие с аналогичной функциональностью), убедитесь, что вы не передаёте произвольные строки, полученные от клиента, в оболочку. Это известная уязвимость безопасности, посредством которой хакеры могут использовать уязвимый скрипт CGI для вызова произвольных команд оболочки. Даже части URL или имена полей нельзя доверять, так как запрос не обязательно должен поступать от вашей формы!

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

6. Установка скрипта CGI на Unix-системе

Прочитайте документацию вашего HTTP-сервера и согласуйтесь с местным системным администратором, чтобы узнать, в какой директории должны быть установлены скрипты CGI; обычно это директория cgi-bin в дереве сервера.

Убедитесь, что скрипт читается и исполняется «другими»; разрешения файла Unix должны быть 0o755 (используйте chmod 0755 filename). Убедитесь, что первая строка скрипта содержит #! в первом столбце, за которой следует путь к интерпретатору Python, например:

#!/usr/local/bin/python

Убедитесь, что интерпретатор Python существует и исполняется «другими».

Убедитесь, что любые файлы, которые скрипт должен читать или записывать, доступны для чтения или записи соответственно «другими» — их разрешения должны быть 0o644 для чтения и 0o666 для записи. Это связано с тем, что по соображениям безопасности 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).

7. Тестирование скрипта CGI

К сожалению, скрипт CGI, как правило, не запускается при попытке его запустить из командной строки, и скрипт, который работает безупречно из командной строки, может работать некорректно при запуске с сервера. Одна причина, по которой вы всё же должны протестировать свой скрипт из командной строки: если в нём есть синтаксическая ошибка, интерпретатор Python не выполнит его, и HTTP-сервер, скорее всего, отправит клиенту неочевидную ошибку.

Предполагая, что в вашем скрипте нет синтаксических ошибок, но он всё равно не работает, вам ничего не остаётся, кроме как обратиться к следующему разделу.

8. Отладка скриптов 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 не происходит, трассировка стека будет читаема.

9. Распространённые проблемы и решения

  • Большинство 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/3.5/library/cgi.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API