CGI-программирование на Python
Что такое CGI?
CGI в настоящее время поддерживается NCSA, и NCSA определяет CGI следующим образом:
CGI (Common Gateway Interface) — это программа, которая выполняется на сервере, например на HTTP-сервере, и обеспечивает интерфейс с клиентскими HTML-страницами.
Примечание:
Начиная с Python 3.11 модуль cgi официально помечен как "Deprecated" и был удалён начиная с Python 3.13.
Рекомендуется перейти наFlask、DjangoилиFastAPIи другие современные веб-фреймворки.
Просмотр веб-страниц
Чтобы лучше понять, как работает CGI, можно рассмотреть процесс перехода по ссылке или URL на веб-странице:
- 1. Используйте браузер, чтобы открыть URL и подключиться к веб-серверу HTTP.
- 2. После получения информации о запросе веб-сервер анализирует URL и проверяет, существует ли запрошенный файл на сервере. Если он существует, возвращается содержимое файла; в противном случае возвращается сообщение об ошибке.
- 3. Браузер получает информацию от сервера и отображает полученный файл или сообщение об ошибке.
Программы CGI могут быть скриптами Python, скриптами Perl, Shell-скриптами, программами на C или C++ и т. д.
Схема архитектуры CGI

Поддержка и настройка веб-сервера
Прежде чем начать программирование CGI, убедитесь, что ваш веб-сервер поддерживает CGI и что обработчик CGI настроен.
Apache поддерживает настройку CGI:
Настройте каталог CGI:
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
CGI-программы, выполняемые всеми HTTP-серверами, хранятся в предварительно настроенном каталоге. Этот каталог называется каталогом CGI, и по соглашению ему присвоено имя /var/www/cgi-bin.
Расширение файла для CGI —.cgi, Python также может использовать расширение.py.py.
По умолчанию каталог cgi-bin, настроенный для работы на серверах Linux, — /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, со следующим содержимым:
#!/usr/bin/python # -*- coding: UTF-8 -*- print "Content-type:text/html" print # 空行,告诉服务器结束头部 print '<html>' print '<head>' print '<meta charset="utf-8">' print '<title>Hello World - 我的第一个 CGI 程序!</title>' print '</head>' print '<body>' print '<h2>Hello World! 我是来自Example的第一CGI程序</h2>' print '</body>' print '</html>'
После сохранения файла отредактируйте hello.py и измените его права доступа к файлу на 755:
chmod 755 hello.py
Откройте вышеуказанную программу в браузере по адресуhttp://localhost/cgi-bin/hello.pyОтображаемый результат выглядит следующим образом:
Hello World! 我是来自Example的第一CGI程序
Этот скрипт 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 выше. Это значение присутствует всегда. Более того, это уникальный идентификатор, который Web-клиент должен предоставить Web-серверу, и его можно использовать в CGI-программе для различения разных Web-клиентов. |
| REMOTE_HOST | Значение этой переменной окружения содержит имя хоста клиента, отправляющего CGI-запрос. Если он не поддерживает запрос, который вы хотите выполнить, эту переменную окружения можно не определять. |
| REQUEST_METHOD | Предоставляет метод, с помощью которого вызывается скрипт. Для скриптов, использующих протокол HTTP/1.0, имеют смысл только GET и POST. |
| SCRIPT_FILENAME | Полный путь к CGI-скрипту |
| SCRIPT_NAME | Имя CGI-сценария |
| SERVER_NAME | Это имя хоста, псевдоним или IP-адрес вашего WEB-сервера. |
| SERVER_SOFTWARE | Значение этой переменной окружения содержит имя и номер версии HTTP-сервера, который вызывает CGI-программу. Например, приведённое выше значение — Apache/2.2.14 (Unix). |
Ниже приведён простой CGI-скрипт, который выводит переменные среды CGI:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.py
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/test.py?name=Example&url=http://www.example.com
Ниже приведён код файла hello_get.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.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>Example 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 выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(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="提交" /> </form> </body> </html>
По умолчанию каталог cgi-bin может хранить только файлы сценариев. Мы сохраняем hello_get.html в каталоге test и изменяем права доступа к файлу на 755:
chmod 755 hello_get.html
Демонстрация GIF показана ниже:

Использование метода POST для передачи данных
Использование метода POST для передачи данных на сервер безопаснее и надёжнее. Конфиденциальную информацию, такую как пароли пользователей, необходимо передавать с помощью POST.
Ниже также приведён hello_get.py, который также может обрабатывать данные POST-формы, отправленные браузером:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 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>Example 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:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(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="提交" /> </form> </body> </html>
Демонстрация GIF показана ниже:

Передача данных флажков через CGI-программу
Флажок используется для отправки данных для одного или нескольких вариантов. HTML-код выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(example.com)</title> </head> <body> <form action="../cgi-bin/checkbox.py" method="POST" target="_blank"> <input type="checkbox" name="example" value="on" /> Example <input type="checkbox" name="google" value="on" /> Google <input type="submit" value="选择站点" /> </form> </body> </html>
Ниже приведён код файла checkbox.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 引入 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>Example CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> Example是否选择了 : %s</h2>" % example_flag
print "<h2> Google 是否选择了 : %s</h2>" % google_flag
print "</body>"
print "</html>"
Измените разрешения checkbox.py:
chmod 755 checkbox.py
Демонстрация доступа через браузер (GIF):

Передача данных радиокнопок через CGI-программу
Радиокнопка передаёт на сервер только один фрагмент данных. HTML-код выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(example.com)</title> </head> <body> <form action="../cgi-bin/radiobutton.py" method="post" target="_blank"> <input type="radio" name="site" value="example" /> Example <input type="radio" name="site" value="google" /> Google <input type="submit" value="提交" /> </form> </body> </html>
Код скрипта radiobutton.py выглядит следующим образом:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 引入 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>Example CGI 测试实例</title>"
print "</head>"
print "<body>"
print "<h2> 选中的网站是 %s</h2>" % site
print "</body>"
print "</html>"
Измените разрешения radiobutton.py:
chmod 755 radiobutton.py
Демонстрация доступа через браузер (GIF):

Передача данных текстовой области через CGI-программу
Текстовая область передаёт на сервер многострочные данные. HTML-код выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(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="提交" /> </form> </body> </html>
Код скрипта textarea.py выглядит следующим образом:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 引入 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>Example 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-поля выпадающего списка выглядит следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example(example.com)</title> </head> <body> <form action="../cgi-bin/dropdown.py" method="post" target="_blank"> <select name="dropdown"> <option value="example" selected>Example</option> <option value="google">Google</option> </select> <input type="submit" value="提交"/> </form> </body> </html>
Код скрипта dropdown.py показан ниже:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 引入 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>Example 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 нельзя использовать знаки ";" и ","). Если значений 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 очень проста. Cookie отправляется отдельно в HTTP-заголовке. В следующем примере в cookie задаются name и expires:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#
print 'Content-Type: text/html'
print 'Set-Cookie: name="Example";expires=Wed, 28 Aug 2016 18:30:00 GMT'
print
print """
<html>
<head>
<meta charset="utf-8">
<title>Example(example.com)</title>
</head>
<body>
<h1>Cookie set OK!</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:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 导入模块
import os
import Cookie
print "Content-type: text/html"
print
print """
<html>
<head>
<meta charset="utf-8">
<title>Example(example.com)</title>
</head>
<body>
<h1>读取cookie信息</h1>
"""
if 'HTTP_COOKIE' in os.environ:
cookie_string=os.environ.get('HTTP_COOKIE')
c=Cookie.SimpleCookie()
c.load(cookie_string)
try:
data=c['name'].value
print "cookie data: "+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, и код выглядит следующим образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example(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>
</form>
</body>
</html>
Код файла сценария save_file.py выглядит следующим образом:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
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(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-заголовков. Функциональный код выглядит так:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# HTTP 头部
print "Content-Disposition: attachment; filename=\"foo.txt\"";
print
# 打开文件
fo = open("foo.txt", "rb")
str = fo.read();
print str
# 关闭文件
fo.close()
Другие расширения