最近开始用Tornado做开发了,究其原因,主要是Tornado基于Python,一来代码量少开发速度快,二来采用epoll方式,能够承载的并发量很高。在我的i5台式机上用ab测试,不连接数据库的情况下,单用get生成页面,大概平均的并发量在7900左右。这比php或者java能够承载并发量都高很多很多。三来Python代码可维护性相对来说比php好很多,语法结构清晰。四来,tornado的框架设计的很黄很暴力,以HTTP请求方式作为方法名称,通常情况下,用户写一个页面只需要有get和post两种方式的方法定义就够了。
在学习的过程中遇到一些比较重要的问题,记录下来以后备查,在学习的过程中遇到不少问题,基本都是靠×××解决,百度实在是令人痛苦不堪。记录比较零散一些,可能不仅限于tornado,也会包括python的一些知识。由于我也还在学习过程中,所以有些东西不一定详尽或者理解到位,tornado高人勿拍。
tornado入门不是很难,只要理解了他处理的方式就很好做了。tornado在处理网页的时候,针对于URL的连接,实际就是对class类的一个路由映射。而类中的方法通常无非就两种,处理连接请求的get或者post。所以tornado的页面编写很简单。比如,这是一个用作验证登录用户的类,逐行解释一下:class SigninHandler(BaseHandler): #引入BaseHandler
def post(self): #HTTP的POST方法,是GET渲染的form中的post method所对应
username = self.get_argument('username') #获取form中username的值
password = self.get_argument('password') #获取form中password的值
conn = MySQLdb.connect('localhost', user = 'root', passwd = '', db = 'datacenter', charset = 'utf8', cursorclass = MySQLdb.cursors.DictCursor) #连接数据库,指定cursorclass的目的是要让返回结果以字典的形式呈现,如果不写,是以元组形式返回
cursor= conn.cursor() #定义数据库指针
sql = 'SELECT * FROM dc_users WHERE username=%s AND password=password(%s)' #写sql,为何这样写后面再说
cursor.execute(sql, (username, password,)) #执行SQL
row = cursor.fetchone() #获取一条,返回值为dict,因为前面连接数据库时定义了cursorclass = MySQLdb.cursors.DictCursor,当然,你需要import MySQLdb.cursors的包
if row: #如果存在记录
self.set_secure_cookie('id', str(row['id']).encode('unicode_escape'), expires_days=None) #设置安全cookie,防止xsrf跨域
self.set_secure_cookie('username', row['username'].encode('unicode_escape'), expires_days=None) #same
self.set_secure_cookie('role', row['role'].encode('unicode_escape'), expires_days=None) #same
ip = self.request.remote_ip #获取来访者IP
sql = 'UPDATE dc_users SET last_access = NOW(), last_ip=%s WHERE id = %s' #认证审计变更的SQL
cursor.execute(sql, (ip, row['id'],)) #执行SQL
conn.commit() #提交执行
cursor.close() #关闭指针
conn.close() #关闭数据库连接
self.redirect('/') #转入首页
return #返回,按照官方文档的要求,在redirect之后需要写空的return,否则可能会有问题,实测确实会有问题
else: #如果不存在记录
self.redirect('/Signin') #跳转回登录页面
return
def get(self): #HTTP GET方式
self.render('users/login_form.html') #渲染登录框HTML
login_form.html内容如下{% include 'header.html' %}
{% module xsrf_form_html() %}
{% include 'footer.html' %}
对于主代码,应如下:#-*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.autoreload
import os
class BaseHandler(tornado.web.RequestHandler): #BaseHandler
def get_current_user(self):
user = self.get_secure_cookie('username')
return user
class IndexHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
if not self.current_user:
self.redirect('/Signin') #如未登录,则跳转Signin,Signin的GET方法调用的就是login_form.html页面
return
self.render('welcome.html') #否则渲染welcome.html
settings = \
{
"cookie_secret": "HeavyMetalWillNeverDie", #Cookie secret
"xsrf_cookies": True, #开启跨域安全
"gzip": False, #关闭gzip输出
"debug": False, #关闭调试模式,其实调试模式是很纠结的一事,我喜欢打开。
"template_path": os.path.join(os.path.dirname(__file__), "./templates"), #定义模板,也就是login_form.html或header.html相对于本程序所在的位置
"static_path": os.path.join(os.path.dirname(__file__), "./static"), #定义JS, CSS等文件相对于本程序所在的位置
"login_url": "/Signin", #登录URL为/Signin
}
application = tornado.web.Application([
(r"/", IndexHandler), #路由设置/ 使用IndexHandler
(r"/signin", SigninHandler) # Signin使用SigninHandler
], **settings)
if __name__ == "__main__": #启动tornado,配置里如果打开debug,则可以使用autoload,属于development模式,如果关闭debug,则不可以使用autoload,属于production模式。autoload的含义是当tornado监测到有任何文件发生变化,不需要重启server即可看到相应的页面变化,否则是修改了东西看不到变化。
server = tornado.httpserver.HTTPServer(application)
server.bind(10002) #绑定到10002端口
server.start(0) #自动以多进程方式启动Tornado,否则需要手工启动多个进程
tornado.ioloop.IOLoop.instance().start()
对于sql部分,执行最好写成cursor.execute(sql, (id,)),将%s的东西以元组形式传递给execute方法,这样做的目的是最大程度避免SQL注入的发生。如果直接写为 'select * from xxx where id = ' + id 或者 'select * from xxx where id = %s' % id 的话,会被注入。另外,如果是sqlite3的话,需要写成 'select * from xxx where id=?' ,然后execute方式一样。
另外,如果开启了禁止xsrf跨域功能的话,在每个HTML的form表单里必须加上{% module xsrf_form_html() %}否则会出现禁止访问的错误。
下篇记录一下编码格式处理,这个在python2上最讨厌。