CGI-программирование на Python
Что такое CGI
CGI в настоящее время поддерживается NCSA, и NCSA определяет CGI следующим образом:
CGI (Common Gateway Interface) — общий шлюзовый интерфейс — это программа, которая выполняется на сервере, таком как HTTP-сервер, предоставляя интерфейс для клиентских HTML-страниц.
Просмотр веб-страниц
Чтобы лучше понять, как работает CGI, можно рассмотреть процесс перехода по ссылке или URL на веб-странице:
- 1. Используйте браузер, чтобы открыть URL и подключиться к веб-серверу HTTP.
- 2. После получения запроса веб-сервер разбирает URL и проверяет, существует ли запрашиваемый файл на сервере. Если он существует, он возвращает содержимое файла; если нет, он возвращает соответствующее сообщение об ошибке.
- 3. Браузер получает информацию от сервера и отображает полученный файл или сообщение об ошибке.
CGI-программы могут быть скриптами Python, скриптами PERL, скриптами SHELL, программами на C или C++ и т. д.
Схема архитектуры CGI

Поддержка и настройка веб-сервера
Прежде чем приступить к CGI-программированию, убедитесь, что ваш веб-сервер поддерживает CGI и что в нём настроен обработчик CGI.
Настройка CGI в Apache:
Настройте каталог CGI:
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
Все CGI-программы, выполняемые HTTP-серверами, хранятся в предварительно настроенном каталоге. Этот каталог называется каталогом CGI и по соглашению имеет имя /var/www/cgi-bin.
CGI-файлы имеют расширение .cgi, а Python также может использовать расширение .py.
По умолчанию Linux-серверы настроены на использование каталога cgi-bin в /var/www.
Если вы хотите указать другой каталог для запуска CGI-скриптов, вы можете изменить файл конфигурации httpd.conf следующим образом:
<Directory "/var/www/cgi-bin"> AllowOverride None Options +ExecCGI Order allow,deny Allow from all </Directory>
Добавьте суффикс .py в AddHandler, чтобы можно было обращаться к файлам скриптов Python, оканчивающимся на .py:
AddHandler cgi-script .cgi .pl .py
Первая CGI-программа
Мы используем Python для создания нашей первой программы CGI с именем hello.py, расположенной в каталоге /var/www/cgi-bin, со следующим содержимым:
Пример
print ("Content-type:text/html")
print () # Пустая строка, сообщает серверу, что заголовок завершён
print ('<html>')
print ('<head>')
print ('<meta charset="utf-8">')
print ('<title>Hello Word - My first CGI program!</title>')
print ('</head>')
print ('<body>')
print ('<h2>Hello Word! I am the first CGI program from Example Tutorial</h2>')
print ('</body>')
print ('</html>')
После сохранения файла измените hello.py и установите права доступа к файлу 755:
chmod 755 hello.py
Результат обращения к вышеприведённой программе в браузере выглядит следующим образом:

Скрипт hello.py — это простой скрипт на Python. Первая строка вывода, "Content-type:text/html", отправляется в браузер, чтобы сообщить ему, что отображаемый тип содержимого — "text/html".
Используйте print для вывода пустой строки, чтобы сообщить серверу, что информация заголовка передана полностью.
HTTP-заголовки
"Content-type:text/html" в файле hello.py является частью HTTP-заголовка. Он отправляется в браузер, чтобы сообщить ему тип содержимого файла.
Формат HTTP-заголовка следующий:
HTTP 字段名: 字段内容
Например:
Content-type: text/html
В следующей таблице описывается информация, обычно используемая в HTTP-заголовках в CGI-программах:
| head | Описание |
|---|---|
| Content-type: | Тип MIME, соответствующий сущности запроса. Например: Content-type:text/html |
| Expires: Дата | Дата и время, когда истекает срок действия ответа |
| Location: URL | Используется для перенаправления получателя на местоположение, отличное от запрошенного URL, для завершения запроса или идентификации нового ресурса |
| Last-modified: Дата | Время последнего изменения запрошенного ресурса |
| Content-length: N | Длина содержимого запроса |
| Set-Cookie: Строка | Установка HTTP Cookie |
Переменные среды CGI
Все CGI-программы получают следующие переменные среды, которые играют важную роль в CGI-программах:
| Имя переменной | Описание |
|---|---|
| CONTENT_TYPE | Значение этой переменной окружения указывает MIME-тип передаваемой информации. В настоящее время переменная окружения CONTENT_TYPE обычно имеет значение: application/x-www-form-urlencoded, что означает, что данные поступают из HTML-формы. |
| CONTENT_LENGTH | Когда сервер взаимодействует с CGI-программой через POST, эта переменная окружения указывает количество допустимых байтов данных, которые можно прочитать из стандартного ввода (STDIN). Эту переменную необходимо использовать при чтении входных данных. |
| HTTP_COOKIE | Содержимое COOKIE на клиентской машине. |
| HTTP_USER_AGENT | Предоставляет информацию о браузере клиента, включая номера версий или другие проприетарные данные. |
| PATH_INFO | Значение этой переменной окружения представляет дополнительную информацию о пути, следующую за именем CGI-программы. Она часто используется как параметр для CGI-программы. |
| QUERY_STRING | Если сервер передаёт информацию CGI-программе методом GET, значением этой переменной окружения является передаваемая информация. Эта информация следует за именем CGI-программы и отделяется знаком вопроса '?'. |
| REMOTE_ADDR | Значение этой переменной окружения — IP-адрес клиента, сделавшего запрос, например 192.168.1.67 выше. Это значение присутствует всегда. Это единственный идентификатор, который веб-клиент должен предоставить веб-серверу, и его можно использовать в CGI-программах для различения разных веб-клиентов. |
| REMOTE_HOST | Значение этой переменной окружения содержит имя хоста клиента, отправляющего CGI-запрос. Если вы не хотите его запрашивать, эту переменную окружения можно не определять. |
| REQUEST_METHOD | Предоставляет метод, с помощью которого был вызван сценарий. Для сценариев, использующих протокол HTTP/1.0, имеют смысл только GET и POST. |
| SCRIPT_FILENAME | Полный путь к CGI-скрипту |
| SCRIPT_NAME | Имя CGI-сценария |
| SERVER_NAME | Это имя хоста, псевдоним или IP-адрес вашего веб-сервера. |
| SERVER_SOFTWARE | Значение этой переменной окружения содержит имя и номер версии HTTP-сервера, вызвавшего CGI-программу. Например, значение выше — Apache/2.2.14(Unix) |
Ниже приведён простой CGI-скрипт, который выводит переменные среды CGI:
Пример
import os
print ("Content-type: text/html")
print ()
print ("<meta charset=\"utf-8\">")
print ("<b>Переменные среды</b><br>")
print ("<ul>")
for key in os.environ.keys():
print ("<li><span style='color:green'>%30s </span> : %s </li>" % (key,os.environ[key]))
print ("</ul>")
Сохраните вышеуказанное как test.py, установите права доступа к файлу в 755, и результат выполнения будет следующим:

Методы GET и POST
Браузерные клиенты передают информацию на сервер двумя методами: методом GET и методом POST.
Передача данных с использованием метода GET
Метод GET отправляет закодированную информацию пользователя на сервер. Данные содержатся в URL запрошенной страницы и разделяются знаком "?", как показано ниже:
http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2Некоторые другие замечания о GET-запросах:
- GET-запросы могут кэшироваться
- GET-запросы остаются в истории браузера
- GET-запросы можно добавлять в закладки
- Запросы GET не следует использовать при обработке конфиденциальных данных
- Запросы GET имеют ограничения по длине
- GET-запросы следует использовать только для получения данных
Простой пример URL: метод GET
Ниже приведён простой URL, использующий метод GET для отправки двух параметров в программу hello_get.py:
/cgi-bin/hello_get.py?name=Example&url=http://www.example.com
Ниже приведён код файла hello_get.py:
Пример
# Модуль обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получить данные
site_name = form.getvalue('name')
site_url = form.getvalue('url')
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2>%s официальный сайт: %s</h2> % (site_name, site_url))
print ("</body>")
print ("</html>")
После сохранения файла измените hello_get.py и установите права доступа к файлу 755:
chmod 755 hello_get.py
Результат вывода запроса браузера:

Простой пример формы: метод GET
Ниже представлена HTML-форма, использующая метод GET для отправки двух элементов данных на сервер. Отправляемый серверный скрипт — это также файл hello_get.py. Код hello_get.html выглядит следующим образом:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="get">
Название сайта:<input type="text" name="name"> <br />
URL сайта:<input type="text" name="url" />
<input type="submit" value=Отправить />
</форма>
</body>
</html>
По умолчанию в каталоге cgi-bin можно хранить только файлы скриптов. Мы сохраняем hello_get.html в каталоге test и изменяем права доступа к файлу на 755:
chmod 755 hello_get.html
Демонстрация GIF показана ниже:

Передача данных с использованием метода POST
Использование метода POST для передачи данных на сервер безопаснее и надежнее. Для конфиденциальной информации, такой как пароли пользователей, для передачи данных следует использовать POST.
Ниже также представлен hello_get.py. Он также может обрабатывать данные POST-формы, отправленные браузером:
Пример
# Модуль обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получить данные
site_name = form.getvalue('name')
site_url = form.getvalue('url')
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2>%s официальный сайт: %s</h2> % (site_name, site_url))
print ("</body>")
print ("</html>")
Ниже приведена форма, использующая метод POST (method="post") для отправки данных серверному скрипту hello_get.py:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="post">
Название сайта:<input type="text" name="name"> <br />
URL сайта:<input type="text" name="url" />
<input type="submit" value=Отправить />
</форма>
</body>
</html>
</форма>
Демонстрация GIF показана ниже:

Передача данных флажка через CGI-программу
Флажок используется для отправки данных одной или нескольких опций. HTML-код выглядит следующим образом:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/checkbox.py" method="POST" target="_blank">
<input type="checkbox" name="example" value="on" />Пример
<input type="checkbox" name="google" value="on" /> Google
<input type="submit" value=Выбрать сайт />
</форма>
</body>
</html>
Ниже приведён код файла checkbox.py:
Пример
# Импорт модуля обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получение данных поля
if form.getvalue('google'):
google_flag = Да
else:
google_flag = Нет
if form.getvalue('example'):
example_flag = Да
else:
example_flag = Нет
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2> Выбран ли Пример: %s</h2> % example_flag)
print (<h2> Выбран ли Google: %s</h2> % google_flag)
print ("</body>")
print ("</html>")
Измените права доступа для checkbox.py:
chmod 755 checkbox.py
GIF-демонстрация доступа через браузер:

Передача данных радиокнопки через CGI-программу
Радиокнопка отправляет на сервер только одно значение. HTML-код выглядит следующим образом:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/radiobutton.py" method="post" target="_blank">
<input type="radio" name="site" value="example" />Пример
<input type="radio" name="site" value="google" /> Google
<input type="submit" value=Отправить />
</форма>
</body>
</html>
Код скрипта radiobutton.py выглядит следующим образом:
Пример
# Импорт модуля обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получение данных поля
if form.getvalue('site'):
site = form.getvalue('site')
else:
site = Отправленные данные пусты
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2> Выбранный сайт: %s</h2> % site)
print ("</body>")
print ("</html>")
Измените права доступа для radiobutton.py:
chmod 755 radiobutton.py
GIF-демонстрация доступа через браузер:

Передача данных Textarea через CGI-программу
Textarea отправляет многострочные данные на сервер. HTML-код выглядит следующим образом:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/textarea.py" method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
Введите содержимое здесь...
</textarea>
<input type="submit" value=Отправить />
</форма>
</body>
</html>
Код скрипта textarea.py выглядит следующим образом:
Пример
# Импорт модуля обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получение данных поля
if form.getvalue('textcontent'):
text_content = form.getvalue('textcontent')
else:
text_content = Нет содержимого
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2> Введено: %s</h2> % text_content)
print ("</body>")
print ("</html>")
Измените права доступа к textarea.py:
chmod 755 textarea.py
GIF-демонстрация доступа через браузер:

Передача данных выпадающего списка через CGI-программу.
Код HTML-поля выпадающего списка выглядит следующим образом:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<form action="/cgi-bin/dropdown.py" method="post" target="_blank">
<select name="dropdown">
<option value="example" selected>Пример</option>
<option value="google">Google</option>
</select>
<input type="submit" value=Отправить/>
</форма>
</body>
</html>
Код скрипта dropdown.py выглядит следующим образом:
Пример
# Импорт модуля обработки CGI
import cgi, cgitb
# Создать экземпляр FieldStorage
form = cgi.FieldStorage()
# Получение данных поля
if form.getvalue('dropdown'):
dropdown_value = form.getvalue('dropdown')
else:
dropdown_value = Нет содержимого
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print (<title>Пример CGI-теста</title>)
print ("</head>")
print ("<body>")
print (<h2> Выбранный вариант: %s</h2> % dropdown_value)
print ("</body>")
print ("</html>")
Измените разрешения для dropdown.py:
chmod 755 dropdown.py
GIF-демонстрация доступа через браузер:

Использование куки в CGI
Существенный недостаток протокола HTTP заключается в том, что он не определяет личность пользователя, что создаёт большие неудобства для программистов. Появление функции cookie восполняет этот недостаток.
Cookie — это запись данных на жёсткий диск клиента через браузер клиента во время доступа клиента к скрипту. При следующем обращении клиента к скрипту эти данные извлекаются, что позволяет реализовать функцию идентификации личности. Cookie обычно используются для проверки личности.
Синтаксис куки
HTTP-cookie передаются через HTTP-заголовок, который предшествует передаче файла. Синтаксис заголовка Set-Cookie выглядит следующим образом:
Set-cookie:name=name;expires=date;path=path;domain=domain;secure
- name=name:Значение cookie необходимо задать (в имени нельзя использовать ";и,знак), когда есть несколько значений name, используйте;; для разделения, например:name1=name1;name2=name2;name3=name3。
- expires=date:Срок действия куки, формат: expires="Wdy,DD-Mon-YYYY HH:MM:SS"
- path=path: Задает путь, поддерживаемый cookie. Если path — это каталог, cookie действует для всех файлов и подкаталогов в этом каталоге, например: path="/cgi-bin/". Если path — это файл, cookie действует только для этого файла, например: path="/cgi-bin/cookie.cgi".
- domain=domain:Домен, для которого действителен cookie, например: domain="www.example.com"
- secure:Если указан этот флаг, это означает, что куки могут передаваться только через HTTPS-сервер с использованием протокола SSL.
- Получение cookie осуществляется путем установки переменной окружения HTTP_COOKIE. CGI-программы могут получить информацию cookie, запросив эту переменную.
Установка куки
Установка cookie очень проста. Cookie отправляются отдельно в HTTP-заголовке. В следующем примере в cookie устанавливаются name и expires:
Пример
print ('Set-Cookie: name="Example";expires=Wed, 28 Aug 2016 18:30:00 GMT')
print ('Content-Type: text/html')
print ()
print ("""
<html>
<head>
<meta charset="utf-8">
<title>Пример (example.com)</title>
</head>
<body>
<h1>Cookie успешно установлен!</h1>
</body>
</html>
""")
Сохраните приведенный выше код в cookie_set.py и измените права доступа к cookie_set.py:
chmod 755 cookie_set.py
В приведенном выше примере для установки информации cookie используется информация заголовка Set-Cookie. Необязательные элементы задают другие атрибуты cookie, такие как время истечения Expires, домен Domain, путь Path. Эта информация задается"Content-type:text/html"перед.
Получение информации Cookie
Получение информации cookie очень просто. Информация cookie хранится в переменной окружения CGI HTTP_COOKIE. Формат хранения следующий:
key1=value1;key2=value2;key3=value3....
Ниже приведена простая CGI-программа для получения информации cookie:
Пример
# Импорт модуля
import os
import http.cookies
print ("Content-type: text/html")
print ()
print ("""
<html>
<head>
<meta charset="utf-8">
<title>Пример учебника (example.com)</title>
</head>
<body>
<h1>Чтение информации из cookie</h1>
""")
if 'HTTP_COOKIE' in os.environ:
cookie_string=os.environ.get('HTTP_COOKIE')
c= http.cookies.SimpleCookie()
# c=Cookie.SimpleCookie()
c.load(cookie_string)
try:
data=c['name'].value
print ("данные cookie: "+data+"<br>")
except KeyError:
print ("Cookie не установлен или срок его действия истек<br>")
print ("""
</body>
</html>
""")
Сохраните приведенный выше код в cookie_get.py и измените права доступа cookie_get.py:
chmod 755 cookie_get.py
GIF-анимация, демонстрирующая установку cookie выше, выглядит следующим образом:

Пример загрузки файла
В HTML-формах для загрузки файлов нужно установить атрибутenctypeв значениеmultipart/form-data, код следующий:
Пример
<html>
<head>
<meta charset="utf-8">
<title>Пример руководства (example.com)</title>
</head>
<body>
<form enctype="multipart/form-data"
action="/cgi-bin/save_file.py" method="post">
<p>Выберите файл:<input type="file" name="filename" /></p>
<p><input type="submit" value="Загрузить" /></p>
</форма>
</body>
</html>
Код файла скрипта save_file.py следующий:
Пример
import cgi, os
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
# Получить имя файла
fileitem = form['filename']
# Проверить, был ли файл загружен
if fileitem.filename:
# Установить путь к файлу
fn = os.path.basename(fileitem.filename)
open('/tmp/' + fn, 'wb').write(fileitem.file.read())
message = 'Файл "' + fn + '" успешно загружен'
else:
message = 'Файл не загружен'
print ("""\
Content-Type: text/html\n
<html>
<head>
<meta charset="utf-8">
<title>Пример учебника (example.com)</title>
</head>
<body>
<p>%s</p>
</body>
</html>
""" % (message,))
Сохраните приведённый выше код в save_file.py и измените права доступа к save_file.py:
chmod 755 save_file.py
GIF-анимация, демонстрирующая установку cookie выше, выглядит следующим образом:

Если ваша система — Unix/Linux, необходимо заменить разделитель пути; в Windows достаточно использовать инструкцию open():
fn = os.path.basename(fileitem.filename.replace("\\", "/" ))
Диалоговое окно загрузки файла
Сначала создайте файл foo.txt в текущем каталоге, чтобы программа могла его скачать.
Скачивание файла реализуется путём задания информации HTTP-заголовков. Функциональный код выглядит так:
Пример
# HTTP-заголовки
print ("Content-Disposition: attachment; filename=\"foo.txt\"")
print ()
# Открыть файл
fo = open("foo.txt", "rb")
str = fo.read();
print (str)
# Закрыть файл
fo.close()