cgi.md
1> **Источник:** https://python-all.ru/3.0/library/cgi.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `cgi` – поддержка Common Gateway Interface.89Вспомогательный модуль для скриптов Common Gateway Interface (CGI).1011Этот модуль определяет ряд утилит для использования CGI-скриптами, написанными на Python.1213## Введение1415CGI-скрипт вызывается HTTP-сервером, обычно для обработки пользовательского ввода, отправленного через элемент HTML `<FORM>` или `<ISINDEX>`.1617Чаще всего CGI-скрипты находятся в специальной директории сервера `cgi-bin`. HTTP-сервер помещает всю информацию о запросе (такую как имя хоста клиента, запрошенный URL, строку запроса и множество других полезных данных) в окружение оболочки скрипта, выполняет скрипт и отправляет его вывод обратно клиенту.1819Ввод скрипта также подключён к клиенту, и иногда данные формы читаются таким образом; в других случаях данные формы передаются через часть URL «строка запроса». Этот модуль предназначен для обработки различных ситуаций и предоставления более простого интерфейса для Python-скрипта. Он также предоставляет ряд утилит, помогающих при отладке скриптов, и последним дополнением является поддержка загрузки файлов из формы (если браузер это поддерживает).2021Вывод CGI-скрипта должен состоять из двух разделов, разделённых пустой строкой. Первый раздел содержит несколько заголовков, сообщающих клиенту, какие данные следуют. Код Python для генерации минимального раздела заголовков выглядит так:2223```python24print("Content-Type: text/html") # Далее HTML25print() # пустая строка, конец заголовков26```2728Второй раздел обычно представляет собой HTML, что позволяет клиентскому программному обеспечению отображать красиво отформатированный текст с заголовками, встроенными изображениями и т.д. Вот код Python, выводящий простой фрагмент HTML:2930```python31print("<TITLE>CGI script output</TITLE>")32print("<H1>This is my first CGI script</H1>")33print("Hello, world!")34```3536## Использование модуля cgi3738Для начала следует написать `import cgi`.3940При написании нового сценария добавьте строку:4142```python43import cgitb; cgitb.enable()44```4546Это активирует специальный обработчик исключений, который при возникновении ошибок будет выводить подробные отчёты в веб-браузер. Если внутренности программы не должны быть видны пользователям, можно сохранять отчёты в файлы, добавив строку вроде такой:4748```python49import cgitb; cgitb.enable(display=0, logdir="/tmp")50```5152Очень полезно использовать эту возможность при разработке скрипта. Отчёты, формируемые [`cgitb`](https://python-all.ru/3.0/library/cgitb.html#module-cgitb), содержат информацию, способную сэкономить много времени при поиске ошибок. Строку `cgitb` можно удалить позже, после тестирования скрипта и уверенности в его корректной работе.5354Для получения отправленных данных формы используйте класс `FieldStorage`. Создайте его ровно один раз без аргументов. Этот конструктор считывает содержимое формы из стандартного ввода или окружения (в зависимости от значений переменных окружения, установленных согласно стандарту CGI). Поскольку он может потребить стандартный ввод, его следует создавать только один раз.5556Экземпляр `FieldStorage` можно индексировать как словарь Python, а также поддерживаются стандартные методы словаря [`__contains__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__contains__) и `keys()`. Также поддерживается встроенная функция [`len()`](https://python-all.ru/3.0/library/functions.html#len). Поля форм, содержащие пустые строки, игнорируются и не попадают в словарь; чтобы сохранять такие значения, передайте истинное значение для необязательного именованного параметра *keep\_blank\_values* при создании экземпляра `FieldStorage`.5758Например, следующий код (который предполагает, что заголовок *Content-Type* и пустая строка уже выведены) проверяет, что поля `name` и `addr` установлены в непустую строку:5960```python61form = cgi.FieldStorage()62if not ("name" in form and "addr" in form):63 print("<H1>Error</H1>")64 print("Please fill in the name and addr fields.")65 return66print("<p>name:", form["name"].value)67print("<p>addr:", form["addr"].value)68...further form processing here...69```7071Здесь поля, доступ к которым осуществляется через `form[key]`, сами являются экземплярами `FieldStorage` (или `MiniFieldStorage`, в зависимости от кодировки формы). Атрибут `value` экземпляра возвращает строковое значение поля. Метод `getvalue()` возвращает это строковое значение напрямую; он также принимает необязательный второй аргумент – значение по умолчанию, которое возвращается, если запрошенный ключ отсутствует.7273Если отправленные данные формы содержат несколько полей с одинаковым именем, то объект, полученный через `form[key]`, будет не экземпляром `FieldStorage` или `MiniFieldStorage`, а списком таких экземпляров. В этой ситуации `form.getvalue(key)` также вернёт список строк. Если вы ожидаете такую возможность (когда ваша HTML-форма содержит несколько полей с одинаковым именем), используйте функцию `getlist()`, которая всегда возвращает список значений (чтобы не приходилось обрабатывать случай с одним элементом отдельно). Например, следующий код объединяет любое количество полей username, разделяя их запятыми:7475```python76value = form.getlist("username")77usernames = ",".join(value)78```7980Если поле представляет загруженный файл, доступ к значению через атрибут `value` или метод `getvalue()` считывает весь файл в память как строку. Это может быть нежелательно. Проверить, является ли поле загруженным файлом, можно по атрибуту `filename` или `file`. Затем можно читать данные в удобном темпе из атрибута `file`:8182```python83fileitem = form["userfile"]84if fileitem.file:85 # Это загруженный файл; подсчитать строки86 linecount = 087 while True:88 line = fileitem.file.readline()89 if not line: break90 linecount = linecount + 191```9293Если при получении содержимого загруженного файла возникает ошибка (например, когда пользователь прерывает отправку формы, нажав кнопку «Назад» или «Отмена»), атрибут `done` объекта поля будет установлен в значение -1.9495Черновик стандарта загрузки файлов допускает возможность загрузки нескольких файлов из одного поля (с использованием рекурсивной кодировки *multipart/\**). В этом случае элемент будет объектом `FieldStorage`, похожим на словарь. Это можно определить, проверив его атрибут [`type`](https://python-all.ru/3.0/library/functions.html#type) – он должен быть равен *multipart/form-data* (или, возможно, другому MIME-типу, соответствующему *multipart/\**). В таком случае его можно обходить рекурсивно, как и объект верхнего уровня формы.9697Когда форма отправляется в «старом» формате (как строка запроса или как единственная часть данных типа *application/x-www-form-urlencoded*), элементы на самом деле являются экземплярами класса `MiniFieldStorage`. В этом случае атрибуты [`list`](https://python-all.ru/3.0/library/functions.html#list), `file` и `filename` всегда равны `None`.9899Форма, отправленная через POST с дополнительной строкой запроса, будет содержать элементы как `FieldStorage`, так и `MiniFieldStorage`.100101## Интерфейс более высокого уровня102103В предыдущем разделе объясняется, как читать данные CGI-формы с помощью класса `FieldStorage`. В этом разделе описывается интерфейс более высокого уровня, добавленный в этот класс для обеспечения более читаемого и интуитивного способа работы. Этот интерфейс не делает устаревшими методы, описанные в предыдущих разделах – они по-прежнему полезны, например, для эффективной обработки загрузки файлов.104105Интерфейс состоит из двух простых методов. С их помощью можно обрабатывать данные формы универсально, не задумываясь о том, было ли отправлено одно или несколько значений под одним именем.106107В предыдущем разделе было показано, как писать следующий код каждый раз, когда ожидается, что пользователь отправит несколько значений под одним именем:108109```python110item = form.getvalue("item")111if isinstance(item, list):112 # Пользователь запрашивает более одного элемента.113else:114 # Пользователь запрашивает только один элемент.115```116117Такая ситуация часто возникает, например, когда форма содержит группу флажков с одинаковым именем:118119```python120<input type="checkbox" name="item" value="1" />121<input type="checkbox" name="item" value="2" />122```123124Однако в большинстве случаев в форме есть только один элемент управления с определённым именем, и тогда ожидается и требуется только одно значение, связанное с этим именем. Поэтому пишется скрипт, содержащий, например, такой код:125126```python127user = form.getvalue("user").upper()128```129130Проблема этого кода в том, что никогда не следует рассчитывать, что клиент передаст в сценарий корректные данные. Например, если любопытный пользователь добавит к строке запроса ещё одну пару `user=foo`, сценарий аварийно завершится, поскольку в этой ситуации вызов `getvalue("user")` вернёт список, а не строку. Вызов метода `toupper()` для списка некорректен (у списков нет такого метода) и приводит к исключению [`AttributeError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.AttributeError).131132Поэтому единственным правильным способом чтения значений данных формы было всегда использовать код, проверяющий, является ли полученное значение одиночным или списком значений. Это утомительно и снижает читаемость скриптов.133134Более удобный подход – использовать методы `getfirst()` и `getlist()`, предоставляемые этим интерфейсом верхнего уровня.135136#### `[cgi.FieldStorage.getfirst]FieldStorage.getfirst(name[, default])`137138Этот метод всегда возвращает только одно значение, связанное с полем формы139140*name*141142. Метод возвращает только первое значение в случае, если под таким именем было отправлено несколько значений. Обратите внимание: порядок получения значений может различаться в разных браузерах, и на него не следует полагаться.143144[\[1\]](https://python-all.ru/3.0/library/cgi.html#id3)145146Если поле формы или значение отсутствует, метод возвращает значение, указанное необязательным параметром147148*default*149150. Если параметр не указан, по умолчанию используется151152`None`153154.155156#### `[cgi.FieldStorage.getlist]FieldStorage.getlist(name)`157158Этот метод всегда возвращает список значений, связанных с полем формы159160*name*161162. Метод возвращает пустой список, если для163164*name*165166не существует такого поля или значения. Возвращает список из одного элемента, если существует только одно значение.167168С помощью этих методов можно писать компактный код:169170```python171import cgi172form = cgi.FieldStorage()173user = form.getfirst("user", "").upper() # Так это безопасно.174for item in form.getlist("item"):175 do_something(item)176```177178## Функции179180Они полезны, если нужен более тонкий контроль или если требуется использовать некоторые алгоритмы, реализованные в этом модуле, в других ситуациях.181182#### `[cgi.parse]cgi.parse(fp[, keep_blank_values[, strict_parsing]])`183184Разбирает запрос из окружения или из файла (по умолчанию файл –185186`sys.stdin`187188). Параметры189190*keep\_blank\_values*191192и193194*strict\_parsing*195196передаются в197198[`urllib.parse.parse_qs()`](https://python-all.ru/3.0/library/urllib.parse.html#urllib.parse.parse_qs)199200без изменений.201202#### `[cgi.parse_qs]cgi.parse_qs(qs[, keep_blank_values[, strict_parsing]])`203204Эта функция устарела в данном модуле. Вместо неё используйте205206[`urllib.parse.parse_qs()`](https://python-all.ru/3.0/library/urllib.parse.html#urllib.parse.parse_qs)207208. Она оставлена только для обратной совместимости.209210#### `[cgi.parse_qsl]cgi.parse_qsl(qs[, keep_blank_values[, strict_parsing]])`211212Эта функция устарела в данном модуле. Вместо неё используйте213214[`urllib.parse.parse_qs()`](https://python-all.ru/3.0/library/urllib.parse.html#urllib.parse.parse_qs)215216. Она оставлена только для обратной совместимости.217218#### `[cgi.parse_multipart]cgi.parse_multipart(fp, pdict)`219220Разбирает входные данные типа *multipart/form-data* (для загрузки файлов). Аргументы: *fp* – входной файл, *pdict* – словарь, содержащий другие параметры из заголовка *Content-Type*.221222Возвращает словарь, аналогичный [`urllib.parse.parse_qs()`](https://python-all.ru/3.0/library/urllib.parse.html#urllib.parse.parse_qs): ключи – имена полей, каждое значение – список значений для этого поля. Это легко использовать, но не очень хорошо, если ожидается загрузка мегабайтов – в этом случае используйте класс `FieldStorage`, который гораздо более гибок.223224Обратите внимание, что эта функция не разбирает вложенные части multipart – для этого используйте `FieldStorage`.225226#### `[cgi.parse_header]cgi.parse_header(string)`227228Разбирает MIME-заголовок (например,229230*Content-Type*231232) на основное значение и словарь параметров.233234#### `[cgi.test]cgi.test()`235236Надёжный тестовый CGI-скрипт, который можно использовать как основную программу. Выводит минимальные HTTP-заголовки и форматирует всю переданную скрипту информацию в виде HTML.237238#### `[cgi.print_environ]cgi.print_environ()`239240Форматирует окружение оболочки в формате HTML.241242#### `[cgi.print_form]cgi.print_form(form)`243244Форматирование формы в HTML.245246#### `[cgi.print_directory]cgi.print_directory()`247248Форматирование текущей директории в HTML.249250#### `[cgi.print_environ_usage]cgi.print_environ_usage()`251252Вывод в HTML списка полезных переменных окружения (используемых CGI).253254#### `[cgi.escape]cgi.escape(s[, quote])`255256Преобразует символы257258`'&'`259260,261262`'<'`263264и265266`'>'`267268в строке269270*s*271272в безопасные для HTML последовательности. Используйте эту функцию, если нужно отобразить текст, который может содержать такие символы в HTML. Если необязательный флаг273274*quote*275276истинен, также преобразуется символ кавычки (277278`'"'`279280); это помогает при включении в значение атрибута HTML, например281282`<A HREF="...">`283284. Если преобразуемое значение может содержать одинарные или двойные кавычки (или и те, и другие), рассмотрите возможность использования функции285286`quoteattr()`287288из модуля289290[`xml.sax.saxutils`](https://python-all.ru/3.0/library/xml.sax.utils.html#module-xml.sax.saxutils)291292.293294## Забота о безопасности295296Есть одно важное правило: если вы вызываете внешнюю программу (через функции [`os.system()`](https://python-all.ru/3.0/library/os.html#os.system) или `os.popen()` или другие с аналогичной функциональностью), убедитесь, что вы не передаёте оболочке произвольные строки, полученные от клиента. Это хорошо известная уязвимость, с помощью которой опытные хакеры из любой точки сети могут использовать доверчивый CGI-скрипт для выполнения произвольных команд оболочки. Даже части URL или имена полей не заслуживают доверия, поскольку запрос не обязательно должен приходить из вашей формы!297298Чтобы быть в безопасности, если вам всё же нужно передать строку из формы команде оболочки, стоит убедиться, что строка содержит только буквенно-цифровые символы, дефисы, символы подчёркивания и точки.299300## Установка CGI-скрипта в системе Unix301302Прочитайте документацию вашего HTTP-сервера и уточните у системного администратора каталог, в который следует устанавливать CGI-скрипты; обычно это каталог `cgi-bin` в дереве сервера.303304Убедитесь, что ваш скрипт доступен для чтения и выполнения «другими»; в Unix режим файла должен быть `0o755` восьмеричный (используйте `chmod 0755 filename`). Убедитесь, что первая строка скрипта содержит `#!`, начиная с первой колонки, после которого идёт путь к интерпретатору Python, например:305306```python307#!/usr/local/bin/python308```309310Убедитесь, что интерпретатор Python существует и доступен для выполнения «другими».311312Убедитесь, что любые файлы, которые ваш скрипт должен читать или записывать, доступны для чтения или записи соответственно для «других» – их режим должен быть `0o644` для чтения и `0o666` для записи. Это связано с тем, что по соображениям безопасности HTTP-сервер выполняет ваш скрипт от имени пользователя «nobody» без каких-либо специальных привилегий. Он может только читать (записывать, выполнять) файлы, которые каждый может читать (записывать, выполнять). Текущий каталог во время выполнения также отличается (обычно это каталог cgi-bin сервера), и набор переменных окружения отличается от того, что вы получаете при входе в систему. В частности, не рассчитывайте на то, что путь поиска исполняемых файлов оболочки (**PATH**) или путь поиска модулей Python ([**PYTHONPATH**](https://python-all.ru/3.0/using/cmdline.html#envvar-PYTHONPATH)) установлены во что-то полезное.313314Если вам нужно загружать модули из директории, которой нет в пути поиска модулей Python по умолчанию, можно изменить путь в скрипте перед импортом других модулей. Например:315316```python317import sys318sys.path.insert(0, "/usr/home/joe/lib/python")319sys.path.insert(0, "/usr/local/lib/python")320```321322(Так директория, вставленная последней, будет просматриваться первой!)323324Инструкции для систем, отличных от Unix, различаются; ознакомьтесь с документацией вашего HTTP-сервера (обычно в ней есть раздел о CGI-скриптах).325326## Тестирование CGI-скрипта327328К сожалению, CGI-скрипт обычно не работает при запуске из командной строки, а скрипт, отлично работающий в командной строке, может таинственно отказываться при запуске через сервер. Но есть одна причина, по которой всё же стоит тестировать скрипт из командной строки: если в нём есть синтаксическая ошибка, интерпретатор Python вообще не выполнит его, и HTTP-сервер, скорее всего, отправит клиенту малопонятную ошибку.329330Если в скрипте нет синтаксических ошибок, но он всё равно не работает, ничего не остаётся, кроме как прочитать следующий раздел.331332## Отладка CGI-скриптов333334Прежде всего проверьте тривиальные ошибки установки – внимательное прочтение раздела выше об установке CGI-скрипта может сэкономить много времени. Если вы сомневаетесь, правильно ли поняли процедуру установки, попробуйте установить копию этого файла модуля (`cgi.py`) как CGI-скрипт. При вызове как скрипт файл выведет своё окружение и содержимое формы в виде HTML. Задайте ему правильный режим и т.д. и отправьте запрос. Если он установлен в стандартный каталог `cgi-bin`, то можно отправить запрос, введя в браузере URL следующего вида:335336```python337http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home338```339340Если это даёт ошибку 404, сервер не может найти скрипт – возможно, его нужно установить в другой каталог. Если возникает другая ошибка, значит, есть проблема с установкой, которую следует устранить, прежде чем двигаться дальше. Если вы получили аккуратно отформатированный список окружения и содержимого формы (в этом примере поля должны быть перечислены как «addr» со значением «At Home» и «name» со значением «Joe Blow»), то скрипт `cgi.py` установлен правильно. Если вы выполните ту же процедуру для своего скрипта, то сможете его отладить.341342Следующим шагом можно вызвать из своего скрипта функцию [`test()`](https://python-all.ru/3.0/library/cgi.html#cgi.test) модуля `cgi`: замените его основной код единственным оператором343344```python345cgi.test()346```347348Это должно дать те же результаты, что и установка самого файла `cgi.py`.349350Когда обычный скрипт Python вызывает необработанное исключение (по любой причине: опечатка в имени модуля, файл, который не удаётся открыть, и т.п.), интерпретатор Python печатает подробную трассировку и завершает работу. Хотя интерпретатор Python всё ещё делает это, когда ваш CGI-скрипт вызывает исключение, скорее всего, трассировка попадёт в один из журнальных файлов HTTP-сервера или вообще будет отброшена.351352Fortunately, once you have managed to get your script to execute *some* code, you can easily send tracebacks to the Web browser using the [`cgitb`](https://python-all.ru/3.0/library/cgitb.html#module-cgitb) module. If you haven’t done so already, just add the line:353354```python355import cgitb; cgitb.enable()356```357358в начало скрипта. Затем попробуйте запустить его снова; при возникновении проблемы вы увидите подробный отчёт, который, вероятно, укажет на причину сбоя.359360Если вы подозреваете, что может быть проблема с импортом модуля [`cgitb`](https://python-all.ru/3.0/library/cgitb.html#module-cgitb), можно использовать ещё более надёжный подход (использующий только встроенные модули):361362```python363import sys364sys.stderr = sys.stdout365print("Content-Type: text/plain")366print()367...your code here...368```369370Этот подход полагается на интерпретатор Python для печати трассировки. Тип содержимого вывода устанавливается в обычный текст, что отключает всю обработку HTML. Если ваш скрипт работает, сырой HTML будет отображён клиентом. Если он вызывает исключение, скорее всего, после вывода первых двух строк будет показана трассировка. Поскольку интерпретация HTML не происходит, трассировка будет читаемой.371372## Частые проблемы и их решения373374- Большинство HTTP-серверов буферизуют вывод CGI-скриптов до завершения скрипта. Это означает, что невозможно отображать индикатор выполнения на экране клиента во время работы скрипта.375- Проверьте инструкции по установке выше.376- Проверьте файлы журнала HTTP-сервера. (`tail -f logfile` в отдельном окне может быть полезно!)377- Всегда сначала проверяйте скрипт на синтаксические ошибки, выполнив что-то вроде `python script.py`.378- Если в вашем скрипте нет синтаксических ошибок, попробуйте добавить `import cgitb; cgitb.enable()` в начало скрипта.379- При вызове внешних программ убедитесь, что они могут быть найдены. Обычно это означает использование абсолютных путей – переменная **PATH**, как правило, не имеет полезного значения в CGI-скрипте.380- При чтении или записи внешних файлов убедитесь, что они могут быть прочитаны или записаны тем идентификатором пользователя (userid), под которым будет выполняться ваш CGI-скрипт: обычно это идентификатор пользователя, под которым работает веб-сервер, или явно указанный идентификатор для функции `suexec` веб-сервера.381- Не пытайтесь установить CGI-скрипту режим set-uid. На большинстве систем это не работает и к тому же создаёт угрозу безопасности.382383Сноски384385| [\[1\]](https://python-all.ru/3.0/library/cgi.html#id2) | Обратите внимание, что некоторые последние версии спецификации HTML действительно указывают, в каком порядке должны передаваться значения полей, но выяснить, был ли запрос получен от соответствующего стандарту браузера или вообще от браузера, утомительно и чревато ошибками. |386| --- | --- |387