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

cgiarch


Поддержка и настройка веб-сервера

Прежде чем начать программирование 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()
Другие расширения