helloworldjavascript
Tornado特色一句話簡介:Tornado是非阻塞式的Web服務器,速度很是快,每秒能夠處理數以千計的連接,所以Tornado是實時Web服務的一個理想框架。Tornado由於其輕量級和可擴展的特性,被使用於大量的應用和工具中。css
安裝Tornado使用pip安裝便可:pip install tornadohtml
運行Tornado的helloworld所需的基本組成java
#!/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;python
2) tornado.ioloop.IOLoop.instance().start():開啓I/O循環,響應客戶端的操做;jquery
3) tornado.web.Application:實例化一個web應用類,用於處理用戶的請求,可傳入一個列表,列表中每一個元素由一個訪問路由和對應的處理類構成;web
4) tornado.web.RequestHandler:定義請求處理類,用於處理對應的請求;正則表達式
Application操做數據庫
一、Application:tornado.web.Aplication新建一個應用,可經過直接實例化這個類或實例化它的子類來新建應用;瀏覽器
二、handlers:實例化時至少須要傳入參數handlers,handlers爲元素爲元組的列表,元組中第一個元素爲路由,第二個元素爲路由對應的RequestHandler處理類;路由爲正則表達式,當正則表達式中有分組時,訪問時會將分組的結果當作參數傳入模板中;
三、settings:還有一個實例化時常常用到的參數settings,這個參數是一個字典:
四、數據庫:能夠在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類中經常使用的方法,在子類中可根據須要進行重寫:
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格式的要求:
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的異步處理主要在如下幾點(能夠保持當前客戶端鏈接不關閉,沒必要等當前請求處理完成後再處理下一個請求):
一、異步裝飾器@tornado.web.asynchronous:在web方法好比get上使用異步裝飾器代表這是一個異步處理方法,被這個裝飾器裝飾的方法永遠不會本身關閉客戶端鏈接(Tornado默認在處理函數返回時自動關閉鏈接),必須使用finish方法手動關閉;
二、異步HTTP客戶端處理類tornado.httpclient.AsyncHTTPClient:
1).AsyncHTTPClient的實例能夠執行異步的HTTP請求;
2).AsyncHTTPClient的fetch方法不會返回url的調用結果(HTTPResponse),而是使用了一個callback參數來指定HTTPResponse的處理方法,將HTTPResponse做爲這個指定方法的參數傳入進去進行相關的處理;
3).在fetch方法的callback參數指定的處理方法的結尾處能夠調用tornado.web.RequestHandler的finish方法來關閉客戶端連接。
三、異步處理模塊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>