Python 3 CGI 编程

CGI,全名 Common Gateway Interface,中文通用网关接口,是描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端向一个提供网络服务的程序请求数据

CGI 是语言无关的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行

Unix shell script, Python, Ruby, PHP, perl, Tcl, C/C++, 和 Visual Basic 都可以用来编写 CGI 程序

CGI 工作原理

为了更好的了解 CGI 是如何工作的,我们需要了解在网页上点击一个链接或 URL 的简单流程

  1. 单击一个连接或者在地址栏里输入一个网站按回车键
  2. 浏览器访问 URL 并连接到 HTTP Web 服务器
  3. Web 服务器接收到请求信息后会解析 URL,并查找访问的文件在服务器上是否存在,如果存在返回文件的内容,否则返回错误信息
  4. 览器从服务器上接收信息,并显示接收的文件或者错误信息

CGI 程序可以是 Python 脚本,PERL 脚本,SHELL 脚本,C 或者 C++ 程序等

CGI 架构图

cgiarch

Web 服务器支持及配置

在我们继续进行 CGI 编程前,需要配置我们的 Web 服务器支持 CGI 和能够运行 CGI 的处理程序

配置 Apache 支持 CGI

设置 CGI 目录

ScriptAlias /cgi-bin/ /var/www/cgi-bin/

所有的 CGI 程序都应该保存在一个预先配置的目录,这个目录被称为 CGI 目录,按照惯例,它被命名为 /var/www/cgi-bin 目录

CGI 文件的扩展名为 .cgi,Python CGI 程序也可以使用 .py 为扩展名

一般情况下, Apache Web 服务器设置的运行 cgi-bin 的目录中为 /var/www

你也可以指定其它运行 CGI 脚本的目录,只要修改 httpd.conf 配置文件

<Directory "/var/www/cgi-bin">
   AllowOverride None
   Options +ExecCGI
   Order allow,deny
   Allow from all
</Directory>

最后使用 AddHandler 指令将 .cgi.pl.py 添加到 CGI 处理器

AddHandler cgi-script .cgi .pl .py

该命令是告诉 Apache Web 服务器,对于这些扩展名结尾的文件调用 CGI 处理程序

第一个 CGI 程序

配置好了程序我们就来写一个小小的 Hello World CGI 脚本练练手吧

在你的 /var/www/cgi-bin 目录下新建一个 hello.cgi 的文件,内容如下

cgi-bin/hello.cgi

#!/usr/bin/python

print ("Content-type:text/html;charset=utf-8")
print ()                              # 空行,告诉服务器结束头部,千万不能删

### 下面是网页的正文
print ('''
<!DOCTYPE html>
<meta charset="utf-8">
<title>我的第一个 CGI 程序 - 简单教程(twle.cn)</title>
<h2>Hello Word! 我是来自简单编程的第一个 CGI 程序</h2>
''')

文件保存后需要修改权限,一般修改文件权限为 0755

chmod 755 hello.cgi

打开浏览器访问 http://localhost:8000/cgi-bin/hello.cgi

如果不出任何问题,那么你会看到类似下图的结果

对了,我的 Web 服务器是运行在 8000 端口的,这个需要改成你自己的

这个 hello.cgi 只是一个简单的 Python 脚本

脚本第一行的输出内容 "Content-type:text/html;charset=utf-8" 发送到浏览器并告知浏览器显示的内容类型为 "text/html",编码格式为 "utf-8"

用 print 输出一个空行用于告诉服务器结束头部信息

对了,如果你遇到类似下面的信息,就说明没有执行权限,需要设置为 0755

HTTP 头部字段

在我们第一个 CGI 程序中已经使用 print 语句输出了

Content-type:text/html;charset=utf-8

对的,这就是一个 HTTP 的头部字段,它会发送给浏览器告诉浏览器文件的内容类型和编码格式

HTTP 头部格式一般如下

HTTP 字段名: 字段内容

例如

Content-type: text/html;charset=utf-8

下表列出了 CGI 程序中经常使用的 HTTP 头部字段

字段 描述
Content-type: 请求的与实体对应的MIME信息
例如: Content-type:text/html
Expires: Date 响应过期的日期和时间
Location: URL 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源
Last-modified: Date 请求资源的最后修改时间
Content-length: N 请求的内容长度
Set-Cookie: String 设置 HTTP Cookie

CGI 环境变量

CGI 程序中,我们还可以获取一次网页请求的请求头信息

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 这是你的 WEB 服务器的主机名、别名或IP地址。
SERVER_SOFTWARE 这个环境变量的值包含了调用CGI程序的HTTP服务器的名称和版本号。例如,上面的值为Apache/2.2.14(Unix)

我们写一个简单的 CGI 脚本来输出 CGI 的环境变量

cgi-bin/env.cgi

#!/usr/bin/python
# filename:test.py

import os

print ("Content-type: text/html;charset=utf-8")
print ('''
<!DOCTYPE html>
<meta charset="utf-8">
<title>CGI 环境变量 - 简单教程(twle.cn)</title>
<h1>环境变量</h1>
<ul>''')

for key in os.environ.keys():
    print ("<li><span style='color:green'>%30s </span> : %s </li>" % (key,os.environ[key]))

print ("</ul>")

打开浏览器访问 http://localhost:8000/cgi-bin/env.cgi,数据非常长,我只是截图了其中一小部分

如果不出任何问题,那么你会看到类似下图的结果

GET 和 POST 方法

如果你了解过 HTML ,那么显然知道, HTML 表单可以使用两种方法向服务器提交数据 : GET 方法和 POST 方法

其实我们点击一个 URL 也是使用 GET 方法的

GET 方法传输数据

GET 方法会将要发送的数据编码后,放到请求网页的 URL 上,以 "?" 号分割

https://twle.cn/cgi-bin/form_get.cgi?key1=value1&key2=value2

GET 方法有一些特性

  1. GET 请求可被缓存
  2. GET 请求保留在浏览器历史记录中
  3. GET 请求可被收藏为书签
  4. GET 请求不应在处理敏感数据时使用
  5. GET 请求有长度限制
  6. GET 请求只应当用于取回数据

下面我们就来创建一个 form_get.cgi 来接收 GET 方法传输的数据 name 和 url

/cgi-bin/form_get.cgi?name=简单编程&url=https://www.twle.cn

下面是 form_get.cgi 文件的内容

cgi-bin/form_get.cgi

#!/usr/bin/python

# CGI处理模块
import cgi, cgitb 

# 创建 FieldStorage 的实例化
form = cgi.FieldStorage() 

# 获取数据
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html;charset=utf-8")
print ()
print ('''
<!DOCTYPE html>
<meta charset="utf-8">
<title>GET 请求-简单教程(twle.cn)</title>
<p>%s官网:%s</p>''' % (site_name, site_url))

打开浏览器访问 http://localhost:8000/cgi-bin/form_get.cgi?name=简单编程&url=https://www.twle.cn,输出结果如下

HTML GET 表单

我们还可以制作一个表单使用 GET 方法提交数据

因为我们的 form_get.cgi 只接收 name 和 url 参数,那么我们就创建这个表单

get.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>GET 表单 - 简单教程 ( twle.cn )</title>
<form action="/cgi-bin/form_get.cgi">
站点名称: <input type="text" name="name" value="简单教程"><br/>
站点 URL: <input type="text" name="url" value="https://www.twle.cn"/><br/>
<input type="submit" value="提交" />
</form>

默认情况下 cgi-bin 目录只能存放脚本文件,我们将 get.html 存储在跟目录下

打开浏览器访问 http://localhost:8000/get.html,演示如下

POST 方法传递数据

POST 方法向服务器传递数据是更安全可靠的,像一些敏感信息如用户密码等需要使用 POST 传输数据

因为不会暴露在浏览器地址栏里,不会被背后偷偷走过的同时看到

在服务器端 CGI 脚本方面, GET 和 POST 方法没什么大区别,我们可以直接使用 form_get.cgi

但是为了区分,我们就将 form_get.cgi 复制一份命名为 form_post.cgi

cgi-bin/form_post.cgi

#!/usr/bin/python

# CGI处理模块
import cgi, cgitb 

# 创建 FieldStorage 的实例化
form = cgi.FieldStorage() 

# 获取数据
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html;charset=utf-8")
print ()
print ('''
<!DOCTYPE html>
<meta charset="utf-8">
<title>POST 请求-简单教程(twle.cn)</title>
<p>%s官网:%s</p>''' % (site_name, site_url))

然后我们再把 get.html 文件复制一份 post.html 将其中的 method="get" 改成 method="post"

post.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>POST 表单 - 简单教程 ( twle.cn )</title>
<form action="/cgi-bin/form_post.cgi" method="post">
站点名称: <input type="text" name="name" value="简单教程"><br/>
站点 URL: <input type="text" name="url" value="https://www.twle.cn"/><br/>
<input type="submit" value="提交" />
</form>

打开浏览器访问 http://localhost:8000/post.html,演示如下


注意看地址栏,GET 方法的数据都放在地址栏,POST 方法的没有

通过 CGI 程序传递 checkbox 表单数据

<input type="checkbox" /> 用于提交一个或者多个选项数据

checkbox.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>checkbox 表单 - 简单教程(twle.cn)</title>
<form action="/cgi-bin/checkbox.cgi" method="POST">
<input type="checkbox" checked name="简单教程" value="on" /> 简单编程
<input type="checkbox" checked name="google" value="on" /> Google
<input type="submit" value="选择站点" />
</form>

然后我们写一个 CGI 程序来接收 checkbox 表单数据

cgi-bin/checkbox.cgi

#!/usr/bin/python

# 引入 CGI 处理模块 
import cgi, cgitb 

# 创建 FieldStorage的实例 
form = cgi.FieldStorage() 

# 接收字段数据
if form.getvalue('google'):
   google_flag = "是"
else:
   google_flag = "否"

if form.getvalue('twle'):
   twle_flag = "是"
else:
   twle_flag = "否"

print ("Content-type:text/html")
print ()

print ('''
<!DOCTYPE html>
<meta charset="utf-8" >
<title>checkbox 表单 - 简单教程(twle.cn)</title>
<p> 简单编程是否选择了 : %s</p>
<p> Google 是否选择了 : %s</p>
''' %  (twle_flag, google_flag))

打开浏览器访问 http://localhost:8000/checkbox.html,演示如下

通过 CGI 程序传递 Radio 数据

<input type="radio" /> 只向服务器传递一个数据

radio.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>radio 表单 - 简单编程( twle.cn )</title>
<form action="/cgi-bin/radio.cgi" method="post">
<input type="radio" name="site" value="简单教程" /> 简单编程
<input type="radio" name="site" value="google" /> Google
<input type="submit" value="提交" />
</form>

然后我们写一个 CGI 脚本 radio.cgi 来接收表单提交的数据

#!/usr/bin/python

# 引入 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 ('''
<!DOCTYPE html>
<meta charset="utf-8" >
<title>radio 表单 - 简单教程(twle.cn)</title>
<p> 选中的网站是: %s</p>''' % site)

打开浏览器访问 http://localhost:8000/radio.html,演示如下

通过 CGI 程序传递 <textarea> 文本框多行数据

先创建一个多行文本框表单

textarea.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>多行文本框表单 - 简单教程(twle.cn)</title>
<form action="/cgi-bin/textarea.cgi" method="post">
<textarea name="textcontent" cols="40" rows="4">
简单教程
简单编程
</textarea>
<br/>
<input type="submit" value="提交" />
</form>

然后创建服务器端接收多行文本框的 CGI 脚本

cgi-bin/textarea.cgi

#!/usr/bin/python

# 引入 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 ("""
<!DOCTYPE html>
<meta charset="utf-8">
<title>多行文本框表单- 简单教程(twle.cn)</title>
<h2> 输入的内容是:</h2>
<pre>%s</pre>""" % text_content)

打开浏览器访问 http://localhost:8000/textarea.html 演示如下

通过 CGI 程序传递下拉数据 ( <select> )

先制作一个下拉表单

dropdown.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>下拉表单-简单教程(twle.cn)</title>
<form action="/cgi-bin/dropdown.cgi">
<select name="dropdown">
<option value="简单教程" selected>简单编程</option>
<option value="google">Google</option>
</select>
<input type="submit" value="提交"/>
</form>

然后创建服务器端接收下拉表单数据的 CGI 脚本

cgi-bin/dropdown.cgi

#!/usr/bin/python

# 引入 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;charset")
print ()
print ("""
<!DOCTYPE html>
<meta charset="utf-8">
<title>下拉表单 CGI - 简单教程(www.twle.cn)</title>"
<h2> 选中的选项是:%s</h2>
""" % dropdown_value)

打开浏览器访问 http://localhost:8000/dropdown.html 演示如下

CGI 中使用 Cookie

HTTP 协议是一种无状态协议,两次请求之间没有任何关系,所以 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
expires=date cookie 的有效期限
格式: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.twle.cn"
secure 如果给出此标志,表示 cookie 只能通过 SSL 协议的 https 服务器来传递

Cookie 的接收是通过 CGI 环境变量 HTTP_COOKIE 来实现的,CGI 程序可以通过检索该变量获取 Cookie 信息

Cookie 设置

Cookie 的设置非常简单,Cookie 会在 HTTP 响应头部单独发送

下面的代码设置了一个 name="www.twle.cn" 的 Cookie

cgi/cookie_set.cgi

#!/usr/bin/python
# 
print ('Content-Type: text/html;charset')
print ('Set-Cookie: name="www.twle.cn";expires=Wed, 28 Aug 2028 18:30:00 GMT')
print ()
print ("""
<!DOCTYPE html>
<meta charset="utf-8">
<title>设置 Cookie - 简单教程 ( twle.cn ) </title>
<h1>Cookie set OK!</h1>
""")

Set-Cookie 可以重复多次来实现设置多个不同属性的 Cookie

打开浏览器访问 http://localhost:8000/cgi-bin/cookie_set.cgi 输出结果如下

读取 Cookie 信息

在 Python CGI 里读取 Cookie 信息非常简单,因为 Cookie 信息都存储在 CGI 的环境变量 HTTP_COOKIE 中

存储格式如下

key1=value1;key2=value2;key3=value3....

我们可以写一个简单的 cgi 脚本演示以下

cgi-bin/cookie_get.cgi

#!/usr/bin/python

# 导入模块
import os
import Cookie

print ("Content-type: text/html;charset=utf-8")
print ()

print ("""
<!DOCTYPE html>
<meta charset="utf-8">
<title>读取 Cookie 信息 - 简单教程(twle.cn)</title>
<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)
    except KeyError:
        print ("cookie 没有设置或者已过期失效")

打开浏览器访问 http://localhost:8000/cgi-bin/cookie_get.cgi 输出结果如下

文件上传

制作一个文件上传表单,需要将 <form> 标签的 enctype 属性设置为 multipart/form-data 和将 method 属性设置为 "POST"

然后放置一个 <input type="file" name="filename" /> 标签就可以了

form_upload.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>文件上传</title>
<form enctype="multipart/form-data" action="/cgi-bin/upload.cgi" method="POST">
   <p>选中文件: <input type="file" name="filename" /></p>
   <p><input type="submit" value="上传" /></p>
</form>

然后再编写服务器端接收脚本

cgi-bin/upload.cgi

#!/usr/bin/python

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;charset=utf-8")
print ()
print ("""
<!DOCTYPE html>
<meta charset="utf-8">
<title>文件上传-简单教程</title>
<p>%s</p>
""" % (message,))

如果你的 Window 系统,那么要使用下面的语句替换文件分隔符

fn = os.path.basename(fileitem.filename.replace("\\", "/" ))

运行以上范例,演示如下

文件下载对话框

下载文件需要设置 Content-Disposition HTTP 响应头

Content-Disposition: attachment; filename="文件名"

我们先在当前目录下创建一个文件 demo.txt 内容如下

简单教程,简单编程

然后就可以用下面的代码实现下载 demo.txt 文件

cgi-bin/download.cgi

#!/usr/bin/python

# 设置 HTTP 头部
print ('Content-Disposition: attachment; filename="demo.txt"')
print ()
print (open("demo.txt", "rb").read())

打开浏览器访问 http://localhost:8000/cgi-bin/download.cgi 就能下载 demo.txt 文件了

Python3 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.