Tornado框架入门
Tornado框架入门
pip install tornado
手动安装
下载安装包tornado-4.3.tar.gz(https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz)
$ tar xvzf tornado-4.3.tar.gz
$ cd tornado-4.3
$ python setup.py build
$ sudo python setup.py install
先写个hello.py
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
执行python hello.py
,打开浏览器访问http://localhost:8888/
就可以看到服务器的正常输出Hello, world
。
一个普通的tornado web服务器通常由四大组件组成:
-
ioloop实例,它是全局的tornado事件循环,是服务器的引擎核心,示例中
tornado.ioloop.IOLoop.current()
就是默认的tornado ioloop实例。 -
app实例,它代表着一个完成的后端app,它会挂接一个服务端套接字端口对外提供服务。一个ioloop实例里面可以有多个app实例,示例中只有1个,实际上可以允许多个,不过一般几乎不会使用多个。
-
handler类,它代表着业务逻辑,进行服务端开发时就是编写一堆一堆的handler用来服务客户端请求。
-
路由表,它将指定的url规则和handler挂接起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。
这四大组件的关系:
一个ioloop包含多个app(管理多个服务端口),一个app包含一个路由表,一个路由表包含多个handler。ioloop是服务的引擎核心,它是发动机,负责接收和响应客户端请求,负责驱动业务handler的运行,负责服务器内部定时任务的执行。
当一个请求到来时,ioloop读取这个请求解包成一个http请求对象,找到该套接字上对应app的路由表,通过请求对象的url查询路由表中挂接的handler,然后执行handler。handler方法执行后一般会返回一个对象,ioloop负责将对象包装成http响应对象序列化发送给客户端。
注:同一个ioloop实例运行在一个单线程环境下。
阶乘服务
下面编写一个正常的web服务器,它将提供阶乘服务。也就是计算n!的值。服务器会提供阶乘的缓存,已经计算过的就存起来,下次就不用重新计算了。使用Python的好处就是,不用当心阶乘的计算结果会溢出,Python的整数可以无限大。
# fact.py
import tornado.ioloop
import tornado.web
class FactorialService(object): # 定义一个阶乘服务对象
def __init__(self):
self.cache = {} # 用字典记录已经计算过的阶乘
def calc(self, n):
if n in self.cache: # 如果有直接返回
return self.cache[n]
s = 1
for i in range(1, n):
s *= i
self.cache[n] = s # 缓存起来
return s
class FactorialHandler(tornado.web.RequestHandler):
service = FactorialService() # new出阶乘服务对象
def get(self):
n = int(self.get_argument("n")) # 获取url的参数值
self.write(str(self.service.calc(n))) # 使用阶乘服务
def make_app():
return tornado.web.Application([
(r"/fact", FactorialHandler), # 注册路由
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
执行python fact.py
,打开浏览器,键入http://localhost:8888/fact?n=50
,可以看到浏览器输出了
608281864034267560872252163321295376887552831379210240000000000
,如果不提供n参数,访问http://localhost:8888/fact
,可以看到浏览器输出了400: Bad Request
,请求错误,因为没有提供参数。
使用Redis
上面的例子是将缓存存在本地内存中,如果换一个端口再其一个阶乘服务,通过这个新端口去访问的话,对于每个n,它都需要重新计算一遍,因为本地内存是无法跨进程跨机器共享的。
所以这个例子,将使用Redis来缓存计算结果,这样就可以完全避免重复计算。另外将不再返回纯文本,而是返回一个json,同时在响应里增加字段来说名本次计算来源于缓存还是事实计算出来的。另外提供默认参数,如果客户端没有提供n,那就默认n=1。
import json
import redis
import tornado.ioloop
import tornado.web
class FactorialService(object):
def __init__(self):
self.cache = redis.StrictRedis("localhost", 6379) # 缓存换成redis了
self.key = "factorials"
def calc(self, n):
s = self.cache.hget(self.key, str(n)) # 用hash结构保存计算结果
if s:
return int(s), True
s = 1
for i in range(1, n):
s *= i
self.cache.hset(self.key, str(n), str(s)) # 保存结果
return s, False
class FactorialHandler(tornado.web.RequestHandler):
service = FactorialService()
def get(self):
n = int(self.get_argument("n") or 1) # 参数默认值
fact, cached = self.service.calc(n)
result = {
"n": n,
"fact": fact,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
def make_app():
return tornado.web.Application([
(r"/fact", FactorialHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
首先需要启动redis,打开一个cmd窗口使用cd命令切换目录到redis的安装目录(如: D:\redis) 运行 redis-server.exe redis.windows.conf
。
当再次访问http://localhost:8888/fact?n=50,可以看到浏览器输出如下
{"cached": false, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}
,再刷新一下,浏览器输出{"cached": true, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}
,可以看到cached
字段由true
变成了false
,表明缓存确实已经保存了计算的结果。重启一下进程,
再次访问这个连接,观察浏览器输出,可以发现结果的cached
依旧为true
。说明缓存结果不再是存在本地内存中了。
圆周率计算服务
现在服务里提供一个参数n,作为圆周率的精度指标,n越大,圆周率计算越准确,同样将计算结果缓存到Redis服务器中,避免重复计算。
# pi.py
import json
import math
import redis
import tornado.ioloop
import tornado.web
class FactorialService(object):
def __init__(self, cache):
self.cache = cache
self.key = "factorials"
def calc(self, n):
s = self.cache.hget(self.key, str(n))
if s:
return int(s), True
s = 1
for i in range(1, n):
s *= i
self.cache.hset(self.key, str(n), str(s))
return s, False
class PiService(object):
def __init__(self, cache):
self.cache = cache
self.key = "pis"
def calc(self, n):
s = self.cache.hget(self.key, str(n))
if s:
return float(s), True
s = 0.0
for i in range(n):
s += 1.0/(2*i+1)/(2*i+1)
s = math.sqrt(s*8)
self.cache.hset(self.key, str(n), str(s))
return s, False
class FactorialHandler(tornado.web.RequestHandler):
def initialize(self, factorial):
self.factorial = factorial
def get(self):
n = int(self.get_argument("n") or 1)
fact, cached = self.factorial.calc(n)
result = {
"n": n,
"fact": fact,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
class PiHandler(tornado.web.RequestHandler):
def initialize(self, pi):
self.pi = pi
def get(self):
n = int(self.get_argument("n") or 1)
pi, cached = self.pi.calc(n)
result = {
"n": n,
"pi": pi,
"cached": cached
}
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(json.dumps(result))
def make_app():
cache = redis.StrictRedis("localhost", 6379)
factorial = FactorialService(cache)
pi = PiService(cache)
return tornado.web.Application([
(r"/fact", FactorialHandler, {"factorial": factorial}),
(r"/pi", PiHandler, {"pi": pi}),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
因为两个Handler都需要用到redis,所以将redis单独抽出来,通过参数传递进去。另外Handler可以通过 initialize 函数传递参数,在注册路由的时候提供一个字典就可以传递任意参数了,字典的key要和参数名称对应。运行python pi.py
,打开浏览器访问http://localhost:8888/pi?n=200
,可以看到浏览器输出{"cached": false, "pi": 3.1412743276, "n": 1000}
,这个值已经非常接近圆周率了。