cgi — Поддержка Common Gateway Interface
Исходный код: Lib/cgi.py
Модуль поддержки Common Gateway Interface (CGI) скриптов.
Этот модуль определяет ряд утилит для использования 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(). Поля формы, содержащие пустые строки, игнорируются и не отображаются в словаре; чтобы сохранить такие значения, укажите истинное значение для необязательного ключевого параметра 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.
Интерфейс высокого уровня
Предыдущий раздел объясняет, как читать данные из формы 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, separator="&") -
Разбирает запрос в среде или из файла (файл по умолчанию
sys.stdin). Параметры keep_blank_values, strict_parsing и separator передаются вurllib.parse.parse_qs()без изменений.Изменено в версии 3.8.8: Добавлен параметр separator.
-
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.8.8: Добавлен параметр separator.
-
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.
Обеспечение безопасности
Существует важное правило: если вы вызываете внешнюю программу (через функции 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 для вывода трассировки стека. Тип содержимого вывода устанавливается в «plain text», что отключает всю обработку 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/cgi.html