day 11 web框架 ajax
Web框架本质
1 #!/usr/bin/env python 2 #coding:utf-8 3 4 import socket 5 6 def handle_request(client): 7 buf = client.recv(1024) 8 client.send("HTTP/1.1 200 OK\r\n\r\n") 9 client.send("Hello, Seven") 10 11 def main(): 12 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 sock.bind(('localhost',8000)) 14 sock.listen(5) 15 16 while True: 17 connection, address = sock.accept() 18 handle_request(connection) 19 connection.close() 20 21 if __name__ == '__main__': 22 main()
上述通过socket来实现了其本质,而对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
1 MVC框架 2 Models 数据相关操作 3 Views 模板HTML文件 4 Controllers 业务逻辑 5 6 7 MTV框架 8 Models 数据相关操作 9 Templates 模板HTML文件 10 Views 业务逻辑
wsgi:
WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。
python标准库提供的独立WSGI服务器称为wsgiref。
1 两类web框架:请求先给socket,之后再交给逻辑处理 2 一:tornado:自身包含socket 3 二: 4 Django,flask 5 wsgi自身包含socket 6 7 Python中的wsgiref遵循了wsgi规范,内部实现了socket。引入这个模块,不需要再自己写socket
通过python标准库提供的wsgiref模块开发一个自己的Web框架
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 from wsgiref.simple_server import make_server 4 5 6 def new(): 7 return 'new' 8 9 10 def index(): 11 return 'index' 12 13 14 URLS = { 15 '/new': new, 16 '/index': index, 17 } 18 19 20 def RunServer(environ, start_response): 21 22 """ 23 :param environ: 封装了所有的请求信息 24 :param start_response:封装要返回给用户的数据,响应头、状态码 25 :return: 26 """ 27 28 start_response('200 OK', [('Content-Type', 'text/html')]) 29 30 url = environ['PATH_INFO'] 31 if url in URLS.keys(): 32 func_name = URLS[url] 33 ret = func_name() 34 else: 35 ret = '404' 36 return ret 37 38 39 if __name__ == '__main__': 40 httpd = make_server('', 8000, RunServer) 41 httpd.serve_forever()
通过jinja2实现动态web自定义框架示例:

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="Refresh" content="100;URL=https://www.baidu.com"> 6 <title>whisky</title> 7 <link rel="icon" href="http://www.jd.com/favicon.ico"> 8 </head> 9 10 <body> 11 <h1>{{ name }}</h1> 12 <ul> 13 {% for item in user_list %} 14 <li>{{item}}</li> 15 {% endfor %} 16 </ul> 17 18 </body> 19 </html>
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 import sys 4 reload(sys) 5 sys.setdefaultencoding('utf8') 6 # 解决报错:UnicodeDecodeError: 'ascii' codec can't decode byte 0xe8 7 import os 8 from jinja2 import Template 9 10 def new(): 11 # html_file = open(os.path.join('views', "html basic.html"), 'r') 12 # data = html_file.read() 13 # html_file.close() 14 # data = data.replace("{{item}}", str(time.time())) 15 # return data 16 17 f = open(os.path.join('views', 'html basic.html')) 18 result = f.read() 19 20 template = Template(result) 21 data = template.render(name='John Doe', user_list=['alex', 'eric','xiaojun', 'sb', 'alex', '222']) 22 return data.encode('utf-8') 23 24 def index(): 25 return 'index' 26 27 28 def home(): 29 return 'home'
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 from wsgiref.simple_server import make_server 4 from urls import URLS 5 6 7 def RunServer(environ, start_response): 8 9 """ 10 :param environ: 封装了所有的请求信息 11 :param start_response: 12 :return: 13 """ 14 15 start_response('200 OK', [('Content-Type', 'text/html')]) 16 17 url = environ['PATH_INFO'] 18 if url in URLS.keys(): 19 func_name = URLS[url] 20 ret = func_name() 21 else: 22 ret = '404' 23 return ret 24 25 26 if __name__ == '__main__': 27 httpd = make_server('', 8000, RunServer) 28 httpd.serve_forever()
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 import controller 4 URLS = { 5 '/new': controller.new, 6 '/index': controller.index, 7 '/home': controller.home, 8 }
tornado:
Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容 服务器,以处理数以千计的客户端的连接的问题,请参阅 C10K problem。
一、快速上手
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class MainHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.write("Hello, world") 11 12 application = tornado.web.Application([ 13 (r"/index", MainHandler), 14 ]) 15 16 17 if __name__ == "__main__": 18 application.listen(8888) 19 tornado.ioloop.IOLoop.instance().start()
tornado之使用模板处理静态文件:

s1.py
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 INPUTS_LIST = [] 7 8 9 # 处理请求的类: 10 class MainHandler(tornado.web.RequestHandler): 11 def get(self): 12 # self.write("Hello, world") 13 self.render('s1.html', xxxooo=INPUTS_LIST) 14 15 def post(self, *args, **kwargs): 16 name = self.get_argument('xxx') # 获取用户提交的数据(参数为HTML标签中name变量的值) 17 INPUTS_LIST.append(name) 18 self.render("s1.html", xxxooo=INPUTS_LIST) 19 20 21 # 对于静态文件的处理 22 settings = { 23 "template_path": "tpl", # 模板路径配置(存放HTML) 24 'static_path': 'static', # 静态文件指定路径(存放JS、CSS) 25 # "static_url_prefix": '/prefix_folder/' # 如果写上了static_url_prefix , 会去static_path路径中去找对应的路径, 26 } 27 28 # 路由映射,路由系统 29 application = tornado.web.Application([ 30 (r"/index", MainHandler), 31 ], **settings) 32 33 # 下面做了两件事:1.创建socket对象;2.循环socket对象。内部用了epoll,select,IO多路复用 34 if __name__ == "__main__": 35 application.listen(8888) 36 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>whisky</title> 6 <link rel="stylesheet" href="../static/common.css"> 7 </head> 8 9 <body> 10 <div style="background-color: pink"> 11 <h1>whisky</h1> 12 </div> 13 <form method="post" action="/index"> 14 <input type="text" name="xxx" /> 15 <h1>提交内容:</h1> 16 <input type="submit" value="提交" /> 17 </form> 18 <h1>展示内容:</h1> 19 <ul> 20 {% for item in xxxooo %} 21 <li>{{item}}</li> 22 {% end %} 23 </ul> 24 <script src="../static/jquery.min.js"></script> 25 <script> 26 $("div").mouseover(function () { 27 $('div').css("backgroundColor", "green") 28 }); 29 $("div").mouseleave(function () { 30 $('div').css("backgroundColor", "pink") 31 }); 32 </script> 33 34 </body> 35 </html>
1 * { 2 margin:0; 3 }
三、模板
Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。
Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}。
控制语句和对应的 Python 语句的格式基本完全相同。我们支持 if、for、while 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块 的代码文档中有着详细的描述。
例:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 5 <title>老男孩</title> 6 <link href="{{static_url("css/common.css")}}" rel="stylesheet" /> 7 {% block CSS %}{% end %} 8 </head> 9 <body> 10 11 <div class="pg-header"> 12 13 </div> 14 15 {% block RenderBody %}{% end %} 16 17 <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script> 18 19 {% block JavaScript %}{% end %} 20 </body> 21 </html>
1 {% extends 'layout.html'%} 2 {% block CSS %} 3 <link href="{{static_url("css/index.css")}}" rel="stylesheet" /> 4 {% end %} 5 6 {% block RenderBody %} 7 <h1>Index</h1> 8 9 <ul> 10 {% for item in li %} 11 <li>{{item}}</li> 12 {% end %} 13 </ul> 14 15 {% end %} 16 17 {% block JavaScript %} 18 19 {% end %}
模板语言的本质:
字符串拼接后做的 exec 或compile
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 5 namespace = {'name':'wupeiqi','data':[18,73,84],} 6 7 code = '''def hellocute():return "name %s ,age %d" %(name,data[0],) ''' 8 9 func = compile(code, '<string>', "exec") 10 11 exec(func,namespace) 12 13 result = namespace['hellocute']() 14 15 print(result)
在模板中默认提供了一些函数、字段、类以供模板使用:
escape:tornado.escape.xhtml_escape的別名xhtml_escape:tornado.escape.xhtml_escape的別名url_escape:tornado.escape.url_escape的別名json_encode:tornado.escape.json_encode的別名squeeze:tornado.escape.squeeze的別名linkify:tornado.escape.linkify的別名datetime: Python 的datetime模组handler: 当前的RequestHandler对象request:handler.request的別名current_user:handler.current_user的別名locale:handler.locale的別名_:handler.locale.translate的別名static_url: forhandler.static_url的別名xsrf_form_html:handler.xsrf_form_html的別名
static_url: for handler.static_url 的別名 用于缓存,静态文件使用这个方法会生成一个文件的md5字符串,当文件修改时,md5会改变,浏览器会识别到重发http请求
Tornado默认提供的这些功能其实本质上就是 UIMethod 和 UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能:
三种模板语言:
一: {{}} 普通表达式替换
二:{% if %} {% end %}
三:自定义: 1.uimethod
2.uimodule
自定义 方法模块 供前端使用:
1、定义
#!/usr/bin/env python # -*-coding utf-8 -*- def func(self, arg): # uimethod 处理函数 print(arg) return 'content:after uimethod processing '
1 #!/usr/bin/env python 2 # -*-coding utf-8 -*- 3 4 from tornado.web import UIModule 5 from tornado import escape 6 7 8 class custom(UIModule): 9 10 def render(self, *args, **kwargs): 11 return 'UIModule 1234'
2.注册:
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 import uimethod as mt # 导入 7 import uimodule as md 8 9 INPUTS_LIST = [] 10 11 12 # 处理请求的类: 13 class MainHandler(tornado.web.RequestHandler): 14 def get(self): 15 # self.write("Hello, world") 16 name = self.get_argument('xxx', None) # 获取用户提交的数据 17 if name: 18 INPUTS_LIST.append(name) 19 self.render('s1.html', npm="NPM_test", xxxooo=INPUTS_LIST) 20 21 # def post(self, *args, **kwargs): 22 # name = self.get_argument('xxx') # 获取用户提交的数据 23 # INPUTS_LIST.append(name) 24 # self.render("s1.html", xxxooo=INPUTS_LIST) 25 26 27 settings = { 28 "template_path": "tpl", # 模板路径配置(存放HTML) 29 'static_path': 'static', # 静态文件指定路径(存放JS、CSS) 30 # "static_url_prefix": '/prefix_folder/' # 如果写上了static_url_prefix , 会去static_path路径中去找对应的路径, 31 'ui_methods': mt, # 注册 32 'ui_modules': md, # 注册 33 } 34 35 # 路由映射,路由系统 36 application = tornado.web.Application([ 37 (r"/index", MainHandler), 38 ], **settings) 39 40 if __name__ == "__main__": 41 application.listen(8888) 42 tornado.ioloop.IOLoop.instance().start()
3.使用:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>whisky</title> 6 <!--<link rel="stylesheet" href='{{ static_url("../static/common.css")}}' />--> 7 <link rel="stylesheet" href='{{ static_url("common.css")}}' /> 8 </head> 9 10 <body> 11 <div style="background-color: pink"> 12 <h1>whisky</h1> 13 </div> 14 <form method="get" action="/index"> 15 <input type="text" name="xxx" /> 16 <h1>提交内容:</h1> 17 <input type="submit" value="提交" /> 18 </form> 19 <h1>展示内容:</h1> 20 <!--uimethod使用--> 21 <h3>{{ func(npm) }}</h3> 22 <!--UImodule使用--> 23 <h3>{% module custom() %}</h3> 24 <ul> 25 {% for item in xxxooo %} 26 {% if item == "alex" %} 27 <li style="color:red">{{item}}</li> 28 {% else %} 29 <li>{{item}}</li> 30 {% end %} 31 {% end %} 32 </ul> 33 <!--<script src='{{ static_url("../static/jquery.min.js")}}'></script>--> 34 <script src='{{ static_url("jquery.min.js")}}'></script> 35 <script> 36 $("div").mouseover(function () { 37 $('div').css("backgroundColor", "green") 38 }); 39 $("div").mouseleave(function () { 40 $('div').css("backgroundColor", "pink") 41 }); 42 </script> 43 44 </body> 45 </html>
tornado---cookie:
基本操作:
index.py:
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 import time 7 # render中的文件不要加/等路径 8 9 10 class IndexHandler(tornado.web.RequestHandler): 11 def get(self, *args, **kwargs): 12 self.render('index.html') 13 14 15 class ManagerHandler(tornado.web.RequestHandler): 16 def get(self, *args, **kwargs): 17 val = self.get_cookie('auth') 18 if val == '1': 19 self.render('manager.html') 20 else: 21 self.redirect('/login') 22 23 24 class LoginHandler(tornado.web.RequestHandler): 25 26 def get(self, *args, **kwargs): 27 self.render('login.html', status_text='') 28 29 def post(self, *args, **kwargs): 30 username = self.get_argument('username', None) 31 pwd = self.get_argument('password', None) 32 check = self.get_argument('auto', None) # 勾选复选框时,check的值为1,未勾选值为0 33 if username == 'alex' and pwd == '123': 34 if check: 35 # self.get_secure_cookie() # 安全的加密cookie,在settings中设置cookie_secret加盐 36 self.set_cookie('username', username, expires_days=7) # 不同用户名区分cookie方法 37 self.set_cookie('auth', '1', expires_days=7) 38 else: 39 # 默认10秒超时 40 r = time.time() + 3 # 设置超时,3s后cookie自动销毁,立即过期:expires=time.time() 41 self.set_cookie('auth', '1', expires=r) 42 self.set_cookie('username', username, expires=r) 43 self.redirect('/manager') 44 else: 45 self.render('login.html', status_text='登录失败') 46 47 ''' 48 def set_cookie(self, name, value, domain=None, expires=None, path="/", 49 expires_days=None, **kwargs): 50 51 set_cookie参数: domain参数是用于浏览器区分不同域名放置cookie文件夹,不同域名cookie放在不同文件夹下 52 path="/",设置cookie生效的不同路径,'/'表示在全局所有url下cookie都生效,/index表示cookie只在index下面生效 53 ''' 54 55 56 class LogoutHandler(tornado.web.RequestHandler): 57 def get(self, *args, **kwargs): 58 self.set_cookie('auth', '0') 59 self.redirect('/login') 60 61 62 settings = { 63 "template_path": "views", # 模板路径配置(存放HTML) 64 # 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh' # 加密cookie的盐 65 } 66 67 # 路由映射,路由系统 68 application = tornado.web.Application([ 69 (r"/index", IndexHandler), 70 (r"/login", LoginHandler), 71 (r"/manager", ManagerHandler), 72 (r"/logout", LogoutHandler), 73 74 ], **settings) 75 76 77 if __name__ == "__main__": 78 application.listen(8000) 79 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/login" method="post"> 9 <input type="text" name="username" /> 10 <input type="password" name="password" /> 11 <input type="checkbox" name="auto" value="1" />7天免登陆 12 <input type="submit" value="登录" /> 13 <span style="color: red;">{{status_text}}</span> 14 </form> 15 </body> 16 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>首页</h1> 9 </body> 10 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <a href="/logout">退出</a> 9 <h1>登录后才能看到我</h1> 10 </body> 11 </html>
cookie:
在浏览器端保存的键值对,特性:每次http请求都会携带cookie信息
1 cookie : 2 存在于浏览器的键值对,用于验证服务端身份信息的 3 存放的值是一个随机字符串(token) 下次会带着这个token来,说明你的身份 4 5 session : 数据信息 可以放在内存,缓存,数据库 6 是一个验证机制,依赖于cookie 7 用于保存用户的敏感身份信息 8 如果服务器性能 可以将一些不重要的信息放在cookie 9 10 生成随机字符串: 11 服务端 12 13 ======= 下面的 为正规 14 15 cookie session区别: 16 cookie放在客户端 17 session的用户信息放在内存,缓存,数据库 18 19 cookie session联系: 20 session的数据信息是我们人为创建的。 21 是依据cookie创建的。 22 23 用户来了给用户一个token,下次用户来还带着这个token
后台获取cookie:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 11 print(self.cookies) # 所有cookie 12 print(self.get_cookie('k2', 'default_cookie')) # 获取cookie,并设置默认k2的cookie值为default_cookie 13 self.set_cookie('k1', '111') 14 self.render('index.html') 15 16 17 settings = { 18 'template_path': 'views', 19 'static_path': 'statics' 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", IndexHandler), 24 25 ], **settings) 26 27 if __name__ == "__main__": 28 application.listen(8888) 29 tornado.ioloop.IOLoop.instance().start() 30 31
JavaScript操作Cookie
由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie。如下:
前台获取cookie:
document.cookie # 得到的是一个字符串,其中包含cookie的键和值
document.cookie.split(';') # 分割之后用列表方法就能取到cookie值
前台设置cookie值:
document.cookie = 'k1=11111';
document.cookie = 'k2=11111;path=/;domain=;expires=UTC时间; # 设置cookie生效的路径
1 /* 2 设置cookie,指定秒数过期,可以在前台直接调用setCookie方法:setCookie('k88', '9090', 5). 3 */ 4 function setCookie(name,value,expires){ 5 // 获取当前时间 6 var current_date = new Date(); 7 //current_date.getSeconds() 获取当前秒数 8 current_date.setSeconds(current_date.getSeconds() + 5); 9 // 当前cookie生效5秒 10 document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString(); 11 } 12 13 14 15 注:jQuery中(http://plugins.jquery.com/cookie/)也有指定的插件 jQuery Cookie 专门用于操作cookie: 16 $.cookie('k3', '555', {'path':'', 'domain':'', expires=7}) 17 18 expires为数字时:jQuery中默认代表天数,不想用天数时,需要自定义时间: 19 20 var date = new Date(); 21 date.toUTCString() # 当前时间转换为UTC时间 22 date.setDate(data.getDate() + 7) #以天为格式 23 date.setSeconds(data.getSeconds() + 7) #以秒为格式 24 document.cookie = 'k2=11111;path=/;domain=;expires=date.toUTCString(); # 过期时间设置为当前时间,马上失效 25 26 {expires:date} #自定义时间后,再传递自定义时间给jQuery
1 <script src="/statics/jquery.cookie.js"></script> 2 <!--导入jQuery-cookie--> 3 <script src="/statics/jquery-1.12.4.js"></script> 4 5 <script> 6 // javascripts方式 在浏览器设置cookie 7 function setCookieBySeconds(name,value,expires) { 8 var current_date = new Date(); 9 current_date.setDate(current_date.getSeconds() + expires); 10 document.cookie = name + "= " +value +";expires=" + current_date.toUTCString() 11 12 }; 13 function setCookieByDays(name,value,expires) { 14 15 var current_date = new Date(); 16 current_date.setDate(current_date.getDay() + expires); 17 document.cookie = name +"= " +value +";expires="+current_date.toUTCString() // 要转换为utc时间 18 19 } 20 // 调用 21 // setCookieBySeconds('k1','v1',5) // 5秒过期 22 // setCookieByDays('k1','v1',5) // 5天过期 23 24 //###################jQuery设置################### 25 $.cookie('k1','v1',{expires:7}); // 7天 设置k1=v1的cookie7天过期 26 27 //如果不是按天的话,需要先定义时间秒变量 28 var current_date = new Date(); 29 current_date.setDate(current_date.getSeconds() + 6) ; //当前时间+6s 30 $.cookie('k1',"v1",{expires:current_date}) //6s 后过期 31 </scrips>
签名cookie本质
签名cookie
Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中
1 class MainHandler(tornado.web.RequestHandler): 2 def get(self): 3 if not self.get_secure_cookie("mycookie"): 4 self.set_secure_cookie("mycookie", "myvalue") 5 self.write("Your cookie was not set yet!") 6 else: 7 self.write("Your cookie was set!") 8 9 application = tornado.web.Application([ 10 (r"/", MainHandler), 11 ], 12 cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=") 13 14 还可以写成 : 15 16 settings = { cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=" 17 } 18 application = tornado.web.Application([ 19 (r"/", MainHandler), 20 ], **settings)
1 def _create_signature_v1(secret, *parts): 2 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 3 for part in parts: 4 hash.update(utf8(part)) 5 return utf8(hash.hexdigest()) 6 7 # 加密 8 def _create_signature_v2(secret, s): 9 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 10 hash.update(utf8(s)) 11 return utf8(hash.hexdigest()) 12 13 def create_signed_value(secret, name, value, version=None, clock=None, 14 key_version=None): 15 if version is None: 16 version = DEFAULT_SIGNED_VALUE_VERSION 17 if clock is None: 18 clock = time.time 19 20 timestamp = utf8(str(int(clock()))) 21 value = base64.b64encode(utf8(value)) 22 if version == 1: 23 signature = _create_signature_v1(secret, name, value, timestamp) 24 value = b"|".join([value, timestamp, signature]) 25 return value 26 elif version == 2: 27 # The v2 format consists of a version number and a series of 28 # length-prefixed fields "%d:%s", the last of which is a 29 # signature, all separated by pipes. All numbers are in 30 # decimal format with no leading zeros. The signature is an 31 # HMAC-SHA256 of the whole string up to that point, including 32 # the final pipe. 33 # 34 # The fields are: 35 # - format version (i.e. 2; no length prefix) 36 # - key version (integer, default is 0) 37 # - timestamp (integer seconds since epoch) 38 # - name (not encoded; assumed to be ~alphanumeric) 39 # - value (base64-encoded) 40 # - signature (hex-encoded; no length prefix) 41 def format_field(s): 42 return utf8("%d:" % len(s)) + utf8(s) 43 to_sign = b"|".join([ 44 b"2", 45 format_field(str(key_version or 0)), 46 format_field(timestamp), 47 format_field(name), 48 format_field(value), 49 b'']) 50 51 if isinstance(secret, dict): 52 assert key_version is not None, 'Key version must be set when sign key dict is used' 53 assert version >= 2, 'Version must be at least 2 for key version support' 54 secret = secret[key_version] 55 56 signature = _create_signature_v2(secret, to_sign) 57 return to_sign + signature 58 else: 59 raise ValueError("Unsupported version %d" % version) 60 61 # 解密 62 def _decode_signed_value_v1(secret, name, value, max_age_days, clock): 63 parts = utf8(value).split(b"|") 64 if len(parts) != 3: 65 return None 66 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 67 if not _time_independent_equals(parts[2], signature): 68 gen_log.warning("Invalid cookie signature %r", value) 69 return None 70 timestamp = int(parts[1]) 71 if timestamp < clock() - max_age_days * 86400: 72 gen_log.warning("Expired cookie %r", value) 73 return None 74 if timestamp > clock() + 31 * 86400: 75 # _cookie_signature does not hash a delimiter between the 76 # parts of the cookie, so an attacker could transfer trailing 77 # digits from the payload to the timestamp without altering the 78 # signature. For backwards compatibility, sanity-check timestamp 79 # here instead of modifying _cookie_signature. 80 gen_log.warning("Cookie timestamp in future; possible tampering %r", 81 value) 82 return None 83 if parts[1].startswith(b"0"): 84 gen_log.warning("Tampered cookie %r", value) 85 return None 86 try: 87 return base64.b64decode(parts[0]) 88 except Exception: 89 return None 90 91 92 def _decode_fields_v2(value): 93 def _consume_field(s): 94 length, _, rest = s.partition(b':') 95 n = int(length) 96 field_value = rest[:n] 97 # In python 3, indexing bytes returns small integers; we must 98 # use a slice to get a byte string as in python 2. 99 if rest[n:n + 1] != b'|': 100 raise ValueError("malformed v2 signed value field") 101 rest = rest[n + 1:] 102 return field_value, rest 103 104 rest = value[2:] # remove version number 105 key_version, rest = _consume_field(rest) 106 timestamp, rest = _consume_field(rest) 107 name_field, rest = _consume_field(rest) 108 value_field, passed_sig = _consume_field(rest) 109 return int(key_version), timestamp, name_field, value_field, passed_sig 110 111 112 def _decode_signed_value_v2(secret, name, value, max_age_days, clock): 113 try: 114 key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value) 115 except ValueError: 116 return None 117 signed_string = value[:-len(passed_sig)] 118 119 if isinstance(secret, dict): 120 try: 121 secret = secret[key_version] 122 except KeyError: 123 return None 124 125 expected_sig = _create_signature_v2(secret, signed_string) 126 if not _time_independent_equals(passed_sig, expected_sig): 127 return None 128 if name_field != utf8(name): 129 return None 130 timestamp = int(timestamp) 131 if timestamp < clock() - max_age_days * 86400: 132 # The signature has expired. 133 return None 134 try: 135 return base64.b64decode(value_field) 136 except Exception: 137 return None 138 139 140 def get_signature_key_version(value): 141 value = utf8(value) 142 version = _get_version(value) 143 if version < 2: 144 return None 145 try: 146 key_version, _, _, _, _ = _decode_fields_v2(value) 147 except ValueError: 148 return None 149 150 return key_version 151 152 内部算法
1 COOKIE: 2 cookie是存放在浏览器的键值对。 3 可以用来做用户验证,但是保存在浏览器的信息太多,所以后面很多用户信息放在服务端,浏览器是一个 4 随机字符串加作为验证信息。 5 cookie验证机制 6 服务端 设置cookie验证的时候 (比较靠谱的验证,而不用担心被人一眼看到浏览器觉得cookie就能请求) 7 1传入值x在tornado内部用base64加密加密后的值比如说是y, 8 即 y = base64(x) 9 比如说将这个y作为要发送给浏览器的cookie值得一部分,然后用|分割。 10 2 时间戳也作为要写入浏览器的cookie值得一部分,然后用|分割。 11 12 3 然后将 y值 时间戳, 还有自定义秘钥这三部分进行了单向加密 md5 或者hmac 作为要写入浏览器的cookie值得一部分,然后用|分割。 13 14 如上三部分 用|分割作为写入浏览器的cookie的value 15 浏览器再次请求的时候就会带着这个cookie信息来验证, 16 服务端会将y值根据base64解密成一个值跟自己的x进行对比,然后,根据服务端自己的y值,自己的时间戳 17 自己的自定义秘钥这三个再次单向加密md5或者hmac 与浏览器传过来的cookie的除了y值 时间戳的那个值 18 进行对比,如果相等就代表浏览器通过了。
签名Cookie的本质是:
写cookie过程:
- 将值进行base64加密
- 对除值以外的内容进行签名,哈希算法(无法逆向解析)
- 拼接 签名 + 加密值
读cookie过程:
- 读取 签名 + 加密值
- 对签名进行验证
- base64解密,获取值内容
注:许多API验证机制和安全cookie的实现机制相同。
应用:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 11 self.set_cookie('name', self.get_argument('u')) 12 self.write('登录成功') 13 else: 14 self.write('请登录') # 省去写前端页面,直接在前端页面输出write方法中的内容 15 16 17 class ManagerHandler(tornado.web.RequestHandler): 18 def get(self): 19 if self.get_cookie('name', None) in ['whisky', 'alex']: 20 self.write('欢迎登录:' + self.get_cookie('name')) 21 else: 22 self.redirect('/index') 23 24 25 settings = { 26 'template_path': 'views', 27 'static_path': 'statics' 28 } 29 30 application = tornado.web.Application([ 31 (r"/index", IndexHandler), 32 (r"/manager", ManagerHandler), 33 ], **settings) 34 35 if __name__ == "__main__": 36 application.listen(8000) 37 tornado.ioloop.IOLoop.instance().start() 38 39 40 # 登录测试:http://127.0.0.1:8000/index?u=whisky
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 11 self.set_secure_cookie('user', self.get_argument('u')) 12 self.write('登录成功') 13 else: 14 self.write('请登录') # 省去写前端页面,直接在前端页面输出write方法中的内容 15 16 17 class ManagerHandler(tornado.web.RequestHandler): 18 def get(self): 19 if str(self.get_secure_cookie('user', None), encoding='utf-8') in ['whisky', 'alex']: 20 self.write('欢迎登录:' + str(self.get_secure_cookie('user'))) 21 else: 22 self.redirect('/index') 23 24 25 settings = { 26 'template_path': 'views', 27 'static_path': 'statics', 28 'cookie_secret': 'qqweqewqf' 29 } 30 31 application = tornado.web.Application([ 32 (r"/index", IndexHandler), 33 (r"/manager", ManagerHandler), 34 ], **settings) 35 36 if __name__ == "__main__": 37 application.listen(8000) 38 tornado.ioloop.IOLoop.instance().start() 39 40 41 # 登录测试:http://127.0.0.1:8000/index?u=whisky
Session:
session其实就是定义在服务器端用于保存用户回话的容器,其必须依赖cookie才能实现。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 container = {} 8 # container = { 9 # '第一个人的随机字符串': {}, 10 # '第二个人的随机字符串': {'k1': 111, 'parents': '你'}, 11 # } 12 13 14 class IndexHandler(tornado.web.RequestHandler): 15 def get(self): 16 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 17 import hashlib 18 import time 19 obj = hashlib.md5() 20 obj.update(bytes(str(time.time()), encoding='utf-8')) 21 random_str = obj.hexdigest() # 根据时间生成随机字符串 22 container[random_str] = {} 23 24 container[random_str]['k1'] = 123 25 container[random_str]['k2'] = self.get_argument('u', None) + 'parents' 26 container[random_str]['is_login'] = True 27 self.set_cookie('iiiiiiii', random_str) 28 self.write('服务器端设置cookie完成') 29 else: 30 self.write('请用正确的用户名登录') 31 32 33 class ManagerHandler(tornado.web.RequestHandler): 34 def get(self): 35 random_str = self.get_cookie('iiiiiiii') 36 current_user_info = container.get(random_str, None) 37 if not current_user_info: # 如果大字典中用户信息为空(random_str键的值为none) 38 self.redirect('/index') 39 else: 40 # current_user_info有值时,还有两种情况:1.存在is_login,且值为true;2.存在is_login,且值为false(服务器端主动设置session过期) 41 if current_user_info.get('is_login', None): # 存在is_login,且值为true; 42 temp = " %s - %s" % (current_user_info.get('k1', ''), current_user_info.get('k2', '')) # 没有默认为空,字符串格式化中用none会报错 43 self.write(temp) 44 else: 45 self.redirect('/index') 46 47 48 settings = { 49 'template_path': 'views', 50 'static_path': 'statics' 51 } 52 53 application = tornado.web.Application([ 54 (r"/index", IndexHandler), 55 (r"/manager", ManagerHandler), 56 ], **settings) 57 58 if __name__ == "__main__": 59 application.listen(8000) 60 tornado.ioloop.IOLoop.instance().start() 61 62 63 # 登录测试:http://127.0.0.1:8000/index?u=whisky
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 # 把session封装为一个类,开发时无需关注生成字符串、设置cookie等细节,直接设置session的key和value即可,注意:代码中cookie的key是写死的 8 9 container = {} 10 # container = { 11 # '第一个人的随机字符串': {}, 12 # '第二个人的随机字符串': {'k1': 111, 'parents': '你'}, 13 # } 14 15 16 class Session: 17 def __init__(self, handler): 18 """ 19 :param handler: 即是:Session(self)中的self,是传进来的IndexHandler对象,具有了set_cookie方法 20 """ 21 self.handler = handler 22 self.random_str = None 23 24 def __generate_random_str(self): 25 # 生成加密串,方法只能在类里面调用 26 import hashlib 27 import time 28 obj = hashlib.md5() 29 obj.update(bytes(str(time.time()), encoding='utf-8')) 30 random_str = obj.hexdigest() # 根据时间生成随机字符串 31 return random_str 32 33 def set_value(self, key, value): 34 """ 35 :param key: 定义session用户信息的key 36 :param value: 定义session用户信息的value 37 :return: 38 """ 39 # 在container中加入随机字符串 40 # 定义session属于各自用户数据 41 # 在客户端中写入随机字符串 42 # 创建随机字符串之前,应该判断请求的用户是否第一次请求、第一次登录,是否已有随机字符串 43 if not self.random_str: 44 random_str = self.handler.get_cookie("___kakaka___") 45 if not random_str: 46 random_str = self.__generate_random_str() 47 # self.handler.set_cookie("___kakaka___", random_str) # 不存在随机字符串时,认为不存在cookie,此处需设置cookie; 48 # 如存在random_str,则认为存在cookie,if条件下面无需再次设置cookie。cookie的key定义要统一记准,值为random_str 49 container[random_str] = {} # 创建专属于自己的session数据空间为一个空字典 50 else: 51 # 客户端有随机字符串 52 if random_str in container.keys(): 53 pass 54 else: # 此种情况发生在服务器端重启时,浏览器端有random_str,服务器端没有,则重新生成random_str 55 random_str = self.__generate_random_str() 56 container[random_str] = {} 57 self.random_str = random_str 58 container[self.random_str][key] = value 59 self.handler.set_cookie("___kakaka___", self.random_str) 60 # 生产开发中,如果没有定义cookie超时时间,此处不需设置cookie,如果设置了cookie超时时间,到了时间之前再次访问时,应该重新写一次cookie,相当于延长一下超时时间,否则原来的cookie到了超时时间则过期 61 62 def get_value(self, key): 63 """ 64 :param key:传入session用户信息的key 65 :return: 返回session用户信息的value 66 """ 67 # 获取客户端的随机字符串 68 # 从container中获取专属于我的数据 69 random_str = self.handler.get_cookie("___kakaka___", None) 70 if not random_str: # 没有随机字符串表示没有登录 71 return None 72 # 客户端有random_str: 73 user_info_dict = container.get(random_str, None) 74 if not user_info_dict: # 服务器端没有random_str对应的值(服务器端可能重启过),返回空 75 return None 76 value = user_info_dict.get(key, None) 77 return value 78 79 80 class BaseHandler(tornado.web.RequestHandler): 81 def initialize(self): # initialize是RequestHandler类中初始化最后执行的方法 82 self.session = Session(self) # self.session对象中封装session;Session(self):传递self对象是为了在Session类中可以调用set_cookie方法 83 84 85 class IndexHandler(BaseHandler): 86 def get(self): # 此处self会默认传递给父类、基类 87 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 88 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 89 self.session.set_value('is_login', True) # 初始化创建session的key和value 90 self.session.set_value('name', self.get_argument('u', None)) 91 print(container) 92 else: 93 self.write('请用正确的用户名登录') 94 95 96 class ManagerHandler(BaseHandler): 97 def get(self): 98 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 99 val = self.session.get_value('is_login') 100 if val: 101 self.write(self.session.get_value('name')) 102 else: 103 self.write('失败') 104 105 106 settings = { 107 'template_path': 'views', 108 'static_path': 'statics' 109 } 110 111 application = tornado.web.Application([ 112 (r"/index", IndexHandler), 113 (r"/manager", ManagerHandler), 114 ], **settings) 115 116 if __name__ == "__main__": 117 application.listen(8000) 118 tornado.ioloop.IOLoop.instance().start() 119 120 121 # 登录测试:http://127.0.0.1:8000/index?u=whisky
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 # 把session封装为一个类,开发时无需关注生成字符串、设置cookie等细节,直接设置session的key和value即可,注意:代码中cookie的key是写死的 8 9 container = {} 10 # container = { 11 # '第一个人的随机字符串': {}, 12 # '第二个人的随机字符串': {'k1': 111, 'parents': '你'}, 13 # } 14 15 16 class Session: 17 def __init__(self, handler): 18 """ 19 :param handler: 即是:Session(self)中的self,是传进来的IndexHandler对象,具有了set_cookie方法 20 """ 21 self.handler = handler 22 self.random_str = None 23 24 def __generate_random_str(self): 25 # 生成加密串,方法只能在类里面调用 26 import hashlib 27 import time 28 obj = hashlib.md5() 29 obj.update(bytes(str(time.time()), encoding='utf-8')) 30 random_str = obj.hexdigest() # 根据时间生成随机字符串 31 return random_str 32 33 def __setitem__(self, key, value): 34 """ 35 :param key: 定义session用户信息的key 36 :param value: 定义session用户信息的value 37 :return: 38 """ 39 # 在container中加入随机字符串 40 # 定义session属于各自用户数据 41 # 在客户端中写入随机字符串 42 # 创建随机字符串之前,应该判断请求的用户是否第一次请求、第一次登录,是否已有随机字符串 43 if not self.random_str: 44 random_str = self.handler.get_cookie("___kakaka___") 45 if not random_str: 46 random_str = self.__generate_random_str() 47 # self.handler.set_cookie("___kakaka___", random_str) # 不存在随机字符串时,认为不存在cookie,此处需设置cookie; 48 # 如存在random_str,则认为存在cookie,if条件下面无需再次设置cookie。cookie的key定义要统一记准,值为random_str 49 container[random_str] = {} # 创建专属于自己的session数据空间为一个空字典 50 else: 51 # 客户端有随机字符串 52 if random_str in container.keys(): 53 pass 54 else: # 此种情况发生在服务器端重启时,浏览器端有random_str,服务器端没有,则重新生成random_str 55 random_str = self.__generate_random_str() 56 container[random_str] = {} 57 self.random_str = random_str 58 container[self.random_str][key] = value 59 self.handler.set_cookie("___kakaka___", self.random_str) 60 # 生产开发中,如果没有定义cookie超时时间,此处不需设置cookie,如果设置了cookie超时时间,到了时间之前再次访问时,应该重新写一次cookie,相当于延长一下超时时间,否则原来的cookie到了超时时间则过期 61 62 def __getitem__(self, key): 63 """ 64 :param key:传入session用户信息的key 65 :return: 返回session用户信息的value 66 """ 67 # 获取客户端的随机字符串 68 # 从container中获取专属于我的数据 69 random_str = self.handler.get_cookie("___kakaka___", None) 70 if not random_str: # 没有随机字符串表示没有登录 71 return None 72 # 客户端有random_str: 73 user_info_dict = container.get(random_str, None) 74 if not user_info_dict: # 服务器端没有random_str对应的值(服务器端可能重启过),返回空 75 return None 76 value = user_info_dict.get(key, None) 77 return value 78 79 80 class BaseHandler(tornado.web.RequestHandler): 81 def initialize(self): # initialize是RequestHandler类中初始化最后执行的方法 82 self.session = Session(self) # self.session对象中封装session;Session(self):传递self对象是为了在Session类中可以调用set_cookie方法 83 84 85 class IndexHandler(BaseHandler): 86 def get(self): # 此处self会默认传递给父类、基类 87 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 88 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 89 # self.session.set_value('is_login', True) # 初始化创建session的key和value 90 # self.session.set_value('name', self.get_argument('u', None)) 91 self.session['is_login'] = True 92 self.session['name'] = self.get_argument('u', None) 93 print(container) 94 else: 95 self.write('请用正确的用户名登录') 96 97 98 class ManagerHandler(BaseHandler): 99 def get(self): 100 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 101 # val = self.session.get_value('is_login') 102 val = self.session['is_login'] 103 if val: 104 # self.write(self.session.get_value('name')) 105 self.write(self.session['name']) 106 else: 107 self.write('失败') 108 109 110 settings = { 111 'template_path': 'views', 112 'static_path': 'statics' 113 } 114 115 application = tornado.web.Application([ 116 (r"/index", IndexHandler), 117 (r"/manager", ManagerHandler), 118 ], **settings) 119 120 if __name__ == "__main__": 121 application.listen(8000) 122 tornado.ioloop.IOLoop.instance().start() 123 124 125 # 登录测试:http://127.0.0.1:8000/index?u=whisky
Tornado扩展:
1 # Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作... 2 3 class BaseHandler(tornado.web.RequestHandler): 4 5 def initialize(self): 6 self.xxoo = "wupeiqi" 7 8 9 class MainHandler(BaseHandler): 10 11 def get(self): 12 print(self.xxoo) 13 self.write('index') 14 15 class IndexHandler(BaseHandler): 16 17 def get(self): 18 print(self.xxoo) 19 self.write('index')
session是用户信息,可以放在内存,数据库 memcahe redis ,是依赖cookie。是一套机制。不是具体数据。 cookie保存单一键值对,如果需要保存其他内容,则需要写多个cookie,而每次请求的话都会发送所有的cookie这样的话,会造成网络拥堵,此外,cookies是保存在浏览器端,如果保存用户名密码的cookie也放在浏览器端的话,也不够安全,这样就引出了session,session是人为生成的。session也是基于cookie来做的,但是只在浏览器端生成一个cookie(随机字符串,sessionId,token),而在服务端也保存着这段cookie,此外服务端还根据这段cookie可以生成一个字典,字典里面就可以放置用户的其他信息,服务端将这段cookie(sessionId)写到浏览器端,以后浏览器端来访问的时候,服务端根据sessionId从其相应的字典里面获取相应的信息来做相应用户认证机制。session可以保存在全局变量里,放在数据库,文件里面,memcached radis但是不能放在局部变量里。 cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这 个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。 会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie 保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏 览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式 session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。 cookie 和session 的区别: 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。 2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 考虑到安全应当使用session。 3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能 考虑到减轻服务器性能方面,应当使用COOKIE。 4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。 5、所以个人建议: 将登陆信息等重要信息存放为SESSION 其他信息如果需要保留,可以放在COOKIE中
session应用:验证码
结合session实现的验证码,每个用户登录生成验证码,保存到用户对应的session中
需要安装pillow模块,并下载字体文件
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 # 结合session实现的验证码,每个用户登录生成验证码,保存到用户对应的session中 8 container = {} 9 # container = { 10 # '第一个人的随机字符串': {}, 11 # '第二个人的随机字符串': {'k1': 111, 'parents': '你'}, 12 # } 13 14 15 class Session: 16 def __init__(self, handler): 17 """ 18 :param handler: 即是:Session(self)中的self,是传进来的IndexHandler对象,具有了set_cookie方法 19 """ 20 self.handler = handler 21 self.random_str = None 22 23 def __generate_random_str(self): 24 # 生成加密串,方法只能在类里面调用 25 import hashlib 26 import time 27 obj = hashlib.md5() 28 obj.update(bytes(str(time.time()), encoding='utf-8')) 29 random_str = obj.hexdigest() # 根据时间生成随机字符串 30 return random_str 31 32 def __setitem__(self, key, value): 33 """ 34 :param key: 定义session用户信息的key 35 :param value: 定义session用户信息的value 36 :return: 37 """ 38 # 在container中加入随机字符串 39 # 定义session属于各自用户数据 40 # 在客户端中写入随机字符串 41 # 创建随机字符串之前,应该判断请求的用户是否第一次请求、第一次登录,是否已有随机字符串 42 if not self.random_str: 43 random_str = self.handler.get_cookie("___kakaka___") 44 if not random_str: 45 random_str = self.__generate_random_str() 46 # self.handler.set_cookie("___kakaka___", random_str) # 不存在随机字符串时,认为不存在cookie,此处需设置cookie; 47 # 如存在random_str,则认为存在cookie,if条件下面无需再次设置cookie。cookie的key定义要统一记准,值为random_str 48 container[random_str] = {} # 创建专属于自己的session数据空间为一个空字典 49 else: 50 # 客户端有随机字符串 51 if random_str in container.keys(): 52 pass 53 else: # 此种情况发生在服务器端重启时,浏览器端有random_str,服务器端没有,则重新生成random_str 54 random_str = self.__generate_random_str() 55 container[random_str] = {} 56 self.random_str = random_str 57 # 定义session的key和value 58 container[self.random_str][key] = value 59 self.handler.set_cookie("___kakaka___", self.random_str) 60 # 生产开发中,如果没有定义cookie超时时间,此处不需设置cookie,如果设置了cookie超时时间,到了时间之前再次访问时,应该重新写一次cookie,相当于延长一下超时时间,否则原来的cookie到了超时时间则过期 61 62 def __getitem__(self, key): 63 """ 64 :param key:传入session用户信息的key 65 :return: 返回session用户信息的value 66 """ 67 # 获取客户端的随机字符串 68 # 从container中获取专属于我的数据 69 random_str = self.handler.get_cookie("___kakaka___", None) 70 if not random_str: # 没有随机字符串表示没有登录 71 return None 72 # 客户端有random_str: 73 user_info_dict = container.get(random_str, None) 74 if not user_info_dict: # 服务器端没有random_str对应的值(服务器端可能重启过),返回空 75 return None 76 value = user_info_dict.get(key, None) 77 return value 78 79 80 class BaseHandler(tornado.web.RequestHandler): 81 def initialize(self): # initialize是RequestHandler类中初始化最后执行的方法 82 self.session = Session(self) # self.session对象中封装session;Session(self):传递self对象是为了在Session类中可以调用set_cookie方法 83 84 85 class IndexHandler(BaseHandler): 86 def get(self): # 此处self会默认传递给父类、基类 87 if self.get_argument('u', None) in ['whisky', 'alex']: # 设置u值默认为None,否则访问不带u参数会报400错误 88 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 89 # self.session.set_value('is_login', True) # 初始化创建session的key和value 90 # self.session.set_value('name', self.get_argument('u', None)) 91 self.session['is_login'] = True 92 self.session['name'] = self.get_argument('u', None) 93 print(container) 94 else: 95 self.write('请用正确的用户名登录') 96 97 98 class ManagerHandler(BaseHandler): 99 def get(self): 100 # s = Session(self) # 传递self对象是为了在Session类中可以调用set_cookie方法 101 # val = self.session.get_value('is_login') 102 val = self.session['is_login'] 103 if val: 104 # self.write(self.session.get_value('name')) 105 self.write(self.session['name']) 106 else: 107 self.write('失败') 108 109 110 class LoginHandler(BaseHandler): 111 def get(self, *args, **kwargs): 112 self.render('index.html', status='') 113 114 def post(self, *args, **kwargs): 115 user = self.get_argument('user', None) 116 pwd = self.get_argument('pwd', None) 117 code = self.get_argument('code', None) 118 check_code = self.session['CheckCode'] 119 try: 120 if code.upper() == check_code.upper(): 121 self.write('验证码正确') 122 else: 123 # self.redirect('/index') 124 self.render('index.html', status='验证码错误') 125 except: 126 print('验证码错误') 127 self.write('验证码错误') 128 self.render('index.html') 129 130 131 class CheckCodeHandler(BaseHandler): 132 def get(self, *args, **kwargs): 133 # 生成图片并返回 134 import io 135 import check_code 136 mstream = io.BytesIO() # 相当于内存中临时创建了一个文件 137 img, code = check_code.create_validate_code() # 创建图片,并写入验证码 138 img.save(mstream, "GIF") 139 # 通过session为每个用户保存其验证码 140 self.session["CheckCode"] = code 141 self.write(mstream.getvalue()) # 返回图片内容 142 143 144 settings = { 145 'template_path': 'views', 146 'static_path': 'statics' 147 } 148 149 application = tornado.web.Application([ 150 (r"/index", IndexHandler), 151 (r"/manager", ManagerHandler), 152 (r"/login", LoginHandler), 153 (r"/check_code", CheckCodeHandler), 154 ], **settings) 155 156 if __name__ == "__main__": 157 application.listen(8000) 158 tornado.ioloop.IOLoop.instance().start() 159 160 161 # 登录测试session:http://127.0.0.1:8000/index?u=whisky 162 # 登录测试验证码:http://127.0.0.1:8000/login
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/login" method="post"> 9 <p><input name="user" type="text" placeholder="用户" /></p> 10 <p><input name="pwd" type="text" placeholder="密码" /></p> 11 <p> 12 <input name="code" type="text" placeholder="验证码" /> 13 <img onclick="Change();" id="imgCode" src="/check_code"> 14 </p> 15 <input type="submit" value="Submit" ><span style="color: red;">{{status}}</span> 16 </form> 17 18 <script> 19 function Change() { 20 var code = document.getElementById('imgCode'); 21 code.src += "?"; 22 } 23 </script> 24 </body> 25 </html>
1 #!/usr/bin/env python 2 #coding:utf-8 3 4 import random 5 from PIL import Image, ImageDraw, ImageFont, ImageFilter 6 7 _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z 8 _upper_cases = _letter_cases.upper() # 大写字母 9 _numbers = ''.join(map(str, range(3, 10))) # 数字 10 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) 11 12 def create_validate_code(size=(120, 30), 13 chars=init_chars, 14 img_type="GIF", 15 mode="RGB", 16 bg_color=(255, 255, 255), 17 fg_color=(0, 0, 255), 18 font_size=18, 19 font_type="Monaco.ttf", 20 length=4, 21 draw_lines=True, 22 n_line=(1, 2), 23 draw_points=True, 24 point_chance = 2): 25 ''' 26 @todo: 生成验证码图片 27 @param size: 图片的大小,格式(宽,高),默认为(120, 30) 28 @param chars: 允许的字符集合,格式字符串 29 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG 30 @param mode: 图片模式,默认为RGB 31 @param bg_color: 背景颜色,默认为白色 32 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF 33 @param font_size: 验证码字体大小 34 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf 35 @param length: 验证码字符个数 36 @param draw_lines: 是否划干扰线 37 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 38 @param draw_points: 是否画干扰点 39 @param point_chance: 干扰点出现的概率,大小范围[0, 100] 40 @return: [0]: PIL Image实例 41 @return: [1]: 验证码图片中的字符串 42 ''' 43 44 width, height = size # 宽, 高 45 img = Image.new(mode, size, bg_color) # 创建图形 46 draw = ImageDraw.Draw(img) # 创建画笔 47 48 def get_chars(): 49 '''生成给定长度的字符串,返回列表格式''' 50 return random.sample(chars, length) 51 52 def create_lines(): 53 '''绘制干扰线''' 54 line_num = random.randint(*n_line) # 干扰线条数 55 56 for i in range(line_num): 57 # 起始点 58 begin = (random.randint(0, size[0]), random.randint(0, size[1])) 59 #结束点 60 end = (random.randint(0, size[0]), random.randint(0, size[1])) 61 draw.line([begin, end], fill=(0, 0, 0)) 62 63 def create_points(): 64 '''绘制干扰点''' 65 chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] 66 67 for w in range(width): 68 for h in range(height): 69 tmp = random.randint(0, 100) 70 if tmp > 100 - chance: 71 draw.point((w, h), fill=(0, 0, 0)) 72 73 def create_strs(): 74 '''绘制验证码字符''' 75 c_chars = get_chars() 76 strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 77 78 font = ImageFont.truetype(font_type, font_size) 79 font_width, font_height = font.getsize(strs) 80 81 draw.text(((width - font_width) / 3, (height - font_height) / 3), 82 strs, font=font, fill=fg_color) 83 84 return ''.join(c_chars) 85 86 if draw_lines: 87 create_lines() 88 if draw_points: 89 create_points() 90 strs = create_strs() 91 92 # 图形扭曲参数 93 params = [1 - float(random.randint(1, 2)) / 100, 94 0, 95 0, 96 0, 97 1 - float(random.randint(1, 10)) / 100, 98 float(random.randint(1, 2)) / 500, 99 0.001, 100 float(random.randint(1, 2)) / 500 101 ] 102 img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 103 104 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) 105 106 return img, strs
session和cookie对比小结:
1 cookie和session区别: 2 3 1. 4 cookie保存在客户端 5 session保存在服务器端(session可以保存在内存中以字典形式存在,也可以保存在数据库中,或redis等缓存中) 6 7 8 2.联系: 9 session是人为创建的,是通过cookie构建出来的,用户请求过来时,发一个token(随机字符串),下次再来请求(携带着token)可识别身份
CSRF:
CSRF:
Tornado中的跨站请求伪造和Django中的相似,跨站伪造请求(Cross-site request forgery)
作用:
限制post请求(get请求不管),拒绝没有携带_xsrf身份证的人的POST请求,当一个用户请求过来时服务器端给予_xsrf身份证,则服务器端运行此用户之后的POST请求
原理:
第一次发get请求,服务器端给与一个标识(token,相当于身份证),第二次请求(POST)过来带着身份证就允许访问
如果服务器遇到没有拿身份证的请求,服务器端一概不接受
做一个CSRF验证:
1 class CsrfHandler(tornado.web.RequestHandler): 2 3 def get(self, *args, **kwargs): 4 self.render('csrf.html') 5 6 def post(self, *args, **kwargs): 7 self.write('csrf.post') 8 9 10 settings = { 11 'template_path': 'views', 12 'static_path': 'statics', 13 'xsrf_cookies': True # 开启CSRF伪造请求验证 14 } 15 16 application = tornado.web.Application([ 17 (r"/csrf", CsrfHandler), 18 ], **settings) 19 20 if __name__ == "__main__": 21 application.listen(8000) 22 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/csrf" method="post"> 9 {% raw xsrf_form_html() %} 10 <input type="text" name="message"/> 11 <input type="submit" value="Post"/> 12 <input type="button" value="Ajax CSRF" onclick="SubmitCsrf();" /> 13 </form> 14 15 </body> 16 </html>
注意:
页面中,不带下面这段代码是无法完成CSRF验证的,xsrf_form_html()信息隐藏在页面中
{% raw xsrf_form_html() %}
通过ajax(页面局部提交)做CSRF验证:
原理:在cookie中封装有_xsrf这个字段 ,取这个字段提交给服务器端即完成CSRF验证
demo中jQuery版本:jquery-1.12.4
1 class CsrfHandler(tornado.web.RequestHandler): 2 3 def get(self, *args, **kwargs): 4 self.render('csrf.html') 5 6 def post(self, *args, **kwargs): 7 self.write('csrf.post') 8 9 10 settings = { 11 'template_path': 'views', 12 'static_path': 'statics', 13 'xsrf_cookies': True # 开启CSRF伪造请求验证 14 } 15 16 application = tornado.web.Application([ 17 (r"/csrf", CsrfHandler), 18 ], **settings) 19 20 if __name__ == "__main__": 21 application.listen(8000) 22 tornado.ioloop.IOLoop.instance().start() 23 24 app.py
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/csrf" method="post"> 9 {% raw xsrf_form_html() %} 10 <input type="text" name="message"/> 11 <input type="submit" value="Post"/> 12 <input type="submit" value="haha"/> 13 <input type="button" value="Ajax CSRF" onclick="SubmitCsrf();" /> 14 </form> 15 <script src="/static/jquery-1.12.4.js"></script> 16 <script> 17 function getCookie(name) { 18 var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); 19 return r ? r[1] : undefined; 20 } 21 22 function SubmitCsrf() { 23 var nid = getCookie('_xsrf'); 24 $.post({ 25 url:'/csrf', 26 data: {'k1': 'v1', "_xsrf": nid}, 27 success: function (callback) { 28 // ajax请求发送成功后,自动执行 29 // callback, 服务器write的数据callback="csrf.post" 30 console.log(callback); 31 } 32 }); 33 } 34 </script> 35 </body> 36 </html>
1 下面这样发起post请求时,浏览器提示403Forbidden,没有携带xsrf字符串 2 3 function SubmitCsrf() { 4 $.post({ 5 url:'/csrf', 6 data: {'k1': 'v1'}, 7 success: function (callback) { 8 console.log(callback); 9 } 10 }); 11 } 12 13 下面这样发起post请求时,通过: 14 function SubmitCsrf() { 15 var nid = getCookie('_xsrf'); 16 $.post({ 17 url:'/csrf', 18 data: {'k1': 'v1', "_xsrf": nid}, 19 success: function (callback) { 20 console.log(callback); 21 } 22 }); 23 }
1 jQuery ajax: 2 http://jquery.cuishifeng.cn/jQuery.Ajax.html 3 4 参数: 5 6 data:发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。 7 8 url:一个用来包含发送请求的URL字符串。 9 10 方法: 11 success(data, textStatus, jqXHR) 12 请求成功后的回调函数。参数:由服务器返回,并根据dataType参数进行处理后的数据;描述状态的字符串。还有 jqXHR(在jQuery 1.4.x的中,XMLHttpRequest) 对象 。在jQuery 1.5, 成功设置可以接受一个函数数组。每个函数将被依次调用
ajax:
作用:防止页面批量刷新,浪费流量
ajax基础:
1 ***** XmlHttpRequest实现Ajax(上)- XmlHttpRequest对象方法和属性的讲解 ***** 2 3 1、概述 4 Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除另类的IE),Ajax首次出现IE5.5中存在(ActiveX控件) 5 6 2、XmlHttpRequest对象的主要方法 7 8 a. void open(String method,String url,Boolen async) 9 用于创建请求 10 11 参数: 12 method: 请求方式(字符串类型),如:POST、GET、DELETE... 13 url: 要请求的地址(字符串类型) 14 async: 是否异步(布尔类型) 15 16 b. void send(String body) 17 用于发送请求 18 19 参数: 20 body: 要发送的数据(字符串类型) 21 22 c. void setRequestHeader(String header,String value) 23 用于设置请求头,可以设置CSRF 24 25 参数: 26 header: 请求头的key(字符串类型) 27 vlaue: 请求头的value(字符串类型) 28 29 d. String getAllResponseHeaders() 30 获取所有响应头 31 32 返回值: 33 响应头数据(字符串类型) 34 35 e. String getResponseHeader(String header) 36 获取响应头中指定header的值 37 38 参数: 39 header: 响应头的key(字符串类型) 40 41 返回值: 42 响应头中指定的header对应的值 43 44 f. void abort() 45 46 终止请求 47 48 3、XmlHttpRequest对象的主要属性 49 50 a. Number readyState 51 状态值(整数) 52 53 详细: 54 0-未初始化,尚未调用open()方法; 55 1-启动,调用了open()方法,未调用send()方法; 56 2-发送,已经调用了send()方法,未接收到响应; 57 3-接收,已经接收到部分响应数据; 58 4-完成,已经接收到全部响应数据; 59 60 b. Function onreadystatechange # 每当上面readyState值发生变化时,就执行这个函数 61 当readyState的值改变时自动触发执行其对应的函数(回调函数) 62 63 c. String responseText 64 服务器返回的数据(字符串类型) 65 66 d. XmlDocument responseXML 67 服务器返回的数据(Xml对象) 68 69 e. Number states 70 状态码(整数),如:200、404... 71 72 f. String statesText 73 状态文本(字符串),如:OK、NotFound...
原生ajax(登录验证):
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 # render中的文件不要加/等路径 9 10 class LoginHandler(tornado.web.RequestHandler): 11 def get(self, *args, **kwargs): 12 self.render('login.html') 13 14 def post(self, *args, **kwargs): 15 dic = {'status': True, "message": ""} 16 user = self.get_argument('username') 17 pwd = self.get_argument('password') 18 # self.write('ok') # write方法作用相当于console.log() 19 if user == 'alex'and pwd == '123': 20 pass 21 else: 22 dic['status'] = False 23 dic['message'] = 'username or password is wrong!' 24 import json 25 self.write(json.dumps(dic)) 26 27 28 settings = { 29 "template_path": "views", # 模板路径配置(存放HTML) 30 # 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh' # 加密cookie的盐 31 } 32 33 # 路由映射,路由系统 34 application = tornado.web.Application([ 35 (r"/login", LoginHandler), 36 ], **settings) 37 38 39 if __name__ == "__main__": 40 application.listen(8000) 41 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input id='user' type="text" name="username" /> 9 <input id='pwd' type="password" name="password" /> 10 <input id='check' type="checkbox" name="auto" value="1" />7天免登陆 11 <input type="button" value="登录" onclick="SubmitForm();" /> 12 13 <script> 14 xhr = null; 15 function SubmitForm() { 16 // 触发事件,创建一个xml对象 17 xhr = new XMLHttpRequest(); 18 // 打开一个链接,最后一个参数默认为true就好,为false时页面会锁死, 19 // xhr.open('GET', '/login', true); 20 // 如果为POST请求,要加一个setRequestHeader请求头,GET方法无需设置setRequestHeader请求头 21 xhr.open('POST', '/login', true); 22 // 绑定一个事件/回调函数,当readyState(XmlHttpRequest对象的一个属性)的值改变时自动触发执行其对应的函数(回调函数) 23 xhr.onreadystatechange = func; 24 xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded, charset=UTF-8'); 25 // 用于发送前台页面输入的用户名和密码请求到后台(后台由get_argument获取相应变量), 参数:body(字符串类型): 要发送的数据 26 xhr.send("username=" + document.getElementById('user').value + ";password=" + document.getElementById('pwd').value); 27 } 28 29 function func() { 30 if(xhr.readyState == 4){ 31 //readyState 32 // 状态值(整数) 33 // 34 // 详细: 35 // 0-未初始化,尚未调用open()方法; 36 // 1-启动,调用了open()方法,未调用send()方法; 37 // 2-发送,已经调用了send()方法,未接收到响应; 38 // 3-接收,已经接收到部分响应数据; 39 // 4-完成,已经接收到全部响应数据 40 console.log(1); 41 // responseText 服务器返回的数据(字符串类型) 42 // console.log(xhr.responseText); 43 var data = xhr.responseText; 44 // 字符串转化为对象 45 var ret_dict = JSON.parse(data); 46 if(ret_dict.status){ // 后台字典中验证通过,status值为True 47 48 }else { 49 alert(ret_dict.message); // 取后台字典中的message的值 50 } 51 } 52 } 53 </script> 54 55 </body> 56 </html>
通过jQuery实现ajax登录验证:
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 # render中的文件不要加/等路径 9 10 class LoginHandler(tornado.web.RequestHandler): 11 def get(self, *args, **kwargs): 12 self.render('login.html') 13 14 def post(self, *args, **kwargs): 15 dic = {'status': True, "message": ""} 16 user = self.get_argument('username') 17 pwd = self.get_argument('password') 18 # self.write('ok') # write方法作用相当于console.log() 19 if user == 'alex'and pwd == '123': 20 pass 21 else: 22 dic['status'] = False 23 dic['message'] = 'username or password is wrong!' 24 import json 25 self.write(json.dumps(dic)) 26 27 28 settings = { 29 "template_path": "views", # 模板路径配置(存放HTML) 30 # 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh' # 加密cookie的盐 31 "static_path": 'static', # 注册引入jQuery的静态目录 32 } 33 34 # 路由映射,路由系统 35 application = tornado.web.Application([ 36 (r"/login", LoginHandler), 37 ], **settings) 38 39 40 if __name__ == "__main__": 41 application.listen(8000) 42 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input id='user' type="text" name="username" /> 9 <input id='pwd' type="password" name="password" /> 10 <input id='check' type="checkbox" name="auto" value="1" />7天免登陆 11 <input type="button" value="登录" onclick="SubmitForm();" /> 12 <script src="{{ static_url('jquery-1.9.1.js')}}"></script> 13 <script> 14 function SubmitForm() { 15 $.post('/login', {'username': $('#user').val(), 'password': $('#pwd').val()}, function (callback) { 16 console.log(callback); 17 }); 18 } 19 </script> 20 21 </body> 22 </html>
ajax详解:
1 AJAX,Asynchronous JavaScript and XML (异步的JavaScript和XML),一种创建交互式网页应用的网页开发技术方案。 2 3 异步的JavaScript: 4 使用 【JavaScript语言】 以及 相关【浏览器提供类库】 的功能向服务端发送请求,当服务端处理完请求之后,【自动执行某个JavaScript的回调函数】。 5 PS:以上请求和响应的整个过程是【偷偷】进行的,页面上无任何感知。 6 7 XML 8 XML是一种标记语言,是Ajax在和后台交互时传输数据的格式之一 9 10 利用AJAX可以做: 11 1、注册时,输入用户名自动检测用户是否已经存在。 12 2、登陆时,提示用户名密码错误 13 3、删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。(博客园)
“伪”AJAX:
由于HTML标签的iframe标签具有局部加载内容的特性(利用form的target属性传输数据到后台不刷新页面,并通过onload事件处理后台返回的数据两个特性),所以可以使用其来伪造Ajax请求。
1 <!DOCTYPE html> 2 <html> 3 4 <head lang="en"> 5 <meta charset="UTF-8"> 6 <title></title> 7 </head> 8 9 <body> 10 11 <div> 12 <p>请输入要加载的地址:<span id="currentTime"></span></p> 13 <p> 14 <input id="url" type="text" /> 15 <input type="button" value="刷新" onclick="LoadPage();"> 16 </p> 17 </div> 18 19 20 <div> 21 <h3>加载页面位置:</h3> 22 <iframe id="iframePosition" style="width: 100%;height: 500px;"></iframe> 23 </div> 24 25 26 <script type="text/javascript"> 27 28 window.onload= function(){ 29 var myDate = new Date(); 30 document.getElementById('currentTime').innerText = myDate.getTime(); 31 32 }; 33 34 function LoadPage(){ 35 var targetUrl = document.getElementById('url').value; 36 document.getElementById("iframePosition").src = targetUrl; 37 } 38 39 </script> 40 41 </body> 42 </html>
原生AJAX:
Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件)。
1、XmlHttpRequest对象(专门用来发ajax请求)介绍
xmlhttprequest对象的主要方法和属性如上ajax基础中介绍。
2、跨浏览器支持
XmlHttpRequest
IE7+, Firefox, Chrome, Opera, etc.
ActiveXObject("Microsoft.XMLHTTP")
IE6, IE5
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <body> 8 9 <h1>XMLHttpRequest - Ajax请求</h1> 10 <input type="button" onclick="XmlGetRequest();" value="Get发送请求" /> 11 <input type="button" onclick="XmlPostRequest();" value="Post发送请求" /> 12 13 <script src="/statics/jquery-1.12.4.js"></script> 14 <script type="text/javascript"> 15 16 function GetXHR(){ 17 var xhr = null; 18 if(XMLHttpRequest){ 19 xhr = new XMLHttpRequest(); 20 }else{ 21 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 22 } 23 return xhr; 24 25 } 26 27 function XhrPostRequest(){ 28 var xhr = GetXHR(); 29 // 定义回调函数 30 xhr.onreadystatechange = function(){ 31 if(xhr.readyState == 4){ 32 // 已经接收到全部响应数据,执行以下操作 33 var data = xhr.responseText; 34 console.log(data); 35 } 36 }; 37 // 指定连接方式和地址----文件方式 38 xhr.open('POST', "/test/", true); 39 // 设置请求头 40 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8'); //原生ajax发送请求时,必须设置请求头,否则后台无法接收到发送的数据,详细可看request.POST源码中对前端发送的数据的处理方式 41 // 发送请求 42 xhr.send('n1=1;n2=2;'); 43 } 44 45 function XhrGetRequest(){ 46 var xhr = GetXHR(); 47 // 定义回调函数 48 xhr.onreadystatechange = function(){ 49 if(xhr.readyState == 4){ 50 // 已经接收到全部响应数据,执行以下操作 51 var data = xhr.responseText; 52 console.log(data); 53 } 54 }; 55 // 指定连接方式和地址----文件方式 56 xhr.open('get', "/test/", true); 57 // 发送请求 58 xhr.send(); 59 } 60 61 </script> 62 63 </body> 64 </html>
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('{"status": 1, "message": "mmm"}') 14 15 settings = { 16 'template_path': 'views', 17 'static_path': 'statics' 18 } 19 20 application = tornado.web.Application([ 21 (r"/index", IndexHandler), 22 ], **settings) 23 24 if __name__ == "__main__": 25 application.listen(8000) 26 tornado.ioloop.IOLoop.instance().start()
小结:
1 # XmlHttpRequest对象包含了所有内容: 2 1.响应的状态码 3 2.响应的内容 4 3.XmlHttpRequest所有的方法和属性 5 6 7 # 在jQuery-ajax中找XmlHttpRequest对象进行操作: 8 $.ajax({ 9 success:function(arg,a1,a2) //其中arg是服务器端返回的数据,a1,a2等待其中有一个包含XmlHttpRequest对象 10 })
jQuery Ajax
jQuery其实就是一个JavaScript的类库,其将复杂的功能做了上层封装,使得开发者可以在其基础上写更少的代码实现更多的功能。
- jQuery 不是生产者,而是大自然搬运工。
- jQuery Ajax本质 XMLHttpRequest 或 ActiveXObject
注:2.+版本不再支持IE9以下的浏览器
1 jQuery.get(...) 2 所有参数: 3 url: 待载入页面的URL地址 4 data: 待发送 Key/value 参数。 5 success: 载入成功时回调函数。 6 dataType: 返回内容格式,xml, json, script, text, html 7 8 9 jQuery.post(...) 10 所有参数: 11 url: 待载入页面的URL地址 12 data: 待发送 Key/value 参数 13 success: 载入成功时回调函数 14 dataType: 返回内容格式,xml, json, script, text, html 15 16 17 jQuery.getJSON(...) 18 所有参数: 19 url: 待载入页面的URL地址 20 data: 待发送 Key/value 参数。 21 success: 载入成功时回调函数。 22 23 24 jQuery.getScript(...) 25 所有参数: 26 url: 待载入页面的URL地址 27 data: 待发送 Key/value 参数。 28 success: 载入成功时回调函数。 29 30 31 # 上述方法,内部都是调用的$.ajax(jQuery.ajax) ,如$.get默认内部method值为"GET" 32 jQuery.ajax(...) 33 url:请求地址 34 type/method:请求方式,GET、POST(1.9.0之后用method) 35 data:要发送的数据(必须为字典形式) 36 success:成功之后执行的回调函数(全局) 37 类型:Function,求成功后的回调函数。 38 参数:由服务器返回,并根据 dataType 参数进行处理后的数据;描述状态的字符串。 39 这是一个 Ajax 事件。 40 例: 41 success: function (data) { // 参数:data(必须为字符串,转化为对象:JSON.parse方法),由服务器端返回到前端的数据放到data中 42 alert(data) 43 } 44 45 contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8") 46 async:是否异步 47 timeout:设置请求超时时间(毫秒) 48 beforeSend:发送请求前执行的函数(全局) 49 complete:完成之后执行的回调函数(全局) 50 error:失败之后执行的回调函数(全局) 51 headers:请求头 52 accepts:通过请求头发送给服务器,告诉服务器当前客户端课接受的数据类型 53 dataType:将服务器端返回的数据转换成指定类型 54 "xml": 将服务器端返回的内容转换成xml格式 55 "text": 将服务器端返回的内容转换成普通文本格式 56 "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。 57 "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式 58 "json": 将服务器端返回的内容转换成相应的JavaScript对象 59 "jsonp": JSONP 格式 60 使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数 61 62 如果不指定,jQuery 将自动根据HTTP包MIME信息返回相应类型(an XML MIME type will yield XML, in 1.4 JSON will yield a JavaScript object, in 1.4 script will execute the script, and anything else will be returned as a string 63 64 converters: 转换器,将服务器端的内容根据指定的dataType转换类型,并传值给success回调函数 65 66 $.ajax({ 67 accepts: { 68 mycustomtype: 'application/x-some-custom-type' 69 }, 70 71 // Expect a `mycustomtype` back from server 72 dataType: 'mycustomtype' 73 74 // Instructions for how to deserialize a `mycustomtype` 75 converters: { 76 'text mycustomtype': function(result) { 77 // Do Stuff 78 return newresult; 79 } 80 }, 81 });
实例:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 print(self.get_argument('k1')) 14 self.write('{"status": 1, "message": "mmm"}') 15 16 17 settings = { 18 'template_path': 'views', 19 'static_path': 'statics', 20 'static_url_prefix': '/statics/' 21 } 22 23 application = tornado.web.Application([ 24 (r"/index", IndexHandler), 25 ], **settings) 26 27 if __name__ == "__main__": 28 application.listen(8000) 29 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <body> 8 9 <p> 10 <input type="button" onclick="XmlSendRequest();" value='Ajax请求' /> 11 </p> 12 13 14 <script type="text/javascript" src="/statics/jquery-1.12.4.js"></script> 15 <script> 16 17 function XmlSendRequest(){ 18 $.ajax({ 19 //url:请求地址 20 url: "/index" 21 //type:请求方式,GET、POST(1.9.0之后用method), 22 type: 'POST', 23 //data:要发送的数据 24 data: {'k1': 'v1'}, 25 //dataType:将服务器端返回的数据转换成指定类型,"text": 将服务器端返回的内容转换成普通文本格式 26 dataType: 'text', 27 //function中data:后台返回的数据,statusText状态码,xmlHttpRequest:jQuery内部创建的xmlHttpRequest对象 28 success: function(data, statusText, xmlHttpRequest){ 29 console.log(data); 30 } 31 }) 32 } 33 34 35 </script> 36 </body> 37 </html>
上传文件:
1、Form表单上传(Form提交会刷新页面)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 IMG_LIST = [] 8 9 10 class IndexHandler(tornado.web.RequestHandler): 11 def get(self): 12 self.render('index.html', img_list=IMG_LIST) 13 14 def post(self, *args, **kwargs): 15 print(self.get_argument('user')) 16 # checkbox 要用get_arguments来获取 17 print(self.get_arguments('favor')) 18 # print(self.get_arguments('fafafa')) 19 # tornado内部是通过yeild来做的self.request.files,所以不支持索引方式,必须用循环方式去取 20 file_metas = self.request.files['file_upload'] # 前台name变量传过来的文件名 21 for meta in file_metas: 22 # 要上传的文件名 23 file_name = meta['filename'] 24 import os 25 with open(os.path.join('statics', 'img', file_name), 'wb') as up: 26 up.write(meta['body']) 27 IMG_LIST.append(file_name) 28 self.write('文件上传完成') 29 30 31 settings = { 32 'template_path': 'views', 33 'static_path': 'statics', 34 'static_url_prefix': '/statics/' 35 } 36 37 application = tornado.web.Application([ 38 (r"/index", IndexHandler), 39 ], **settings) 40 41 if __name__ == "__main__": 42 application.listen(8000) 43 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <body> 8 <ul> 9 {% for item in img_list%} 10 <li><img style="height: 200px; width: 200px;" src="/statics/img/{{item}}"></li> 11 {% end %} 12 </ul> 13 14 <form action="/index" method="POST" enctype="multipart/form-data"> 15 <input type="text" name="user" /> 16 <h3>爱好</h3> 17 <input name="favor" type="checkbox" value="1"/>篮球; 18 <input name="favor" type="checkbox" value="2"/>足球; 19 <input name="favor" type="checkbox" value="3"/>玻璃球; 20 <input type="file" name="file_upload" /> 21 <input type="submit" value="提交"/> 22 </form> 23 24 </body> 25 </html>
2、ajax形式上传(不会刷新页面,依赖于FormData对象)
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <body> 8 <input type="file" id="img" /> 9 <input type="button" onclick="UploadFile();" value="原生ajax-XMLHttpRequest上传文件"/> 10 <script> 11 function UploadFile(){ 12 // 获取文件对象 13 var fileObj = document.getElementById("img").files[0]; 14 // 创建Form对象 15 var form = new FormData(); 16 form.append("user", "uuu"); 17 form.append("favor", "1"); 18 form.append("file_upload", fileObj); 19 20 var xhr = new XMLHttpRequest(); 21 xhr.open("post", '/index', true); 22 xhr.send(form); 23 } 24 </script> 25 </body> 26 </html>
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <body> 8 <input type="file" id="img" /> 9 <input type="button" onclick="UploadFile();" value="jQuery-ajax上传文件" /> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 function UploadFile(){ 13 var fileObj = $("#img")[0].files[0]; 14 var form = new FormData(); 15 form.append("user", "v1"); 16 form.append("favor", "1"); 17 form.append("file_upload", fileObj); 18 19 $.ajax({ 20 type:'POST', 21 url: '/index', 22 data: form, 23 processData: false, // tell jQuery not to process the data 告诉jQuery不要处理我提交的数据,一定要加上 24 contentType: false, // tell jQuery not to set contentType 告诉jQuery不要处理我提交的数据,一定要加上 25 success: function(arg){ 26 console.log(arg); 27 } 28 }) 29 } 30 </script> 31 </body> 32 </html>
注意:
出于对兼容性的考虑,FormData对象并不是所有浏览器都兼容的
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <style> 7 .hide{ 8 display: none; 9 } 10 </style> 11 </head> 12 <body> 13 <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" > 14 <div id="main"> 15 <input name="user" type="text" /> 16 <input name="favor" type="text" /> 17 <input name="file_upload" id="my_file" type="file" /> 18 <input type="button" name="action" value="Upload" onclick="redirect()"/> 19 <iframe id='my_iframe' name='my_iframe' src="" class="hide"></iframe> 20 </div> 21 </form> 22 23 <script src="/statics/jquery-1.12.4.js"></script> 24 <script> 25 function redirect(){ 26 document.getElementById('my_iframe').onload = Testt; // 加载完成后自动执行Testt函数 27 document.getElementById('my_form').target = 'my_iframe'; // target定义form表单提交到iframe 28 document.getElementById('my_form').submit(); // 提交到iframe,利用页面不刷新 29 30 } 31 32 function Testt(ths){ 33 var t = $("#my_iframe").contents().find("body").text(); // 去iframe中找到返回的内容 34 console.log(t); 35 } 36 </script> 37 </body> 38 </html>
iframe补充知识:
1 # iframe结合Form实现ajax提交:利用form的target属性提交到iframe标签,再传到后台 2 <form action="/ajax_json/" method="POST" target="ifm1"> 3 <iframe id="ifm1" name="ifm1" ></iframe> 4 <input type="text" name="username" /> 5 <input type="text" name="email" /> 6 <input type="submit" onclick="sumitForm();" value="Form提交"/> 7 </form> 8 9 function sumitForm(){ 10 $('#ifm1').load(function(){ 11 var text = $('#ifm1').contents().find('body').text(); 12 var obj = JSON.parse(text); 13 }) 14 } 15 20 21 # 基于iframe处理服务器端返回数据: 22 23 iframe标签:自带onload事件 24 25 每当有服务器端数据返回时(在浏览器中发现iframe的body中接收返回的数据),iframe标签会自动执行自带的onload事件 26 27 # 视图函数: 28 def ajax(request): 29 return render(request, 'ajax.html') 30 31 def ajax_json(request): 32 import time 33 time.sleep(3) 34 print(request.POST) 35 ret = {'code': True , 'data': request.POST.get('username')} 36 import json 37 return HttpResponse(json.dumps(ret)) 38 39 def upload(request): 40 return render(request,'upload.html') 41 42 def upload_file(request): 43 username = request.POST.get('username') 44 fafafa = request.FILES.get('fafafa') 45 import os 46 img_path = os.path.join('static/imgs/',fafafa.name) 47 with open(img_path,'wb') as f: 48 for item in fafafa.chunks(): 49 f.write(item) 50 51 ret = {'code': True , 'data': img_path} 52 import json 53 return HttpResponse(json.dumps(ret)) 54 55 # HTML: 56 <form action="/ajax_json/" method="POST" target="ifm1"> 57 <iframe id="ifm1" name="ifm1" ></iframe> 58 <input type="text" name="username" /> 59 <input type="text" name="email" /> 60 <input type="submit" onclick="sumitForm();" value="Form提交"/> 61 </form> 62 63 function sumitForm(){ 64 $('#ifm1').load(function(){ //点击提交时给iframe绑定onload事件 65 var text = $('#ifm1').contents().find('body').text(); 66 var obj = JSON.parse(text); // 拿到后台返回的字符串转换为json对象,进一步处理 67 }) 68 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title></title> 6 <style> 7 .upload{ 8 display: inline-block;padding: 10px; 9 background-color: brown; 10 position: absolute; 11 top: 0; 12 bottom: 0; 13 right: 0; 14 left: 0; 15 z-index: 90; 16 } 17 .file{ 18 width: 100px;height: 50px;opacity: 0; 19 position: absolute; 20 top: 0; 21 bottom: 0; 22 right: 0; 23 left: 0; 24 z-index: 100; 25 } 26 </style> 27 </head> 28 <body> 29 <div style="position: relative;width: 100px;height: 50px;"> 30 <input class="file" type="file" id="fafafa" name="afafaf" /> // 自定义上传按钮,input标签设置在上面并设置为透明 31 <a class="upload">上传</a> 32 </div> 33 <input type="button" value="提交XHR" onclick="xhrSubmit();" /> 34 <input type="button" value="提交jQuery" onclick="jqSubmit();" /> 35 <hr/> 36 37 <form id="form1" action="/upload_file/" method="POST" enctype="multipart/form-data" target="ifm1"> 38 <iframe id="ifm1" name="ifm1" style="display: none;"></iframe> 39 <input type="file" name="fafafa" onchange="changeUpalod();" /> 40 {# <input type="submit" onclick="iframeSubmit();" value="Form提交"/>#} 41 </form> 42 43 <div id="preview"></div> 44 45 46 <script src="/static/jquery-1.12.4.js"></script> 47 <script> 48 function changeUpalod(){ 49 $('#ifm1').load(function(){ 50 var text = $('#ifm1').contents().find('body').text(); 51 var obj = JSON.parse(text); 52 53 $('#preview').empty(); 54 var imgTag = document.createElement('img'); 55 imgTag.src = "/" + obj.data; 56 $('#preview').append(imgTag); 57 }); 58 $('#form1').submit(); // 绑定onchange事件 input内容有变化提交表单 59 } 60 61 function jqSubmit(){ 62 // $('#fafafa')[0] 63 var file_obj = document.getElementById('fafafa').files[0]; 64 65 var fd = new FormData(); 66 fd.append('username','root'); 67 fd.append('fafafa',file_obj); 68 69 $.ajax({ 70 url: '/upload_file/', 71 type: 'POST', 72 data: fd, 73 processData: false, // tell jQuery not to process the data 74 contentType: false, // tell jQuery not to set contentType 75 success:function(arg,a1,a2){ 76 console.log(arg); 77 console.log(a1); 78 console.log(a2); 79 } 80 }) 81 } 82 83 function xhrSubmit(){ 84 // $('#fafafa')[0] 85 var file_obj = document.getElementById('fafafa').files[0]; 86 87 var fd = new FormData(); 88 fd.append('username','root'); 89 fd.append('fafafa',file_obj); 90 91 var xhr = new XMLHttpRequest(); 92 xhr.open('POST', '/upload_file/',true); 93 xhr.onreadystatechange = function(){ 94 if(xhr.readyState == 4){ 95 // 接收完毕 96 var obj = JSON.parse(xhr.responseText); 97 console.log(obj); 98 } 99 }; 100 xhr.send(fd); 101 } 102 103 function iframeSubmit(){ 104 $('#ifm1').load(function(){ 105 var text = $('#ifm1').contents().find('body').text(); 106 var obj = JSON.parse(text); 107 108 $('#preview').empty(); // 清空已存在imgTag标签 109 var imgTag = document.createElement('img'); 110 imgTag.src = "/" + obj.data; // 路径前面加/ 111 $('#preview').append(imgTag); 112 }) 113 } 114 115 </script> 116 </body> 117 </html>
小结:
1 iframe结合Form实现ajax提交:利用form的target属性提交到iframe标签,再传到后台 2 iframe处理服务器端返回数据:利用iframe标签自带onload事件, 3 4 发送ajax请求时方式的选择: 5 如果发送的是【普通数据,key-value】 -> 依次优先选择:Query,XMLHttpRequest,iframe 6 9 文件上传方式选择: 10 如果发送的是【文件】 -> 依次优先选择iframe(不依赖任何东西),jQuery(FormData),XMLHttpRequest(FormData), 15 16
跨域AJAX(和别的网站交互时会用到):
由于浏览器存在同源策略机制,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性。
特别的:由于同源策略是浏览器的限制,所以请求的发送和响应是可以进行,只不过浏览器不接受罢了,没有跨域处理会报错。
浏览器同源策略并不是对所有的请求均制约:
- 制约: XmlHttpRequest
- 不叼: img、iframe、script等具有src属性的标签
跨域,跨域名访问,如:http://www.c1.com 域名向 http://www.c2.com域名发送请求。
进一步,对于ajax请求,如果跨域名请求,默认浏览器不允许(报错,在跨域请求到达服务器后,服务器返回请求信息,返回给请求端,请求端浏览器报错,因为请求的信息并非是本域名的,所以报错)
利用src实现简单跨域,获取到$.cookie值说明成功:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('t1.post') 14 15 16 settings = { 17 'template_path': 'views', 18 'static_path': 'statics', 19 'static_url_prefix': '/statics/' 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", IndexHandler), 24 ], **settings) 25 26 if __name__ == "__main__": 27 application.listen(8001) 28 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <script src="/statics/jquery-1.12.4.js"></script> 10 <script src="http://whisky.com:8002/statics/jquery.cookie.js"></script> 11 <script> 12 function DoAjax() { 13 $.ajax({ 14 url:'http://whisky.com:8002/index', 15 type:'POST', 16 data:{'k1': 'v1'}, 17 success:function (arg) { 18 console.log(arg); 19 } 20 }); 21 } 22 </script> 23 </body> 24 </html>
小结:对于浏览器来说,带有src属性的标签(script块、img、iframe)一般都允许跨域,对于ajax默认都不允许跨域,src中的地址在请求时相当于发送get请求
1、JSONP实现跨域请求(用的最多的方式,兼容性好,任何浏览器都支持)
JSONP(JSONP - JSON with Padding是JSON的一种“使用模式”),利用script标签的src属性(浏览器允许script标签跨域)

1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('t1.post') 14 15 16 settings = { 17 'template_path': 'views', 18 'static_path': 'statics', 19 'static_url_prefix': '/statics/' 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", IndexHandler), 24 ], **settings) 25 26 if __name__ == "__main__": 27 application.listen(8001) 28 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="JsonpAjax" onclick="JsonpAjax();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script src="http://whisky.com:8002/statics/jquery.cookie.js"></script> 12 <script> 13 function func(arg) { 14 console.log(arg) 15 } 16 function DoAjax() { 17 //直接跨域请求,服务器端返回数据被浏览器阻止 18 $.ajax({ 19 url:'http://whisky.com:8002/index', 20 type:'POST', 21 data:{'k1': 'v1'}, 22 success:function (arg) { 23 console.log(arg); 24 } 25 }); 26 } 27 // function JsonpAjax() { 28 // // JSONP方式原生ajax实现跨域请求 29 // // 服务器端instance_2返回t2.get,而非t2.post,说明用的是get方法 30 // var tag = document.createElement('script'); // 构造script标签 31 // tag.src = 'http://whisky.com:8002/index'; // 构造script标签 32 // document.head.appendChild(tag); 33 // document.head.removeChild(tag); 34 // } 35 36 function JsonpAjax() { 37 // jQuery方式实现Jsonp跨域请求 38 $.ajax({ 39 url: "http://whisky.com:8002/index", 40 dataType: 'jsonp', 41 //jsonP: 'callback', // 表示把callback=func发送到服务器端 42 jsonpCallBack: 'func' // 发送成功之后自动执行的函数,相当于告知服务器你要把数据封装到我指定的函数中再返回,在客户端定义好服务器返回的函数即可 43 }) 44 } 45 </script> 46 </body> 47 </html>
注意:jsonp方式跨域,服务器端返回的是相当于一个<script><script>块,即一个js脚本。因此,返回一个函数名,前端可以直接调用(如果前端定义了对应函数名的函数的话)函数,jsonp只能发get请求
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 # self.write('t2.get') 11 self.write('func([11, 22, 33])') 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 settings = { 18 'template_path': 'views', 19 'static_path': 'statics', 20 'static_url_prefix': '/statics/' 21 } 22 23 application = tornado.web.Application([ 24 (r"/index", IndexHandler), 25 ], **settings) 26 27 if __name__ == "__main__": 28 application.listen(8002) 29 tornado.ioloop.IOLoop.instance().start()
1 127.0.0.1 whisky.com 2 127.0.0.1 cainiao.com 3 4 5 访问: 6 whisky.com:8001/index
应用:江西电视台接口 基于 jsonp方式的跨域ajax请求
// 江西电视台的服务端的url接口 http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('t1.post') 14 15 16 settings = { 17 'template_path': 'views', 18 'static_path': 'statics', 19 'static_url_prefix': '/statics/' 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", IndexHandler), 24 ], **settings) 25 26 if __name__ == "__main__": 27 application.listen(8001) 28 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="JsonpAjaxJX" onclick="JsonpAjaxJX();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 13 function list(dict) { 14 console.log(dict) 15 } 16 function JsonpAjaxJX() { 17 // 跨域访问:http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list 18 // var tag = document.createElement('script'); 19 // tag.src = 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list'; // 对这个url默认用get方法去请求 20 // document.head.appendChild(tag); 21 // document.head.removeChild(tag); 22 23 $.ajax({ 24 url:"http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list", 25 dataType: 'jsonp', 26 jsonpCallBack: 'list' 27 }) 28 } 29 30 </script> 31 </body> 32 </html>
上面例子中,服务器端对于客户端的请求中的函数定义过于死板(写死了),jsonp之客户端自定义函数名,服务器端根据前端请求的函数名做对应处理后返回给客户端:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('t1.post') 14 15 16 settings = { 17 'template_path': 'views', 18 'static_path': 'statics', 19 'static_url_prefix': '/statics/' 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", IndexHandler), 24 ], **settings) 25 26 if __name__ == "__main__": 27 application.listen(8001) 28 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="JsonpAjaxJX" onclick="JsonpAjaxJX();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 13 function uo(dict) { 14 console.log(dict) 15 } 16 function JsonpAjaxJX() { 17 // 跨域访问:http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list 18 // var tag = document.createElement('script'); 19 // tag.src = 'http://cainiao.com:8002/index?callback=uo'; // 自定义callback=uo,服务器端get_argument获取uo,处理(比如加个参数)后再返回 20 // document.head.appendChild(tag); 21 // document.head.removeChild(tag); 22 23 $.ajax({ 24 url:"http://cainiao.com:8002/index", 25 dataType: 'jsonp', 26 jsonp:'callback', 27 jsonpCallback: 'uo' // 26、27两行相当于url:"http://cainiao.com:8002/index?callback=uo",服务器端就能拿到了,处理后再返回回来给前端 28 }) 29 } 30 </script> 31 </body> 32 </html> 33 34 //
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 callback = self.get_argument('callback') 11 self.write('%s([11,22,33]);' % callback) 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 settings = { 18 'template_path': 'views', 19 'static_path': 'statics', 20 'static_url_prefix': '/statics/' 21 } 22 23 application = tornado.web.Application([ 24 (r"/index", IndexHandler), 25 ], **settings) 26 27 if __name__ == "__main__": 28 application.listen(8002) 29 tornado.ioloop.IOLoop.instance().start()


1 #jsonp跨域: 2 # 原理:由于浏览器具有同源策略,阻止ajax请求,但无法阻止<script src='...'></script> 3 4 操作 5 - 创建script标签 6 - src='远程URL' 7 - 返回数据必须是js格式(因为是在script标签中),会有报错(如:Refused to execute script from 'https://www.gxyclub.com/statics/front/images/gxy-fx.jpg' because its MIME type ('image/jpeg') is not executable.) 8 注意:jsonp只能发get请求 9 10 11 # 原生jsonp: 12 <input type="button" value="Jsonp" onclick="Jsonp()"> 13 <script> 14 function Jsonp() { 15 var tag = document.createElement('script'); 16 tag.src = 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list'; // ?表示发送get请求,callback是后台定义的回调函数,值为前台中定义的函数 17 {# tag.src = 'http://whisky.com:8000/jsonp.html?callback=list'; //本地测试 #} 18 document.head.appendChild(tag); 19 document.head.removeChild(tag) 20 } 21 function list(arg) { //后台回调函数返回的函数和参数 22 console.log(arg) 23 } 24 </script> 25 26 # 视图函数view: 27 def article(request): 28 return render(request,'article.html',) 29 30 31 def jsonp(request): 32 func = request.GET.get('callback') 33 content = '%s(1000)' % func 34 return HttpResponse(content) 35 36 37 38 39 jQuery-ajax实现jsonp(原理也是在HTMLhead中添加script标签,再删除): 40 <input type="button" value="Jsonp" onclick="AjaxJsonp()"> 41 <script src="/static/jquery-1.12.4.js"></script> 42 <script> 43 function AjaxJsonp() { 44 $.ajax({ 45 url:'http://www.jxntv.cn/data/jmd-jxtv2.html', 46 type:'POST', 47 dataType:'jsonp', 48 jsonp:'callback', 49 jsonpCallback:'list' //jsonp,jsonpCallback合起来相当于?callback=list 50 }) 51 } 52 function list(arg) { //后台回调函数返回的函数和参数 53 console.log(arg) 54 } 55 </script> 56 57 58 59 #注意:a标签,form,img标签,引用cdn的css等也属于跨域(跨不同的域拿过来文件来使用),不是所有的请求都给做跨域, 60 (为什么要进行跨域呢?因为我想用人家的数据,所以得去别人的url中去拿,借助script标签) 61 62 63 CORS: 64 设置响应头,只在服务器端配置即可 65
1 http://www.cnblogs.com/haiyan123/p/8436452.html
2、CORS(最新的浏览器支持,兼容性不好)
随着技术的发展,现在的浏览器可以支持主动设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是客户端不变(复杂请求只需添加对应参数),服务器端设置响应头,使得浏览器允许跨域请求。
--简单请求 OR 非简单请求
条件:
1、请求方式:HEAD、GET、POST
2、请求头信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 对应的值是以下三个中的任意一个
application/x-www-form-urlencoded
multipart/form-data
text/plain
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
简单请求和非简单请求的区别?
简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
关于“预检”
- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
=> 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
Access-Control-Request-Method
=> 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
Access-Control-Request-Headers
a、支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('index.html') 11 12 def post(self, *args, **kwargs): 13 self.write('t1.post') 14 15 16 class CorsHandler(tornado.web.RequestHandler): 17 def get(self): 18 self.render('cors.html') 19 20 21 settings = { 22 'template_path': 'views', 23 'static_path': 'statics', 24 'static_url_prefix': '/statics/' 25 } 26 27 application = tornado.web.Application([ 28 (r"/index", IndexHandler), 29 (r"/cors", CorsHandler), 30 ], **settings) 31 32 if __name__ == "__main__": 33 application.listen(8001) 34 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <script src="/statics/jquery-1.12.4.js"></script> 10 <script> 11 12 function DoAjax() { 13 $.ajax({ 14 url:'http://whisky.com:8002/cors', 15 type:'POST', 16 data:{'k1': 'v1'}, 17 success:function (arg) { 18 console.log(arg); 19 } 20 }); 21 } 22 23 </script> 24 </body> 25 </html> 26 27 <!-- 没有跨域时,访问:http://whisky.com:8001/cors点击Ajax报错:Failed to load http://whisky.com:8002/cors: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://whisky.com:8001' is therefore not allowed access. 28 说明服务器端返回数据时没有携带Access-Control-Allow-Origin这个响应头-->
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 callback = self.get_argument('callback') 11 self.write('%s([11,22,33]);' % callback) 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 class CorsHandler(tornado.web.RequestHandler): 18 def get(self): 19 self.write('{"status":1, "message": "get"}') 20 21 def post(self, *args, **kwargs): 22 # set_header设置响应头和它的值(客户端的值),表示允许这个域名来我这发跨域的ajax请求,携带我这里服务器端的响应头回去,浏览器不再阻止 23 # 多个域名后面逐一加上逗号分隔,让所有客户端都能访问域名的值设置为“*”:self.set_header('Access-Control-Allow-Origin', '*') 24 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') 25 self.write('{"status":1, "message": "post"}') 26 27 28 settings = { 29 'template_path': 'views', 30 'static_path': 'statics', 31 'static_url_prefix': '/statics/' 32 } 33 34 application = tornado.web.Application([ 35 (r"/index", IndexHandler), 36 (r"/cors", CorsHandler), 37 ], **settings) 38 39 if __name__ == "__main__": 40 application.listen(8002) 41 tornado.ioloop.IOLoop.instance().start()
b、支持跨域,复杂请求,需要进行“预检”
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
- “预检”缓存时间,服务器设置响应头:Access-Control-Max-Age
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="DoAjaxComplex" onclick="DoAjaxComplex();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 13 function DoAjax() { 14 $.ajax({ 15 url:'http://whisky.com:8002/cors', 16 type:'POST', 17 data:{'k1': 'v1'}, 18 success:function (arg) { 19 console.log(arg); 20 } 21 }); 22 } 23 24 function DoAjaxComplex() { 25 $.ajax({ 26 url:'http://whisky.com:8002/cors', 27 type:'PUT', 28 headers: {"h1": "xxoo"}, // 设置headers 29 data:{'k1': 'v1'}, 30 success:function (arg) { 31 console.log(arg); 32 } 33 }); 34 } 35 </script> 36 </body> 37 </html>
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 callback = self.get_argument('callback') 11 self.write('%s([11,22,33]);' % callback) 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 class CorsHandler(tornado.web.RequestHandler): 18 def get(self): 19 self.write('{"status":1, "message": "get"}') 20 21 def post(self, *args, **kwargs): 22 # set_header设置响应头和它的值(客户端的值),表示允许这个域名来我这发跨域的ajax请求,携带我这里服务器端的响应头回去,浏览器不再阻止 23 # 多个域名后面逐一加上逗号分隔,让所有客户端都能访问域名的值设置为“*”:self.set_header('Access-Control-Allow-Origin', '*') 24 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') 25 self.write('{"status":1, "message": "post"}') 26 27 def options(self, *args, **kwargs): 28 # 对于复杂请求,预检的时候默认先执行这个potions方法 29 print('options') 30 self.set_header('Access-Control-Allow-Origin', '*') # 允许客户端网站来请求 31 self.set_header('Access-Control-Allow-Methods', "PUT, DELETE") # 允许PUT方法 32 self.set_header('Access-Control-Allow-Headers', "h1,h2") # 允许请求头的key 33 # self.set_header('Access-Control-Max-Age', 10) # 设置超时时间,10s内只需通过一次预检,10s后再次预检 34 35 def put(self, *args, **kwargs): 36 self.set_header('Access-Control-Allow-Origin', '*') 37 self.write('ok') 38 39 40 settings = { 41 'template_path': 'views', 42 'static_path': 'statics', 43 'static_url_prefix': '/statics/' 44 } 45 46 application = tornado.web.Application([ 47 (r"/index", IndexHandler), 48 (r"/cors", CorsHandler), 49 ], **settings) 50 51 if __name__ == "__main__": 52 application.listen(8002) 53 tornado.ioloop.IOLoop.instance().start()
客户端app.py同a中,注意:当需要设置请求头时,客户端要设置请求头参数如:headers: {"h1": "xxoo"}
c、跨域获取响应头 (获取浏览器端设置的头)
默认客户端获取到的所有响应头只有基本信息,拿不到自定义响应头,如果想要获取自定义的响应头,则需要再服务器端设置Access-Control-Expose-Headers。
1 class MainHandler(tornado.web.RequestHandler): 2 3 def put(self): 4 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 5 6 self.set_header('xxoo', "seven") 7 self.set_header('bili', "daobidao") 8 9 self.set_header('Access-Control-Expose-Headers', "xxoo,bili") 10 11 12 self.write('{"status": true, "data": "seven"}') 13 14 def options(self, *args, **kwargs): 15 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 16 self.set_header('Access-Control-Allow-Headers', "k1,k2") 17 self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") 18 self.set_header('Access-Control-Max-Age', 10) 19 20 Tornado
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 callback = self.get_argument('callback') 11 self.write('%s([11,22,33]);' % callback) 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 class CorsHandler(tornado.web.RequestHandler): 18 def get(self): 19 self.write('{"status":1, "message": "get"}') 20 21 def post(self, *args, **kwargs): 22 # set_header设置响应头和它的值(客户端的值),表示允许这个域名来我这发跨域的ajax请求,携带我这里服务器端的响应头回去,浏览器不再阻止 23 # 多个域名后面逐一加上逗号分隔,让所有客户端都能访问域名的值设置为“*”:self.set_header('Access-Control-Allow-Origin', '*') 24 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') 25 self.write('{"status":1, "message": "post"}') 26 27 def options(self, *args, **kwargs): 28 # 对于复杂请求,预检的时候默认先执行这个potions方法 29 # print('options') 30 # self.set_header('Access-Control-Allow-Origin', '*') # 允许客户端网站来请求 31 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') # 在允许cookie时,域名值不能为* 32 self.set_header('Access-Control-Allow-Methods', "PUT, DELETE") # 允许PUT方法 33 self.set_header('Access-Control-Allow-Headers', "h1,h2") # 允许携带请求头的key 34 self.set_header('Access-Control-Allow-Credentials', "true") # 设置响应头允许发cookie,否则console会报错 35 36 def put(self, *args, **kwargs): 37 print(self.cookies) 38 self.set_cookie('k1', '111111111') 39 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') # cookie预检通过后,此处也要加入响应头Access-Control-Allow-Origin 40 self.set_header('Access-Control-Allow-Credentials', "true") # 在响应头中,设置允许携带cookie 41 self.set_header('xxoo', "seven") # 自定义响应头 42 self.set_header('bili', "daobidao") # 自定义响应头 43 self.set_header('Access-Control-Expose-Headers', "xxoo,bili") # 获取自定义的响应头服务器端需要设置Access-Control-Expose-Headers 44 self.write('ok') 45 46 47 settings = { 48 'template_path': 'views', 49 'static_path': 'statics', 50 'static_url_prefix': '/statics/' 51 } 52 53 application = tornado.web.Application([ 54 (r"/index", IndexHandler), 55 (r"/cors", CorsHandler), 56 ], **settings) 57 58 if __name__ == "__main__": 59 application.listen(8002) 60 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="DoAjaxComplex" onclick="DoAjaxComplex();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 13 function DoAjax() { 14 $.ajax({ 15 url:'http://whisky.com:8002/cors', 16 type:'POST', 17 data:{'k1': 'v1'}, 18 success:function (arg) { 19 console.log(arg); 20 } 21 }); 22 } 23 24 function DoAjaxComplex() { 25 $.ajax({ 26 url:'http://whisky.com:8002/cors', 27 type:'PUT', 28 headers: {"h1": "xxoo"}, // 设置headers请求头 29 data:{'k1': 'v1'}, 30 xhrFields:{withCredentials: true}, //客户端设置发送请求时携带cookie 31 success:function (arg) { 32 console.log(arg); 33 } 34 }); 35 } 36 </script> 37 </body> 38 </html> 39 40 <!-- 没有跨域时,访问:http://whisky.com:8001/cors点击Ajax报错:Failed to load http://whisky.com:8002/cors: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://whisky.com:8001' is therefore not allowed access. 41 说明服务器端返回数据时没有携带Access-Control-Allow-Origin这个响应头-->
d.跨域传输cookie,复杂请求,需要进行“预检”
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
- 浏览器端:XMLHttpRequest的withCredentials为true
- 服务器端:Access-Control-Allow-Credentials为true
- 注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *
客户端添加参数:xhrFields:{withCredentials: true}, 表示客户端再发请求时会携带cookie
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="button" value="Ajax" onclick="DoAjax();"/> 9 <input type="button" value="DoAjaxComplex" onclick="DoAjaxComplex();"/> 10 <script src="/statics/jquery-1.12.4.js"></script> 11 <script> 12 13 function DoAjax() { 14 $.ajax({ 15 url:'http://whisky.com:8002/cors', 16 type:'POST', 17 data:{'k1': 'v1'}, 18 success:function (arg) { 19 console.log(arg); 20 } 21 }); 22 } 23 24 function DoAjaxComplex() { 25 $.ajax({ 26 url:'http://whisky.com:8002/cors', 27 type:'PUT', 28 headers: {"h1": "xxoo"}, // 设置headers 29 data:{'k1': 'v1'}, 30 xhrFields:{withCredentials: true}, //客户端设置发送请求时携带cookie 31 success:function (arg) { 32 console.log(arg); 33 } 34 }); 35 } 36 </script> 37 </body> 38 </html> 39 40 <!-- 没有跨域时,访问:http://whisky.com:8001/cors点击Ajax报错:Failed to load http://whisky.com:8002/cors: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://whisky.com:8001' is therefore not allowed access. 41 说明服务器端返回数据时没有携带Access-Control-Allow-Origin这个响应头-->
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class IndexHandler(tornado.web.RequestHandler): 9 def get(self): 10 callback = self.get_argument('callback') 11 self.write('%s([11,22,33]);' % callback) 12 13 def post(self, *args, **kwargs): 14 self.write('t2.post') 15 16 17 class CorsHandler(tornado.web.RequestHandler): 18 def get(self): 19 self.write('{"status":1, "message": "get"}') 20 21 def post(self, *args, **kwargs): 22 # set_header设置响应头和它的值(客户端的值),表示允许这个域名来我这发跨域的ajax请求,携带我这里服务器端的响应头回去,浏览器不再阻止 23 # 多个域名后面逐一加上逗号分隔,让所有客户端都能访问域名的值设置为“*”:self.set_header('Access-Control-Allow-Origin', '*') 24 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') 25 self.write('{"status":1, "message": "post"}') 26 27 def options(self, *args, **kwargs): 28 # 对于复杂请求,预检的时候默认先执行这个potions方法 29 # print('options') 30 # self.set_header('Access-Control-Allow-Origin', '*') # 允许客户端网站来请求 31 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') # 在允许cookie时,域名值不能为* 32 self.set_header('Access-Control-Allow-Methods', "PUT, DELETE") # 允许PUT方法 33 self.set_header('Access-Control-Allow-Headers', "h1,h2") # 允许携带请求头的key 34 self.set_header('Access-Control-Allow-Credentials', "true") # 设置响应头允许发cookie,否则console会报错 35 36 def put(self, *args, **kwargs): 37 print(self.cookies) 38 self.set_cookie('k1', '111111111') 39 self.set_header('Access-Control-Allow-Origin', 'http://whisky.com:8001') # cookie预检通过后,此处也要加入响应头Access-Control-Allow-Origin 40 self.set_header('Access-Control-Allow-Credentials', "true") # 在响应头中,设置允许携带cookie 41 self.write('ok') 42 43 44 settings = { 45 'template_path': 'views', 46 'static_path': 'statics', 47 'static_url_prefix': '/statics/' 48 } 49 50 application = tornado.web.Application([ 51 (r"/index", IndexHandler), 52 (r"/cors", CorsHandler), 53 ], **settings) 54 55 if __name__ == "__main__": 56 application.listen(8002) 57 tornado.ioloop.IOLoop.instance().start()
客户端app.py同a中
分页:

1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 from controllers import home 7 8 # render中的文件不要加/等路径 9 10 settings = { 11 "template_path": "views", # 模板路径配置(存放HTML) 12 "static_path": 'statics', # 注册引入jQuery的静态目录 13 } 14 15 # 路由映射,路由系统,正则匹配输入的url(有分组时,分组的组名传给类),交给对应的类处理 16 application = tornado.web.Application([ 17 (r"/index/(?P<page>\d*)", home.IndexHandler), 18 ], **settings) 19 20 21 if __name__ == "__main__": 22 application.listen(8000) 23 tornado.ioloop.IOLoop.instance().start()
1 #!/usr/bin/env python 2 # -*-coding utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 LIST_INFO = [ 8 {'username': 'alex', 'email': 'whisky@163.com'}, 9 ] 10 for i in range(200): 11 temp_dic = {'username': 'alex'+str(i), 'email': str(i) + '123@qq.com'} 12 LIST_INFO.append(temp_dic) 13 14 15 16 17 class IndexHandler(tornado.web.RequestHandler): 18 # 路由系统来调用这个类 19 def get(self, page): 20 try: 21 page = int(page) 22 except: 23 page = 1 24 if page < 1: 25 page = 1 26 27 # 每页取5条数据 28 start = (page - 1) * 5 29 end = page * 5 30 31 # 定义总页数 32 all_pager, surplus = divmod(len(LIST_INFO), 5) # 每页显示5条数据 33 if surplus > 0: # all_pager为页数,如果存在余数并大于0,则说明有多余的内容,总页数加一来显示,余数为0,则页数为all_pager 34 all_pager += 1 # 总页数加一来显示余下内容 35 36 37 current_list = LIST_INFO[start:end] 38 # 调整分页开始 39 start_page = page - 5 40 if start_page < 0: 41 start_page = 0 42 end_page = page + 5 43 if end_page > all_pager: 44 end_page = all_pager 45 46 if page < 5: 47 end_page = 10 48 if page + 5 > all_pager: 49 start_page = all_pager - 10 50 # 调整分页结束 51 52 list_page = [] 53 for p in range(start_page, end_page): # 生成HTML页码 54 if p + 1 == page: # 当p等于用户输入的页码:page时,表示处在当前页 55 temp = '<a class="active" href="/index/%s">%s</a>' % (p + 1, p + 1) 56 else: 57 temp = '<a href="/index/%s">%s</a>' % (p+1, p+1) 58 list_page.append(temp) 59 str_page = "".join(list_page) 60 61 self.render('home/index.html', list_info=current_list, current_page=page, str_page=str_page) 62 63 def post(self, page): 64 # 这个page是从前端html中传过来的 65 user = self.get_argument('username') 66 email = self.get_argument('email') 67 temp = {'username': user, 'email': email} 68 LIST_INFO.append(temp) 69 self.redirect('/index/' + str(page))
1 #!/usr/bin/env python 2 # -*-coding utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 LIST_INFO = [ 8 {'username': 'alex', 'email': 'whisky@163.com'}, 9 ] 10 for i in range(200): 11 temp_dic = {'username': 'alex'+str(i), 'email': str(i) + '123@qq.com'} 12 LIST_INFO.append(temp_dic) 13 14 15 class IndexHandler(tornado.web.RequestHandler): 16 17 def get(self, page): 18 # print(nid) 19 # self.write('ok') 20 try: 21 page = int(page) 22 except: 23 page = 1 24 if page < 1: 25 page = 1 26 # if page > len(LIST_INFO): 27 # page = 28 start = (page - 1) * 5 29 end = page * 5 30 current_list = LIST_INFO[start:end] 31 all_pager, surplus = divmod(len(LIST_INFO), 5) 32 if surplus > 0: # all_pager为页数,如果存在余数并大于0,则说明有多余的内容,总页数加一来显示,余数为0,则页数为all_pager 33 all_pager += 1 # 总页数加一来显示余下内容 34 list_page = [] 35 # 调整分页开始 36 start_page = page - 5 37 if start_page < 0: 38 start_page = 0 39 end_page = page + 5 40 if end_page > all_pager: 41 end_page = all_pager 42 43 if page < 5: 44 end_page = 10 45 if page + 5 > all_pager: 46 start_page = all_pager - 10 47 # 调整分页结束 48 for p in range(start_page, end_page): # 生成HTML页码 49 if p + 1 == page: # 当p等于用户输入的页码:page时,表示处在当前页 50 temp = '<a class="active" href="/index/%s">%s</a>' % (p + 1, p + 1) 51 else: 52 temp = '<a href="/index/%s">%s</a>' % (p+1, p+1) 53 list_page.append(temp) 54 str_page = "".join(list_page) 55 56 self.render('home/index.html', list_info=current_list, current_page=page, str_page=str_page) 57 58 def post(self, page): 59 # 这个page是从前端html中传过来的 60 user = self.get_argument('username') 61 email = self.get_argument('email') 62 temp = {'username': user, 'email': email} 63 LIST_INFO.append(temp) 64 self.redirect('/index/' + str(page))
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .page{ 8 padding: 10px; 9 } 10 .page a{ 11 margin: 3px; 12 padding: 5px; 13 background-color: cornflowerblue; 14 } 15 .page a.active{ 16 background-color: pink; 17 font-size: 20px; 18 } 19 </style> 20 </head> 21 <body> 22 <h1>提交数据</h1> 23 <form method="post" action="/index/{{current_page}}"> 24 <input name="username" type="text" /> 25 <input name="email" type="text" /> 26 <input type="submit" value="提交" /> 27 </form> 28 <h1>显示数据</h1> 29 <table border="1"> 30 <thead> 31 <tr> 32 <th>用户名</th> 33 <th>邮箱</th> 34 </tr> 35 </thead> 36 <tbody> 37 {% for line in list_info%} 38 <tr> 39 <td>{% raw line['username'] %}</td> 40 <td>{{line['email']}}</td> 41 </tr> 42 {% end %} 43 </tbody> 44 </table> 45 <div class="page"> 46 {% raw str_page %} 47 </div> 48 </body> 49 </html>
1 #!/usr/bin/env python 2 # -*-coding utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class LoginHandler(tornado.web.RequestHandler): 9 10 def get(self, *args, **kwargs): 11 self.write('ok') 12 13 14 class LogoutHandler(tornado.web.RequestHandler): 15 16 def get(self, *args, **kwargs): 17 self.write('ok') 18 19 20 class RegisterHandler(tornado.web.RequestHandler): 21 22 def get(self, *args, **kwargs): 23 self.write('ok')
分页代码整理为一个类和若干方法,以便于调用:
1 #!/usr/bin/env python 2 # -*-coding utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 LIST_INFO = [ 8 {'username': 'alex', 'email': 'whisky@163.com'}, 9 ] 10 for i in range(200): 11 temp_dic = {'username': 'alex'+str(i), 'email': str(i) + '123@qq.com'} 12 LIST_INFO.append(temp_dic) 13 14 15 class Pagination: 16 def __init__(self, current_page, all_item, n): 17 """ 18 :param current_page: 当前页 19 :param total_page: 总页数 20 :param n: 每页显示n条数据 21 """ 22 # 定义总页数 23 self.n = n 24 all_pager, surplus = divmod(all_item, self.n) # 每页显示5条数据 25 if surplus > 0: # all_pager为页数,如果存在余数并大于0,则说明有多余的内容,总页数加一来显示,余数为0,则页数为all_pager 26 all_pager += 1 # 总页数加一来显示余下内容 27 self.total_page = all_pager 28 try: 29 current_page = int(current_page) 30 except: 31 current_page = 1 32 if current_page < 1: 33 current_page = 1 34 35 self.current_page = current_page 36 37 @property 38 def start(self): 39 # 根据当前页取5条数据,确定每页显示的信息数目 40 return (self.current_page - 1) * self.n 41 42 @property 43 def end(self): 44 # 根据当前页取5条数据,确定每页显示的信息数目 45 return self.current_page * self.n 46 47 def page_str(self, base_url): 48 """ 49 用于生成页码,返回给调用者 50 :param base_url: 自定义路径 格式: /path/ 51 :return: 52 """ 53 # 调整分页开始, 固定显示十个页码号 54 start_page = self.current_page - 5 55 if start_page < 0: 56 start_page = 0 57 end_page = self.current_page + 5 58 if end_page > self.total_page: 59 end_page = self.total_page 60 61 if self.current_page < 5: 62 end_page = 10 63 if self.current_page + 5 > self.total_page: 64 start_page = self.total_page - 10 65 # 调整分页结束 66 67 list_page = [] 68 69 # 首页 70 firt_page = '<a href="%s%s">首页</a>' % (base_url, 1) 71 list_page.append(firt_page) 72 # 上一页 73 if self.current_page == 1: 74 pre_page = '<a href="javascript:;">上一页</a>' 75 else: 76 pre_page = '<a href="%s%s">上一页</a>' % (base_url, self.current_page - 1) 77 list_page.append(pre_page) 78 79 for p in range(start_page, end_page): # 生成HTML页码 80 # first_page = '<a href="%s">首页</a>' % base_url, 81 # last_page = '<a href="%s">首页</a>' % base_url 82 if p + 1 == self.current_page: # 当p等于用户输入的页码:page时,表示处在当前页 83 temp = '<a class="active" href="%s%s">%s</a>' % (base_url, p + 1, p + 1) 84 else: 85 temp = '<a href="%s%s">%s</a>' % (base_url, p+1, p+1) 86 list_page.append(temp) 87 88 # 下一页 89 if self.current_page == self.total_page: 90 next_page = '<a href="javascript:;">下一页</a>' 91 else: 92 next_page = '<a href="%s%s">下一页</a>' % (base_url, self.current_page + 1) 93 list_page.append(next_page) 94 # 尾页 95 last_page = '<a href="%s%s">尾页</a>' % (base_url, self.total_page) 96 list_page.append(last_page) 97 98 # 跳转页码: 99 jump_page = """<input type="text" /><a onclick="Jump('%s', this);" >Go</a>""" % (base_url, ) 100 script = """<script> 101 var reg = /^[0-9]+$/ 102 function Jump(baseUrl, ths) { 103 var val = ths.previousElementSibling.value; 104 if(val.trim().length>0 && reg.test(val)){ 105 location.href = baseUrl + val; 106 }else{ 107 alert('请输入数字') 108 } 109 } 110 </script>""" 111 112 list_page.append(jump_page) 113 list_page.append(script) 114 115 str_page = "".join(list_page) 116 return str_page 117 118 119 class IndexHandler(tornado.web.RequestHandler): 120 # 路由系统来调用这个类 121 def get(self, page): 122 page_obj = Pagination(page, len(LIST_INFO), 5) # 传入当前输入页和总页数,每页显示信息数 123 current_list = LIST_INFO[page_obj.start:page_obj.end] # page_obj 返回,每页应该显示的信息数目 124 str_page = page_obj.page_str('/index/') 125 # 组装好分页数据render由方法传给前台 126 self.render('home/index.html', list_info=current_list, current_page=page_obj.current_page, str_page=str_page) 127 128 def post(self, page): 129 # 这个page是从前端html中传过来的 130 user = self.get_argument('username') 131 email = self.get_argument('email') 132 temp = {'username': user, 'email': email} 133 LIST_INFO.append(temp) 134 self.redirect('/index/' + str(page))
tornado路由系统之二级域名
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 import buy 7 import home 8 9 10 settings = { 11 "template_path": "views", # 模板路径配置(存放HTML) 12 # 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh' # 加密cookie的盐 13 } 14 15 # 路由映射,路由系统 16 application = tornado.web.Application([ # 访问whisky.com:8000/index 走这里 17 (r"/index", home.IndexHandler), 18 19 ], **settings) 20 21 application.add_handlers('buy.whisky.com$', [ # 访问buy.whisky.com:8000/index 走这里 22 (r"/index", buy.IndexHandler), 23 ]) 24 25 if __name__ == "__main__": 26 application.listen(8000) 27 tornado.ioloop.IOLoop.instance().start()
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.web 5 6 7 class IndexHandler(tornado.web.RequestHandler): 8 def get(self, *args, **kwargs): 9 self.render('login.html')
1 #!/usr/bin/env python 2 # -*-coding:utf-8 -*- 3 4 import tornado.web 5 6 7 class IndexHandler(tornado.web.RequestHandler): 8 def get(self, *args, **kwargs): 9 self.render('index.html')
继承extend 与 引用include
说明:
1 extend 继承
master是公共母版,其他是继承者
母版格式: 需要子板继承的格式都写在母版,各自子板的格式自己写
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>母版</title> 6 <style> 7 .pg-header{ 8 height: 48px; 9 background-color: black; 10 } 11 .pg-footer{ 12 height: 100px; 13 background-color: #dddddd; 14 } 15 </style> 16 <!--上面是公用样式--> 17 <!--下面是某页面自定义样式--> 18 {% block css %}{% end %} 19 </head> 20 <body> 21 <div class="pg-header"> 22 </div> 23 <div class="pg-content"> 24 {% block body %}{% end %} 25 </div> 26 <div class="pg-footer">asdfasd</div> 27 <script src="xxxxj"></script> 28 <!--上面是公用JS--> 29 <!--下面是某页面自定义JS--> 30 {% block js %}{% end %} 31 </body> 32 </html>
子板格式 : extend/index.html extend/fuck.html
1 {% extends '../master/layout.html'%} // 继承模板 2 3 {% block body %} // 定义自己的block块 4 <h1>Fuck</h1> 5 <h1>end</h1> 6 {% end %} 7 8 {% block js %} 9 <script> 10 console.log('fuck'); 11 </script> 12 {% end %}
1 {% extends '../master/layout.html'%} 2 3 {% block css %} 4 <style> 5 div{ 6 border: 1px solid red; 7 } 8 </style> 9 {% end %} 10 11 {% block body %} 12 <h1>Index</h1> 13 {% end %} 14 15 16 {% block js %} 17 <script> 18 console.log('sd'); 19 </script> 20 {% end %}
2 引用 include
1 引用相对位置的页面即可 2 {% include "../include/form.html" %}
同时还可以把很多其他组件放到form.html中,直接调用即可
1 {% extends "../master/layout.html" %} 2 3 {% block body %} 4 <h1>继承2index</h1> 5 6 {% include "../include/form.html" %} 7 {% end %} 8 9 10 {% block js %} 11 <script> 12 13 console.log("sd") 14 </script> 15 16 {% end %}
form.html文件:
1 <br>include/form.html<br><br><form> 2 <input type="text"> 3 <input type="submit"> 4 </form> 5 6 <ul> 7 {% for i in list_info %} 在tornado后台get的时候传过来的一个列表 8 <li>{{ i }}</li> 9 {% end %} 10 11 </ul>
同样在这些文件中可以使用三种模板语言
1 master: 2 3 -- ------ -- -- - // 公用样式 4 -- ------ -- -- - // 公用样式 5 -- ------ -- -- - // 公用样式 6 -- ------ -- -- - // 公用样式 7 {% block css %}{% end %} //子板替换样式 block后面的名字可自定义 8 9 -- ------ -- -- - // 公用body 10 -- ------ -- -- --- ------ -- -- - // 公用body 11 -- ------ -- -- --- ------ -- -- - // 公用body 12 -- ------ -- -- - // 公用body 13 14 {% block body %}{% end %} //子板替换body 15 16 -- ------ -- -- --- ------ -- -- - 17 -- ------ -- -- --- ------ -- -- - 18 -- ------ -- -- - 19 20 {% block js %}{% end %} //子板替换JS 21 22 -- ------ -- -- --- ------ -- -- - 23 -- ------ -- -- --- ------ -- -- - 24 -- ------ -- -- - 25 26 27 28 子板:fuck.html 29 {% extends '../master/layout.html'%} // 开头声明继承模板 30 31 {% block body %} // 定义自己的block块 32 <h1>Fuck</h1> 33 <h1>end</h1> 34 {% end %} 35 36 {% block js %} 37 <script> 38 console.log('fuck'); 39 </script> 40 {% end %} 41 42 43 44 45 子版:index.html 46 {% extends '../master/layout.html'%} // 开头声明继承模板 47 48 {% block css %} 49 <style> 50 div{ 51 border: 1px solid red; 52 } 53 </style> 54 {% end %} 55 56 {% block body %} 57 <h1>Index</h1> 58 {% end %} 59 60 61 {% block js %} 62 <script> 63 console.log('sd'); 64 </script> 65 {% end %} 66 67 注意:一个HTMl页面只能继承一个模板 68 69 总结: 70 71 继承:extends 72 页面整体布局时,将不需要动的地方放到母板里面,单独的某个页想要改变,直接继承母板,想要替换母版的位置用block代码替换即可,同时在母版中对应位置写上对应的block代码 73 74 导入:include 75 相同的功能不用再写很多次,需要用时直接include导入对应HTML文件即可, 导入多次,出现多个组件
自定义Form表单验证:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 import re 7 # form验证:要求前端name字段和后端类中封装的字段名字一致 8 # checkbox验证 9 # 文件名验证匹配不支持中文汉字 10 11 12 class IPFiled: 13 REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" # 静态 字段 14 15 def __init__(self, error_dict=None, required=True): 16 """ 17 :param error_dict: 自定义显示用户输入错误 18 :param required: 默认表示必须要填值 19 """ 20 # 封装了错误信息 ,如果没有传入一个包含错误信息的字典,则使用默认,如果有则覆盖默认 21 self.error_dict = {} 22 if error_dict: # 81 行实例化 传入的错误信息,如果不传 则为None,则不会执行 23 self.error_dict.update(error_dict) # 将实例化时候 传入的错误信息封装 24 25 self.required = required # # 81 行实例化 传入的是否为空字段,默认是浏览器输入不许为空 26 27 self.error = None # 错误信息 28 self.value = None # 匹配的值是什么 29 self.is_valid = False # 是否匹配 30 31 def validate(self, name, input_value): 32 """ 33 :param name: 字段名 34 :param input_value: 用户表单中输入的内容 35 :return: 36 """ 37 if not self.required: # 当比如81行我们传入可以为空的时候即False时候走这一段代码 # 翻译:如果 没有 内容: 即如果输入空(not这里不能翻译为不,要翻译为没有) 38 # 用户输入允许为空 39 self.is_valid = True # 设置封装值为真,表示验证通过 40 self.value = input_value # 将空值封装 41 else: # 当81行要求输入不为空时候 翻译:否则,如果 有 内容,即不为空时 42 # 用户有输入要求不允许为空,这一要验证正则了。 43 if not input_value.strip(): # 输入为空,返回错误信息。 错误信息字典 44 if self.error_dict.get('required', None): # 81行定义的错误信息。如果未定义则自己定义 45 self.error = self.error_dict['required'] 46 else: 47 self.error = "%s is required" % name # 默认错误信息 48 else: # 用户输入不为空, 49 ret = re.match(IPFiled.REGULAR, input_value) # 获取 静态字段的正则字符串,跟用户的input输入内容匹配 50 if ret: # 匹配通过, 51 self.is_valid = True 52 self.value = input_value # self.value = ret.group()也是一样的 53 else: # 匹配不通过,返回错误字典 54 if self.error_dict.get('valid', None): #81 行定义的错误信息,如果未定义则自己定义 55 self.error = self.error_dict['valid'] 56 else: 57 self.error = "%s is invalid" % name 58 59 60 class StringFiled: 61 REGULAR = "(.*)" # 静态 字段 62 63 def __init__(self, error_dict=None, required=True): 64 """ 65 :param error_dict: 自定义显示用户输入错误 66 :param required: 默认表示必须要填值 67 """ 68 # 封装了错误信息 ,如果没有传入一个包含错误信息的字典,则使用默认,如果有则覆盖默认 69 self.error_dict = {} 70 if error_dict: # 81 行实例化 传入的错误信息,如果不传 则为None,则不会执行 71 self.error_dict.update(error_dict) # 将实例化时候 传入的错误信息封装 72 73 self.required = required # # 81 行实例化 传入的是否为空字段,默认是浏览器输入不许为空 74 75 self.error = None # 错误信息 76 self.value = None # 匹配的值是什么 77 self.is_valid = False # 是否匹配 78 79 def validate(self, name, input_value): 80 """ 81 :param name: 字段名 82 :param input_value: 用户表单中输入的内容 83 :return: 84 """ 85 if not self.required: # 当比如81行我们传入可以为空的时候即False时候走这一段代码 # 翻译:如果 没有 内容: 即如果输入空(not这里不能翻译为不,要翻译为没有) 86 # 用户输入允许为空 87 self.is_valid = True # 设置封装值为真,表示验证通过 88 self.value = input_value # 将空值封装 89 else: # 当81行要求输入不为空时候 翻译:否则,如果 有 内容,即不为空时 90 # 用户有输入要求不允许为空,这一要验证正则了。 91 if not input_value.strip(): # 输入为空,返回错误信息。 错误信息字典 92 if self.error_dict.get('required',None): # 81行定义的错误信息。如果未定义则自己定义 93 self.error = self.error_dict['required'] 94 else: 95 self.error = "%s is required" % name # 默认错误信息 96 else: # 用户输入不为空, 97 ret = re.match(IPFiled.REGULAR, input_value) #获取 静态字段的正则字符串,跟用户的input输入内容匹配 98 if ret: # 匹配通过, 99 self.is_valid = True 100 self.value = input_value # self.value = ret.group()也是一样的 101 else: # 匹配不通过,返回错误字典 102 if self.error_dict.get('valid', None): #81 行定义的错误信息,如果未定义则自己定义 103 self.error = self.error_dict['valid'] 104 else: 105 self.error = "%s is invalid" % name 106 107 108 class CheckBoxFiled: 109 """ 110 不做正则匹配,只需看列表中是否有值 111 """ 112 def __init__(self, error_dict=None, required=True): 113 """ 114 :param error_dict: 自定义显示用户输入错误 115 :param required: 默认表示必须要填值 116 """ 117 # 封装了错误信息 ,如果没有传入一个包含错误信息的字典,则使用默认,如果有则覆盖默认 118 self.error_dict = {} 119 if error_dict: # 81 行实例化 传入的错误信息,如果不传 则为None,则不会执行 120 self.error_dict.update(error_dict) # 将实例化时候 传入的错误信息封装 121 122 self.required = required # # 81 行实例化 传入的是否为空字段,默认是浏览器输入不许为空 123 124 self.error = None # 错误信息 125 self.value = None # 匹配的值是什么 126 self.is_valid = False # 是否匹配 127 128 def validate(self, name, input_value): 129 """ 130 :param name: 字段名:favor 131 :param input_value: 用户选中的值 如[1, 2],或者为None 132 :return: 133 """ 134 if not self.required: # 当比如81行我们传入可以为空的时候即False时候走这一段代码 # 翻译:如果 没有 内容: 即如果输入空(not这里不能翻译为不,要翻译为没有) 135 # 用户输入允许为空 136 self.is_valid = True # 设置封装值为真,表示验证通过 137 self.value = input_value 138 else: # 不允许为空 翻译:否则,如果 有 内容,即不为空时 139 if not input_value: # 输入为空,返回错误信息。 错误信息字典 140 if self.error_dict.get('required', None): # 81行定义的错误信息。如果未定义则自己定义 141 self.error = self.error_dict['required'] 142 else: 143 self.error = "%s is required" % name # 默认错误信息 144 else: # 用户输入不为空 145 self.is_valid = True 146 self.value = input_value 147 148 149 class FileFiled: 150 REGULAR = "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)|(\w+\.txt)$" # 静态 字段 151 152 def __init__(self, error_dict=None, required=True): 153 """ 154 :param error_dict: 自定义显示用户输入错误 155 :param required: 默认表示必须要填值 156 """ 157 # 封装了错误信息 ,如果没有传入一个包含错误信息的字典,则使用默认,如果有则覆盖默认 158 self.error_dict = {} 159 if error_dict: # 81 行实例化 传入的错误信息,如果不传 则为None,则不会执行 160 self.error_dict.update(error_dict) # 将实例化时候 传入的错误信息封装 161 162 self.required = required # 81 行实例化 传入的是否为空字段,默认是浏览器输入不许为空 163 164 self.error = None # 错误信息 165 self.value = [] # 匹配的值是什么 166 self.is_valid = True 167 self.name = None 168 self.success_file_name_list = [] 169 170 def validate(self, name, all_file_name_list): 171 """ 172 :param name: 字段名 173 :param all_file_name_list: 所有文件的文件名 174 :return: 175 """ 176 self.name = name # 上传文件的字段名fafafa 177 if not self.required: 178 # 用户输入允许为空 179 self.is_valid = True # 设置封装值为真,表示验证通过 180 self.value = all_file_name_list 181 else: 182 if not all_file_name_list: 183 self.is_valid = False 184 if self.error_dict.get('required', None): 185 self.error = self.error_dict['required'] 186 else: 187 self.error = "%s is required" % name 188 else: 189 # 循环所有文件名 190 for file_name in all_file_name_list: 191 ret = re.match(FileFiled.REGULAR, file_name) # 文件名中含有中文无法匹配 192 if not ret: 193 self.is_valid = False # 验证没有通过 194 if self.error_dict.get('valid', None): 195 self.error = self.error_dict['valid'] 196 else: 197 self.error = "%s is invalid" % name 198 break 199 else: # 可能会上传多个文件,有一个文件正确就加入文件列表 200 self.value.append(file_name) 201 202 def save(self, request, path='statics'): 203 """ 204 :param request: request中封装了所有用户发过来的前端中的数据 205 :param path: 206 :return: 207 """ 208 file_metas = request.files.get(self.name) # 所有文件列表,self.name取前端的name变量值,get得到文件列表[{文件1}, {文件2}] 209 # print(self.name) # 输出fafafa 210 # print(file_metas) 211 for meta in file_metas: # 循环文件列表,列表中包含很多字典,每个字典包含一个文件,meta为单个字典 212 print('meta in for loop---', meta) 213 file_name = meta['filename'] 214 import os 215 new_file_name = os.path.join(path, file_name) 216 if file_name and file_name in self.value: # 文件名为空不上传,且文件名要在成功的列表中才会上传保存 217 with open(new_file_name, 'wb') as up: 218 up.write(meta['body']) # 字典中的body字段包含文件内容 219 # print(file_metas)输出 220 # [{'body': b'1', 'content_type': 'text/plain', 'filename': '21.txt'}, 221 # {'body': b'1', 'content_type': 'text/plain', 'filename': '12.txt'}] 222 # print('meta in for loop---', meta)输出 223 # meta in for loop - -- {'body': b'1', 'content_type': 'text/plain', 'filename': '21.txt'} 224 # meta in for loop - -- {'body': b'1', 'content_type': 'text/plain', 'filename': '12.txt'} 225 226 227 class BaseForm: 228 def check_valid(self, handle): 229 """ 230 获取用户提交的数据,将用户提交的数据传递到对应的类中进行正则表达式匹配,再调用类中匹配结果和正确/错误等自定义信息 231 :param handle: post方法中的self,里面封装了get_argument方法,可以直接取前端传过来的参数值 232 :return:返回正则匹配结果:成功-True;失败:false,正则匹配成功后返回一个字典,失败返回错误信息 233 """ 234 flag = True 235 error_message_dict = {} 236 success_value_dict = {} 237 for key, regular in self.__dict__.items(): # 遍历self中封装的参数 238 # key是host、ip、port、phone 239 # regular:当调用HomeForm类时值为IPField(required=True)对象 240 if type(regular) == CheckBoxFiled: # 复选框功能 241 # print('question:--------' + str(type(regular))) 242 # print('question:--------' + str(CheckBoxFiled)) 243 input_value = handle.get_arguments(key) # 对于CheckBox来说,input_value就是用户提交name="favor"的列表 244 elif type(regular) == FileFiled: # 文件上传功能,获取文件名,放入一个列表中 245 file_list = handle.request.files.get(key) # [{'body':'xx', 'filename':'xx'},{'body':'xx', 'filename':'xx'}] 246 # print(file_list) 247 input_value = [] 248 if file_list: # 如果没有提交文件,会报错,此处用个if避免报错 249 for item in file_list: 250 input_value.append(item['filename']) # 加入列表,由validate方法进行所有文件名验证 251 else: # 输入内容功能 252 input_value = handle.get_argument(key) 253 # print(input_value + '输入内容功能==========') 254 # 将具体的验证放在validate方法中 255 regular.validate(key, input_value) # regular为每次循环时HomeForm类中封装的对象 256 if regular.is_valid: # 如果正则匹配成功(调用IPField中封装的字段) 257 success_value_dict[key] = regular.value # 把匹配到的值放到字典中 258 else: 259 error_message_dict[key] = regular.error # 把错误信息放到字典中 260 flag = False # HomeForm类中,有一种对象不符合正则匹配,就返回失败 261 return flag, success_value_dict, error_message_dict 262 263 264 class IndexForm(BaseForm): 265 def __init__(self): 266 self.host = "(.*)" 267 self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" 268 self.port = '(\d+)' 269 self.phone = '^1[3|4|5|8][0-9]\d{8}$' 270 271 272 class HomeForm(BaseForm): 273 def __init__(self): 274 self.ip = IPFiled(required=True, error_dict={'required': '不能为空...', 'valid': '格式错误了'}) 275 self.host = StringFiled(required=False) 276 self.favor = CheckBoxFiled(required=True, error_dict={'required': '必须选择一个...', 'valid': '格式错误了'}) # 验证成功后,favor中的数据为用户选择列表 277 self.fafafa = FileFiled(required=True) 278 279 280 class IndexHandler(tornado.web.RequestHandler): 281 def get(self): 282 self.render('index.html') 283 284 def post(self, *args, **kwargs): 285 obj = IndexForm() 286 is_valid, success_dict, error_dict = obj.check_valid(self) 287 if is_valid: 288 print(value_dict) 289 290 291 class HomeHandler(tornado.web.RequestHandler): 292 def get(self): 293 self.render('home.html', error_dict=None) 294 295 def post(self, *args, **kwargs): 296 # files = self.request.files.get('fafafa', []) # 表示获取提交过来的所有文件,如[文件1, 文件2],前端文件name相同 297 298 obj = HomeForm() 299 is_valid, success_dict, error_dict = obj.check_valid(self) 300 if is_valid: # 如果HomeForm类中所有对象校验成功 301 print('success', success_dict) 302 obj.fafafa.save(self.request) # self.request中封装了所有用户发过来的前端中的数据 303 else: 304 print('error', error_dict) 305 self.render('home.html', error_dict=error_dict) # 错误信息传递给前端渲染 306 307 308 settings = { 309 'template_path': 'views', 310 'static_path': 'statics', 311 'static_url_prefix': '/statics/' 312 } 313 314 application = tornado.web.Application([ 315 (r"/index", IndexHandler), 316 (r"/home", HomeHandler), 317 ], **settings) 318 319 if __name__ == "__main__": 320 application.listen(8001) 321 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/home" method="POST" enctype="multipart/form-data" > 9 <input type="text" name="ip" placeholder="ip" /> 10 <input type="text" name="host" placeholder="host" /> 11 <p> 12 <input type="checkbox" name="favor" value="1" />篮球; 13 <input type="checkbox" name="favor" value="2" />足球; 14 <input type="checkbox" name="favor" value="3" />羽毛球; 15 </p> 16 <p> 17 <input type="file" name="fafafa" /> 18 <input type="file" name="fafafa" /> 19 </p> 20 <!--当error_dict有值时再提示--> 21 {% if error_dict and 'ip' in error_dict %} 22 <span>{{error_dict['ip']}}</span> 23 {% end %} 24 <input type="submit" /> 25 </form> 26 </body> 27 </html>
扩展:tornado文件上传之request.files.get方法详解
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 import re 7 8 9 class IndexHandler(tornado.web.RequestHandler): 10 def get(self): 11 self.render('home.html') 12 13 def post(self, *args, **kwargs): 14 val = self.request.files.get('fafafa', []) # 注意checkbox必须用get_arguments,加s的参数 15 # val = self.request.files['fafafa'] # 注意checkbox必须用get_arguments,加s的参数 16 print(type(val), val) 17 # 当提交一个文件时:输出 <class 'list'> [{'content_type': 'text/plain', 'body': b'1', 'filename': 'app.py'}] 18 # 两个文件输出: 19 # <class 'list'> [{'filename': 'app.py', 'body': b'1', 'content_type': 'text/plain'}, 20 # {'filename': 'app1.py', 'body': b'2', 'content_type': 'text/plain'}] 21 # 提交空输出: <class 'list'> [] 22 23 24 settings = { 25 'template_path': 'views', 26 'static_path': 'statics', 27 'static_url_prefix': '/statics/' 28 } 29 30 application = tornado.web.Application([ 31 (r"/home", IndexHandler), 32 ], **settings) 33 34 if __name__ == "__main__": 35 application.listen(8001) 36 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/home" method="POST" enctype="multipart/form-data" > 9 <!--<input type="text" name="ip" placeholder="ip" />--> 10 <!--<input type="text" name="host" placeholder="host" />--> 11 <!--<p>--> 12 <!--<input type="checkbox" name="favor" value="1" />篮球;--> 13 <!--<input type="checkbox" name="favor" value="2" />足球;--> 14 <!--<input type="checkbox" name="favor" value="3" />羽毛球;--> 15 <!--</p>--> 16 <p> 17 <input type="file" name="fafafa" /> 18 <input type="file" name="fafafa" /> 19 </p> 20 21 <input type="submit" /> 22 </form> 23 </body> 24 </html>

浙公网安备 33010602011771号