02: tornado進階篇

目錄:Tornado其餘篇

01: tornado基礎篇html

02: tornado進階篇jquery

03: 自定義異步非阻塞tornado框架web

04: 打開tornado源碼剖析處理過程ajax

目錄:

1.1 自定製tornado路由分層     返回頂部

  一、Tornado 源碼路由處理機制正則表達式

    1.  第一步:重新建 tornado項目能夠看出,路由參數列表 是傳遞給tornado.web.Application這個類實例化了算法

application = tornado.web.Application([
   (r"/index", MainHandler),
])
實例化application類

     2. 第二步:從tornado源碼中能夠看出在實例化時傳入的路由列表 賦值給了第一個參數handlers瀏覽器

def __init__(self, handlers=None, default_host="", transforms=None,
                 **settings):
        ...
        if handlers:
            self.add_handlers(".*$", handlers)
        ...
實例化application中構造方法

    3. 第三步:從初始化方法中咱們知道了路由 list 在 Application 裏叫 handlers, 其中self.add_handlers() 就是 Tornado 處理路由的關鍵安全

class Application(object):
    def add_handlers(self, host_pattern, host_handlers):
        #若是主機模型最後沒有結尾符,那麼就爲他添加一個結尾符。
        if not host_pattern.endswith("$"):
            host_pattern += "$"
        handlers = []
        #對主機名先作一層路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com
        #即:safe對應一組url映射,www對應一組url映射,那麼當請求到來時,先根據它作第一層匹配,以後再繼續進入內部匹配。

        #對於第一層url映射來講,因爲.*會匹配全部的url,所將 .* 的永遠放在handlers列表的最後,否則 .* 就會截和了...
        #re.complie是編譯正則表達式,之後請求來的時候只須要執行編譯結果的match方法就能夠去匹配了
        if self.handlers and self.handlers[-1][0].pattern == '.*$':
            self.handlers.insert(-1, (re.compile(host_pattern), handlers))
        else:
            self.handlers.append((re.compile(host_pattern), handlers))

        #遍歷咱們設置的和構造函數中添加的【url->Handler】映射,將url和對應的Handler封裝到URLSpec類中(構造函數中會對url進行編譯)
        #並將全部的URLSpec對象添加到handlers列表中,而handlers列表和主機名模型組成一個元祖,添加到self.Handlers列表中。
        for spec in host_handlers:
            if type(spec) is type(()):
                assert len(spec) in (2, 3)
                pattern = spec[0]
                handler = spec[1]
                if len(spec) == 3:
                    kwargs = spec[2]
                else:
                    kwargs = {}
                spec = URLSpec(pattern, handler, kwargs)
            handlers.append(spec)
            
            if spec.name:
                #未使用該功能,默認spec.name = None
                if spec.name in self.named_handlers:
                    logging.warning("Multiple handlers named %s; replacing previous value",spec.name)
                self.named_handlers[spec.name] = spec
add_handlers()源碼註釋

    add_handlers說明:能夠看到handlers方法接受的是一個 tuple 的 list 並經過處理返回一個 URLSpec() 的 list,服務器

                                   那麼其實只要把分層路由信息統一成一個 tuple 的 list 傳給 Application就能夠實現分層路由的實現
cookie

   二、自定製tornado路由分層(基於上述路由處理機制)

    1. 爲了實現統一分層路由須要寫兩個方法

        1. 一個是 include(url_module) 引入子層路由信息統一輸出
        2. 另外一個 url_wrapper(urls) 則是將分層、不分層信息統一格式化成一個 tuple 的 list

    2. 代碼展現

      說明: 執行 http://127.0.0.1:8888/app01/index 就能夠訪問子項目中app01的index頁面了

C:
├─myTornadoPro                 #第一步:建立一個項目,名字爲:myTornadoPro
│  │  urls.py                  #第二步:建立主項目urls.py,啓動程序,路由分發
│  │  url_router.py            #第三步:建立url_router.py處理url分層
│  │  __init__.py
│  │
│  ├─app01                     #第四步:建立子項目app01
│  │  │  urls.py               #第五步:在子項目的urls.py中寫入本身的路由系統和處理函數
│  │  │  __init__.py
readme
import tornado.ioloop
import tornado.web
from myTornadoPro.url_router import include, url_wrapper

application = tornado.web.Application(url_wrapper([
    (r"/app01/", include('app01.urls')),
]))

if __name__ == "__main__":
   application.listen(8888)
   tornado.ioloop.IOLoop.instance().start()

# import tornado.httpserver
# if __name__ == "__main__":
#     http_server = tornado.httpserver.HTTPServer(application)
#     http_server.listen(8888)
#     tornado.ioloop.IOLoop.instance().start()
/myTornadoPro/urls.py
from importlib import import_module

# include(url_module) 引入子層路由信息統一輸出
def include(module):
    res = import_module(module)
    urls = getattr(res, 'urls', res)
    return urls

# url_wrapper(urls) 則是將分層、不分層信息統一格式化成一個 tuple 的 list
def url_wrapper(urls):
    wrapper_list = []
    for url in urls:
        path, handles = url
        if isinstance(handles, (tuple, list)):
            for handle in handles:
                pattern, handle_class = handle
                wrap = ('{0}{1}'.format(path, pattern), handle_class)
                wrapper_list.append(wrap)
        else:
            wrapper_list.append((path, handles))
    return wrapper_list
/myTornadoPro/url_router.py
import tornado.web

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      self.write("Hello, world!!")

urls = [
    (r'index', MainHandler),
]
/myTornadoPro/app01/urls.py

   三、tornado第二種路由方法(裝飾器)

import tornado.ioloop
import tornado.web

application = tornado.web.Application([])

def decorator(view):
    # view:  view是函視圖函數類( <class '__main__.UserstHandler'>、<class '__main__.IndexHandler'>)
    # URL = view.URL  : 獲取的URL路徑是類中定義的:URL = '/users'    URL = '/'
    URL = view.URL

    application.add_handlers('.*$', [(r'%s' % (URL), view)])

@decorator
class UserstHandler(tornado.web.RequestHandler):
    URL = '/users/login'

    def get(self, *args, **kwargs):
        self.write("UserstHandler")

@decorator
class IndexHandler(tornado.web.RequestHandler):
    URL = '/'

    def get(self, *args, **kwargs):
        self.write("IndexHandler")

if __name__ == "__main__":
    application.listen(8000)
    print("http://127.0.0.1:8000")
    print("http://127.0.0.1:8000/users/login")
    tornado.ioloop.IOLoop.instance().start()
app.py

1.2 cookie     返回頂部

  一、cookie基本操做

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      #1. 設置普通cookie
      self.set_cookie('name','tom1')
      print(self.get_cookie('name',''))

      #2. 設置加密cookie
      self.set_secure_cookie('name','tom2')
      print(self.get_secure_cookie("name", None))
      
      #3. 清除key爲name的這個cookie
      self.clear_cookie('name')
      
      #4. 清除全部cookie
      self.clear_all_cookies()

      self.write('cookie test')
cookie基本操做

  二、set_secure_cookie與set_cookie的區別

      一、set_secure_cookie與set_cookie的區別就是value通過 create_signed_value的處理。

      二、create_signed_value,獲得當前時間,將要存的value base64編碼,經過_cookie_signature將 加上name,

          這三個值加密生成簽名

      三、而後將簽名,value的base64編碼,時間戳用|鏈接,做爲cookie的值。

      四、_cookie_signature,就是根據settings裏邊的 保密的密鑰生成簽名返回

      五、get_secure_cookie,用|分割cookie的value,經過name,原value的base64的編碼,時間戳獲得簽名,驗

           證簽名是否正確,正確返回,還多了一個過時時間的判斷

      六、若是別人想僞造用戶的cookie,必需要知道密鑰,才能生成正確的簽名,否則經過 get_secure_cookie獲取

           value的時候,不會經過驗證,而後就不會返回僞造的cookie值。

   三、使用基本cookie實現登陸,註銷功能

import tornado.ioloop
import tornado.web

'''1. 登陸功能'''
class LoginHandler(tornado.web.RequestHandler):
   def get(self):
      self.render('login.html', **{'status': ''})
   def post(self, *args, **kwargs):
      username = self.get_argument('name')
      password = self.get_argument('pwd')
      if username == 'tom' and password == '123':
         self.set_secure_cookie('login_user', '武沛齊')
         self.redirect('/index')
      else:
         self.render('login.html', **{'status': '用戶名或密碼錯誤'})

'''2. 訪問首頁'''
class MainHandler(tornado.web.RequestHandler):
   def get(self):
      login_user = self.get_secure_cookie("login_user", None)
      if login_user:
         self.write(login_user)
      else:
         self.redirect('/login')

'''3. 註銷登陸'''
class LogoutHandler(tornado.web.RequestHandler):
   def get(self):
      self.clear_cookie("login_user")
      self.write('註銷成功')

settings = {
   'template_path': 'template',
   'static_path': 'static',
   'static_url_prefix': '/static/',
   'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'   #使用cookie必須設置cookie_secret
}

application = tornado.web.Application([
   (r"/index", MainHandler),
   (r"/login", LoginHandler),
   (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
   application.listen(8888)
   print('主頁:/index/','http://127.0.0.1:8888/index')
   print('登陸:/login/','http://127.0.0.1:8888/login')
   print('註銷:/logout/','http://127.0.0.1:8888/logout')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/login">
        <input type="text" name="name">
        <input type="password" name="pwd">
        <input type="submit" value="提交">
    </form>
</body>
</html>
/template/login.html

   四、加密cookie的基本使用

    1. 加密cookie的做用

        一、Cookie 很容易被惡意的客戶端僞造,加入你想在 cookie 中保存當前登錄用戶的 id 之類的信息,你須要對
            cookie 做簽名以防止僞造
        二、Tornado 經過 set_secure_cookie 和 get_secure_cookie 方法直接支持了這種功能
        三、要使用這些方法,你須要在建立應用時提供一個密鑰,名字爲 cookie_secret。 你能夠把它做爲一個關鍵詞
             參數經過settings傳入應用的設置中

    2. 加密cookie的基本使用

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      if not self.get_secure_cookie("mycookie"):
         self.set_secure_cookie("mycookie", "myvalue")
         self.write("Your cookie was not set yet!")
      else:
         self.write("Your cookie was set!")

application = tornado.web.Application([
   (r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
加密cookie基本使用

    3. 簽名Cookie的本質

# 寫cookie過程:
    將值進行base64加密
    對除值之外的內容進行簽名,哈希算法(沒法逆向解析)
    拼接 簽名 + 加密值

# 讀cookie過程:
    讀取 簽名 + 加密值
    對簽名進行驗證
    base64解密,獲取值內容
簽名Cookie的本質

  五、使用加密cookie實現登陸,註銷功能

import tornado.ioloop
import tornado.web

# 裝飾器、獲取當前用戶名
class BaseHandler(tornado.web.RequestHandler):
   def get_current_user(self):
      return self.get_secure_cookie("login_user")

'''1. 登陸功能'''
class LoginHandler(tornado.web.RequestHandler):
   def get(self):
      self.render('login.html', **{'status': ''})
       
   def post(self, *args, **kwargs):
      username = self.get_argument('name')
      password = self.get_argument('pwd')
      if username == 'tom' and password == '123':
         self.set_secure_cookie('login_user', 'TOM')
         self.redirect('/index')
      else:
         self.render('login.html', **{'status': '用戶名或密碼錯誤'})

'''2. 訪問首頁'''
class MainHandler(BaseHandler):
   @tornado.web.authenticated
   def get(self):
      login_user = self.current_user
      self.write(login_user)

'''3. 註銷登陸'''
class LogoutHandler(tornado.web.RequestHandler):
   def get(self):
      self.clear_cookie("login_user")
      self.write('註銷成功')

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),
   (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
   application.listen(8888)
   print('主頁:/index/','http://127.0.0.1:8888/index')
   print('登陸:/login/','http://127.0.0.1:8888/login')
   print('註銷:/logout/','http://127.0.0.1:8888/logout')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST" action="/login">
        <input type="text" name="name">
        <input type="password" name="pwd">
        <input type="submit" value="提交">
    </form>
</body>
</html>
login.html

   六、JavaScript操做Cookie

<script>
    /*
    設置cookie,指定秒數過時
     */
    function setCookie(name,value,expires){
        var temp = [];
        var current_date = new Date();
        current_date.setSeconds(current_date.getSeconds() + 5);
        document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
    }
</script>

對於參數:
domain   指定域名下的cookie
path       域名下指定url中的cookie
secure    https使用
JavaScript操做Cookie

1.3 tornado之自定義session     返回頂部

  一、Session做用 & 原理(session操做依賴cookie)

      1. 基於Cookie作用戶驗證時:敏感信息不適合放在cookie中

      2. 用戶成功登錄後服務端會生成一個隨機字符串並將這個字符串做爲字典key,將用戶登陸信息做爲value

      3. 當用戶再次登錄時就會帶着這個隨機字符串過來,就沒必要再輸入用戶名和密碼了

      4. 用戶使用cookie將這個隨機字符串保存到客戶端本地,當用戶再來時攜帶這個隨機字符串,服務端根據

          這個隨機字符串查找對應的session中的值,這樣就避免敏感信息泄露

  二、Cookie和Session對比

      一、Cookie是保存在用戶瀏覽器端的鍵值對

      二、Session是保存在服務器端的鍵值對

  三、自定義session原理講解

      一、當用戶登陸成功,調用__setitem__方法在服務器端設置用戶登陸信息的session字典,字典的key就是生成的隨機字符串

      二、__setitem__方法還會調用tornado中的cookie,設置cookie:  set_cookie("__kakaka__", self.random_str)

      三、當用戶訪問資源時調用__getitem__方法,首先經過get_cookie("__kakaka__") 獲取cookie中的隨機字符串

      四、服務器端的session字典的key就是這個隨機字符串,經過這個隨機字符串就能獲取到用戶更多信息

  四、面向對象基礎:如何觸發__setitem__   __getitem__方法

class Foo(object):
    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)

obj = Foo()
result = obj['k1']           # __getitem__ k1
obj['k2'] = 'wupeiqi'        # __setitem__ k2 wupeiqi
del obj['k1']                # __delitem__ k1
如何觸發__setitem__ __getitem__方法

  五、自定義session代碼

import tornado.ioloop
import tornado.web
from check_session import Session

class BaseHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.session = Session(self)

class IndexHandler(BaseHandler):
    def get(self):
        if self.get_argument('u', None) in ['tom','fly']:    # 這裏是模仿登陸成功
            # self.session['is_login'] 實際上執行的就是__setitem__('is_login','True')
            self.session['is_login'] = True
            self.session['name'] = self.get_argument('u',None)
            self.write('login index success')
        else:
            self.write("已經登錄")

class ManagerHandler(BaseHandler):
    def get(self):
        # self.session['is_login'] 實際上執行的就是__getitem__('is_login')
        val = self.session['is_login']         # 返回結果:True
        if val:
            self.write(self.session['name'])   # 返回的結果:tom
        else:
            self.write("失敗")

class LogoutHandler(BaseHandler):
    def get(self):
        self.clear_cookie("__kakaka__")
        self.write("註銷成功")

settings = {
    'template_path': 'template',
    'static_path': 'statics',
    'static_url_prefix':'/statics/',
    'xsrf_cookies':True
}

application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/manager", ManagerHandler),
    (r"/logout", LogoutHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8881)
    print('主頁:/index/','http://127.0.0.1:8881/index?u=tom')
    print('測試:/manager/','http://127.0.0.1:8881/manager  只有登陸成功才能返回登陸名,不然返回"失敗"')
    print('註銷:/logout/','http://127.0.0.1:8881/logout')
    tornado.ioloop.IOLoop.instance().start()
app.py
container = {}
#登陸成功後: container = {'1c63448337411806584e72f735a908a9': {'is_login': True, 'name': 'tom'}}

class Session:
    def __init__(self, handler):
        self.handler = handler            # 定義self.handler是爲了能夠經過對象調用set_cookie
        self.random_str = None            # session中的隨機字符串

    #一、生成加密的隨機數字
    def __genarate_random_str(self):
        import hashlib
        import time
        obj = hashlib.md5()
        obj.update(bytes(str(time.time()), encoding='utf-8'))
        random_str = obj.hexdigest()
        return random_str

    #二、設置登陸信息:用戶身份驗證經過纔會調用這個函數,生成session字典,設置cookie建爲__kakaka__; 值爲隨機字符串
    def __setitem__(self, key,value):
        print('__setitem__',key,value)
        # 在container中加入隨機字符串
        # 定義專屬於本身的數據
        # 在客戶端中寫入隨機字符串
        # 判斷,請求的用戶是否已有隨機字符串
        if not self.random_str:                                  # 客戶端沒有隨機字符串
            random_str = self.handler.get_cookie('__kakaka__')
            if not random_str:
                random_str = self.__genarate_random_str()
                container[random_str] = {}
            else:                                                 # 客戶端有隨機字符串
                if random_str in container.keys():
                    pass
                else:
                    random_str = self.__genarate_random_str()
                    container[random_str] = {}
            self.random_str = random_str                           # self.random_str = asdfasdfasdfasdf
        container[self.random_str][key] = value
        self.handler.set_cookie("__kakaka__", self.random_str)    # 把隨機字符串放到tornado的cookie中

    #三、驗證登陸:若是登陸成,返回name和is_login的值,不然返回None
    def __getitem__(self,key):
        print('__getitem__',key)
        # 獲取客戶端的隨機字符串
        # 從container中獲取專屬於個人數據
        # 獲取cookie中設置的隨機字符串:4ddd718f9a2d48224e
        random_str =  self.handler.get_cookie("__kakaka__")    # 拿到的是隨機字符串
        if not random_str:                                      # 若是沒有直接返回None,驗證登陸失敗
            return None
        user_info_dict = container.get(random_str,None)         # 從全局字典中獲取:is_login,和name 的值
        if not user_info_dict:
            return None
        value = user_info_dict.get(key, None)
        return value                                            # 返回is_login獲取的值:True;或者name的值:tom
check_session.py

1.4 tornado中解決csrf     返回頂部

  一、CSRF原理

      一、當用戶第一次發送get請求時,服務端不只給客戶端返回get內容,並且中間包含一個隨機字符串
      二、這個字符串是加密的,只有服務端本身能夠反解
      三、當客戶端發送POST請求提交數據時,服務端會驗證客戶端是否攜帶這個隨機字符串, 沒有就會引起csrf錯誤

      四、若是沒有csrf,那麼黑客能夠經過任意表單向咱們的後臺提交數據,不安全

  二、form提交解決csrf: {% raw xsrf_form_html() %}

import tornado.ioloop
import tornado.web

class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    def post(self, *args, **kwargs):
        self.write('Csrf_POST')

#三、 配置settings
settings = {
    'template_path':'template',
    'static_path':'static',
    'xsrf_cookies': True,
}

#4 路由系統
application = tornado.web.Application([
    (r"/login/",LoginHandler ),
], **settings)

#5 啓動這個tornado這個程序
if __name__ == "__main__":
   application.listen(8888)
   print('/login/: http://127.0.0.1:8888/login/')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/login/">
        {% raw xsrf_form_html() %}
        <p><input type="text" name="username" placeholder="用戶名"></p>
        <p><input type="password" name="password" placeholder="密碼"></p>
        <p><input type="submit" value="提交"></p>
    </form>
</body>
</html>
login.html

   三、使用jQuery解決ajax提交csrf

import tornado.ioloop
import tornado.web

class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    def post(self, *args, **kwargs):
        print('id', self.get_argument('id'))
        print('username', self.get_argument('username'))
        print('pwd', self.get_argument('pwd'))
        self.write('Csrf_POST')

#三、 配置settings
settings = {
    'template_path':'template',
    'static_path':'static',
    'xsrf_cookies': True,
}

#4 路由系統
application = tornado.web.Application([
    (r"/login/",LoginHandler ),
], **settings)

#5 啓動這個tornado這個程序
if __name__ == "__main__":
   application.listen(8888)
   print('/login/: http://127.0.0.1:8888/login/')
   tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p onclick="ajaxSubmit();">提交AJAX</p>

    <script src="/static/jquery-1.12.4.js"></script>
    <script>
            function getCookie(name) {
                var r = document.cookie.match("\\b"+name+"=([^:]*)\\b");
                return r ? r[1] : undefined;
            }

            function ajaxSubmit(){
                $.ajax({
                    url: "/login/",
                    type:'POST',
                    data: {id:1,username:'zhangsan',pwd:'123', _xsrf:getCookie("_xsrf")},
                    success: function(r){
                        console.log(r)
                    }
                });
            }
    </script>
</body>
</html>
login.html

1.5 tornado重定向錯誤     返回頂部

import tornado.ioloop
import tornado.web

def write_error(self, stat, **kw):
    self.write('訪問url不存在!')

tornado.web.RequestHandler.write_error = write_error

application = tornado.web.Application([])

if __name__ == "__main__":
   application.listen(8888)
   print('訪問不存在的url會定向多去 : http://127.0.0.1:8888/fdsafds/')
   tornado.ioloop.IOLoop.instance().start()
app.py
相關文章
相關標籤/搜索