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
相关文章
相关标签/搜索