一、建立一个简单的 hello world 网页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
tornado.ioloop
import
tornado.web
class
MainHandler( tornado.web.RequestHandler ):
def
get(
self
):
self
.write(
"Hello, world"
)
return
settings
=
{
"cookie_secret"
:
"XmuwPAt8wHdnik4Xvc3GXmbXLifVmPZYhoc9Tx4x1iZ"
}
application
=
tornado.web.Application([
(r
"/"
, MainHandler)
],
*
*
settings
)
if
__name__
=
=
"__main__"
:
application.listen(
8090
)
tornado.ioloop.IOLoop.instance().start()
|
然后访问 http://localhost:8090/ 就可以访问最简单的网页了
具体的一些简单构建的细节在文档里说的很清楚,我就不累述了
tornado中文入门: http://www.tornadoweb.cn/
tornado中文文档: http://www.tornadoweb.cn/documentation
我主要想记载开发过程中遇到的一些比较技术性的东西
二、用户身份验证
1、一个简单的办法是,根据用户的IP
1
2
|
# 取得用户的IP地址
self
.request.headers.get(
'X-Real-IP'
,
self
.request.remote_ip).decode(
"utf-8"
)
|
2、根据 cookie
上面这个办法当然太简陋了,另外一个办法是通过 cookies
首先要修改 settings
1
2
3
4
|
settings
=
{
"cookie_secret"
:
"XmuwPAt8wHdnik4Xvc3GXmbXLifVmPZYhoc9Tx4x123"
,
"login_url"
:
"/login"
}
|
指定一个加密 cookie 的 secret,再指定一个登陆跳转的页面,当未认证用户尝试登陆需要身份认证的页面的时候就会跳转到这一页面,一般习惯性用 /login
然后给 GET 或 POST 添加一个装饰器
1
2
3
4
5
|
class
MainHandler( BaseHandler ):
@tornado
.web.authenticated
def
get(
self
):
self
.write(
"Hello, World"
)
return
|
有了这个装饰器的网页就会要求用户认证身份,认证身份的方法也很简单,注意看 MainHandler 继承于 BaseHandler,这是我自己写的一个类
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
BaseHandler(tornado.web.RequestHandler):
def
__init__(
self
,
*
args,
*
*
kwargs ):
# 继承于 tornado.web.RequestHandler
tornado.web.RequestHandler.__init__(
self
,
*
args,
*
*
kwargs )
self
.conn
=
DB()
self
.cursor
=
self
.conn.cursor()
# 这一方法就是 tornado 来判断用户是否已验证的方法
# 改写这一方法。当返回 True 的时候,tornado就会认为用户已验证
def
get_current_user(
self
):
# 我通过返回用户 cookie 的方式来判断是否已验证
return
self
.get_secure_cookie(
"userid"
)
|
这时候你就需要有一个登陆页面了,你可以在里面放一个简单的 input 来输入账号密码,然后 POST 给服务器来进行验证,如果通过的话就存入 cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
Login( BaseHandler ):
def
get(
self
):
# 显示登陆页面,等待输入
def
post(
self
):
# 获取用户输入的账号密码
userid
=
self
.get_argument(
"userid"
, strip
=
True
)
password
=
self
.get_argument(
"password"
, strip
=
True
)
# 对账号密码进行验证
# 验证成功后存入cookie
self
.set_secure_cookie(
"userid"
,
str
(userid))
# 这样用户就可以通过 BaseHandler 的检查了
|
三、静态文件夹
有时候需要用到一些静态文件夹,比如 js、css 和 ico 文件
可以通过在 settings 中增加设置来实现
1
2
3
4
5
6
7
8
9
10
11
|
settings
=
{
# 设置静态文件夹,此处设置为了 ./static
# 就可以直接访问 http://localhost:8090/static/* 的文件了
"static_path"
: os.path.join(pwd,
"static"
),
# 设置 cookie_secret
"cookie_secret"
:
"XmuwPAt8wHdnik4Xvc3GXmbXLifVmPZYhoc9Tx4x1iZ"
,
# 设置登录页面
"login_url"
:
"/login"
,
# 是否防跨域 POST (具体见文档)
"xsrf_cookies"
:
True
}
|
四:render 的时候将不该显示的 xsrf_form_html() 显示出来了
按照文档设置了 xsrf_cookies: True ,并且在 form 中添加了 {{ xsrf_form_html() }} 后,这条内容却被显示在了网页
这是因为 2.1 之后的 tornado 自动开启了escape,它会自动的将字符符号转义,有两种解决办法,一种是在 HTML 中改写成 {% raw xsrf_form_html() %} 来标明此条语句不转义,另外一种就是在 settings 中将自动转义关掉
1
2
3
4
5
6
7
8
|
settings
=
{
"static_path"
: os.path.join(pwd,
"static"
),
"cookie_secret"
:
"XmuwPAt8wHdnik4Xvc3GXmbXLifVmPZYhoc9Tx4x1iZ"
,
"login_url"
:
"/login"
,
"xsrf_cookies"
:
True
,
# 关掉自动 escape
"autoescape"
:
None
,
}
|
五、当 static 文件夹不在本地的时候
static_url 貌似只能解析本地的路径,如果部署在类似 SAE 这样的云平台的时候,代码服务器和存放文件的 Storage 服务器不在一个 domain 上时,static就没法工作了
我的解决办法是自己重写了 tornado.web.RequestHandler 中的 static_url 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
BaseHandler(tornado.web.RequestHandler):
def
__init__(
self
,
*
args,
*
*
kwargs ):
tornado.web.RequestHandler.__init__(
self
,
*
args,
*
*
kwargs )
self
.conn
=
DB()
self
.cursor
=
self
.conn.cursor()
def
get_current_user(
self
):
return
self
.get_secure_cookie(
"userid"
)
# 覆写 static_url 方法,让其解析到文件服务器的地址
def
static_url(
self
, path, include_host
=
None
,
*
*
kwargs):
self
.require_setting(
"static_path"
,
"static_url"
)
base
=
"http://forecastexam-public.stor.sinaapp.com/static/"
return
base
+
path
|
六、异步操作
可以看看下面这几篇文章
看完这几篇应该大概明白了吧,tornado 是单线程的,要让单线程的效率达到最高,而且不因此而阻塞,最好的办法就是使用异步,异步说白了就是事件驱动,有事(触发)的时候就叫我(callback),没事的时候就 yield 挂着跑别的程序去,不阻塞主线程
tornado 自己带了一个异步的 httpclient ,所以最简单的办法就是把耗时操作单独拿出来放到另外一个 tornado 服务器里,然后用主服务器的 Asynchttpclient 去请求,等数据服务器处理好了返回数据触发 callback
上面的文章里提到了 celery,我没用过 celery,但是 Python 里最常用的异步就是 select 了吧,在 Windows 上只支持 socket,道理同上,把耗时操作放到另外的模块里,然后用 socket 通信传递数据。如果是 epoll 的话,还需要考虑是 level(状态触发) 还是 edge(边缘触发),这个是后话…(我也还在学习中
代码就不贴了,上面的例子里代码挺多了