20.5 建立CGI应用程序
20.5.1 建立Web服务器
为了可以用Python进行CGI开发,你首先需要安装一个Web服务器,将其配置成可以处理Python CGI请求的模式,然后让你的Web服务器访问CGI脚本。其中有些操作你也许需要获得系统管理员的帮助。
如果你需要一个真正的Web服务器,可以下载并安装Aphache. Aphache的插件或模块可以处理Python CGI,但这在我们的例子里并不是必要的。如果你准备把自己的服务“带入真实世界”,也许会想安装这些软件,尽管它们似乎过于强大。
为了学习的目的或者是建立小型的Web站点,使用Python自身带的Web服务器就已经足够了。在第20.8节,你将会实际地学习如何建立和配置简单的基于Python的Web服务器。如果你想在本阶段获得更多知识,也可以现在提前阅读那部分。然而,这并不是本章的焦点。
如果你只是想建立一个基于Web的服务器,可以直接执行下边的Python语句。
-m选项是在2. 4中新引进的,如果你使用的是比这旧的Python版本,或者想看一下它执行的不同方式,请看14.4.3节。无论如何,最终它需要工作起来。
这将会在当前机器的当前目录下建立一个端口号为8000的Web服务器,然后可以在启动这个服务器的目录下建立一个Cgi-bin,将Python CGI脚本放到那里。将一些HTML文件放到那个目录下,或许有些。py CGI脚本在Cgi-bin中,然后就可以在地址栏中输入这些地址来访问Web站点了。
http://localhost:8000/friends.htm
http://localhost:8000/cgi-bin/friends2.py
20.5.2 建立表单页
在例20.3中,我们写了一个简单的Web表单friends.html。
正如你可以在代码中看到的一样,这个表单包括两个输入变量:person和howmany,这两个值将会被传到我们的CGI脚本friendsl.py中。
你会注意到在例子中我们将CGI脚本初始化到主机默认的cgi-bin目录下(“Action”连接)(如果这个信息与你开发环境不一样的话,在测试Web页面和CGI之前请更新你的表单事件)。同时由于表单事件中缺少METHOD子标签,所有的请求将会采用默认的GET方法。选择GET方法是因为我们的表单没有太多的字段,同时我们希望我们的请求字段可以在“位置”(aka “Address”, “Go To”)条中显示,以便你可以看到被送到服务器端的URL。
例20.3 静态表单页(friends. htm)
这个HTML文件展示给用户一个空文档,含有用户名和一系列可供用户选择的单选按钮。
让我们看看friends.htm提交后在客户端屏幕上的显示(图20-4 Safari, MacOS和图20-5 IE6)
图 20-4 Friends表单页在Mac OS X操作系统Safari浏览器上的显示(friends.htm)
图 20-5 friends表单页面在Win32操作系统IE 6浏览器上的显示(friends.htm)
通过本章,我们将会展示来自不同Web浏览器和操作系统的屏幕截图。
20.5.3 生成结果页
这些输入是由用户完成的,然后按下了“Submit”按钮(可选的,用户也可以在该文本字段中按下回车键获得相同的效果)。当这些发生后,在例20.4中的脚本,friendsl.py将会随CGI一起被执行。
这个脚本包含了所有的编程功能,读出并处理表单的输入,同时向用户返回结果HTML页面。所有的这些“实际”的工作仅是通过4行Python代码来实现的(14〜17行).
表单的变量是FieldStorage的实例,包含person和howmanyh字段的值。我们把这些值本分别存入Python的who和howmany变量中。变量reshtml包含需要返回的HTML文本的正文,还有一些动态填好的字段,这些数据都是从表单中读入的。
例20.4 CGI代码结果示图(friendsl.py)
CGI脚本在表单上抓取person和howmany字段,并用这些数据生成动态的结果示图。
核心提示:HTML头文件是从HTML中分离出来的
有一点需要向CGI初学者指明的是,在向CGI脚本返回结果时,须先返回一个适当的HTTP头文件后才会返回结果HTML页面。进一步说,为了区分这些头文件和结果HTML页面,需要在friendsl.py的第5行中插入几个换行符。在本章后边的代码中也是这样处理的。
图20-6是可能出现的屏幕显示,假设用户输入的名字为“erick allen”,单击“10friends”单选按钮。这次的屏幕截图展示的是在Windows环境下IE 3浏览器中的效果。
图 20-6 Friends的结果页面在Win32操作系统IE 3浏览器上的显示
如果你是一个Web站点的生产商,你也许会想,“如果这个人忘记了的话,我能自动地将这个人的名字大写,会不会更好些? ”这个通过Python的CGI可以很容易地实现(我们很快就会进行试验!)。
注意GET请求是如何将表单中的变量和值加载在URL地址条中的。你是否观察到了friends. htm页面的标题有个“static”,而friends.py脚本输出到屏幕上的则是“dynamic”?我们这样做的一个原因就是:指明friends. htm文件是一个静态的文本,而结果页面却是动态生成的。换句话说,结果页面的HTML不是以文本文件的形式存在硬盘上的,而是由我们的CGI脚本生成的,并且将其以本地文件的形式返回。
在下边的例子中,我们将会更新我们的CGI脚本,使其变得更灵活些,从而完全绕过静态文件。
20.5.4 生成表单和结果页面
我们删除fiends. html文件并将其合并到friends2.py中。这个脚本现在将会同时生成表单页和结果页面。但是我们如何控制生成哪个页面呢?好吧,如果有表单数据被发送,那就意味着我们需要建立一个结果页面。如果我们没有获得任何的信息,这就说明我们需要生成一个用户可以输入数据的表单页面。
例20.5展示的就是我们的新脚本friends2.py。
那么我们改变了哪些脚本呢?让我们一起看下这个脚本的代码块。
逐行解释
1 ~ 5行
除了通常的起始和模块导入行,我们还把HTTP MTMI头从后面的HTML正文部分分离出来,放在了这里。因为我们将在返回的两种页面(表单页面和结果页面)中都使用它,而又不想重复写文本。当需要输出时,我们将把这个头字串加在相应的HTML正文中。
7 ~ 29行
所有这些代码都是为了整合CGI脚本里的friends.htm表单页面。我们对表单页面的文本使用一个变量formhtml,还有一个用来创建单选按钮的字符串变量fradio。我们从friends. htm复制了这个单选按钮HTML文本,但我们意在展示如何使用Python来生成更多的动态输出——见22〜27行的for循环。
showForm()函数负责对用户输入生成表单页。它为单选按钮创建了一个文字集,并把这些HTML文本行合并到了formhtml主体中,然后给表单加上头信息,最后通过把整个字符串输出到标准输出的方式给客户端返回了整块数据。
例20.5 生成表单和结果页面(friends2.py)
将friends.html和friendsl.py合并成friends2.py。得到的脚本可以同时显示表单和动态生成的HTML结果页面,同时可以巧妙地知道应该输出哪个页面。
这段代码中有两件有趣的事值得注意。第一点是表单中12行action处的“hidden”变量,这里的值为“edit”。我们决定显示哪个页面(表单页面或是结果页面)的唯一途径是通过这个字段。我们将在第53~56行看到这个字段如何起作用。
还有,请注意我们在生成所有按钮的循环里把单选按钮0设置为默认按钮。这表明我们可以在一行代码里(第18行)更新单选按钮的布局和/或它们的值,而不用再写多行文字。这也同时提供了更多的灵活性,可以用逻辑来判断哪个单选按钮被选中,见我们脚本的下一个升级版,后面的friends3.py。
现在你或许会想“既然我也可以选择person或howmany是否出现,那为什么我们要用一个action变量呢? ”这是一个很好的问题,因为在这种情况下你当然可以只用person或hwomany。
然而,action变量代表了一种更明显的出现,不光是它的名字还有它的作用,其代码很容易理解。person和howmany变量都是对其值起作用,而action变量则被用作一个标志。
创立action的另一个原因是我们将会再一次使用它来帮助我们决定生成哪一页。具体来说,我们需要在person变量出现时会显示一个表单(而不是生成结果页面)——如果在这里仅依赖person变量,你的代码运行将失败。
31 ~ 39行
显示结果页的代码实际上和friendsl.py中的一样。
41 ~ 56行
因为这个脚本可以产生出不同的页面,所以我们创建了一个包括一切的process()函数来获得表单数据并决定采用何种动作。看起来process()的主体部分也和friendsl.py中主体部分的代码相似。然而它们有两个主要的不同。
因为这个脚本也许可以,也许不能取得所期待的字段(例如,第一次运行脚本时生成一个表单页,这样的话就不会给服务器传递任何字段),我们需要用if语句把从表单项取得的值“括起来”,并检查它们此时是否有效。还有我们上面提到的action字段,它可以帮助我们判定应生成哪一个页面。第53〜56行作了这种判定。
在图20-7和图20-8中,你会先看到脚本生成的表单页面(已经输入了一个名字并选择了一个单选按钮),然后是结果页面,也是这个脚本生成的。
图 20-7 Friends表单页面在Win32操作系统Firefox1.x浏览器上的显示(friends2.py)
图 20-8 Friends结果页面在Win32操作系统Firefox浏览器上的显示(friends2.py)
如果看一下位置或“转到”栏,你将不会看到一个对friends. htm静态文件的URL,而在图20-4和图20-5中都有。
20.5.5 全面交互的Web站点
我们最后一个例子将会完成这个循环。如在前面中,用户在表单页中输入他的信息,然后我们处理这些数据,并输出一个结果页面。现在我们将会在结果页面上加个链接允许返回到表单页面,但是我们返回的是含有用户输入信息的页面而不是一个空白页面。我们页加上了一些错误处理程序,来展示它是如何实现的。
现在在例子20.6中我们展示我们最后的更新,friends3.py。
friends3.py和friends2.py没有太大的不同。我们请读者比较不同处;这里我们简要的介绍了主要的不同点。
简略的逐行解释
第8行
我们把URL从表单中抽出来是因为现在有两个地方需要它,结果页面是它的新顾客。
10 ~ 19行、69 ~ 71行、75 ~ 82行
所有这些行都用来处理新特性——错误页面。如果用户没有选择单选按钮,指明朋友数量,那么howmany字段就不会传送给服务器,在这种情况下,showError()函数会返回一个错误页面给客户。
错误页面的显示使用了JavaScript的“后退”按钮。因为按钮都是输入类型的,所以需要一个表单,但不需要有动作,因为我们只是简单地后退到浏览器历史中的上一个页面。尽管我们的脚本目前只支持(或者说探测、测试)一种类型的错误,但我们仍然使用了一个通用的error变量,这是为了以后还可以继续开发这个脚本,给它增加更多的错误检测。
例20.6 全用户交互和错误处理(friends3.py)
通过加上返回输入信息的表单页面的连接,我们实现了整个循环,给了用户一次完整的Web应用体验。我们的应用程序现在也进行了一些简单的错误验证,在用户没有选择任何单选按钮时,可以通知用户。
第27行、38 ~ 41行、49行、52 ~ 55行
这个脚本的一个目的是创建一个有意义的链接,以便从结果页面返回表单页面。当有错误发生时,用户可以使用这个链接返回表单页面去更新他/她填写的数据。新的表单页面只有当它包含了用户先前输入的信息时才有意义(如果让用户重复输入这些信息会很令人沮丧)。
为了实现这一点,我们需要把当前值嵌入到更新过的表单中。在第27行,我们给name新增了一个值。这个值如果给出的话,会被插入到name字段。显然,在初始表单页面上它将是空值。第38〜41行,我们根据当前选定的朋友数目设置了单选按钮。最后,通过第49行和第52〜55行更新了的doResults()函数,我们创建了这个包含已有信息的链接,它会让用户“返回”到我们更改后的表单页面。
62行
最后我们从美学角度上加了一个简单的特性。在friendsl.py和friends2.py的截屏中,可以看到返回结果和用户的输入一字不差。在上述的截屏中,如果用户的名字没有大写这将影响返回的页面。我们加了一个对string.capwords()函数的调用从而自动的将用户名置成大写。capwords()函数可以将传进来的每个单词的第一个字母置成大写的。这也许是或许不是必要的特性,但是我们还是愿意一起分享它,以便你知道这个功能的存在。
下边我们将会展示4个截屏,表明用户和CGI表单及脚本的交互过程。
在图20-9中,我们调用friends3.py生成了一个熟悉的新表单页面。输入”fool bar”,同时故意忘记检查单选按钮。单击Submit按钮后将会返回错误页面,请看图20-10。
图 20-9 Friends的初始表单页面在MacOS X操作系统Camino浏览器上的显示(friends3.py)
图 20-10 Friends的错误页面(无效的用户输入)在Camino浏览器上的显示(Friends3.py)
我们单击“后退”按钮,选择“50”单选按钮,重新提交表单。结果页面如图20-11所示,看起来很熟悉,但是现在在页面底部有个额外的连接。这个连接将会把我们带到表单页面。新表单页面和最初的页面的唯一区别是所有用户输入的数据都被设置成了”默认值”,这意味着这些值在表单中已经存在了。我们可以看图20-12。
图 20-11 带有当前信息的更新后的friends表单页面
图 20-12 friends结果页面(无效输入)(friends3.py)
这时用户可以更改任何一个字段,或者重新提交表单。
毫无疑问你会开始注意到我们的表单和数据已经变得复杂多了,生成的HTML页面是这样,结果页面更是复杂。如果你有HTML文本和应用程序的接入点的话,你可能会考虑与Python的HTMLgen模块的连接,HTMLgen是Python的一个扩展模块,专用于生成HTML页面。