19.5 处理输入
多数想建立动态网站的人都会想和用户交互。有两种取得用户输入的方法:通过表单(或类似表单的东西)字段和URL的附加部分。如果您已经学习了第18章(关于CGI),您就会清楚CGI也提供同样的选择(如果您对客户端感兴趣,第6章介绍了从客户端提交数据)。为了帮您理解在mod_python中输入是如何工作的,我修改了第18章中的CGI例子。如果您想从CGI和mod_python中选择一个,那么通过对比这两个例子,就可以看出这两种技术的区别。
19.5.1 附加的URL部分
在CGI中,您可以在URL的Python处理程序名后面加上任何您喜欢的部分。附加的部分在对象中保存为req.path_info,并传递给handler()函数。下面是把这个CGI例子中的函数改写为标准mod_python的程序。这个例子会询问您今天的日期,代码如下:
# mod_python path_info example -- Chapter 19 -- pathinfo.py
from mod_python import apache
import time
monthmap = {1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May',
6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October',
11: 'November', 12: 'December'}
daymap = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday',
4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
def getscriptname(req):
if not len(req.path_info):
return req.uri
return req.uri[:-len(req.path_info)]
def month_quiz(req):
req.write("What month is it?<P>\n")
for code, name in monthmap.items():
req.write('<A HREF="%s/%d">%s</A><BR>' % (getscriptname(req),
code, name))
def day_quiz(req):
month = time.localtime()[1]
req.write("What day is it?<P>\n")
for code, name in daymap.items():
req.write('<A HREF="%s/%d/%d">%s</A><BR>' % (getscriptname(req),
month, code, name))
def check_month_answer(req, answer):
month = time.localtime()[1]
if int(answer) == month:
req.write("Yes, this is <B>%s</B>.<P>\n" % monthmap[month])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
month_quiz(req)
return 0
def check_day_answer(req, answer):
day = time.localtime()[6]
if int(answer) == day:
req.write("Yes, this is <B>%s</B>.\n" % daymap[day])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
day_quiz(req)
return 0
def handler(req):
req.content_type = "text/html"
if req.header_only:
return apache.OK
req.write("""<HTML>
<HEAD>
<TITLE>mod_python PATH_INFO Example</TITLE></HEAD><BODY>""")
input = req.path_info.split('/')[1:]
if not len(input):
month_quiz(req)
elif len(input) == 1:
ismonthright = check_month_answer(req, input[0])
if ismonthright:
day_quiz(req)
else:
ismonthright = check_month_answer(req, input[0])
if ismonthright:
check_day_answer(req, input[1])
req.write("\n</BODY></HTML>\n")
return apache.OK
让我们来看一下这个程序,您会看到它和第18章中的pathinfo.cgi非常类似。事实上,这里唯一的改动是从req中取得数据,而不是环境变量,而且使用req.write()把数据传回客户端。
程序会在URL的脚本名后面添加信息。这些新的数据会被Apache传入req.path_info,而且可以被程序使用。通过这些新的数据,程序可以确定用户的输入并产生适当的应答。
如果您用过本章前面介绍的Apache配置文件的例子,您就可以通过http://localhost/py/ pathinfo.prog这个地址来运行该例子。首先,您将被问到当前是几月。当您回答正确后,您将被问今天是星期几。在您正确回答这两个例子后,您会看到一条确认您选择的信息。
为什么不是CGI处理程序
mod_python的发行版本中包含一个CGI处理程序,它设计成仿效传统的Python CGI环境。然而,由于Apache mod_python环境和CGI环境还是有很大不同的,所以这个仿效还有些缺点,而且事实上,最主要的是它失去了使用mod_python的好处。因此mod_python的作者和我都不建议使用mod_python中的CGI处理程序,如果您将要使用mod_python,那么最好更新代码使用纯mod_python,不要使用其中的CGI处理程序。
19.5.2 GET方法
不用添加一个虚拟文件路径,GET方法可以用来把数据传递给程序。GET方法会在URL后面编码参数。您可以手工构造URL,或是使用HTML表单来让浏览器根据输入为您构造。下面
是用GET方法改写前面例子的程序。和前面的例子一样,这个例子会根据今天的日期产生一个问题:
# mod_python GET example -- Chapter 19 -- get.py
from mod_python import apache, util
import time
monthmap = {1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May',
6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October',
11: 'November', 12: 'December'}
daymap = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday',
4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
def month_quiz(req):
req.write("What month is it?<P>\n")
for code, name in monthmap.items():
req.write('<A HREF="%s?month=%d">%s</A><BR>' % (req.uri,
code, name))
def day_quiz(req):
month = time.localtime()[1]
req.write("What day is it?<P>\n")
for code, name in daymap.items():
req.write('<A HREF="%s?month=%d&day=%d">%s</A><BR>' % \
(req.uri, month, code, name))
def check_month_answer(req, answer):
month = time.localtime()[1]
if int(answer) == month:
req.write("Yes, this is <B>%s</B>.<P>\n" % monthmap[month])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
month_quiz(req)
return 0
def check_day_answer(req, answer):
day = time.localtime()[6]
if int(answer) == day:
req.write("Yes, this is <B>%s</B>.\n" % daymap[day])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
day_quiz(req)
return 0
def handler(req):
req.content_type = "text/html"
if req.header_only:
return apache.OK
req.write("""<HTML>
<HEAD>
<TITLE>mod_python GET Example</TITLE></HEAD><BODY>""")
form = util.FieldStorage(req)
if form.getfirst('month') == None:
month_quiz(req)
elif form.getfirst('day') == None:
ismonthright = check_month_answer(req, form.getfirst('month'))
if ismonthright:
day_quiz(req)
else:
ismonthright = check_month_answer(req, form.getfirst('month'))
if ismonthright:
check_day_answer(req, form.getfirst('day'))
req.write("</BODY></HTML>\n")
return apache.OK
在这里,我使用了mod_python的util.FieldStorage类来解析GET请求。这个类被设计成尽可能和CGI的FieldStorage类兼容。事实上,在第18章中,使用GET的CGI例子中的表单不用做任何修改,就可以用在这里。对于这个程序来说,它不关心数据是由表单提交的,还是像这个例子中,通过手工产生的URL提交的。
还请注意,在这个程序中,req.uri保存着Python脚本本身的名称,这和pathinfo.py那个例子不同。当使用pathinfo输入的风格,Apache虽然不从req.uri中取得输入的数据,但是它会在您使用表单提交GET方法时这样做。因此,这个例子不需要getscriptname()函数。
如果您用过本章前面介绍的Apache配置文件的例子,您就可以通过http://localhost/py/ get.prog这个地址来运行这个例子。接口将和pathinfo.py那个例子一样。
19.5.3 POST方法
POST方法通过外在方式取得HTML表单提交的数据。相对于GET方法,它的主要优点是可以处理大容量的数据。有时,不能把POST结果添加到地址簿也是一个优点,比如您正处理敏感数据的时候。下面是另外一个版本询问月份和日期的例子,这次使用的是mod_python的POST方法,代码如下:
# mod_python POST example -- Chapter 19 -- post.py
from mod_python import apache, util
import time
import cgi, time, os
monthmap = {1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May',
6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October',
11: 'November', 12: 'December'}
daymap = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday',
4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
def month_quiz(req):
req.write("What month is it?<P>\n")
req.write('<FORM METHOD="POST" ACTION="%s">' % req.uri)
for code, name in monthmap.items():
req.write('<INPUT NAME="month" TYPE="radio" VALUE="%d"> %s<BR>' % \
(code, name))
req.write('<INPUT TYPE="submit" NAME="submit" VALUE="Next >>">')
req.write("</FORM>\n")
def day_quiz(req):
month = time.localtime()[1]
req.write("What day is it?<P>\n")
req.write('<FORM METHOD="POST" ACTION="%s">' % req.uri)
req.write('<INPUT TYPE="hidden" NAME="month" VALUE="%d">' % month)
for code, name in daymap.items():
req.write('<INPUT NAME="day" TYPE="radio" VALUE="%d"> %s<BR>' % \
(code, name))
req.write('<INPUT TYPE="submit" NAME="submit" VALUE="Next >>">')
req.write("</FORM>\n")
def check_month_answer(req, answer):
month = time.localtime()[1]
if int(answer) == month:
req.write("Yes, this is <B>%s</B>.<P>\n" % monthmap[month])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
month_quiz(req)
return 0
def check_day_answer(req, answer):
day = time.localtime()[6]
if int(answer) == day:
req.write("Yes, this is <B>%s</B>.\n" % daymap[day])
return 1
else:
req.write("Sorry, you're wrong. Try again:<P>\n")
day_quiz(req)
return 0
def handler(req):
req.content_type = "text/html"
if req.header_only:
return apache.OK
req.write("""<HTML>
<HEAD>
<TITLE>mod_python POST Example</TITLE></HEAD><BODY>""")
form = util.FieldStorage(req)
if form.getfirst('month') == None:
month_quiz(req)
elif form.getfirst('day') == None:
ismonthright = check_month_answer(req, form.getfirst('month'))
if ismonthright:
day_quiz(req)
else:
ismonthright = check_month_answer(req, form.getfirst('month'))
if ismonthright:
check_day_answer(req, form.getfirst('day'))
req.write("</BODY></HTML>\n")
return apache.OK
这个程序和GET版本的例子使用完全一样的逻辑。事实上,唯一改变的地方是产生选择菜单的HTML代码。提交POST数据的方法和CGI脚本一样还要感谢FieldStorage的接口兼容性。
既然mod_python的FieldStorage很大程度上兼容CGI的FieldStorage,在CGI那章所介绍的处理数据的原则同样也适用于mod_python。
如果您用过本章前面介绍的Apache配置文件的例子,您就可以通过载入http://localhost/py/ post.prog来运行这个例子。







