Learn Python The Hard Way学习(51) - 从浏览器取得输入

来源:岁月联盟 编辑:exp 时间:2012-07-14

下面我们学习让用户从一个表单提交文本到程序中,并且把相关信息保存在session中。

web的工作原理
在建立表单前你需要了解一下web是怎么工作的,虽然不太完整,但是也能帮助你找出一些错误,而且创建表单也会更加容易。

我们从一个图开始,这个图展示了web请求不同部分和信息流向:

我加了一些线和字母来展示请求的过程:
 请求再通过B线进入英特网,通过C线被服务器接收到。
 web程序通过D线取得请求,python代码运行index.GET.
但有return的时候,返回信息通过D线返回到服务器。
服务器再通过C线发送回应信息。
通过B线网络接口接收到信息,通过A线发送到浏览器。
最后,你的浏览器就可以展示回应的结果。
下面是一些常用的词汇表:

浏览器 - 这个不用解释了吧,IE,火狐这些东西就是了。

地址 - 通常指一个URL,就像http://learnpythonthehardway.org/这样的东西,http是你要使用的协议,就是“超文本传输协议”。你也可以试试ftp://ibiblio.ort,这是“文件传输协议”,后面的learnpythonthehardway.org就是域名,或者说是一个好记得地址,这个地址映射一个IP地址。最后,URL还有一个路径,比如http://learnpythonthehardway.org/book/中的/book/,它对应一个文件或者一些资源,还会有很多其他部分,不过这些是主要成分。

链接 - 一旦浏览器知道了你使用http协议,服务器、和资源,那么就要建立一个链接。浏览器会让操作系统打开一个端口,通常是80端口。当端口打开后,系统会回传一个类似文件的东西给你程序,这个文件的作用就是在你的电脑和服务器间发送和接收数据。使用http://localhost:8080的话,浏览器访问的是本机,使用的端口是8080替代了默认的80。你可以访问http://learnpythonthehardway.org:80/,和没加后面的端口是一样的,因为默认就是访问80端口。

请求 - 你的浏览器通过地址连接后,那么你需要从服务器请求你需要的东西。如果地址后有/book/,那么你想要取得book文件或者资源。通常服务器使用/book/index.html这个真实的文件,我们不关注具体是怎么做的,我们要知道我们发生一个请求给服务器,服务器返回python代码生成的东西。

服务器 - 服务器指的是浏览器另一端接收请求并返回文件或者资源的东西。大部分web服务器只是发生文件,只是主要的流量,但是你是用python组建一个服务器,它知道怎么接收请求,并且返回字符串。你可以假定是传输了文件,其实只是一些代码。

响应 - 这是服务器返回给浏览器的HTML代码。这些内容包含一些特定的头部信息,这样浏览器才知道获取的是什么类型的内容。以你的web程序为例,你发送时一样的东西,包括头部信息,只不过这些信息是python代码生成的。

上面这些可以帮助你更好的理解本节的内容,如果你还不是很理解上面的内容,去找一些资料了解了解。可以对照上面的图,把50章的代码对应到相应的部分,这样你就能大致明白它的工作原理了。

表单是怎么工作的
学习表单最好的办法就是写一个表单程序,修改你的app.py文件:
[python] 
import web 
 
urls = ( 
    '/hello', 'Index' 

 
app = web.application(urls, globals()) 
 
render = web.template.render('templates/') 
 
class Index(object): 
    def GET(self): 
        form = web.input(name="Nobody") 
        greeting = "Hello, %s" % form.name 
        return render.index(greeting = greeting) 
 
if __name__ == "__main__": 
    app.run() 

 

用CTRL+C停止程序,然后重新启动它,用浏览器访问http://localhost:8080/hello。你可以看到输出是“I just wanted to say Hello, Nobody.”改变地址为http://localhost:8080/hello?name=Frank,你会看到“Hello,Frank”。

让我们看看刚才做了什么:
使用web.input从浏览器取得数据,刚开始里面有一个默认值,当有?name=Frank的时候,Frank替换掉默认值。
用form.name替换greeting里面的值。
其他的和以前就一样了。
你也可以使用多个参数,比如地址改为http://localhost:8080/hello?name=Frank&greet=Hola,代码中改成这样:
greeting = "%s, %s" % (form.greet, form.name)

如果去掉&greet=Hola访问,你会得到一个错误,因为greet没有默认值。现在我们到程序中给他加一个空的默认值吧,并且判断一下他是否有值:
       
[python] 
form = web.input(name="Nobody", greet=None) 
 
        if form.greet: 
            greeting = "%s, %s" % (form.greet, form.name) 
            return render.index(greeting = greeting) 
        else: 
            return "ERROR: greet is required" 


创建HTML表单
在URL中输入参数比较麻烦,我们需要一个POST表单,这个HTML文件中包含<form>标签,标签中包含用户需要发送的信息。

创建一个包含表单的文件templates/hello_form.html:
[html
<html> 
    <head> 
        <title>Sample Web Form</title> 
    </head> 
<body> 
 
<h1>Fill Out This Form</h1> 
 
<form action="/hello" method="POST"> 
    A Greeting: <input type="text" name="greet"> 
    <br/> 
    Your Name: <input type="text" name="name"> 
    <br/> 
    <input type="submit"> 
</form> 
 
</body> 
</html> 

 

修改app.py文件:
[python]
import web 
 
urls = ( 
    '/hello', 'Index' 

 
app = web.application(urls, globals()) 
 
render = web.template.render('templates/') 
 
class Index(object): 
    def GET(self): 
        return render.hello_form() 
 
    def POST(self): 
        form = web.input(name="Nobody", greet="Hello") 
        greeting = "%s, %s" % (form.greet, form.name) 
        return render.index(greeting = greeting) 
     
if __name__ == "__main__": 
    app.run() 

 

重启web程序,然后用浏览器访问一下。

现在你会看到一个表单,让你输入欢迎词和名字,当你点击提交按钮的时候,就会出现欢迎页面,而你的URL地址还是http://localhost:8080/hello。

在hello_form.html中的<form action="/hello" method="POST">告诉浏览器:
从form中收集用户输入。
通过POST方式发生数据给服务器。
发送的地址是/hello
你会看到<input>中的name和GET方式的相对应。

新的代码做了什么:
先以GET的方式访问/hello,代码里面是访问了hello_form.html文件。
在浏览器中添加了一个form,这个表单设定了发生数据的方式给POST。
web程序运行POST部分的代码处理发送过来的数据。
POST像以前一样发送数据到hello页面。
在index.html页面中添加一个返回的连接,让你可以返回到表单页面。

创建一个布局模板
下一个练习中,我们要把游戏放进来,这样会有很多HTML页面要写,这样很麻烦,幸运的是,我们可以创建一个布局模板,可以让其他页面包含相同的头部和尾部文件。一个好的程序员要减少重复。

改变index.html文件成这样:
[html] 
$def with (greeting) 
 
$if greeting: 
    I just wanted to say <em style="color: green; font-size: 2em;">$greeting</em>. 
$else: 
    <em>Hello</em>, world! 

 

把hello_form.html改成下面这样:
[html] 
<h1>Fill Out This Form</h1> 
 
<form action="/hello" method="POST"> 
    A Greeting: <input type="text" name="greet"> 
    <br/> 
    Your Name: <input type="text" name="name"> 
    <br/> 
    <input type="submit"> 
</form> 

 

我们做的就是去掉了所有页面都一样的头部和尾部。然后再添加这么一个页面templates/layout.html:
[html] 
$def with (content) 
 
<html> 
<head> 
    <title>Gothons From Planet Percal #25</title> 
</head> 
<body> 
 
$:content 
 
</body> 
</html> 

 

这个文件看起来像模板。它从其他页面取得content,然后包含进来。注意$:content的用法,和其他模板变量不太一样。

最后我们改一下web程序:
render = web.template.render('templates/', base="layout")

这就告诉了lpthw.web使用layout.html作为基础模板文件。重启程序看看效果吧。

为表单写自动化测试用例
测试web程序只要刷新浏览器就可以了,不过我们是程序员嘛,为什么不写一个简单的测试代码呢。

在bin中创建一个__init__.py文件。这样python就会认为bin是一个目录了。

创建tests/tools.py文件,加入下面的代码:
[python] 
from nose.tools import * 
import re 
 
def assert_response(resp, contains=None, matches=None, headers=None, status="200"): 
 
    assert status in resp.status, "Expected response %r not in %r" % (status, resp.status) 
 
    if status == "200": 
        assert resp.data, "Response data is empty." 
 
    if contains: 
        assert contains in resp.data, "Response does not contain %r" % contains 
 
    if matches: 
        reg = re.compile(matches) 
        assert reg.matches(resp.data), "Response does not match %r" % matches 
 
    if headers: 
        assert_equal(resp.headers, headers) 

 

然后可以编写你的自动化测试代码了,建立文件tests/app_tests.py:
[python] 
from nose.tools import * 
from bin.app import app 
from tests.tools import assert_response 
 
def test_index(): 
    # check that we get a 404 on the / URL 
    resp = app.request("/") 
    assert_response(resp, status="404") 
 
    # test our first GET request to /hello 
    resp = app.request("/hello") 
    assert_response(resp) 
 
    # make sure default values work for the form 
    resp = app.request("/hello", method="POST") 
    assert_response(resp, contains="Nobody") 
 
    # test that we get expected values 
    data = {'name': 'Zed', 'greet': 'Hola'} 
    resp = app.request("/hello", method="POST", data=data) 
    assert_response(resp, contains="Zed") 

 

最后使用nosetests测试程序:
root@he-desktop:~/python/projects/gothonweb# nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.192s

OK

我就是把app.py模块都导入进来,然后手动运行这个程序,lpthw.web有个API用来处理请求:
app.request(localpart='/', method='GET', data=None, host='0.0.0.0:8080',
            headers=None, https=False)
你可以把URL作为第一个参数,修改里面的东西,这样就不用启动web服务器,你就可以自动测试了。

你要用tests.tools中的assert_response函数来验证响应:
assert_response(resp, contains=None, matches=None, headers=None, status="200")
这个函数包括挺多东西,自己研究一下吧。

在app_tests.py中,我们先确定/返回一个404错误。因为这个地址是不存在的。然后检查了/hello在GET和POST的请求能正常工作。这些代码很好懂的。

加分练习
了解更多HTML的知识,设计一个更好的布局。
研究一下怎么上传文件,试着上传一个图片然后保存到目录中。
找到HTTP RFC文件阅读一下。
找人帮你设置一个服务器,比如Apache, Nginx, 或者thttpd。
多创建一些web程序。你应该仔细阅读web.py中关于会话的内容。这样你能够明白怎么保存用户状态信息。
作者:lixiang0522
 

图片内容