cgi
— Поддержка интерфейса общего шлюза¶
Исходный код: Lib/cgi.py
Утратил актуальность с версии 3.11, будет удален в версии 3.13: Модуль cgi
устарел (подробности и альтернативные варианты см. в разделе PEP 594).
Класс FieldStorage
обычно можно заменить на urllib.parse.parse_qsl()
для запросов GET
и HEAD
, а модуль email.message
или multipart для запросов POST
и PUT
. У большинства utility functions есть запасные части.
Модуль поддержки сценариев интерфейса Common Gateway (CGI).
Этот модуль определяет ряд утилит для использования CGI-скриптами, написанными на Python.
Глобальной переменной maxlen
может быть присвоено значение целого числа, указывающее максимальный размер запроса POST. Запросы POST, превышающие этот размер, приведут к возникновению ValueError
во время синтаксического анализа. Значение этой переменной по умолчанию равно 0
, что означает, что размер запроса не ограничен.
Availability: это не Emscripten, это был не я.
Этот модуль не работает или недоступен на платформах WebAssembly wasm32-emscripten
и wasm32-wasi
. Дополнительную информацию смотрите в разделе Платформы веб-сборки.
Вступление¶
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, значение которого соответствует кодировке, определенной для документа. Обычно он содержится в МЕТА-теге в разделе HEAD HTML-документа или в заголовке Content-Type. При этом содержимое формы считывается из стандартного ввода или из среды (в зависимости от значения различных переменных среды, установленных в соответствии со стандартом CGI). Поскольку он может использовать стандартный ввод, его следует создать только один раз.
Экземпляр FieldStorage
может быть проиндексирован как словарь Python. Он позволяет проверять принадлежность с помощью оператора in
, а также поддерживает стандартный метод словаря keys()
и встроенную функцию len()
. Поля формы, содержащие пустые строки, игнорируются и не отображаются в словаре; чтобы сохранить такие значения, укажите значение true для необязательного параметра keep_blank_values keyword при создании экземпляра 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()
, который всегда возвращает список значений (так что вам не нужно использовать специальный регистр для одного элемента). Например, этот код объединяет любое количество полей имени пользователя, разделенных запятыми:
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
.
Форма, отправленная по ПОЧТЕ, которая также содержит строку запроса, будет содержать как 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" />
Однако в большинстве случаев в форме есть только один элемент управления form с определенным именем, и тогда вы ожидаете и нуждаетесь только в одном значении, связанном с этим именем. Итак, вы пишете скрипт, содержащий, например, этот код:
user = form.getvalue("user").upper()
Проблема с кодом заключается в том, что вы никогда не должны ожидать, что клиент предоставит корректные входные данные для ваших скриптов. Например, если любопытный пользователь добавит другую пару user=foo
к строке запроса, то скрипт завершит работу с ошибкой, потому что в этой ситуации вызов метода getvalue("user")
возвращает список вместо строки. Вызов метода upper()
для списка недопустим (поскольку в списках нет метода с таким именем) и приводит к исключению AttributeError
.
Поэтому подходящим способом считывания значений данных формы было всегда использовать код, который проверяет, является ли полученное значение единичным или списком значений. Это раздражает и приводит к менее удобочитаемым скриптам.
Более удобным подходом является использование методов getfirst()
и getlist()
, предоставляемых этим высокоуровневым интерфейсом.
- FieldStorage.getfirst(name, default=None)¶
Этот метод всегда возвращает только одно значение, связанное с полем формы имя. Метод возвращает только первое значение в случае, если под таким именем было опубликовано больше значений. Пожалуйста, обратите внимание, что порядок получения значений может варьироваться от браузера к браузеру, и на это не следует рассчитывать. [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, separator='&')¶
Выполните синтаксический анализ запроса в среде или из файла (значение файла по умолчанию равно
sys.stdin
). Параметры keep_blank_values, strict_parsing и separator передаются вurllib.parse.parse_qs()
без изменений.Утратил актуальность с версии 3.11, будет удален в версии 3.13: Эта функция, как и остальная часть модуля
cgi
, устарела. Его можно заменить вызовомurllib.parse.parse_qs()
непосредственно в нужной строке запроса (за исключением вводаmultipart/form-data
, который может быть обработан так, как описано дляparse_multipart()
).
- cgi.parse_multipart(fp, pdict, encoding='utf-8', errors='replace', separator='&')¶
Проанализируйте вводимые данные типа multipart/form-data (для загрузки файлов). Аргументами являются fp для входного файла, pdict для словаря, содержащего другие параметры в заголовке Content-Type, и encoding, кодировка запроса.
Возвращает словарь точно так же, как
urllib.parse.parse_qs()
: ключи - это имена полей, каждое значение - это список значений для этого поля. Для полей, не относящихся к файлам, значением является список строк.Это просто в использовании, но не очень полезно, если вы ожидаете загрузки мегабайт - в этом случае используйте класс
FieldStorage
, который является гораздо более гибким.Изменено в версии 3.7: Добавлены параметры encoding и errors. Для полей, не относящихся к файлам, значение теперь представляет собой список строк, а не байтов.
Изменено в версии 3.10: Добавлен параметр separator.
Утратил актуальность с версии 3.11, будет удален в версии 3.13: Эта функция, как и остальная часть модуля
cgi
, устарела. Его можно заменить функциональностью в пакетеemail
(например,email.message.EmailMessage
/email.message.Message
) который реализует те же RFC-запросы MIME) или в проекте multipart PyPI.
- cgi.parse_header(string)¶
Преобразуйте заголовок MIME (например, Content-Type) в основное значение и словарь параметров.
Утратил актуальность с версии 3.11, будет удален в версии 3.13: Эта функция, как и остальная часть модуля
cgi
, устарела. Ее можно заменить функциональностью из пакетаemail
, который реализует те же RFC-запросы в формате MIME.Например, с помощью
email.message.EmailMessage
:from email.message import EmailMessage msg = EmailMessage() msg['content-type'] = 'application/json; charset="utf8"' main, params = msg.get_content_type(), msg['content-type'].params
- cgi.test()¶
Надежный тестовый CGI-скрипт, который можно использовать в качестве основной программы. Записывает минимальные HTTP-заголовки и форматирует всю информацию, предоставляемую скрипту, в формате HTML.
- cgi.print_environ()¶
Отформатируйте оболочку в формате HTML.
- cgi.print_form(form)¶
Отформатируйте форму в формате HTML.
- cgi.print_directory()¶
Отформатируйте текущий каталог в формате HTML.
- cgi.print_environ_usage()¶
Выведите список полезных (используемых CGI) переменных среды в формате HTML.
Забота о безопасности¶
Есть одно важное правило: если вы вызываете внешнюю программу (через os.system()
, os.popen()
или другие функции с аналогичной функциональностью), убедитесь, что вы не передаете произвольные строки, полученные от клиента, в командную строку. Это хорошо известная брешь в системе безопасности, с помощью которой хитроумные хакеры в любой точке Интернета могут использовать доверчивый CGI-скрипт для вызова произвольных команд оболочки. Даже частям URL-адреса или имен полей нельзя доверять, поскольку запрос не обязательно должен исходить из вашей формы!
На всякий случай, если вам необходимо передать строку, полученную из формы, в команду оболочки, вы должны убедиться, что строка содержит только буквенно-цифровые символы, тире, подчеркивания и точки.
Установка вашего CGI-скрипта в системе Unix¶
Ознакомьтесь с документацией к вашему HTTP-серверу и проконсультируйтесь с вашим локальным системным администратором, чтобы найти каталог, в который следует установить CGI-скрипты; обычно это каталог cgi-bin
в дереве серверов.
Убедитесь, что ваш скрипт доступен для чтения и выполнения «другими»; режим файла Unix должен быть 0o755
восьмеричным (используйте chmod 0755 filename
). Убедитесь, что первая строка скрипта содержит #!
, начиная с столбца 1, за которым следует путь к интерпретатору Python, например:
#!/usr/local/bin/python
Убедитесь, что интерпретатор Python существует и может быть выполнен «другими».
Убедитесь, что все файлы, которые ваш скрипт должен прочитать или записать, доступны для чтения или записи, соответственно, «другими» - их режим должен быть 0o644
для чтения и 0o666
для записи. Это связано с тем, что по соображениям безопасности HTTP-сервер выполняет ваш скрипт от имени пользователя «никто», без каких-либо специальных привилегий. Он может читать (записывать, выполнять) только те файлы, которые могут читать (записывать, выполнять) все. Текущий каталог во время выполнения также отличается (обычно это каталог 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, сервер не может найти скрипт - возможно, вам нужно установить его в другой каталог. Если при этом выдается другая ошибка, это означает, что возникла проблема с установкой, которую вам следует устранить, прежде чем пытаться продолжить. Если вы получите хорошо отформатированный список окружения и содержимого формы (в этом примере поля должны быть указаны как «адрес» со значением «Дома» и «имя» со значением «Джо Блоу»), значит, скрипт 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. Это не работает в большинстве систем и также является проблемой безопасности.
Сноски