webpy

更新时间:2024-05-30 23:52:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

web.py笔记

本文基于web.py cook写成。

web.py是一个非常轻量级的python web框架,使用起来非常的方便。虽然现在用python用来做网站的不是很多,但大名鼎鼎的GAE就支持python,其中就包括web.py框架,所以玩玩web.py也是很有意思的事情。

1Web.py的安装

Web.py的主页是http://webpy.org/,代码下载页面地址是http://webpy.org/download,在本页上还有着模块的安装方法,以本文的写作时间为准,代码包下载地址是

http://webpy.org/static/web.py-0.33.tar.gz,按照页面上的说明,只要把包解开,在包的目录下使用命令python setup.py install就可以自动将包安装到python中。当然,如果是在linux下也可以使用网络从软件仓库中下载安装web.py包。

2Hello World

几乎任何程序设计教材都要以Hello World作为入门程序,本文也不能免俗。

打开你的python ide(我习惯于用eclipse+pydev),当然也可以是任何一个文本编辑软件,新建一个py代码文件,写入如下代码: # -*- coding: utf-8 -*- import web

urls = (“/”, “hello”)

app = web.application(urls, globals())

class hello: def GET(self): return ?Hello, world!?

if __name__ == “__main__”: app.run()

保存,比如说叫main.py,然后在运行这个py文件,出现控制台窗口,内有http://0.0.0.0:8080/一行字,这时如果在浏览器中输入这个网址,你将看到我们刚刚建立的这个页面,当然页面很简单,只有Hello, world!这么一句话,但这就已经表明我们用web.py写的页面可以正常运行了,简单吧。

直接运行我们写的这个文件就是启动web.py内置的一个小服务器用于解析我们写的代码,这里的8080是web.py默认的端口,如果想自定义端口的话需要在运行时额外附加参数,比如如果想以80作为端口,则用命令python main.py 80。

简单介绍一下代码内容。Import web就是导入web.py包啦,urls = (“/”, “hello”)是用来指定url处理类的,具体含义和使用方法下一节讲解,app = web.application(urls, globals())表示创建一个绑定urls中指定的处理映射关系以及全局命名空间的web程序,class hello就是处理绑定到hello类的页面请求类,而GET函数就是在页面受到GET请求时要做出的反应,app.run()则是表明开始运行web.py程序,处理页面请求。

2.1Debug模式

在使用web.py内置的服务器时,程序自动处于debug模式下,在这种情况下,对模板和代码的修改可以立即反应在输出页面上,同时输出更利于调试的信息。而在正式发布到服务器上后,则默认处于非debug模式下,如果需要强制程序处于debug模式下,那么需要在代码中创建程序(app = web.application(urls, globals())命令)或模板之前使用如下命令: web.config.debug = True

2.2使用静态文件

在web.py中除了py代码文件和模板外,其他文件,比如页面中的图片、影音文件、css和js文件等,是不能直接使用的,这些文件被称为静态文件,必须集中存放在网站根目录下的static文件夹中。

比如对网址http://your.domain.name/static/logo.png的访问将发送 ./static/logo.png 给客户端。

3URL处理

Web.py提供了强大而灵活的url处理能力,也就是用户在用某url地址访问时,网站需要用那个页面代码对用户的请求作出反应。

通常对网站url的映射关系是放在一个元组对象中的,即如上节hello world代码中的urls = (“/”, “hello”)。这里表示对网站/路径的访问都由hello类来处理,也如hello world中所示,是在页面上打印出Hello, world!字样。

3.1URL匹配

url可以是具体的某一页面,如”/”仅对应网站根http://your.domain.name/,”/about”则对应http://your.domain.name/about。同时,url也可以是正则表达式,动态得对应各种url页面请求。

比如/(test1|test2)表示匹配http://your.domain.name/test1或者http://your.domain.name/test2。

如果在映射url时,使用了类似/name/(.*)这样的模式,括号中的内容可以作为参数传递给处理类的GET或POST函数。如在main.py中添加一个name类: class name: def GET(self,name): return ?Hello, %s!? % name 在url映射中修改为如下:

urls = (“/”, “hello”, “/name/(.*)”,”name”)

在用诸如http://your.domain.name/name/lisa访问网站时,就会触发name类的GET函数,并将lisa作为name参数传递给GET函数,并在返回的页面上打印Hello, lisa!。

3.2处理类结构

而对应类部分也可以包括类的包和模块名称,比如我们可以见立一个about包,包下有一个aboutme模块,模块中有一个类whoami,如下所示: class whoami: def GET(self): return ?Who am i??

在main.py中的url映射部分是:

urls = (“/”, “hello”, “/about”, “about.aboutme.whoami”)

那么,在浏览器中敲入http://your.domain.name/about ,对应的就将是这个about.aboutme.whoami类,页面上打印一句话,Who am i?

3.3页面重定向

如果希望用户访问某页面的时候自动转向其他页面,需要使用web.seeother或web.redirect。 如新建一个类redirect: class redirect: def GET(self): raise web.seeother(?/?)

在url映射中添加一条“/redirect”,”redirect”,则在访问http://your.domain.name/redirect 时会自动转向根路径,也就是hello world页面。

除了seeother,还可以使用redirect方法,区别在于,seeother是向客户端发送303 http代码,而redirect是发送的301,303是临时转向而301是永久转向,对于301,浏览器一般会缓存新地址,因此如果不是确定旧地址是需要永久性转向新地址的话,还是尽量使用seeother方法。

3.4使用子程序

在指定url映射的时候除了映射到指定类外,还可以映射到指定的子程序中去。 比如我们新建一个模块,叫做subapp.py,在其中写入如下代码: import web urls = ( “”, “sa”,

“/(.*)”, “saname” ) class sa:

def GET(self): raise web.seeother(?/sub app?) class saname: def GET(self, name): return name

app = web.application(urls, locals())

然后在main.py里加入suapp包的引用import subapp,在urls映射中添加一项映射“/sa”, subapp.app,然后用url地址http://your.domain.name/sa,相应的就是subapp包下的sa类,被重定向到saname类中,传递给saname的参数是sub app,因此最终页面上显示的是sub app字样。这里要注意的就是,在子程序中的url路径都是相对于子程序自身的相对路径,而不是从网站根开始的路径。

4模板

在向用户返回页面内容的时候,可以在GET或POST处理函数的return处返回html页面的代码,但是这样做显然很麻烦,这时候就需要使用模板了。这里的模板就有点像用Java写web程序时用的jsp,用return返回页面就有点像直接用servelet。

4.1创建模板

这里,我们先在项目中新建一个目录templates集中存放模板文件,便于管理。然后在templates下新建一个index.html文件,在新文件中写入如下代码: $def with (name)

Template Hi, $name

这是一个最简单的html页面代码。

4.2使用模板

然后在修改使用这个模板的类,比如说我们在main.py模块中的新写一个index类,入下: class index: def GET(self):

render=web.template.render(“templates”) return render.index(“Lisa”) 同时修改urls为: urls = (“/”, “index”)

这样,网站默认调用index类,进而使用index.html模板,打开浏览器查看结果,页面就会打印出Hello, Lisa的字样。

除此之外还有两种使用模板的方法,一是使用frender直接指定模板文件,GET函数最后两行改为:

render=web.template.frender(“templates/index.html”) return render(“Lisa”)

或者直接在代码里写出模板文件,如把GET最后两行改为: template = “$def with (name)\\nHello $name”

render = web.template.Template(template) return render(“Lisa”)

4.3模板含义

解释一下这个模板的含义:

在index.html第一行$def with (name)表示本模板接受一个名为name的参数,也就是对应index类中return render.index(“Lisa”)中的Lisa。

而render=web.template.render(“templates”)表示创建一个模板对象,模板是存放于templates目录下,然后就可以用所创建的render对象来访问相应的模板,比如templates目录下的index.html就是用render.index来表示(实际上是匹配寻找index.*文件,第一个匹配的就认为是所对应的模板文件),如果templates下还有个a目录,a目录下有个pagea.html,那么访问这个pagea模板就要用render.a.pagea的形式了。

4.4页面参数

页面接收的参数可以多于一个,也可以没有,如果不需要参数,则就不需要$def with (name)这样的代码,删除掉这一句,同时修改模板中对name变量的引用,修改index类最后一句为return render.index()就可以了。

如果有参数,那么模板的第一行代码就必须是这个$def with (…),可以多于一个参数,比如是这样$def with (gname, fname)。模板下面的那行字改为Hi, $gname $fname。同时Index类GET返回的时候赋予对应两个参数return render.index(“Lisa”,”Hayes”)。这样,页面最后显示的是打印出Hi, Lisa Hayes的字样。

另外,模板接受的这个参数也可以是一个元组,比如像下面这样: return render.index((“Lisa”,”Hayes”)) 在模板中可以如下以元组方式访问参数数据: Hi, $name[0] $name[1]

4.5模板语法

模板语法与python语法基本一致,主要差别可以从上面的代码中看到,要使用到$符号表明这不是文本而是模板代码。也就是每当用到程序代码、对象的时候就必须用$来与html代码和页面显示文本相区别。

4.5.1对象赋值

向对象赋值时需要在$与对象名之间留空格,如为一个名为vara的字符串对象赋值apple的代码为$ vara = “apple”。

另外,对象赋值语句必须独占一行,前面或后面有其他代码则会程序出错。

4.5.2对象引用

引用对象的时候直接使用$+对象名的形式,如$vara。

另外引用对象时还可以用{}或()将对象进行明确的分组,如$(vara)s就会表示apples,如果没有括号,程序则会把$ varas作为一个整体,也就变成对varas对象的引用而发生错误。 另如果向如下定义两个数字型对象: $ varb = 1 $ varc = 2

然后希望计算两个值的和,如果用$varb+$varc的形式,页面上只会得到1+2而不是3,这时也就需要把两个对象放在括号里,如$(varb+varc)的形式才能得到正确答案3。

4.5.3注释

模板中支持单行注释,以$#符号开始到行末都是注释内容。 $#This is comment

注释前面可以有其他内容,但是不可以有赋值代码。 如下代码是正确的: Hi $#This is comment

但下面的则会出错:

$ vara = “apple” $#This is comment

4.5.4Filtering 4.5.5打印$符号

由于$符号在模板中有特殊用途,所以在页面上输出$时需要进行转义操作,用连续两个$表示在页面上输出一个$符号。 Can you lend me $$50?

4.5.6控制代码(循环、条件判断)

模板中支持for、while、if、elif、else,用法与在python一致,只是控制代码行要以$开始(包括break和continue命令),$开始的代码行中对象不需要在前面再加$符号,同时要注意缩进规则,如:

$for i in range(10): line:$i

但要是第二行代码与for行代码写在同一行中,i对象前还是需要有$的: $for i in range(10):line:$i
While循环: $while a: hello $a.pop() if else判断: $if times > max:

Stop! In the name of love. $else:

Keep on, you can do it.

在for循环中,有一组内置的变量可以使用,非常方便,分别如下所示:

loop.index: 循环次数计数 (1-开始) loop.index0: 循环次数计数(0-开始) loop.first: 如果是第一次循环则为True loop.last: 如果是最后一次循环则为True loop.odd: 如果是第奇数次循环则为True loop.even: 如果是第偶数次循环则为True

loop.parity: 如果循环次数为奇数值为“odd” ,反之为 “even” loop.parent: 本循环的外层循环对象 $for a in [\

$loop.index,$loop.index0,$loop.first,$loop.last,$loop.odd,$loop.even,$loop.parity
将在页面上打印出:

1,0,True,False,True,False,odd 2,1,False,False,False,True,even 3,2,False,False,True,False,odd 4,3,False,True,False,True,even

4.5.7函数-$def

函数定义也是与在python中类似,用def,只是也要在前面加$,代码也要注意$的使用和缩进: $def hello(name=”\ Hello $name!

函数调用也是用$加函数名的形式: $hello(“Lisa”)

当然,定义函数时也可以与html代码混编: $def hello(name=”\

Hello $name!

但是调用的时候需要在函数名前用$:前缀,否则html代码将以plain text形式打印到页面上。 $:hello(“Lisa”)

4.5.8输出程序代码-$code块

如果想在模板里输入写一段python代码而又不想被$所烦恼,那么可以用到$code块。 页面上输出一段代码而不希望被系统理解为模板程序代码,就需要用到$code命令,比如在模板文件中写入下面一段: $code: x=10

def print_num(num): return “num is %d” % num 然后再加上下面代码: $print_num(x)
$x

这里就用在$code块中定义的print_num函数以x变量为参数在页面上输出一行: num is 10

然后下一行直接引用x变量,直接在页面上输出数字10。

4.5.9$var

$var命令可以在模板中定义变量,在其他地方引用此模板对象的时候可以访问此定义的变量。 比如我们可以在index.html中添加如下一行: $var vara: this is vara

表示定义了一个名为vara的变量,变量值是字符串this is vara。 把index的GET函数改为:

def GET(self):

render=web.template.render(“templates”) return render.index(“Lisa”, “Hayes”).vara

那么结果显示在页面上的就是this is vara这句话。 要注意一点的是,这种变量是字符串,即便如下定义变量: $var vara: 0

Vara也并不是数字0, 如果把GET函数最后改成:

return render.index(“Lisa”, “Hayes”).vara+1

会导致程序出错。如果希望得到期望中的结果1,则需要如下形式代码: return int(render.index(“Lisa”, “Hayes”).vara)+1

4.6builtins and globals

在模板中,用户可以直接使用python的内建函数和变量,写函数变量包括range, min, max 以及 True 和False等。 除此之外,如果希望在模板中使用其他的非内建功能,就需要一点特殊操作。要在创建render的时候显式指定所需要的功能函数。 import web import markdown

globals = {?markdown?: markdown.markdown}

render = web.template.render(?templates?, globals=globals)

这样,在模板中就可以用$markdown来引用markdown.markdown了。 同样,也可以用这种办法来禁用builtins # disable all builtins

render = web.template.render(?templates?, builtins={})

4.7模板复用

当多个页面有着相同的结构框架的时候,为每一个页面单独维护一个模板就显得比较麻烦,web.py提供了一种简易的解决方法。

这时候就要用到创建render时使用base参数:

render=web.template.render(“templates”,base=”layout”) return render.index(“Lisa”, “Hayes”)

这个layout表示要以templates下的layout.html模板为通用模板框架。 因此我们还要在templates目录下新建一个layout.html文件,写下如下代码: $def with (content)

Layout $:content

可以看到,这个模板文件必须是有一个参数content。 然后修改index.html,只保留如下代码,其他删掉: $def with(gname, fname)

Hi, $(gname) $(fname)

运行程序,页面上打印Hi, Lisa Hayes,查看代码会发现最终代码就是index.html和layout.html合并在一起的结果,index.html中的内容被嵌入到layout.html中的$:content处。

在layout.html模板中还可以引用index.html中定义的var变量,这为程序带来了更多的灵活性,比如我们希望在不同的页面在使用同一个layout模板的时候能有不同的title,可以在使用layout的模板中定义如下一个var变量: $var title:This is index.html

然后在layout.html中的title处修改为: $content.title

这样,访问index.html时显示在浏览器上的title就是This is index.html,而不是原来的Layout了。

4.8在模板中使用python代码模块

在默认状态下,在模板中是不能直接调用其他python代码模块文件中的程序的,必须做一些额外的操作。

首先,我们新建一个模块,叫module1.py,在里面写一个函数: def hello_from_m1(name=”\

return “hello %s, this is module1″ % name 在main.py里导入module1: import module1

并且修改GET函数中创建render的代码为: def GET(self):

render=web.template.render(“templates”,base=”layout”,globals={“m1″:module1}) return render.index(“Lisa”)

globals参数中传递的是一个字典,key以字符串表示模块在模板中使用时的名称,value部分就是这个要在模块中使用的模块或对象的真实名称了。

最后在要使用此模块的模板中就可以用$m1来引用此模块了。比如在index.html中添加下面一行代码:

$m1.hello_from_m1(gname)

就会调用module1中的hello_from_m1函数,在页面上打印出: hello Lisa, this is module1

4.9在web.py模板中使用jQuery

在jQuery中$也是一个关键字,这样的话如果在模板中使用jQuery就会冲突,这时候只需要用$$做一下转义就可以了,比如:

5用户输入

在web.py中,要响应用户使用GET或POST方法向服务器提交数据需要用到web.input方法。Input方法生成一个web.storge对象,其中的数据以字典形式存在,其键值对就是在GET和POST时对应的参数名和参数值。

5.1GET

我们先从GET方法开始,以http://your.domain.name/input?gname=Lisa&fname=Hayes 的形式提交数据。

在这里也就是有两个参数,分别为gname和fname,传递给服务器的值分别为Lisa和Hayes。 为了响应这个请求,我们在main.py中新建个类inputpage: class inputpage: def GET(self):

render=web.template.render(“templates”,base=”layout”) input_data=web.input(gname=”\

return render.inputpage(input_data.gname,input_data.fname)

在创建input对象的时候赋给参数的值是作为链接中没有此参数值的时候的缺省参数值。 当然别忘了修改urls对象: urls = (“/”, “index”, “/input”, “inputpage”)

新建Inputpage.html模板,这个模板也作为layout.html的内容嵌入,其中写入: $def with(gname,fname) $var title:This is inputpage.html hi, $gname $fname

刷新页面,就会根据fname和gname的参数值打印出响应的文本。

5.2POST

然后我们用POST方法来提交数据,也就是用form。 把刚才的用的inputpage.html改成以下内容: $def with(gname,fname) $var title:This is inputpage.html hi, $gname $fname

这里有一个form,其中有两个文本框用于输入文本内容,并将这两个文本框中的值作为gname和fname参数的值以POST模式提交给input页面。

同时把inputpage类的GET函数改成POST函数,内容不动,只是把函数的名字改一下就行。 然后用http://your.domain.name/input 访问这个新页面,在两个文本输入框中输入文本,点击submit按钮,看看页面发生什么变化。

5.3上传文件

首先创建一个新的模板页面,叫做upload.html,其中写入如下代码: $def with(message)

upload


$message

也就是在页面里放置一个上传文件的form,从浏览器中选择要上传的文件,点击提交按钮将文件提交给服务器,由本页面的对应post请求处理函数处理这个上传文件。 那么对应着我们就在main.py中新建一个upload类: class upload: def GET(self):

render=web.template.render(“templates”) return render.upload(“”) def POST(self):

render=web.template.render(“templates”)

x = web.input(myfile={})

filedir = “./” #上传文件保存的目录 if “myfile” in x:

filepath=x.myfile.filename.replace(?\\\\?,'/?) # 将windows格式的路径分隔符\\转换为unix格式的/ filename=filepath.split(?/?)[-1] # 从文件的完整路径中取出单独的文件名 fout = open(filedir +?/'+ filename,?wb?) # 创建上传保存的文件 fout.write(x.myfile.file.read()) # 把上传的文件写入指定的文件中 fout.close() # 关闭文件对象,上传完毕 return render.upload(“upload succeed.”)

GET函数也就是显示上面的那个模板,在页面中提交上传请求后就由POST函数来处理,将上传的文件保存在filedir目录下,在这里也就是网站的根目录下。要注意的是fout = open(filedir +?/'+ filename,?wb?) 这一句中,文件类型参数应该是”wb”,而cookbook中的是”w”,也就是按照文本格式处理,结果上传的文件会被修改,大概就是回车换行处理出的问题,而换成二进制处理文件就正常了。 限制上传文件大小

Web.py中可以利用cgi模块来限制上传文件的大小。 import cgi

cgi.maxlen = 10 * 1024 * 1024 # 10MB class upload: def GET(self):

render=web.template.render(“templates”) return render.upload(“”)

def POST(self):

render=web.template.render(“templates”) try:

x = web.input(file={}) except ValueError: return “File too large” finally:

return render.upload(“File too large”)

上面这段代码可以限制上传文件的大小在10mb以内

6Session与用户状态

6.1Session

首先要明确一点,在debug模式下,session是无效的。所以为了使用session就要关闭debug模式,也就是在创建程序和模板对象前使用web.config.debug = False。

6.1.1使用session

为了使用session,我们需要把main.py改成如下形式:

import web

web.config.debug = False urls = (“/”, “index”)

app = web.application(urls, globals())

session = web.session.Session(app, web.session.DiskStore(?sessions?), initializer={?count?: 0}) class index: def GET(self):

render=web.template.render(“templates”)

session.count+=1

return render.index(session.count) if __name__ == “__main__”: app.run()

index.html模板改为: $def with(count) $count

这样,页面就会显示count的值,每刷新一次页面,count值就自增1。 解释一下Main.py:

代码第二行就是要关闭debug模式,这样session功能才可以起效。

第五行代码便是创建session的命令,这样本网站的session数据就保存在session对象中。在这里我们用DiskStore类来创建session,表明我们的session是保存在磁盘中的文件里,当程序运行时,就会在项目根目录下出现一个sessions目录,里面的文件就是用于保存session数据的。 Initializer参数是用于初始化session中的数据,在这里就是向session中添加一个名为count的属性,其值为0,然后在后面的代码中就可以用session.count或者session[“count”]的形式访问这个属性值了。

如果要向session中添加新的属性,只需用session.newkey=value或session[“newkey”]=value的形式为属性赋值,然后像访问count属性那样使用新属性就可以了,只不过要注意,在没有为属性赋值前,这个属性没有被初始化,也就是不存在的,直接访问这个属性时会出错的。 如我们可以把index类改为: class index: def GET(self):

render=web.template.render(“templates”)

session.username=”Lisa”

return render.index(session.username)

这里就是向session中添加了一个username属性,并赋值为Lisa,页面最终也就会打印出Lisa的字样。

6.1.2在debug模式下使用session

上文提到了,要使用session必须要把debug模式关闭,否则session不可用,但是这样的话debug下页面被修改后自动重新载入的功能也就无法使用了,这样我们要看修改结果就必须重启服务器,这给开发调试带来了麻烦,为了在debug模式下使用session功能,我们就需要做一些特殊处理。 首先,我们要打开debug模式,也就是web.config.debug = True,如果是在使用web.py内置的服务器,则只需删掉此代码,因为缺省状态下就是debug模式。 再把上节中创建session的那句改成下面的样子就可以解决这个问题了:

if web.config.get(?_session?) is None:

session = web.session.Session(app, web.session.DiskStore(?sessions?), {?count?: 0})

web.config._session = session else:

session = web.config._session

6.1.3在模板中使用session

在模板中使用session同在session使用其他python模块的功能类似,也是在创建render时用到global参数: class index: def GET(self):

render=web.template.render(“templates”,globals={“session”:session}) return render.index()

这样在index.html模板中就能用$session访问session。 比如在index.html改成下面这一句: $session.count

刷新页面显示的就是不断增加的count值。

6.1.4有关session的相关参数修改

web.config. The default values are shown below.

web.config.session_parameters['cookie_name'] = ?webpy_session_id? web.config.session_parameters['cookie_domain'] = None

web.config.session_parameters['timeout'] = 86400, #24 * 60 * 60, # 24 hours in seconds

web.config.session_parameters['ignore_expiry'] = True web.config.session_parameters['ignore_change_ip'] = True

web.config.session_parameters['secret_key'] = ?fLjUfxqXtfNoIldA0A0J? web.config.session_parameters['expired_message'] = ?Session expired? cookie_name – name of the cookie used to store the session id? cookie_domain – domain for the cookie used to store the session id?

timeout – number of second of inactivity that is allowed before the session expires? ignore_expiry – if true, the session timeout is ignored?

ignore_change_ip – if true, the session is only valid when it is accessed from the? same ip address that created the session secret_key – requires explanation?

expired_message – message displayed when the session expires?

6.2Cookie

6.2.1设定cookie属性值

设定cookie属性值用web.setcookie()函数。格式为:

setcookie(name, value, expires=”\ name(string):cookie属性名称 value(string):属性值

expires(int):cookie失效时间,单位是秒,负值表示立刻失效,0表示浏览器关闭时失效,也就是跟session一样

domain(string):此cookie使用的域

secure(bool):如果为True,表示这个cookie必须使用https

比如在代码中我们可以如下为cookie赋值,设定username属性为Lisa,失效时间为1个小时: web.setcookie(“username”,”Lisa”,3600)

另外也可以用类似操作session的形式,如下面后两行都可以用来为cookie中的password属性赋值:

cookies=web.cookies(username=”\ cookies[\ cookies.password=”222222″

6.2.2获取cookie属性值

在代码中获取cookie中username属性值的方法如下: cookies=web.cookies(username=”\ print cookies.username print cookies[\

在这里获取cookie对象的时候为username指定了一个缺省值,如果当前cookie还没有这个属性,则创建此属性并赋值为这里的缺省值,而如果不指定属性缺省值,同时这个属性还不存在的话,则在后面访问这个username属性时会抛出异常。

另外一种获取属性的方法是使用get函数,此时如果username属性不存在的话,函数返回None而不是抛出异常,如:。 print cookie.get(“username”)

7高级应用

7.1Ctx

Web.ctx可以用于获取一些有用的上下文变量。

比如下面代码可以返回当前浏览器发来的header中的引用页信息,如果没有则返回默认的google.com:

web.ctx.env.get(?HTTP_REFERER?, ?http://google.com?) 其他可以从ctx中获取的信息包括: Request

environ a.k.a. env – a dictionary containing the standard WSGI environment variables home – the base path for the application, including any parts “consumed” by outer applications http://example.org/admin

homedomain – ? (appears to be protocol + host) http://example.org

homepath – The part of the path requested by the user which was trimmed off the current app. That is homepath + path = the path actually requested in HTTP by the user. E.g. /admin host – the hostname (domain) and (if not default) the port requested by the user. E.g. example.org, example.org:8080

ip – the IP address of the user. E.g. xxx.xxx.xxx.xxx method – the HTTP method used. E.g. GET

path – the path requested by the user, relative to the current application. If you are using subapplications, any part of the url matched by the outer application will be trimmed off. E.g. you have a main app in code.py, and a subapplication called admin.py. In code.py, you point /admin to admin.app. In admin.py, you point /stories to a class called stories. Within stories, web.ctx.path will be /stories, not /admin/stories. E.g. /articles/845 protocol – the protocol used. E.g. https

query – an empty string if there are no query arguments otherwise a ? followed by the query string. E.g. ?fourlegs=good&twolegs=bad

fullpath a.k.a. path + query – the path requested including query arguments but not including homepath. E.g. /articles/845?fourlegs=good&twolegs=bad Response

status – the HTTP status code (default ?200 OK?) 401 Unauthorized headers – a list of 2-tuples containing HTTP headers output – a string containing the response entity

7.2Application processors

web.py 提供的processors 可以在request执行前和执行后进行一些处理工作。 def my_processor(handler): print ?before handling? result = handler() print ?after handling? return result

app.add_processor(my_processor)

或者使用hooks 和 unload 实现在request开始和结束的时候进行某些处理工作。 def my_loadhook():

print “my load hook”

def my_unloadhook(): print “my unload hook”

app.add_processor(web.loadhook(my_loadhook)) app.add_processor(web.unloadhook(my_unloadhook)) 在hook 函数中还可以使用global变量,如 web.header() def my_loadhook():

web.header(?Content-type?, “text/html; charset=utf-8″)

app.add_processor(web.loadhook(my_loadhook))

7.3自定义NotFound页面

首先,我们先定义一个函数如下: def notfound():

return web.notfound(“Sorry, the page you were looking for was not found.”) 然后把这个函数赋给app的notfound属性: app.notfound = notfound

这样,当用户访问了不存在的网页的时候就会在页面上打印出notfound返回语句中notfound函数中的参数那句话了。

当然,也可以使用模板定义一个更复杂一些的页面,比如我们可以在templates目录下新建一个notfound.html文件作为模板,比如是这样:

$def with(message)

NotFound $:message

然后把notfound函数改为如下形式: def notfound():

render=web.template.render(“templates”)

return web.notfound(render.notfound(“Sorry, the page you were looking for was not found. “)) 类似的我们也可以定义内部错误页面: def internalerror():

return web.internalerror(“Bad, bad server. No donut for you.”)

app.internalerror = internalerror

7.4在Google App Engine上使用web.py

Google App Engine是支持使用web.py的,使用方法是把web.py安装目录,也就是python安装目录下的Lib\\site-packages\\web目录,整个拷贝到GAE的项目中,把app.run()改成app.cgirun(),然后上传到GAE上,就可以用web.py开发GAE程序了。

但是要注意的是,由于某种安全原因,在GAE中是不能直接使用web.py的模板的,必须在本地编译后才上传到GAE服务器上才能使用web.py模板,比如说我们有一个GAE项目叫

webpyongae,webpyongae/web目录就是拷贝进来的web.py程序,webpyongae/templates为放置模板的目录,那么编译的方法就是在webpyongae目录下,执行python /web/template.py –complie templates。

在http://webpy.appspot.com/ 这个GAE网站上就运行着一个简单的web.py示例程序,并且在http://webpy.appspot.com/source此页中可以查看这个站点首页的代码。

8其他

8.1解决css在windows下解析过慢的问题

web.py3.*如果使用到了独立于html之外的css文件,页面加载的时间会比较长,其原因是webpy对windows格式的换行无法正常的解析,无法判断css文件的实际长度,所以服务器会一直等待直到超时。解决方法如下:

#解决windows下css文件解析太慢的问题–>>

from SimpleHTTPServer import SimpleHTTPRequestHandler

send_head = SimpleHTTPRequestHandler.send_head

def new_send_head(*a, **kw): print ?new_send_head?, a, kw f = send_head(*a, **kw)

return f and open(f.name, ?rb?)

SimpleHTTPRequestHandler.send_head = new_send_head #解决windows下css文件解析太慢的问题<<– if __name__ == “__main__”: app.run()

从浏览器中获取输入

虽然能让浏览器显示“Hello World”是很有趣的一件事情,但是如果能让用户通过表单(form)向你的应用程序提交文本就更有趣了。这节习题中,我们将使用 form 改进你的 web 程序,并且将用户相关的信息保存到他们的“会话(session)”中。

Web 的工作原理

该学点无趣的东西了。在创建 form 前你需要先多学一点关于 web的工作原理。这里讲并不完整,但是相当准确,在你的程序出错是,它会帮你找到出错的原因。另外,如果你理解了 form 的应用,那么创建 form 对你来说就会更容易了。 我将以一个简单的图示讲起,它向你展示了 web 请求的各个不同的部分,以及信息传递的大致流程:

为了方便讲述 HTTP 请求(request) 的流程,我在每条线上面加了字母标签以作区别。

1. 你在浏览器中输入网址 http://learnpythonthehardway.org/,然后浏览器会通过你的

电脑的网络设备发出 request(线路 A)。

2. 你的 request 被传送到互联网(线路 B),然后再抵达远端服务器(线路 C),然

后我的服务器将接受这个 request。

3. 我的服务器接受 request 后,我的 web 应用程序就去处理这个请求(线路 D),

然后我的 Python 代码就会去运行 index.GET 这个“处理程序(handler)”。

4. 在代码 return 的时候,我的 Python 服务器就会发出响应(response),这个响应会

再通过线路 D 传递到你的浏览器。

5. 这个网站所在的服务器将响应由线路 D 获取,然后通过线路 C 传至互联网。 6. 响应通过互联网由线路 B 传至你的计算机,计算机的网卡再通过线路 A 将响应传

给你的浏览器。

7. 最后,你的浏览器显示了这个响应的内容。

这段详解中用到了一些术语。你需要掌握这些术语,以便在谈论你的 web 应用时你能明白而且应用它们:

浏览器(browser)

这是你几乎每天都会用到的软件。大部分人不知道它真正的原理,他们只会把它叫作“网”。它的作用其实是接收你输入到地址栏网址(例如 http://learnpythonthehardway.org),然后使用该信息向该网址对应的服务器提出请求(request)。

地址(address)

通常这是一个像 http://learnpythonthehardway.org/ 一样的 URL (Uniform Resource Locator,统一资源定位器),它告诉浏览器该打开哪个网站。前面的 http 指出了你要使用的协议(protocol),这里我们用的是“超文本传输协议(Hyper-Text Transport Protocol)”。你还可以试试 ftp://ibiblio.org/ ,这是一个“FTP 文件传输协议(File Transport Protocol)”的例子。learnpythonthehardway.org 这部分是“主机名(hostname)”,也就是一个便于人阅读和记忆的字串,主机名会被匹配到一串叫作“IP

地址”的数字上面,这个“IP 地址”就相当于网络中一台计算机的电话号码,通过这个号码可以访问到这台计算机。最后,URL 中还可以尾随一个“路径”,例如 http://learnpythonthehardway.org/book/ 中的 /book/,它对应的是服务器上的某个文件或者某些资源,通过访问这样的网址,你可以向服务器发出请求,然后获得这些资源。网站地址还有很多别的组成部分,不过这些是最主要的。

连接(connection)

一旦浏览器知道了协议(http)、服务器(learnpythonthehardway.org)、以及要获得的资源,它就要去创建一个连接。 这个过程中,浏览器让操作系统(Operating System, OS)打开计算机的一个“端口(port)”(通常是 80 端口),端口准备好以后,操作系统会回传给你的程序一个类似文件的东西,它所做的事情就是通过网络传输和接收数据,让你的计算机和 learnpythonthehardway.org 这个网站所属的服务器之间实现数据交流。 当你使用 http://localhost:8080/ 访问你自己的站点时,发生的事情其实是一样的,只不过这次你告诉了浏览器要访问的是你自己的计算机(localhost),要使用的端口 不是默认的 80,而是 8080。你还可以直接访问 http://learnpythonthehardway.org:80/, 这和不输入端口效果一样,因为 HTTP 的默认端口本来就是 80。

请求(request)

你的浏览器通过你提供的地址建立了连接,现在它需要从远端服务器要到它(或你)想要的资源。如果你在 URL 的结尾加了 /book/, 那你想要的就是 /book/ 对应的文件或资源,大部分的服务器会直接为你调用 /book/index.html 这个文件,不过我们就假装不存在好了。浏览器为了获得服务器上的资源,它需要向服务器发送一个“请求”。这里我就不讲细节了,为了得到服务器上的内容,你 必须先向服务器发送一个请求才行。有意思的是,“资源”不一定非要是文件。例如当浏览器向你的应用程序提出请求的时候,服务器返回的其实是你的 Python 代码生成的一些东西。

服务器(server)

服务器指的是浏览器另一端连接的计算机,它知道如何回应浏览器请求的文件和资源。大部分的 web 服务器只要发送文件就可以了,这也是服务器流量的主要部分。不过你学的是使用 Python 组建一个服务器,这个服务器知道如何接受请求,然后返回用 Python 处理过的字符串。当你使用这种处理方式时,你其实是假装把文件发给了浏览器,其实你用的都只是代码而已。就像你在《习题 50》中看到的,要构建一个“响应”其实也不需要多少代码。

响应(response)

这就是你的服务器回复你的请求,发回至浏览器的 HTML,它里边可能有 css、javascript、或者图像等内容。以文件响应为例,服务器只要从磁盘读取文件,发送给浏览器就可以了,不过它还要将这些内容包在一个特别定 义的“头部信息(header)”中,这样浏览器就会知道它获取的是什么类型的内容。以你的 web 应用程序为例,你发送的其实还是一样的东西,包括 header 也一样,只不过这些数据是你用 Python 代码即时生成的。

这个可以算是你能在网上找到的关于浏览器如何访问网站的最快的快速课程了。这节课程应该可以帮你更容易地理解本节的习题,如果你还是不明白,就到处 找资料多多了解这方面的信息,知道你明白为止。有一个很好的方法,就是你对照着上面的图示,将你在《习题 50》中创建的 web 程序中的内容分成几个部分,让其中的各部分对应到上面的图示。如果你可以正确地将程序的各部分对应到这个图示,你就大致开始明白它的工作原理了。

表单(form) 的工作原理

熟悉“表单”最好的方法就是写一个可以接收表单数据的程序出来,然后看你可以对它做些什么。先将你的bin/app.py 修改成下面的样子: 1 import web 2 3 urls = ( 4 '/hello', 'Index' 5 ) 6 7 8 app = web.application(urls, globals()) 9 10 render = web.template.render('templates/') 11 12 class Index(object): 13 def GET(self): 14 form = web.input(name=\15 greeting = \16 17 return render.index(greeting = greeting) 18 19 if __name__ == \20 app.run()

重启你的 web 程序(按 CTRL-C 后重新运行),确认它有运行起来,然后使用浏览器访问http://localhost:8080/hello,这时浏览器应该会显示“I just wanted to say Hello, Nobody.”,接下来,将浏览器的地址改

成 http://localhost:8080/hello?name=Frank,然后你可以看到页面显示为“Hello, Frank.”,最后将 name=Frank 修改为你自己的名字,你就可以看到它对你说“Hello”了。

让我们研究一下你的程序里做过的修改。

1. 我们没有直接为 greeting 赋值,而是使用了 web.input 从浏览器获取数据。这个函数

会将一组 key=value 的表述作为默认参数,解析你提供的 URL 中

的 ?name=Frank 部分,然后返回一个对象,你可以通过这个对象方便地访问到表单的值。

2. 然后我通过 form 对象的 form.name 属性为 greeting 赋值,这句你应该已经熟悉了。 3. 其他的内容和以前是一样的,我们就不再分析了。

URL 中该还可以包含多个参数。将本例的 URL 改成这样

子: http://localhost:8080/hello?name=Frank&greet=Hola。然后修改代码,让它去获取form.name 和 form.greet,如下所示: greeting = \

修改完毕后,试着访问新的 URL。然后将 &greet=Hola 部分删除,看看你会得到什么样的错误信息。由于我们在 web.input(name=\ 中没有

为 greet 设定默认值,这样 greet 就变成了一个必须的参数,如果没有这个参数程序就会报错。现在修改一下你的程序,在 web.input 中为 greet 设一个默认值试试看。另外你还可以设 greet=None,这样你可以通过程序检查 greet 的值是否存在,然后提供一个比较好的错误信息出来,例如: form = web.input(name=\

if form.greet:

greeting = \ return render.index(greeting = greeting) else:

return \

创建 HTML 表单

你可以通过 URL 参数实现表单提交,不过这样看上去有些丑陋,而且不方便一般人使用,你真正需要的是一个“POST 表单”,这是一种包含了

标签的特殊 HTML 文件。这种表单收集用户输入并将其传递给你的 web 程序,这和你上面实现的目的基本是一样的。

让我们来快速创建一个,从中你可以看出它的工作原理。你需要创建一个新的 HTML 文件, 叫做templates/hello_form.html:

Sample Web Form

Fill Out This Form

然后将 bin/app.py 改成这样:

1 import web 2 3 urls = ( 4 '/hello', 'Index' 5 ) 6 7 app = web.application(urls, globals()) 8 9 render = web.template.render('templates/') 10 11 class Index(object): 12 def GET(self): 13 return render.hello_form() 14 15 def POST(self): 16 form = web.input(name=\17 greeting = \18 return render.index(greeting = greeting) 19 20 if __name__ == \21 app.run()

都写好以后,重启 web 程序,然后通过你的浏览器访问它。

这回你会看到一个表单,它要求你输入“一个问候语句(A Greeting)”和“你的名字(Your Name)”,等你输入完后点击“提交(Submit)”按钮,它就会输出一个正常的问候页面,不过这一次你的URL 还是http://localhost:8080/hello,并没有添加参数进去。

在 hello_form.html 里面关键的一行

上面这些修改的目的,是将每一个页面顶部和底部的反复用到的“boilerplate”代码剥掉。这些被剥掉的代码会被放到一个单独的 templates/layout.html 文件中,从此以后,这些反复用到的代码就由 layout.html 来提供了。

上面的都改好以后,创建一个 templates/layout.html 文件,内容如下: $def with (content)

Gothons From Planet Percal #25

$:content

这个文件和普通的模板文件类似,不过其它的模板的内容将被传递给它,然后它会将其它 模板的内容“包裹”起来。任何写在这里的内容多无需写在别的模板中了。你需要注意$:content 的用法,这和其它的模板变量有些不同。 最后一步,就是将 render 对象改成这样:

render = web.template.render('templates/', base=\

这会告诉 lpthw.web 让它去使用 templates/layout.html 作为其它模板的基础模板。重启你的程序观察一下,然后试着用各种方法修改你的 layout 模板,不要修改你别的模板,看看输出会有什么样的变化。

为表单撰写自动测试代码

使用浏览器测试 web 程序是很容易的,只要点刷新按钮就可以了。不过毕竟我们是程序员嘛,如果我们可以写一些代码来测试我们的程序,为什么还要重复手动测试呢?接下来你要做 的,就是为你的 web 程序写一个小测试。这会用到你在《习题 47》学过的一些东西,如果你不记得的话,可以回去复习一下。 为了让 Python 加载 bin/app.py 并进行测试,你需要先做一点准备工作。首先创建一个 bin/__init__.py空文件,这样 Python 就会将 bin/ 当作一个目录了。(在《习题 52》中你会去修改 __init__.py,不过这是后话。)

我还为 lpthw.web 创建了一个简单的小函数,让你判断(assert) web 程序的响应,这个函数有一个很合适的名字,就叫 assert_response。创建一个 tests/tools.py 文件,内容如下:

1 from nose.tools import * 2 import re 3 4 def assert_response(resp, contains=None, matches=None, headers=None, 5 status=\ 6 7 assert status in resp.status, \

8 (status, resp.status) 9 10 if status == \11 assert resp.data, \12 13 if contains: 14 assert contains in resp.data, \does not contain %r\% 15 contains 16 17 if matches: 18 reg = re.compile(matches) 19 assert reg.matches(resp.data), \does not match %r\% matches

if headers:

assert_equal(resp.headers, headers) 准备好这个文件以后,你就可以为你的 bin/app.py 写自动测试代码了。创建一个新文件,叫做tests/app_tests.py,内容如下:

1 from nose.tools import * 2 from bin.app import app 3 from tests.tools import assert_response 4 5 def test_index(): 6 # check that we get a 404 on the / URL 7 resp = app.request(\ 8 assert_response(resp, status=\ 9 10 # test our first GET request to /hello 11 resp = app.request(\12 assert_response(resp) 13 14 # make sure default values work for the form 15 resp = app.request(\16 assert_response(resp, contains=\17 18 # test that we get expected values 19 data = {'name': 'Zed', 'greet': 'Hola'} 20 resp = app.request(\21 assert_response(resp, contains=\22 最后,使用 nosetests 运行测试脚本,然后测试你的 web 程序。

$ nosetests

.

----------------------------------------------------------------------

Ran 1 test in 0.059s OK

这里我所做的,是将 bin/app.py 这个模块中的整个 web 程序都 import 进来,然后手动运行这个 web 程序。lpthw.web 有一个非常简单的 API 用来处理请求,看上去大致是这样子的:

app.request(localpart='/', method='GET', data=None, host='0.0.0.0:8080',

headers=None, https=False)

你可以将 URL 作为第一个参数,然后你可以修改修改 request 的方法、form 的数据、以及 header 的内容,这样你无须启动 web 服务器,就可以使用自动测试来测试你的 web 程序了。

为了验证函数的响应,你需要使用 tests.tools 中定义的 assert_response 函数,用法属下:

assert_response(resp, contains=None, matches=None, headers=None, status=\

把你调用 app.request 得到的响应传递给这个函数,然后将你要检查的内容作为参数传递给诶这个函数。你可以使用 contains 参数来检查响应中是否包含指定的值,使用status 参数可以检查指定的响应状态。这个小函数其实包含了很多的信息,所以你还是自己研究一下的比较好。

在 tests/app_tests.py 自动测试脚本中,我首先确认 / 返回了一个“404 Not Found”响应,因为这个 URL 其实是不存在的。然后我检查

了 /hello 在 GET 和 POST 两种请求的情况下都能正常工作。就算你没有弄明白测试的原理,这些测试代码应该是很好读懂的。

花一些时间研究一下这个最新版的 web 程序,重点研究一下自动测试的工作原理。确认你理解了将bin/app.py 做为一个模块导入,然后进行自动化测试的流程。这是一个很重要的技巧,它会引导你学到更多东西。

本文来源:https://www.bwwdw.com/article/5hz6.html

Top