Python Web 编程 - CGI

所谓的动态网页就是会根据某些变量,渲染出不同的页面,曾经的 Web 后端开发主要就是通过服务端脚本生成 HTML 文档给用户,但是这已经过时了,现在流行前后端分离的架构,后端只提供 API,前端通过调用后端 API 获取数据,因为现在前端可以做很多事,是门大学问,坑也非常多,这里就不说了,本文主要介绍已经过时的 Web 开发的基础, CGI 协议

嗯,我在胡说八道

不知道该写啥,随便看个例子吧

cgi 脚本一般通过 HTTP Server 调用,所以我先装个 httpd

$ yum install -y httpd	# centos 大法好

httpd 默认把 cgi 脚本放在 /var/www/cgi-bin

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# test.py
"""
第一行必须得写指定的解释器,因为 cgi 脚本并不限定语言,所以必须指定
"""
print('Content-type: text/html') # 设置 content-type 为 text/html
print('') # 结束头部,得打印空行

print('<h1>Life is short</h1>') # 这就是输出的 html

来,试一试

$ chmod +x test.py # 加个执行权限,当前在 cgi-bin 目录下

$ curl localhost/cgi-bin/test.py
<h1>Life is short</h1> # 实在懒得开浏览器截图了,直接用 curl

说实话,第一次写这个的时候感觉 low 到爆

cgi 模块

如果需要传递参数,提交表单啊 啥的就需要 cgi 模块来搞定了

传递参数

先看看怎么用吧

#!/usr/bin/env python
# cgi-module.py

import cgi

print('')

args = cgi.FieldStorage()
print(args)

发送带参数的请求试试

$ curl 'localhost/cgi-bin/cgi-module.py?name=AnyISalIn&email=anyisalin@gmail.com'
FieldStorage(None, None, [MiniFieldStorage('name', 'AnyISalIn'), MiniFieldStorage('email', 'anyisalin@gmail.com')])

通过传递的参数渲染页面

可以通过 getvalue 方法来获取参数的值

#!/usr/bin/env python
# args2.py

import cgi

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

args = cgi.FieldStorage()
print('<h1>Hello {}, Website {}</h1>'.format(args.getvalue('name'), args.getvalue('url')))

测试,多个参数之前用 & 符号分隔

$ curl 'localhost/cgi-bin/args2.py?name=aaa&url=http://anyisalin.github.io'
<h1>Hello aaa, Website http://anyisalin.github.io</h1>

GET 表单测试

文档路径为 /var/www/html/form1.html

<form action="/cgi-bin/args2.py" method="GET">
name: <input type="text" name="name"/>
url: <input type="text" name="url"/>
<input type="submit" value="submit"/>
</form>

输入参数

form1

点击 submit

form2

POST 表单测试

通过 POST 传递数据更加的安全,密码之类的字段不会明文显示在请求 url 上

文档路径为 /var/www/html/form2.html

<form action="/cgi-bin/args3.py" method="POST">
name: <input type="text" name="name"/>
url: <input type="text" name="url" />
password: <input type="password" name="password"/>
<input type="submit" value="submit"/>
</form>

建一个 args3.py 的 cgi 脚本,简单的登录支持

#!/usr/bin/env python
# args3.py

import cgi

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

args = cgi.FieldStorage()
print('<h1>Hello {}, Website {}</h1>'.format(args.getvalue('name'), args.getvalue('url')))

if args.getvalue('password') == 'password':
print('Login Pass')
else:
print('Deny Login')

故意输错密码

拒绝登录

密码输入正确,登录成功

上传文件

还是通过表单的方式上传文件

文档路径为 /var/www/html/form3.html

<form enctype="multipart/form-data" action="/cgi-bin/upfile.py" method="POST">
select file: <input type="file" name="file" />
<input type="submit" value="submit" />
</form>

建一个 upload 目录来存上传的文件,并设置所有者为 apache 用户

$ pwd
/var/www/cgi-bin

$ install -d -g apache -o apache upload

编写一个 upfile.py 的 cgi 脚本

#!/usr/bin/env python
# upfile.py

import cgi, os

args = cgi.FieldStorage()

print('')

fileitem = args['file']

if fileitem.filename:
f = os.path.join('upload', fileitem.filename)
with open(f, 'wb') as fd:
fd.write(fileitem.file.read())
print('upload success')

选择文件

点击上传

查看 upload 目录

$ ls upload/
export.json

异常捕获

如果我们的 cgi 脚本运行出错,一般情况下 HTTP Server 会抛出 500 错误,但是我们可能希望 HTTP Server 能够准确的抛出异常,供我们排错,这时候就要用到 cgitb 这个模块了

#!/usr/bin/env python
# traceback.py

import cgi
import cgitb

cgitb.enable()

print('')

print('hello, world', aaaa) #故意打错

请求一下页面试试

CGI 的一些问题

有几个比较关键的问题

  • 所有脚本都必须有执行权限
  • 不同操作系统的换行符可能会有问题
  • 要使用特定的扩展名去访问
  • cgi 脚本中必须指定解释器
  • 性能不行

在 Python 中解决这些问题可能就需要用到 WSGI

参考资料:

Python CGI | 菜鸟教程

Python doc CGI

HOWTO Use Python in the web