helloworld
Tornado特点一句话简介:Tornado是非阻塞式的Web服务器,速度非常快,每秒可以处理数以千计的链接,因此Tornado是实时Web服务的一个理想框架。Tornado因为其轻量级和可扩展的特性,被使用于大量的应用和工具中。
安装Tornado使用pip安装即可:pip install tornado
运行Tornado的helloworld所需的基本组成
#!/usr/bin/env python # -*- coding: utf-8 -*- import tornado.web import tornado.ioloop # 用于处理网页的请求 class MainHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.write('Hello Tornado!') # 设置不同路由的网页对应的处理类 app = tornado.web.Application([ (r'/', MainHandler), ]) # 开始主程序I/O循环 if __name__ == '__main__': app.listen(8888) tornado.ioloop.IOLoop.instance().start()
1) app.listen(8888):设置服务器监听的端口,这里可以随意设置可用的port,比如:8080;
2) tornado.ioloop.IOLoop.instance().start():开启I/O循环,响应客户端的操作;
3) tornado.web.Application:实例化一个web应用类,用于处理用户的请求,可传入一个列表,列表中每个元素由一个访问路由和对应的处理类构成;
4) tornado.web.RequestHandler:定义请求处理类,用于处理对应的请求;
Application操作
1、Application:tornado.web.Aplication新建一个应用,可通过直接实例化这个类或实例化它的子类来新建应用;
2、handlers:实例化时至少需要传入参数handlers,handlers为元素为元组的列表,元组中第一个元素为路由,第二个元素为路由对应的RequestHandler处理类;路由为正则表达式,当正则表达式中有分组时,访问时会将分组的结果当做参数传入模板中;
3、settings:还有一个实例化时经常用到的参数settings,这个参数是一个字典:
- template_path:设置模板文件(HTML文件);
- static_path:设置静态文件(如图像、CSS文件、JavaScript文件等)的路径;
- debug:设置成True时开启调试模式,tornado会调用tornado.autoreload模块,当Python文件被修改时,会尝试重新启动服务器并在模板改变时刷新浏览器(在开发时使用,不要在生产中使用);
- ui_modules:设置自定义UI模块,传入一个模块名(变量名)为键、类为值的字典,这个类为tornado.web.UIModule的子类,在HTML中使用{% module module_name %}时会自动包含module_name类中render方法返回的字符串(一般为包含HTML标签内容的字符串),如果是HTML文件,返回的就是HTML模板中内容的字符串。
4、数据库:可以在Application的子类中连接数据库“self.db = database”,然后在每个RequestHandler中都可以使用连接的数据库“db_name = self.application.db.database”。
1 # 定义了模板路径和静态文件路径后,在使用到模板和静态文件的地方就不需要逐一添加和修改了 2 settings = { 3 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), 4 'static_path': os.path.join(os.path.dirname(__file__), 'static'), 5 'debug': True, 6 } 7 8 # 对应的RequestHandler类代码未贴出来 9 app = tornado.web.Application( 10 handlers=[(r'/',MainHandler), 11 (r'/home', HomePageHandler), 12 (r'/demo/([0-9Xx-]+)', DemoHandler), # 有分组时会将分组结果当参数传入对应的模板中 13 ], 14 **settings 15 )
1 class Application(tornado.web.Application): 2 def __init__(self): 3 handlers = [ 4 (r'/', MainHandler), 5 (r'/demo/([0-9Xx-]+)', DemoHandler), 6 ] 7 8 settings = dict( 9 template_path=os.path.join(os.path.dirname(__file__), 'templates'), 10 static_path=os.path.join(os.path.dirname(__file__), 'static'), 11 ui_modules={'Mymodule': Mymodule}, 12 debug=True, 13 ) 14 15 # 这里使用的数据库是MongoDB,Python有对应的三方库pymongo作为驱动来连接MongoDB数据库 16 conn = pymongo.MongoClient() 17 self.db = conn['demo_db'] 18 tornado.web.Application.__init__(self, handlers, **settings) 19 20 class DemoHandler(tornado.web.RequestHandler): 21 def get(self): 22 demo_db = self.application.db.demo_db # 直接使用连接的数据库 23 sets = demo_db.find() 24 self.render( 25 'demo.html', 26 sets=sets, 27 ) 28 29 class Mymodule(tornado.web.UIModule): 30 def render(self): 31 return self.render_string('modules/mod.html',) 32 33 # 定义css文件路径 34 def css_files(self): 35 return '/static/css/style.css' 36 37 # 定义js文件路径 38 def javascript_files(self): 39 return '/static/js/jquery-3.2.1.js'
RequestHandler操作
在Application中定义了路由及对应的RequestHandler处理类后,浏览器中输入对应的URL后,返回的页面其实是经过RequestHandler处理后render的页面;即RequestHandler用于处理请求,程序会为每一个请求创建一个RequestHandler对象,然后调用对应的HTTP方法。
RequestHandler类中常用的方法,在子类中可根据需要进行重写:
- get()和post():在RequestHandler子类中可以重写get和post方法,当有数据提交到本路由的页面后,就可以根据提交的方式get或post执行对应的get或post方法,并返回方法中render函数传入的模板页面;
- render(template_name,**kwargs):render方法有两个参数,第一个为模板名称,即需要跳转到的目标页面,第二个参数为需要传递进模板页面的参数字典,在模板中可以使用控制语句“{% %}”和表达语句“{{ }}”使用这些参数;
- render_string(template_name,**kwargs):将一个HTML模板内容以字符串的形式传递,后面的参数为传入HTML的参数;
- write(chunk):一般是直接将字符串中的HTML代码写入浏览器客户端,chunk只能是bytes/unicode_type/dict中的一种,这个方法以chunk“块”写入HTTP的响应;
- get_argument(argument_name, default=object()):获取从客户端传入的参数值,但只能获取单个参数的值,没有获取到就返回默认的值;
- get_arguments(argument_names):获取从客户端传入的某类参数值,即多个值;
- request.files['filename']:获取从客户端传入的文件;
- set_status(status_code, reason=None):设置在HTTP响应的状态码,status_code是int类型,reason即状态码对应的信息,如果没有设置reason,那么就必须能在HTTP的responses中找到状态码对应的reason;
- redirect(url):重定向到一个给定的路由。
1 # 一个简单的RequestHandler子类示例 2 class DemoHandler(tornado.web.RequestHandler): 3 # 处理get方式的请求,并跳转到demo页面 4 def get(self, arg_get=None): 5 if arg_get is None: 6 arg_get = 'test arg' 7 self.render( 8 'demo.html', 9 arg_demo=arg_get, 10 ) 11 12 # 获取post方式请求中提交的参数demo_title,如果没有内容就重定向到demo页面 13 def post(self): 14 arg_title = self.get_argument('demo_title', None) 15 if arg_title is None: 16 self.redirect('/demo/')
UIModule
tornado.web.UIModule子类为自定义的UI模板,在HTML中使用{% module module_name %}时会自动包含module_name类中render方法返回的字符串(一般为包含HTML标签内容的字符串),如果是HTML文件,返回的就是HTML模板中内容的字符串,返回的HTML内容字符串的“style”可以由其他的方法设置。
继承UIModule时一般需要重写render方法,以render_string方法返回特定格式HTML内容。
UIModule类中常用的方法,可以重写这些方法以满足自己对HTML格式的要求:
- embedded_css():函数中返回的CSS规则被包裹在<style>中,并被直接添加到<head>的闭标签之前;
- css_files():返回外部CSS文件路径的字符串;
- embedded_javascript():函数中返回的js代码字符串将被<script>包围,并被插入到<body>的闭标签中;
- javascript_files():返回外部js文件路径的字符串;
- html_body():函数中返回的包含HTML标签的字符串在闭合的</body>标签前添加进去。
1 # 自定模板,可将其设置的HTML内容嵌入到其他HTML模板中 2 class MyModule(tornado.web.UIModule): 3 def render(self, arg_mod): 4 return self.render_string( 5 'module/mod.html', 6 arg_mod=arg_mod, 7 ) 8 9 # 设置外部的css文件 10 def css_files(self): 11 return '/static/css/style.css' 12 13 # 设置外部的js文件 14 def javascript_files(self): 15 return '/static/js/jquery-3.2.1.js'
Tornado异步
同步:同步的意思就像是代码的顺序执行,当这一行的代码没有执行完时,就会一直等它,直到它执行完了再执行下一行,所以遇到耗时较长的代码行时,这行代码说不定就是整个程序的“锅”了;
阻塞:当程序在某一处代码“停住”时,其他的代码就不能执行了,程序就阻塞在了此处了,比如同步的程序中卡在某行耗时长的代码时,这行代码就阻塞了后面代码的执行;
异步:异步就像是你做你的,我不用等你,你做完了自然会往下执行,我还是自己做自己的,比如web中,当一个请求没有处理完时,程序还可以同时处理另一个请求,不用等你这请求处理完再来处理下一个请求,所以在访问者较多时,异步的优势就不言而喻了。
Tornado的异步处理主要在以下几点(可以保持当前客户端连接不关闭,不必等当前请求处理完成后再处理下一个请求):
1、异步装饰器@tornado.web.asynchronous:在web方法比如get上使用异步装饰器表明这是一个异步处理方法,被这个装饰器装饰的方法永远不会自己关闭客户端连接(Tornado默认在处理函数返回时自动关闭连接),必须使用finish方法手动关闭;
2、异步HTTP客户端处理类tornado.httpclient.AsyncHTTPClient:
1).AsyncHTTPClient的实例可以执行异步的HTTP请求;
2).AsyncHTTPClient的fetch方法不会返回url的调用结果(HTTPResponse),而是使用了一个callback参数来指定HTTPResponse的处理方法,将HTTPResponse作为这个指定方法的参数传入进去进行相关的处理;
3).在fetch方法的callback参数指定的处理方法的结尾处可以调用tornado.web.RequestHandler的finish方法来关闭客户端链接。
3、异步处理模块tornado.gen:
1).装饰器@tornado.gen.engine:告诉tornado被装饰的方法将使用tornado.gen.Task类;
2).使用yield生成tornado.gen.Task类的实例,将我们想要的调用(比如:fetch方法)和需要传入该调用函数的参数传入这个Task实例:相比于第2点的fetch方法的callback回调功能将处理方法分成两个部分,这个异步生成器的好处在于,一是该请求的处理会在yield处停止执行,直到这个回调函数返回,但这并不会影响其他请求的处理,它依然是异步的,二是回调函数中可能还有回调函数,这样循环下去不容易维护,但是这个异步生成器可以让所有处理都在一个方法里,易开发和维护;
同步示例:当运行以下代码时,在两个窗口短时间内(10s)访问如http://localhost:8000/?word=tornado时,在第一个窗口(请求)未加载出来时(这儿设置了等待10s),第二个窗口(请求)无法处理并加载,第一个窗口加载完成后,第二个窗口也要等待10s后才能加载出来,相当于第二个窗口等待了20s。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import tornado.httpserver 5 import tornado.ioloop 6 import tornado.options 7 import tornado.web 8 import tornado.httpclient 9 10 import urllib 11 import time 12 13 from tornado.options import define, options 14 15 define('port', default=8000, help='run on the given port', type=int) 16 17 18 class IndexHandler(tornado.web.RequestHandler): 19 def get(self): 20 query = self.get_argument('word') 21 client = tornado.httpclient.HTTPClient() 22 response = client.fetch('https://www.baidu.com/baidu?' + 23 urllib.urlencode({'word': query})) 24 time.sleep(10) # 在这个请求未处理完之前,其他的请求只能“排队” 25 self.write('<h1>Tornado Synchronous Test(request_time): %s</h1>' % response.request_time) 26 27 28 if __name__ == '__main__': 29 tornado.options.parse_command_line() # 可以从命令行解析命令 30 app = tornado.web.Application(handlers=[(r'/', IndexHandler)]) 31 http_server = tornado.httpserver.HTTPServer(app) 32 http_server.listen(options.port) 33 tornado.ioloop.IOLoop.instance().start()
异步示例:与同步的代码相比,不同之处在于三点:①@tornado.web.asynchronous,②tornado.httpclient.AsyncHTTPClient(),③fetch的callback参数(需要在回调函数中使用self.finish(),不然浏览器不会将处理结果加载出来,因为它不知道你已经处理完了)。同样运行代码发现访问如http://localhost:8000/?word=tornado时,如果开了两个窗口,这两个窗口都只需等待10s就可加载出来,第二个窗口无需等待第一个窗口加载完成。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import tornado.httpserver 5 import tornado.ioloop 6 import tornado.options 7 import tornado.web 8 import tornado.httpclient 9 10 import urllib 11 import time 12 13 from tornado.options import define, options 14 15 define('port', default=8000, help='run on the given port', type=int) 16 17 18 class IndexHandler(tornado.web.RequestHandler): 19 @tornado.web.asynchronous 20 def get(self): 21 query = self.get_argument('word') 22 client = tornado.httpclient.AsyncHTTPClient() 23 client.fetch('https://www.baidu.com/baidu?' + 24 urllib.urlencode({'word': query}), 25 callback=self.response_process) 26 27 def response_process(self, response): 28 time.sleep(10) # 在这个请求未处理完之前,其他的请求不用“排队”,可以直接处理 29 self.write('<h1>Tornado Synchronous Test(request_time): %s</h1>' % response.request_time) 30 self.finish() 31 32 33 if __name__ == '__main__': 34 tornado.options.parse_command_line() # 可以从命令行解析命令 35 app = tornado.web.Application(handlers=[(r'/', IndexHandler)]) 36 http_server = tornado.httpserver.HTTPServer(app) 37 http_server.listen(options.port) 38 tornado.ioloop.IOLoop.instance().start()
异步生成器示例:异步效果与之前的代码一样,但与之前的异步代码相比,不同之处在于:①tornado.gen,②@tornado.gen.engine(注意调用顺序),③response是通过yield返回的。与之前的异步代码优势在于易于维护,之前的异步代码可能在callback的回调中还有callback回调,如果回调太多就很不好维护了。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import tornado.httpserver 5 import tornado.ioloop 6 import tornado.options 7 import tornado.web 8 import tornado.httpclient 9 import tornado.gen 10 11 import urllib 12 import time 13 14 from tornado.options import define, options 15 16 define('port', default=8000, help='run on the given port', type=int) 17 18 19 class IndexHandler(tornado.web.RequestHandler): 20 # 以下装饰器的使用顺序不能乱 21 @tornado.web.asynchronous 22 @tornado.gen.engine 23 def get(self): 24 query = self.get_argument('word') 25 client = tornado.httpclient.AsyncHTTPClient() 26 response = yield tornado.gen.Task(client.fetch, 27 'https://www.baidu.com/baidu?' + 28 urllib.urlencode({'word': query}) 29 ) 30 time.sleep(10) 31 self.write('<h1>Tornado Synchronous Test(request_time): %s</h1>' % response.request_time) 32 self.finish() 33 34 35 if __name__ == '__main__': 36 tornado.options.parse_command_line() # 可以从命令行解析命令 37 app = tornado.web.Application(handlers=[(r'/', IndexHandler)]) 38 http_server = tornado.httpserver.HTTPServer(app) 39 http_server.listen(options.port) 40 tornado.ioloop.IOLoop.instance().start()
Tronado的web应用安全(cookie和CSRF/XSRF)
安全cookies是web应用的安全防范之一,浏览器中的cookies存储了用户的个人信息,当然包括了某些重要的敏感的信息,如果一些恶意的脚本得到甚至修改了用户的cookies的信息,用户的信息就得不到安全的保障,所以应该对用户的cookies进行保护。Tornado的安全cookies可以对cookies签名进行安全加密,以检查cookies是否被修改过,因为恶意脚本不知道安全密钥,所以无法修改(但是恶意脚本仍然可以截获cookies来“冒充”用户,只是不能修改cookies而已,这也是另外一个安全隐患,本文并不讨论这点)。
Tornado的get_secure_cookie()和set_secure_cookie()可以安全的获取和发送浏览器的cookies,可以防止浏览器中的恶意修改,但是为了使用这个功能,必须在tornado.web.Application的settings中设置cookie_secret,其值为一个唯一的随机字符串(比如:base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)可以产生一个唯一的随机字符串)。
CSRF或XSRF,即跨站请求伪造,是web应用都涉及到的一个安全漏洞,它利用了浏览器的一个安全漏洞:浏览器允许恶意攻击者在受害者网站注入脚本使未授权请求代表一个已登录用户。即别人可以“冒充”你做一些事情,而服务器也会认为这些操作是你做的。
为了防止XSRF攻击,应该注意:一是开发者考虑某些重要的请求时需要使用POST方法,二是Tornado的一个防范伪造POST的功能(这个功能是一种策略,tornado也实现了这种策略),就是在每个请求中包含一个参数值(隐藏的HTML表单元素值)和存储的cookie值,若两者(称之为令牌)匹配上了,则证明请求有效,当某个不可信的站点没有访问cookie数据的权限时,它就不能在请求中包含这个令牌cookie值,自然就无法发送有效的请求了。tornado中使用这个功能需要在tornado.web.Application的settings中设置xsrf_cookies,值为True,同时必须在HTML的表单中包含xsrf_form_html()函数,以此形成cookie令牌。
tornado的用户验证可以使用装饰器@tornado.web.authenticated,使用这个装饰器时需注意:①必须重写get_current_user()方法,这个返回的值将赋给self.current_user,②被装饰的方法被执行前会检查self.current_user的bool值是否为False(默认是None),若为False则会重定向到tornado.web.Application的settings中login_url指定的URL,③需要在tornado.web.Application的settings中login_url的URL,以便用户验证self.current_user之前可以重定向到这个URL。④当Tornado构建重定向URL时,它还会给查询字符串添加一个next参数,其值为重定向之前的URL,可以使用如self.redirect(self.get_argument('next', '/'))这样的语句在用户验证成功后回到原来的页面。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import tornado.httpserver 4 import tornado.ioloop 5 import tornado.web 6 import tornado.options 7 import os.path 8 import base64 9 import uuid 10 11 from tornado.options import define, options 12 13 define('port', default=8000, help='run on the given port', type=int) 14 15 16 class BaseHandler(tornado.web.RequestHandler): 17 def get_current_user(self): 18 """重写此方法,返回的值将赋给self.current_user""" 19 return self.get_secure_cookie('username') # 获取cookies中username的值 20 21 22 class LoginHandler(BaseHandler): 23 def get(self): 24 self.render('login.html') 25 26 def post(self): 27 self.set_secure_cookie('username', self.get_argument('username')) # 将请求中的username值赋给cookie中的username 28 self.redirect('/') 29 30 31 class HomeHandler(BaseHandler): 32 @tornado.web.authenticated # 使用此装饰器必须重写方法get_current_user 33 def get(self): 34 """被@tornado.web.authenticated装饰的方法被执行前会检查self.current_user的bool值是否为False,不为False时才会执行此方法""" 35 self.render('index.html', user=self.current_user) 36 37 38 class LogoutHandler(BaseHandler): 39 def get(self): 40 if self.get_argument('logout', None): 41 self.clear_cookie('username') # 清楚cookies中名为username的cookie 42 self.redirect('/') 43 44 45 if __name__ == '__main__': 46 tornado.options.parse_command_line() 47 48 settings = { 49 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), 50 'cookie_secret': base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), # 设置cookies安全,值为一个唯一的随机字符串 51 'xsrf_cookies': True, # 设置xsrf安全,设置了此项后必须在HTML表单中包含xsrf_form_html() 52 'login_url': '/login' # 当被@tornado.web.authenticated装饰器包装的方法检查到self.current_user的bool值为False时,会重定向到这个URL 53 } 54 55 application = tornado.web.Application([ 56 (r'/', HomeHandler), 57 (r'/login', LoginHandler), 58 (r'/logout', LogoutHandler) 59 ], **settings) 60 61 http_server = tornado.httpserver.HTTPServer(application) 62 http_server.listen(options.port) 63 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Welcom Back!</title> 6 </head> 7 <body> 8 <h1>Welcom back, {{ user }}</h1> 9 </body> 10 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Please Log In</title> 6 </head> 7 <body> 8 <form action="/login" method="POST"> 9 {% raw xsrf_form_html() %}<!-- 这其实是一个隐藏的<input>元素,定义了“_xsrf”的值,会检查POST请求以防止跨站点请求伪造 --> 10 Username: <input type="text" name="username" /> 11 <input type="submit" value="Log In" /> 12 </form> 13 </body> 14 </html>