tornado是一個非阻塞的web服務器框架,每秒能夠處理上千個客戶端鏈接(都是在一個線程中,不須要爲每一個客戶端建立線程,資源消耗少),適合用來開發web長鏈接應用,如long polling(輪詢),WebSocket協議等(http協議爲短鏈接)。javascript
1,簡單使用css
#coding:utf-8 import tornado.ioloop import tornado.web from controllers.login import LoginHandler class HomeHandler(tornado.web.RequestHandler): #處理'/index'的請求,如果get請求,即調用get方法 def get(self, *args, **kwargs): self.write('home page') settings = { 'template_path':'views' #配置html文件的目錄,即html文件存儲在views文件夾路徑下
'static_path':'statics', # 配置靜態url路徑,用來存放css,js文件等
} app = tornado.web.Application([ (r'/index',HomeHandler), # 路由分發器,HomeHandler爲該路由的處理類 (r'/login',LoginHandler), ],**settings) #加入配置文件 if __name__ == '__main__': app.listen(8080) #監聽端口號 tornado.ioloop.IOLoop.instance().start() #開啓服務器
上面代碼即創建起一個web服務器,在瀏覽器輸入127.0.0.1:8080/index, 就會獲得包含‘home page’字符的網頁。另外,上面將全部代碼寫在了有個代碼文件中,也能夠利用MVC的設計方式分開來寫,以下面的的架構和代碼:將處理‘/login’請求的類LoginHandler放在controllers文件夾下,將視圖文件login.html放在views文件夾下(須要配置‘template_path’),而models文件夾下能夠存放和數據庫處理相關的代碼,statics中存放靜態文件,如css,js等,須要配置路徑:'static_path':'statics'。html
#coding:utf-8 import tornado.ioloop import tornado.web from controllers.login import LoginHandler class HomeHandler(tornado.web.RequestHandler): #處理'/index'的請求,如果get請求,即調用get方法 def get(self, *args, **kwargs): self.write('home page') settings = { 'template_path':'views' #配置html文件的目錄,即html文件存儲在views文件夾路徑下 } app = tornado.web.Application([ (r'/index',HomeHandler), # 路由分發器,HomeHandler爲該路由的處理類 (r'/login',LoginHandler), ],**settings) #加入配置文件 if __name__ == '__main__': app.listen(8080) #監聽端口號 tornado.ioloop.IOLoop.instance().start() #開啓服務器
#coding:utf-8 import tornado class LoginHandler(tornado.web.RequestHandler): def get(self): self.render('login.html')
2.模板前端
tornado也支持和django相似的模板引擎語言,表達語句用{{ item[0] }},控制語句{% if %}。。。。 {% end %},tornado支持if,while,for,try等,但都是以{% end %}結束,不一樣於django。tornado也支持模板繼承,{% extends 'index.html' %} 和 {% block body%}。。。。{% end %}(也是以{% end %}結尾)。java
http://www.tornadoweb.org/en/stable/template.htmlpython
https://github.com/tornadoweb/tornado/blob/master/tornado/template.pyjquery
Tornado默認提供的這些功能其實本質上就是 UIMethod 和 UIModule,咱們也能夠自定義從而實現相似於Django的simple_tag的功能:git
定義:github
#coding:utf-8 from tornado import escape def mytag(request,value): #默認會傳遞一個參數(HomeHandler object),前端須要傳值時須要再加一個參數value #print request return '<h3>我是tag%s</h3>'%value # 前端默認會對和h3進行轉義,須要不轉義時前端使用raw 關鍵字
#coding:utf-8 from tornado import escape from tornado.web import UIModule class CustomUIModule(UIModule): def embedded_javascript(self): # render執行時,會在html文件中加入javascript return "console.log(123);" def javascript_files(self): ## render執行時,會在html文件中引入javascript文件 return 'commons.js' def embedded_css(self): return '.but{color:red}' def css_files(self): return 'commons.css' def render(self, value): v = '<h3>我是一個UIModule tag%s</h3>'%value #默認不轉義</h3>,前端顯示我是一個UIModule tag3 #v = escape.xhtml_escape(v) # 轉義</h3>,前端顯示<h3>我是一個UIModule tag3</h3> return v
設置:web
#coding:utf-8 import tornado.ioloop import tornado.web from controllers.login import LoginHandler import uimethods import uimodules class HomeHandler(tornado.web.RequestHandler): #處理'/index'的請求,如果get請求,即調用get方法 def get(self, *args, **kwargs): #self.write('home page') self.render('home.html') settings = { 'template_path':'views', #配置html文件的目錄,即html文件存儲在views文件夾路徑下 'static_path':'statics', # 配置靜態url路徑,用來存放css,js文件等 'ui_methods':uimethods, 'ui_modules':uimodules, } app = tornado.web.Application([ (r'/index',HomeHandler), # 路由分發器,HomeHandler爲該路由的處理類 (r'/login',LoginHandler), ],**settings) #加入配置文件 if __name__ == '__main__': app.listen(8080) #監聽端口號 tornado.ioloop.IOLoop.instance().start() #開啓服務器
使用
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>主頁</title> </head> <body> {{ mytag(1)}} {% raw mytag(2) %} {% module CustomUIModule(3) %} <p class="but">驗證css代碼</p> <p class="but2">驗證css文件</p> </body> </html>
網頁效果:
注意的是在UIModule中能夠向html文件中加入css,js代碼及文件。
3,靜態文件設置
app配置
settings = { 'static_path':'statics', # 配置靜態url路徑,用來存放css,js文件等 'static_url_prefix':'/statics/', #href中的起始路徑 }
html
<link rel="stylesheet" href="/statics/commons.css"> #statics目錄下的commons.css
4. 跨站請求僞造(cross site request forgery)
https://www.tornadoweb.org/en/stable/guide/security.html?highlight=ajax
app設置
settings = { "xsrf_cookies": True, }
表單使用
<form action="/new_message" method="post"> {% module xsrf_form_html() %} <input type="text" name="message"/> <input type="submit" value="Post"/> </form>
ajax使用:
本質上去cookie中獲取_xsrf,再攜帶_xsrf值提交數據(document.cookie:
)function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } jQuery.postJSON = function(url, args, callback) { args._xsrf = getCookie("_xsrf"); $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", success: function(response) { callback(eval("(" + response + ")")); }}); };
function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } $('#send').click(function () { var _xsrf = getCookie('_xsrf') var msg = $('#msg').val(); $.ajax({ url:'/login', data:{ '_xsrf':_xsrf, 'msg':msg, }, type:"POST", success:function (callback) { console.log(callback); } }); });
5,ajax上傳文件
不用ajax前端
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <div> <input type="file" id="img"/> <button onclick="upload();">上傳</button> </div> </body> <script src="/statics/jquery-3.3.1.min.js"></script> <script> function upload() { var file = document.getElementById('img').files[0]; var form = new FormData(); //form.append('k1','v1'); form.append('fileobj',file); var request = new XMLHttpRequest(); request.open('post','/index',true); request.send(form); } </script> </html>
ajax前端
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <div> <input type="file" id="img"/> <button onclick="upload();">上傳</button> </div> </body> <script src="/statics/jquery-3.3.1.min.js"></script> <script> function upload() { var file = document.getElementById('img').files[0]; var form = new FormData(); //form.append('k1','v1'); form.append('fileobj',file); //var request = new XMLHttpRequest(); //request.open('post','/index',true); //request.send(form); $.ajax({ url:'/index', type:'POST', data:form, processData:false, //讓jquery不處理數據 contentType:false, // 讓jquery不設置contentType success:function (callback) { console.log(callback); } }); } </script> </html>
後端
#coding:utf-8 import tornado.web class HomeHandler(tornado.web.RequestHandler): def get(self): self.render('LoadFile.html') def post(self): fileobjs = self.request.files['fileobj'] #fileobjs爲一個列表 for file in fileobjs: file_name = file['filename'] #fileobjs[0]['filename'] print type(file_name) with open(file_name,'wb') as f: f.write(file['body']) settings={ 'template_path':'views', 'static_path':'statics', 'static_url_prefix':'/statics/', } application = tornado.web.Application([ (r'/index', HomeHandler) ],**settings) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
6,cookie
獲取和設置cookie(不加密):
get_cookie(self, name, default=None): 未取到時返回默認值
def set_cookie(self, name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs):
class HomeHandler(tornado.web.RequestHandler): #處理'/index'的請求,如果get請求,即調用get方法 def get(self, *args, **kwargs): #self.write('home page') if self.get_cookie(name='id'): print self.get_cookie(name='id') else: self.set_cookie(name='id',value='asdfg') self.render('home.html')
獲取和設置cookie(加密):須要在配置中設置祕鑰:'cookie_secret'
get_secure_cookie(self, name, value=None, max_age_days=31, min_version=None): 對於加密後的cookie,get_secure_cookie拿到的爲解密後的cookie值,get_cookie拿到的爲加密的值
set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs):
class HomeHandler(tornado.web.RequestHandler): #處理'/index'的請求,如果get請求,即調用get方法 def get(self, *args, **kwargs): if self.get_secure_cookie(name='secret_id'): print self.get_secure_cookie(name='secret_id') ##前端顯示的爲加密後,拿到的爲明文 else: self.set_secure_cookie(name='secret_id',value='message') self.render('home.html') settings = { 'template_path':'views', #配置html文件的目錄,即html文件存儲在views文件夾路徑下 'static_path':'statics', # 配置靜態url路徑,用來存放css,js文件等 'static_url_prefix':'/statics/', 'ui_methods':uimethods, 'ui_modules':uimodules, 'xsrf_cookies':True, 'cookie_secret':'asdfghhj', }
cookie兩個版本的加密算法:
def _create_signature_v1(secret, *parts): hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) for part in parts: hash.update(utf8(part)) return utf8(hash.hexdigest()) def _create_signature_v2(secret, s): hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) hash.update(utf8(s)) return utf8(hash.hexdigest())
#加密 def create_signed_value(secret, name, value, version=None, clock=None, key_version=None): if version is None: version = DEFAULT_SIGNED_VALUE_VERSION if clock is None: clock = time.time timestamp = utf8(str(int(clock()))) value = base64.b64encode(utf8(value)) if version == 1: signature = _create_signature_v1(secret, name, value, timestamp) value = b"|".join([value, timestamp, signature]) return value elif version == 2: # The v2 format consists of a version number and a series of # length-prefixed fields "%d:%s", the last of which is a # signature, all separated by pipes. All numbers are in # decimal format with no leading zeros. The signature is an # HMAC-SHA256 of the whole string up to that point, including # the final pipe. # # The fields are: # - format version (i.e. 2; no length prefix) # - key version (integer, default is 0) # - timestamp (integer seconds since epoch) # - name (not encoded; assumed to be ~alphanumeric) # - value (base64-encoded) # - signature (hex-encoded; no length prefix) def format_field(s): return utf8("%d:" % len(s)) + utf8(s) to_sign = b"|".join([ b"2", format_field(str(key_version or 0)), format_field(timestamp), format_field(name), format_field(value), b'']) if isinstance(secret, dict): assert key_version is not None, 'Key version must be set when sign key dict is used' assert version >= 2, 'Version must be at least 2 for key version support' secret = secret[key_version] signature = _create_signature_v2(secret, to_sign) return to_sign + signature else: raise ValueError("Unsupported version %d" % version) #解密: def _decode_signed_value_v1(secret, name, value, max_age_days, clock): parts = utf8(value).split(b"|") if len(parts) != 3: return None signature = _create_signature_v1(secret, name, parts[0], parts[1]) if not _time_independent_equals(parts[2], signature): gen_log.warning("Invalid cookie signature %r", value) return None timestamp = int(parts[1]) if timestamp < clock() - max_age_days * 86400: gen_log.warning("Expired cookie %r", value) return None if timestamp > clock() + 31 * 86400: # _cookie_signature does not hash a delimiter between the # parts of the cookie, so an attacker could transfer trailing # digits from the payload to the timestamp without altering the # signature. For backwards compatibility, sanity-check timestamp # here instead of modifying _cookie_signature. gen_log.warning("Cookie timestamp in future; possible tampering %r", value) return None if parts[1].startswith(b"0"): gen_log.warning("Tampered cookie %r", value) return None try: return base64.b64decode(parts[0]) except Exception: return None def _decode_fields_v2(value): def _consume_field(s): length, _, rest = s.partition(b':') n = int(length) field_value = rest[:n] # In python 3, indexing bytes returns small integers; we must # use a slice to get a byte string as in python 2. if rest[n:n + 1] != b'|': raise ValueError("malformed v2 signed value field") rest = rest[n + 1:] return field_value, rest rest = value[2:] # remove version number key_version, rest = _consume_field(rest) timestamp, rest = _consume_field(rest) name_field, rest = _consume_field(rest) value_field, passed_sig = _consume_field(rest) return int(key_version), timestamp, name_field, value_field, passed_sig def _decode_signed_value_v2(secret, name, value, max_age_days, clock): try: key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value) except ValueError: return None signed_string = value[:-len(passed_sig)] if isinstance(secret, dict): try: secret = secret[key_version] except KeyError: return None expected_sig = _create_signature_v2(secret, signed_string) if not _time_independent_equals(passed_sig, expected_sig): return None if name_field != utf8(name): return None timestamp = int(timestamp) if timestamp < clock() - max_age_days * 86400: # The signature has expired. return None try: return base64.b64decode(value_field) except Exception: return None def get_signature_key_version(value): value = utf8(value) version = _get_version(value) if version < 2: return None try: key_version, _, _, _, _ = _decode_fields_v2(value) except ValueError: return None return key_version
tornado自帶的基於cookie的驗證機制:
必須重寫方法get_current_user(self):,self.current_user()會調用該方法,拿到當前用戶
@tornado.web.authenticated,裝飾器修飾的請求會要求驗證,self.current_user()中拿到值時,能進行訪問,無值時跳轉到登陸頁面(必須進行配置:'login_url':'/login')
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("login_user") class MainHandler(BaseHandler): @tornado.web.authenticated #須要登陸後才能訪問(self.current_user()拿到當前用戶),不然跳轉到登陸頁面 def get(self): login_user = self.current_user self.write(login_user) class LoginHandler(tornado.web.RequestHandler): def get(self): self.current_user() self.render('login.html', **{'status': ''}) def post(self, *args, **kwargs): username = self.get_argument('name') password = self.get_argument('pwd') if username == 'wupeiqi' and password == '123': self.set_secure_cookie('login_user', 'zack') self.redirect('/') else: self.render('login.html', **{'status': '用戶名或密碼錯誤'}) settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
7, 自定義session框架
預備知識一:字典
任何類實現了__getitem__(), __setitem__(), __delitem__()方法,就能向字典同樣存取,刪除數據
class Adict(object): def __init__(self): self.container = {} def __getitem__(self, key): print 'get' if key in self.container: return self.container[key] else: return None def __setitem__(self, key, value): print 'set' self.container[key]=value def __delitem__(self, key): print 'del' del self.container[key] D = Adict() D['user']='zack' #調用 __setitem__方法 D['user'] #調用 __getitem__方法 del D['user'] # 調用 __delitem__方法
預備知識二:類繼承
#coding:utf-8 #C實例化時,先調用A的實例化方法,而其會調用self.initialize()時會只執行B的initialize()方法 class A(object): def __init__(self): print 'A' self.initialize() def initialize(self): print 'A初始化' class B(A): def initialize(self): print 'B初始化' class C(B): pass c = C()
#coding:utf-8 #C實例化時,先調用A的實例化方法,而其會調用self.initialize()時會只調用B的initialize()方法,而B的initialize()方法又調用了A的initialize方法 class A(object): def __init__(self): print 'A' self.initialize() def initialize(self): print 'A初始化' class B(object): def initialize(self): print 'B初始化' super(B,self).initialize() #此處super先尋找其父類,沒找到,再找A的initialize方法,(先深度,後廣度) class C(B,A): pass c = C()
預備知識三:在RequestHandler的源碼中,__init__()函數調用了self.initialize()函數
class RequestHandler(object): """Base class for HTTP request handlers. Subclasses must define at least one of the methods defined in the "Entry points" section below. """ SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS") _template_loaders = {} # type: typing.Dict[str, template.BaseLoader] _template_loader_lock = threading.Lock() _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]") def __init__(self, application, request, **kwargs): super(RequestHandler, self).__init__() self.application = application self.request = request self._headers_written = False self._finished = False self._auto_finish = True self._transforms = None # will be set in _execute self._prepared_future = None self._headers = None # type: httputil.HTTPHeaders self.path_args = None self.path_kwargs = None self.ui = ObjectDict((n, self._ui_method(m)) for n, m in application.ui_methods.items()) # UIModules are available as both `modules` and `_tt_modules` in the # template namespace. Historically only `modules` was available # but could be clobbered by user additions to the namespace. # The template {% module %} directive looks in `_tt_modules` to avoid # possible conflicts. self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules) self.ui["modules"] = self.ui["_tt_modules"] self.clear() self.request.connection.set_close_callback(self.on_connection_close) self.initialize(**kwargs) def initialize(self): """Hook for subclass initialization. Called for each request. A dictionary passed as the third argument of a url spec will be supplied as keyword arguments to initialize(). Example:: class ProfileHandler(RequestHandler): def initialize(self, database): self.database = database def get(self, username): ... app = Application([ (r'/user/(.*)', ProfileHandler, dict(database=database)), ]) """ pass
自定義session框架
#coding:utf-8 import tornado.ioloop import tornado.web from hashlib import sha1 import time import os container={} create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest() class Session(object): #一個類實現了__setitem__,__getitem__就能夠向字典同樣讀取和存取數據 session_id='session_id' def __init__(self,request): session_value = request.get_cookie(Session.session_id,None) if not session_value: self._id = create_session_id() else: if session_value in container: self._id=session_value else: self._id = create_session_id() request.set_cookie(Session.session_id,self._id) if self._id not in container: container[self._id]={} def __setitem__(self, key, value): container[self._id][key]=value print container def __getitem__(self, key): if key in container[self._id]: return container[self._id][key] else: return None def __delitem__(self, key): del container[self._id][key] def clear(self): del container[self._id] # class BaseHandler(object): # def initialize(self): # self.session = Session(self) # super(BaseHandler,self).initialize() #不會覆蓋tornado.web.RequestHandler的initialiaze方法 # # class HomeHandler(BaseHandler,tornado.web.RequestHandler): # class BaseHandler(tornado.web.RequestHandler): def initialize(self): # 覆蓋tornado.web.RequestHandler的initialiaze方法,初始化時父類中會調用該方法 self.session = Session(self) class HomeHandler(BaseHandler): def get(self): user = self.session['user'] if user: self.write(user) else: self.redirect('/login') class LoginHandler(BaseHandler): def get(self): self.render('login.html') def post(self): username = self.get_body_argument('username') password = self.get_body_argument('password') if username=='zack' and password=='1234': self.session['user']='zack' self.session['pwd']='1234' self.redirect('/index') else: self.render('login.html') settings={ 'template_path':'views' } application = tornado.web.Application([ (r'/index', HomeHandler), (r'/login', LoginHandler), ],**settings) if __name__ == '__main__': application.listen(9999) tornado.ioloop.IOLoop.instance().start()
8,異步非阻塞
http://www.tornadoweb.org/en/stable/guide/async.html
上面都是利用tornado的同步訪問請求,當一個請求被阻塞時,下一個請求訪問時不能被處理。以下面代碼,當先訪問‘/mani’時,因爲MainHandler中,get方法sleep會阻塞在此處,此時若訪問‘/page’,也會阻塞,等待MainHandler中get方法執行完成後,纔會執行PageHandler中的get方法。
#coding:utf-8 import tornado.web import tornado.ioloop from tornado.concurrent import Future import time class MainHandler(tornado.web.RequestHandler): def get(self): time.sleep(10) self.write('main') class PageHandler(tornado.web.RequestHandler): def get(self): self.write('page') application = tornado.web.Application([ (r'/main',MainHandler), (r'/page',PageHandler) ]) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
tornado中,利用裝飾器@gen.coroutine +yield Future對象,來支持異步非阻塞。以下面代碼,當給MainHandler中get方法加上裝飾器@gen.coroutine,並返回Future對象時,就變成了異步非阻塞,也就是說,當咱們先訪問‘/mani’時,MainHandler中get方法會阻塞在這裏,但當咱們此時去訪問訪問‘/page’,PageHandler中的get方法會當即執行,而不會阻塞。
#coding:utf-8 import tornado.web import tornado.ioloop from tornado import gen from tornado.concurrent import Future import time class MainHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): future = Future() yield future self.write('main') class PageHandler(tornado.web.RequestHandler): def get(self): self.write('page') application = tornado.web.Application([ (r'/main',MainHandler), (r'/page',PageHandler) ]) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
上面寫的異步非阻塞並沒實際用途,下面是它的一個應用場景,在代碼中,MainHandler的get方法中,fetch()比較耗時,但其返回一Future對象,當咱們先訪問‘/mani’時,MainHandler中get方法會阻塞在這裏,但當咱們此時去訪問訪問‘/page’,PageHandler中的get方法會當即執行
#coding:utf-8 import tornado.web import tornado.ioloop from tornado import gen, httpclient from tornado.concurrent import Future class MainHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): http = httpclient.AsyncHTTPClient() #發送異步請求 data = yield http.fetch('https://www.youtube.com/',raise_error=False) #其源碼中能夠看到return future,即返回future對象 print 'done',data self.write('main') self.finish('dd') # 加入回調函數處理 # @gen.coroutine # def get(self): # http = httpclient.AsyncHTTPClient() #發送異步請求 # yield http.fetch('https://www.youtube.com/',callback=self.done,raise_error=False) #其源碼中能夠看到return future,即返回future對象 # # def done(self,response): # print 'done',response # self.write('main') # self.finish('dd') class PageHandler(tornado.web.RequestHandler): def get(self): self.write('page') application = tornado.web.Application([ (r'/main',MainHandler), (r'/page',PageHandler) ]) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
從python 3.5 開始,關鍵字async 和 await能夠用來代替@gen.coroutine +yield,代碼以下:
http://www.tornadoweb.org/en/stable/guide/coroutines.html
async def fetch_coroutine(url): http_client = AsyncHTTPClient() response = await http_client.fetch(url) return response.body ''' # Decorated: # Native: # Normal function declaration # with decorator # "async def" keywords @gen.coroutine def a(): async def a(): # "yield" all async funcs # "await" all async funcs b = yield c() b = await c() # "return" and "yield" # cannot be mixed in # Python 2, so raise a # special exception. # Return normally raise gen.Return(b) return b '''
其實現異步阻塞的關鍵在於Future對象,下面是其部分源碼,能夠看到其_result屬性初始化沒有值,tornado內部會監聽每個Future對象的_result屬性值,若沒有值時,繼續阻塞,如有值時,若某個Future對象的_result屬性值有值了,處理該請求,結束阻塞,繼續監聽其餘Future對象。
關於Future類能夠參考:https://www.cnblogs.com/silence-cho/p/9867499.html
class Future(object): """Represents the result of an asynchronous computation.""" def __init__(self): """Initializes the future. Should not be called by clients.""" self._condition = threading.Condition() self._state = PENDING self._result = None self._exception = None self._traceback = None self._waiters = [] self._done_callbacks = []
參考文章:
官方文檔:http://www.tornadoweb.org/en/stable/index.html
http://www.cnblogs.com/wupeiqi/articles/5341480.html
http://www.cnblogs.com/wupeiqi/articles/5702910.html
http://www.cnblogs.com/wupeiqi/p/6536518.html