Spec-Zone.ru › Python 3.7

cgi — Поддержка Common Gateway Interface

Исходный код: Lib/cgi.py

Модуль поддержки CGI-скриптов (Common Gateway Interface).

Этот модуль определяет ряд утилит для использования CGI-скриптами, написанными на Python.

Введение

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!")

Использование модуля 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(). Поля формы, содержащие пустые строки, игнорируются и не отображаются в словаре; чтобы сохранить такие значения, при создании экземпляра FieldStorage укажите значение True для необязательного ключевого параметра 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 прежде, чем он автоматически закроется в ходе сбора мусора экземпляра 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.

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

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

Функции

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

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, encoding="utf-8", errors="replace")

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

Возвращает словарь, аналогичный urllib.parse.parse_qs(): ключи — имена полей, каждое значение — список значений для этого поля. Для полей, не являющихся файлами, значение представляет собой список строк.

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

Изменено в версии 3.7: Добавлены параметры encoding и errors. Для полей, не являющихся файлами, значением теперь является список строк, а не байтов.

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 по умолчанию имеет значение false, и поэтому устарела. Используйте html.escape() вместо неё.

Обеспечение безопасности

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

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

Установка вашего 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-скриптах).

Тестирование вашего CGI-скрипта

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

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

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

Общие проблемы и решения

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

Spec-Zone.ru

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