整理了下网络上几片tornado 的入门教程,方便下后来者。
Tornado是一个强大而可伸缩的web服务器,用Python语言开发而成。虽然是轻量级的,但是他很健壮,可以应用于正式的多种应用和工具。
Tornado是基于一个叫公司FriendFeed开发的web框架,起初由Bret Taylor等人开发的,后来,在他们要求下,代码开源了。商业的web框架最多可以同时具有10000个连接,而Tornado开发目标是解决大规模并发网络(C10K Problem)问题的高性能web框架。它还包括了安全处理、用户认证、社交网络、异步模式及数据库、web接口函数的扩展服务。
自2009年10月发布以来,获得许多团体支持,并被应用于各种不同的目标。除了FriendFeed和Facebook之外,很多公司在生产中开始转向Tornado,包括Quora、Turntable.fm、 Bit.ly、Hipmunk和 MyYearbook。
简单地说,如果你正在寻求大型CMS或大而慢的开发框架,Tornado不会是你的选择。Tornado并不要求你以特别的形式建立大型模型或一定风格的处理形式。你可以做到敏捷开发。你要创建可伸缩而友好的应用、实时的分析引擎或RESTful API,用这个框架是很好的选择。
Tornado起步
在类Linux系统中安装很容易,可以用PyPI获取,也可以从Github下载源代码,用以下命令安装:
$ curl -L -O http://github.com/downloads/facebook/tornado/tornado-2.1.1.tar.gz
$ tar xvzf tornado-2.1.1.tar.gz
$ cd tornado-2.1.1
$ python setup.py build
$ sudo python setup.py install
它没有windows系统的官方支持,但可以通过ActivePython的PyPM包管理器安装:
C:\> pypm install tornado
安装时,会带有演示模块。它包括建立博客、集成用来运行聊天服务的Facebook等。后面章节将一步一步地讲解一些例子。
简易web服务器
前文已经简单介绍了Tornado,下面将开始如何用Tornado写一个基础的web服务器。
Hello Tornado实例
Tornado是个可以用来写HTTP请求应答的框架。作为程序员,你需要编写匹配一个特别形式的HTTP请求应答。以下是一个全功能Tornado应用实例程序:
Example 1-1. The basics: hello.py
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
写Tornado应用基本工作就是定义RequestHandler的子类。本例中,我们编写了一个简单的侦听给定端口号并应答根请求的应用。
而后在命令行下输入以下命令来测试:
$ python hello.py --port=8000
现在在浏览器中浏览http://localhost:8000/地址,或再开一个终端窗口来测试:
$ curl http://localhost:8000/
Hello, friendly user!
$ curl http://localhost:8000/?greeting=Salutations
Salutations, friendly user!
下面分代码块一句一句分析:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
程序开始,导入多个Tornado助手库。其中,还包括有其它的一些包,但至少需要导入以下四个才能使本演示实例运行:
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
为读取命令行参数,Tornado包中含有助手库(tornado.options)。本例中使用它可以让我们特别指定HTTP服务端口。它是这样工作的:在define语句中给出选项参数会成为全局options对象,否则选项参数在命令行中以相同的名字给出。如果用户带--help参数运行程序,程序会输出定义的所有选项提示。如果用户没有提供我们定义参数的值,默认值会自动应用。Tornado用type参数做参数类型检查,如果类型错误,会抛出一个错误。本例中允许用户使用整数类型的port参数,我们也可以在程序中以options.port形式给出。如果用户没有指定,其默认值为8000。
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
这是一个Tornado请求处理类。当处理请求时,Tornado实例化这个类并调用对应的HTTP请求的方法。本例中,我们只定义了一个get方法,意思是这个处理器将回应HTTP GET请求。我们在后在部分会看到更多HTTP方法接口。
greeting = self.get_argument('greeting', 'Hello')
Tornado请求处理器类拥有许多有用的内建方法,其中包括get_argument,在这里用它来从HTTP请求参数中获取greeting参数,而Hello字符串作为默认值。
self.write(greeting + ', friendly user!')
Tornado请求处理类另一个方法是write,他获取一个字符串参数并写入到HTTP应答中去。在这里,我们使用请求参数中的greeting参数,并输出到应答中。
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
以上几行用于运行Tornado应用程序。首先,我们使用Tornado options库分析命令行。然后创建Application类的实例。传给Application类 __init__方法最重要的参数是handlers。它指出哪个类用来处理哪个请求。
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
以上代码是一个模板式内容,传入一个Application对象给Tornado的HTTPServer对象,它会监听我们在命令行在指出的端口(否则使用默认端口)。最后,我们创建Tornado的IOLoop的实例,之后会指出程序准备好接收HTTP请求。
handlers参数
再看一下hello.py中的这一行:
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
这个参数很重要,值得仔细进步研究。它必须是一个元组的列表,其中每个元组第一个元素是正则式,第二个元素是一个RequestHandler类。在hello.py中,我们只给出了一对,你可以根据需要给出更多。
用正则指示特殊的地址
Tornado使用正则来匹配HTTP请求地址,它会自动把其中的正则表达包括在开始和结束正则符号中(例如'/'约定为'^/$')
当正则表达式中有捕获组时,组匹配内容会作为参数传给RequestHandler对象中的对应HTTP请求的方法。下一个例子中会看到这种情况。
更多
例1-2程序比上一个例子包含更多内容,介绍更多Tornado基本概念。
Example 1-2. Handling input: string_service.py
import textwrap
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, width))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
同上一例子一样,你可以用以下命令运行这个实例:
$ python string_service.py --port=8000
这个程序是以字符处理为目标的web服务基本框架。现在可以做两个操作,首先,到/reverse/string的GET请求会返回反转后的字符串:
$ curl http://localhost:8000/reverse/stressed
desserts
$ curl http://localhost:8000/reverse/slipup
pupils
第二,POST请求/wrap会传来指定的文本,并返回包装后的文本。如果POST中没有指定参数width,它就会是默认值40:
$ curl http://localhost:8000/wrap ?
-d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.
这个string服务实例和上一个实例有许多相同的代码。让我们看下不同的代码。首先,看下传给Application对象的handlers参数:
app = tornado.web.Application(handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
])
在这个代码中,Application类实例化时带有两个RequestHandlers参数的handlers参数。第一个请求匹配的正则表达式:
/reverse/(\w+)
这个正则表达式匹配的请求是/reverse/后跟一个或多个字母数字混合字符。括号中的内容使Tornado保存匹配的字符串,并作为参数传给请求处理器的方法。看看这个反转字符串的处理器是如何工作的:
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
以上代码中,get方法有一个附加的参数input,这个参数会包含那个正则表达式匹配的字符串部分。
再看下WrapHandler的定义:
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, width))
这个类处理url为/wrap的请求,定义的是POST方法,意味着它接受HTTP的POST请求。
前面我们用过RequestHandler对象的get_argument方法来获取请求的附带的字符串。我们也可用这个方法获取POST请求的参数。当我们从POST请求体中取得文本和width参数后,就可以使用python内建的textwrap库来格式化处理获得的文本,并将结果字符串返回给HTTP response。
有关RequestHandler的更多内容
到此为止,我们已经探索了公开的基本RequestHandler对象:如何从HTTP请求中获取参数,如何返回HTTP应答。以后章节中,还有更多的需要学习更多的内容。
HTTP请求的方法
以上例子中,每个RequestHandler类只定义了一个方法。然而,更有用的是在其中定义更多的方法。把相关的方法放在同一个类中是很有用的。例如:你可能用一个处理器类同时接受GET和POST请求来处理数据库中一个特定的ID。这里有一个假想的例子,GET请求获取ID相关信息关返回,POST请求提交相关数据到数据库中。
# matched with (r"/widget/(\d+)", WidgetHandler)
class WidgetHandler(tornado.web.RequestHandler):
def get(self, widget_id):
widget = retrieve_from_db(widget_id)
self.write(widget.serialize())
def post(self, widget_id):
widget = retrieve_from_db(widget_id)
widget['foo'] = self.get_argument('foo')
save_to_db(widget)
例子中我们只使用了GET和POST方法,而Tornado支持任何可用的HTTP方法(GET、POST、PUT、DELETE、HEAD、OPTIONS)。可以在RequestHandler类中定义上述任一个方法,并匹配一个特定的名字的请求。以下是另一个假想的例子,HEAD请求只是根据ID返回其是否存在,GET方法返回这个对象。
# matched with (r"/frob/(\d+)", FrobHandler)
class FrobHandler(tornado.web.RequestHandler):
def head(self, frob_id):
frob = retrieve_from_db(frob_id)
if frob is not None:
self.set_status(200)
else:
self.set_status(404)
def get(self, frob_id):
frob = retrieve_from_db(frob_id)
self.write(frob.serialize())
HTTP状态代码
正如前面的例子中,你可以调用set_status()方法设置HTTP状态代码。更为重要的是在一些情形下Tornado会自动设置应答HTTP状态代码。以下是最常见的实例:
404 Not Found
HTTP请求路由没有匹配到任何与之相关联的RequestHandler类
400 Bad Request
你调用不带默认值的get_argument()方法,它又没有这个参数。Tornado会自动返回400代码。
405 Method Not Allowed
请求的HTTP方法匹配的RequestHandler类没有定义对应方法
500 Internal Server Error
有不能退出程序的严重错误及没有捕获到的意外
200 OK
回应成功,并且没有设置其他的状态代码。
以上任何一个错误发生时,Tornado会默认返回带有简短信息的HTML代码和状态代码。你要改变默认的错误返回,可以覆盖RequestHandler类中的write_error方法。如下例:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
def write_error(self, status_code, **kwargs):
self.write("Gosh darnit, user! You caused a %d error." % status_code)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
下面就是HTTP POST请求而没有定义该方法的的回应。正常情况下返回默认信息,但是此处重载了write_error方法,获得了以下的回应:
$ curl -d foo=bar http://localhost:8000/
Gosh darnit, user! You caused a 405 error.
tornado开发学习之2.输入输出,数据库操作,内置模板,综合示例
使用python环境中的tornado进行web开发上篇已经解决了urlmap和基本运行机制的问题。接下来进行web编程就是一下几个问题
- 1.输入数据的获取和输出运算结果
- 2.数据库操作
- 3.自带模板的使用
当然还有cookies和session等,这些杂项,不在本篇进行学习。
1.输入数据的获取和输入运算结果
WEB作为一种UI表达方式,最重要的是获取数据和表达运算结果,所以如何在一个web开发框架中获取url或post传递过来的参数十分重要,我建议学习所有的Web开发语言或框架都从此开始比较好。在tornado中从我自己的理解来看有两种获取参数的方式
-
a.通过urlmap做正则表达式,通过抓取组来直接获取参数
这就需要我们在做URL配置时就想好要传递的参数如,我们需要给ArticleDetail类传递一个文章编号,我们知道这个编号只能是数字类型的。那么可以在配置时写成如下样子
handlers = [(r'/detail/(\d+)', ArticleDetail),]
其中的(d+)就是一个正则的抓取组,用于获取多位的数字。 那么在ArticleDetail类的get或post函数实现时就要写成
class ArticleDetail(BlogHandler): def get(self, id): ....具体的实现
入口时多了一个id参数,当然你也可以用别的变量名,都会将URL匹配的结果放进这个参数传入的。
-
b.通过在get或post中使用tornado的RequestHandler的get_argument函数来获取参数
为了同样获取到文章id这个参数,如果我们不在URL配置时做正则(一般我都不做,因为我对正则不很熟练),也可以在具体处理某个地址的类方法中使用get_argument来获得参数值,还是以获取文章编号为例,我们需要如下方式来编写Url配置
handlers = [(r'/detail', ArticleDetail),]
在地址定义中我们取消了正则匹配组,让地址直接交给ArticleDetail类全权负责。 接下来在ArticleDetail中get或post函数的入口就不能在有除self意外的参数了,当使用/detail?id=1访问系统时
class ArticleDetail(BlogHandler): def get(self): id=self.get_argument('id')#获取到id ....具体的实现 def post(self): id=self.get_argument('id')#获取到id ....具体的实现
对于输出,一般在任何WEB开发框架中都比较简单,tornado中就是简单的self.write(htmlsrc),其中htmlsrc就是即将交给浏览器显示的html文件的源码。一般是由模板引擎将数据与模板结合后字符串结果。
2.数据操作
WEB开发与其他开发一致,也需要涉及数据的持久化和数据的读取。WEB的UI是HTML解决软件的界面,参数的收集,事件的触发,结果的显示;开 发语言负责解决运算逻辑,数据读取和保存;数据库和数据文件或其他方式的持久化解决对象持久化和数据源问题(当然还有一种叫oracle的数据库!!!! 它太NB了,自己都能写WEB,也能写程序,与我理解上数据库应该干的活区别较大,请观众随意吐槽);所以解决参数获取和数据库操作,基本上就解决了 WEB开发的最大问题了(jquery?html?这也是很大的问题,但属于前端开发的主要学习方向)。
tornado内置了一个简单封装了Mysql的操作,pypm install python-mysql需要事先安装好驱动哦。公布出来的函数比较少,与直接使用python的数据接口基本一致,稍稍简单写。
-
a.数据连接
#导入tornado的数据库包 from tornado import database #创建数据库连接,db_host数据库主机名,db_port开放的端口号,db_user用户名,db_passwd登录密码,idle_time最大空闲时间默认是7小时以秒为单位,mysql has gone away一般跟这个有关系。这种连接方式不支持连接池。较原始,效率还可以。 db = database.Connection('%s:%s' % (db_host, db_port), db_name, db_user, db_passwd, idle_time)
创建数据库连接后就可以使用db来进行后边介绍的基本操作了。
-
b.查询 常用的查询方法如下
1). query(),用于执行select 语句,返回的是行集list,例如
users=db.query("select * from users") for u in users: print u.username,u.userpwd user=db.query("select * from users where id=%s",id) if user: print user[0].username,user[0].userpwd
2).get(),返回的是符合条件的结果集的第一行
user=db.get("select * from users where id=%s",id) if user: print user.username,user.userpwd
3).execute(),用于执行select以外的语句,返回值基本无视
rv=db.execute("update customer set sex='男' where id=10128") print rv
基本上如果你的语句没写错,得到的都是0
-
c.数据结果
知道如何查询后续就是如何利用查询得到的结果了。
对于query()操作返回的是[]数组(命名位items),
数组中每一个元素均是{}字典(每一个都是item)。
如果确切知道字段名(属性名)如确切知道查询结果中至少有一行数据,包含名为title的字段,可以直接使用items[0].title得到数据。
如果不确切知道字段名,或字段值可能为None,可使用items[0].get('title','')来获取值。 最常用的方法是循环
for item in items: print item.get('title','')
-
d.其他操作
-
execute_rowcount(sql)用于获取查询或更新得到的记录行集的行数,特别有意思的是如果你要更新的内容本来已经是那个样子,如
print db.execute_rowcount("update customer set sex='女' where id=10128") print db.execute_rowcount("update customer set sex='女' where id=10128")
执行两遍得到的结果是1,0。我原来一直以为数据库很弱智的,看来是我那时候不懂事,很天真。
- close()用于手动关闭数据连接,在小型应用中我们基本不会看到他的出现
-
3.自带的模板的使用
web编程其实可以不用模板的,但是那样不仅写的时候痛苦,写出来以后更痛苦
写的时候,html代码与python代码混合,连接字符串非常费力
改的时候很容易出现错误,python又不是编译执行的,上线了执行到某错误时,多恼火,低级愚蠢的错误容易犯
换web的UI时这种写法根本没办法操作,那就是重写一次程序啊,杀人的心都有的
所以如果你开发了一个Web系统,但是没有用模板技术,这个系统就只能给钱多人傻的单位做了。
mako是我最喜欢的模板,但是学习tornado么,而且不是那种排版敏感的,先来学习用用它自带的。
web使用开发使用模板引擎我么要主要解决一下几个主要的问题,在tornado中我们一个个破解
- 1.路径和模板存放方式
善用os.path.dirname(file)来解决路径问题,例如我们的模板准备放在工程的根目录下的/T/目录中,对于模板存放的目录我们可以这样定义
loader = template.Loader(os.path.dirname(__file__)+"/T/")
当然我更倾向直接定义绝对路径的方法来解决这个问题,因为使用上也特别简单,而且可以把模板放在与源代码无关的目录中,上线使用时也更加安全
TP="D:\\T\\" loader=template.Loader(TP)
模板一般就是普通的文本文件,方便起见一般以html扩展名存放比较好。比如index.html,这样我们把index.html放入我们定义的模板加载路径后使用以下代码加载模板引擎
t=loader.load("index.html")
其中t就是我们直接可以使用的模板了。
- 2.数据与模板如何结合
模板主要功能之一就是解决数据的显示方式,如何把我们的数据交给模板使用?首先我们来获取数据
cus1=db.query("select * from customers limit 3")#得到数据 cus2=db.query("SELECT * FROM customer LIMIT 5 OFFSET 5")#得到数据 htmlsrc=t.generate(datas1=cus1,datas2=cus2)#模板运算出的字符串作为html显示 self.write(htmlsrc)#向客户端发送结果
代码第三行的datas1和data2是在tornado模板文件中将要引用的变量名我们的模板编写如下
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"> <html> <body> <!--这里从datas1中读取数据--> <table style="border:1px solid"> {% for u in datas1 %} <tr> <td>{{ u.truename }}</td><td>{{ u.sex }}</td> </tr> {% end %} </table> <hr/> <table style="border:1px solid"> {% for u in datas2 %}<tr><td>{{ u.truename }}</td><td>{{ u.sex }}</td></tr> {% end %} </table> </body> </html>
可以高兴地讲:
1.tornado的模板不需要严格按照python的缩进来书写
2.tornado的模板使用{{变量或对象}}来获取具体的变量值或对象属性值
3.tornado的模板中使用{% for var in vars %}....{% end%}来实现循环
4.tornado的模板中使用{% if 条件 %}...{% elif 条件 %}...{% else %}...{% end %}方式实现模板内的判断
5.tornado的模板使用{% include 文件名 %}来包含子模板,同时也会运算子模板的运算逻辑
OK知道以上这些内容或许我们已经可以开始一个稍稍复杂一点儿程序了。下面我们来联系一下。
4.综合示例
制作的目标做一个对一个数据表进行增加、删除、修改和简单查询的程序 数据表我们假定为通讯录数据 为了简化程序的运行逻辑,尽可能不用ajax或其他需要一些javascript基础的写法 数据库当然使用mysql啦
我按照一般习惯的开发过程来完成这个demo,综合以上的知识,我尽量少写代码以外的文字了,写尽可能细致的注释。功能虽然特别简单,都是按照平时实际项目的开发规范和架构来执行的。
a.数据结构及访问权限
我们使用root用户来访问数据库,假定访问的密码是root123456,使用以下语句来创建数据表:
Create Table CREATE TABLE `d_contacts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `truename` varchar(20) DEFAULT NULL, `sex` varchar(2) DEFAULT NULL, `phoneno` varchar(20) DEFAULT NULL, `email` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
b.规划目录结构
我们假定开发的项目文件存放在D:\projecting目录中,项目名称叫GingerTornadoDemo1
c.关键性源码解释
- 1).处理类的基类 basehandler.py
代码如下,通过定义基础类,可以让后续的开发变得更加简单,这里使用了一个技巧性的变化。 利用了Aaron Swartz的web.py框架中的Storage类来简化参数获取。以前需要一个个用get_argument()来获取的参数现在只要用 i=self.input() 然后直接获取参数名如i.id就可以得到了。当然参数如果不存在仍然会报错的。获取后最好用utils包中的InitStorage做一次初始化(不会污 染已有属性值),确保调用无误。
# -*- coding: utf-8 -*- import json
__author__ = 'jy@cjlu.edu.cn' from tornado.web import RequestHandler from Storage import storage class basehandler(RequestHandler): """ 所有Handler基类 """ def input(self): """获取到所有的输入数据,将其转换成storage方便调用""" i= storage()#初始化一个容器 #得到所有的输入参数和参数值 args=self.request.arguments #将参数写入i的属性 for a in args: i[a]=self.get_argument(a) #获取file类型的参数 i["files"]=storage(self.request.files) #获取path i["path"]=self.request.path #获取headers i["headers"]=storage(self.request.headers) return i
- 2).数据操作类 d_contacts.py
此文件保存于models目录中,用来做dcontacts数据表的基本操作,我简单封装了一些基础的函数。有些可能未在本demo中使用 过,但我认为很重要也编写了出来。 在python中JAVA和C#中的失血模型由一个简单的Storage基本全部搞定,而且可以很方便的进行数据结构的调整和属性类型变化和增补属性。所 以这个类也可以看出来Java和C#那种长时间对我编码风格的侵害有多深了。所有的程序都可以放一个文件中解决,但我仍然要把他们的功能按照多层的思想来 细分。 这个类可以视作DAL类,如果有更复杂的业务逻辑操作还可以继承或调用dcontacts来编写更多的业务逻辑(至于是继承好还是调用好,我一般选择继承,虽然有违与封闭开发的面向对象接线的传统,但方便啊。谁用谁知道。)。
# -*- coding: utf-8 -*- __author__ = 'jy@cjlu.edu.cn' from config import sdb from Storage import storage def GC_set(obj): return "" class d_contacts: fields=["id","truename","sex","phoneno","email"] #定义好数据表有的数据 def getAll(self): """获取全部记录集合""" items=sdb.query("select * from d_contacts") if items: for i in items: i=storage(i) return items def delEntityById(self,id): """ 根据id来删除数据 """ return sdb.execute("delete from d_contacts where id=%s",id) def getEntityById(self,id): """ 根据id来或取对象实例""" if id: id=int(id) item=sdb.get("select * from d_contacts where id=%s",id) return storage(item) else: return None def getRowsBySqlwhere(self,sqlwhere): """ 根据sqlwhere来查询数据集合""" items=sdb.query("select * from d_contacts where id>0 "+sqlwhere) if items: for i in items: i=storage(i) return items def update(self,obj): """ 更新数据行集 """ obj=storage(obj) return sdb.execute("update d_contacts set truename=%s,sex=%s,phoneno=%s,email=%s where id=%s",obj.truename,obj.sex,obj.phoneno,obj.email,obj.id) def insert(self,obj): """ 写入数据 """ obj=storage(obj) return sdb.execute("insert into d_contacts (truename,sex,phoneno,email) values(%s,%s,%s,%s)",obj.truename,obj.sex,obj.phoneno,obj.email) def getRowBySqlwhere(self,sqlwhere): """ 根据sqlwhere来获取一个数据实例 """ item=sdb.get("select * from d_contacts where id>0 "+sqlwhere) if item: item=storage(item) return item
- 3).具体业务控制实现类 f1001.py
这是所有操作数据表dcontacts的具体业务逻辑的实现。此文件存放于f10目录中,同过它调用models包的dcontacts等来实现对d_contacts数据的操作和视觉表达。 既然是demo我就顺便使用了一下简单的ajax操作,来完成增、删、改的功能。这样就基本涵盖了大部分的web开发的基础性功能的实现方式了。
# -*- coding: utf-8 -*- __author__ = 'jy@cjlu.edu.cn' import os from tornado import template from Storage import storage #导入工具函数 from utils import * #导入基础类 from basehandler import basehandler #导出持久类 from models.d_contacts import d_contacts #导入模板 from config import TP #创建持久对象 optobj=d_contacts() #建立模板,指定模板路径 tl=template.Loader(os.path.join(TP,"f10/T")) class List(basehandler): """ 用于处理显示列表 """ def get(self): #获取全部输入参数 i=self.input() #获取全部数据,继续改下去一定要考虑分页 i.recs=optobj.getAll() #调用模板 t=tl.load("f1001_list.html") #把数据推给模板进行运算 htmlsrc=t.generate(i=i) #显示运算结果 self.write(htmlsrc) def post(self): """由于不需要处理POST,所以不实现 """ pass class Update(basehandler): """ 用于处理显示修改界面,处理ajax修改、新增、删除的请求 """ def get(self): i=self.input() #初始化i的rec属性,让Rec具有与数据表字段名的全部属性 i.rec=InitStorage(i,["id","truename","phoneno","email"]) #从数据库中获取数据 i.rec=optobj.getEntityById(i.id) #调用模板 t=tl.load("f1001_update.html") #把数据推给模板进行运算 htmlsrc=t.generate(i=i) #显示运算结果 self.write(htmlsrc) def post(self): """
处理写入数据删除数据和保存数据等操作
""" def valid(v): """ 验证邮件是否正确
""" if IsEmail(v.email): return True else: return False #特别要注意,因为是响应的ajax的请求,返回数据类型需指定为json self.set_header("Content-Type", "application/json") #做一个容器变量 v=storage() #获取到全部的输入值 i=self.input() #将输入参数中与数据表字段名一致的值赋值给v对象的属性 v=CopyData_INC(v,i,optobj.fields) #根据操作类型来进行处理 if i.act=="add": vv=valid(i) if vv==True: optobj.insert(v) #返回处理结果JSON值 self.write(JsonResult("OK")) else: self.write(JsonResult("NOOK")) if i.act=="update": vv=valid(i) if vv==True: optobj.update(v) self.write(JsonResult("OK")) else: self.write(JsonResult("NOOK")) if i.act=="del": optobj.delEntityById(i.id) self.write(JsonResult("OK"))
- 4).其他
开发必然需要用到html和javascript,是难点但不是我们学习tornado的重点。具体看代码吧。 我只解释一下f1001_update.html模板中一段javascript
var rv={}; $("[rel='v']").each(function(){ rv[$(this).attr("id")]=$(this).val(); }); ....body中有如下元素.... <td><input type="text" rel="v" name="truename" id="truename" value=""/>*</td> <td><input type="text" rel="v" name="sex" id="sex" value=""/>*</td> <td><input type="text" rel="v" name="phoneno" id="phoneno" value=""/>*</td> <td><input type="text" rel="v" name="email" id="email" value=""/>*</td> ....
这是我几乎在所有项目的录入模板中都使用的一个小技巧,我额外地给所有需要获取值的控件增加了一个rel属性。 利用jquery的选择特性来遍历id后自动获取这些组件的值,再将值赋值给实现定义的字典rv。 这样做的好处是,你几乎从前台到后台都不需要在参数值的获取上再一个个地进行赋值了。rv对象可直接在ajax调用时使用,无需处理。ajax调用参数传给处理的类后,使用i=self.input(),又将所有的参数和值变成了i的属性和值。 接下来你要做的就是使用i这个已经赋值好的对象来做你的预算了,所有以往获取参数和传递参数的麻烦就全解决了。
简单的描述你肯定感受不到这样做的好处,但当你面对一个具有几十个参数需要录入,后台对应一个数据对象时,你就会深深地爱上我的这种写法。超级爽,不用写一个赋值和获取参数的代码啦。还是去体验源码吧。
-
5).效果和源码下载
列表功能:
修改功能:
最好的文档是源码:tornado综合示例
5.结论与其他
- 本来想写3~5篇的帖子,发现两篇就搞定了
- 肯定是疏漏了很多东西啊,权鉴、404、UI组件、分页等等,还有很多。但我认为那些不是主干。主干是我介绍的这些,有这些你就可以使用tornado来工作了
- 我学习到这里感受tornado挺不错的,但感觉在开发的效率上来看,与web.py还存在相当大的差距。我自己的想法是使用tornado来做 web服务器,做urlmap。好了,其他的数据库组件还是继续使用web.py的,模板继续使用mako比较和我的胃口。因为我开始学习tornado 仅是因为它的性能不错,名气不小。而且在Aaron Swartz自杀后,web.py后续的发展给我自己在组件的选择上撬开了一个小小的缺口。别无它。
- 喜欢的兄弟拿来学习用用,不喜欢也可以来吐槽。我感觉我基本说清楚了tornado的Web开发。言语有限,我对他的系统学习到此为止了,再有问题,个别看它的文档相信是小菜了。