Spec-Zone.ru › Python 3.6

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

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

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

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

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

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

21.2.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)

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

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

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

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

21.2.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-скриптах).

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

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

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

21.2.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 не происходит, отладочная информация будет читаемой.

21.2.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.6/library/cgi.html

Spec-Zone.ru

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