web框架的本质其实就是socket服务端再加上业务逻辑处理, 比如像是Tornado这样的框架. 有一些框架则只包含业务逻辑处理, 例如Django, bottle, flask这些框架, 它们的使用需要依赖包含socket的第三方模块(即 wsgiref)来运行
在python中常见的web框架构建模式有以下两种:
1MVC框架:Models 数据相关操作Views 模板html文件Controllers 业务逻辑mvc类似于抽象工厂设计模式,确认了架构以后才考虑应用设计模式, 三层架构大于MVC模式 2 3三层架构将整个项目划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)

1MTV框架:Models 数据相关操作Templates 模板html文件Views 业务逻辑相当于文件夹的归类, 只是命名不同, 所遵循的的思想也只是大同小异flask, django, bottle 框架中使用模板引擎Jinja2:一个模板系统为Flask提供模板支持,其灵活,快速和安全等优点被广泛使用。web框架本质是socket, 通过socket发送的就是字符串,第一块:协议和方式第二块:请求头第三块:发送内容 2 3第一块:协议和状态第二块:响应头第三块:响应内容 4 5python web 框架分类:自己的socket ====> Tornado第三方:wsgi +框架 ===>Django, flash, bottl都是基于wsgiTornado:1.a.导入tornado模块b.写类xxxxxHandler, 必须继承tornado的模块c.路由系统,就是url和类的关系d.程序运行e.模板路劲配置和静态文件配置2.写表单<input type="text" name="use" / >提交后台.self.get_argument('use)后台返回请求:self.render("index.html") 找到文件, 渲染, 得到字符串self.write("字符串") 字符串返回给用户self.redirect("/manager") url跳转3.模板语言:a.{%%}if else 等代码块b.{{}}c.自定义 UIMethod, UIModule模板语言本质:字符串形式的函数 "def execute(): xxx" compile execcookie: Tornado: self.set_cookie() self.get_cookie()Ajax: 本质就是浏览器在用户未感知的情况下发请求 XMLHttpRequest ==>发请求 jquery内部本质还是调用这个发送XMLHttpRequest
XSS是通过网页使用JavaScript跨站脚本的攻击是代码注入的一种。
-
通常是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。
-
攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。
import os import time from jinja2 import Template 模板语言配合jinja2使用 css, js, 静态文件需要引用

1from wsgiref.simple_server import make_server #首次得下载wsgiref模块, 2#建立动态响应函数 3def application(environ,start_response):#要实现的是做一个web应用 4 # 响应首行和响应头,响应头可以是空,响应首行必须有内容 5 start_response('200 OK', [('Content-Type', 'text/html')]) 6 # environ, start_response 这两个参数是两个形参,所以叫什么名字都无所谓, 7 # 关键是实参谁来调用它 8 # application的调用取决于make_server在调用serve_forever的时候要跑application 9 # 相当于实参是什么取决于模块给他放什么参数 10 # 这个wsgiref模块里面有个make_server类 11 # application(a,b)里面有两个参数,a就是打包好的数据也就是上面的environ 12 # 换句话来说environ里面放的就是所有的数据,,按着http协议解析数据:environ 13 # start_response:确定响应头的信息,,,按者http协议组装数据:start_response 14 print("environ",environ) 15 path=environ.get("PATH_INFO")# 当前的请求路径 16 if path=="/login": 17 with open("template/login.html","rb")as f: 18 data=f.read() 19 return [data] 20 elif path=="/auth": 21 22 return [b"<h1>hello</h1>"] 23 24#类似socket创建的socket对象,绑定IP端口,开启监听3步 25s=make_server("127.0.0.1",8084,application) 26print("servering") 27s.serve_forever()#类似socket建立连接,等待接收用户请求 28 29 30import os 31def new(): 32 f = open(os.path.join("文件夹","文件夹下级文件","r")) 33 data = f.read() 34 f.close() 35 return data
wsgiref

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 //静态文件引入写法 7 <link rel="stylesheet" href="{{start_url('commons.css')}}" /> 8</head> 9<body> 10 <h1>提交内容:</h1> 11 <form method="post" action="/index"> 12 <input type="text" name="xxx" /> 13 <input type="submit" value="提交" /> 14 </form> 15 <h1>展示内容:</h1> 16 //模板语言:1.支持普通方式 2.支持代码块方式, 3.支持自定义方式(uimethod,uimodule) 17 <ul> 18 //模板语言固定写法(普通方式), 使用for循环展示数据 19 {% for item in xxoo %} 20 <li>{{item}}</li> 21 {% end %} 22 </ul> 23//转换后的新字符串格式 24 <ul> 25 <li>[11]</li> 26 <li>[22]</li> 27 </ul> 28 //静态文件引入写法 29 <script src='{{static_url("oldboy.js")}}'></script> 30</body> 31</html>
模板语言

1import tornado.ioloop 2import tornado.web 3INPUTS_LIST = [] 4# 创建MainHandler类继承tornado.web.RequestHandler 5class MainHandler(tornado.web.RequestHandler): 6 def get(self): 7 self.write("hello world") 8 #1.打开s1.html文件,读取内容(包含特殊语法) 9 #2.xxoo = [11,22] && 读取内容(包含特殊语法),结合起来 10 #3.得到新的字符串 11 #4.返回给用户 12 self.render("s1.html", xxoo = INPUTS_LIST)#默认去当前目录下找文件,渲染成html文件 13 def post(self, *args, **kwargs): 14 name = self.get_argument('xxx') 15 #get_argument获取前台提交的数据,get&post都可以获取,get在url中可以传输,post只能以提交方式传输,两者区别还有长度安全性 16 INPUTS_LIST.append(name) 17 print("post") 18 # self.write("hello world") 19 self.render("s1.html", xxoo = INPUTS_LIST) 20settings = { 21 "tempalte_path": "tpl",#模板路径配置, 相当于配了全局的变量,以后所有使用html的页面都可以使用tempalte找到 22 "static_path": "static",#静态文件配置 23 "static_url_prefix": "/sss/", 24 "ui_methods" : mt, 25 "ui_modules": md, 26 27 28} 29 30# 匹配用户URL,和类, 叫:路由映射,路由系统 31application = tornado.web.Application([ 32 (r"/index",MainHandler) 33], **settings) #配置文件settings生效 34 35 36if __name__ == "__main": 37 #socket运行起来 38 application.listen("127.0.0.1",8888) 39 tornado.ioloop.IOLoop.instance().start()
tornado


1import tornado.ioloop 2import tornado.web 3 4 5 6class MainHandler(tornado.web.RequestHandler): 7 def get(self): 8 # self.write("Hello, world") 9 self.render('js2.html') 10 11settings = { 12 'template_path':'template',#路径配置,就是自动取到该路径下寻找文件 13 'static_path':'static',#静态文件配置,需要特殊处理 14} 15 16 17 18#路由映射,根据不同url对应到不同的类里面 19application = tornado.web.Application([ 20 (r"/index", MainHandler), 21],**settings) 22#第二个接收配置文件 23 24if __name__ == "__main__": 25 application.listen(8888)#监听端口 26 tornado.ioloop.IOLoop.instance().start()
s1代码


1import tornado.ioloop 2import tornado.web 3 4input_list=[]#用来接收用户的数据 5 6class MainHandler(tornado.web.RequestHandler): 7 def get(self): 8 # self.write("Hello, world") 9 self.render('js2.html',xxxooo = input_list) 10 def post(self, *args, **kwargs): 11 12 13 name = self.get_argument('jjj')#根据value提取用户输入的名字 14 input_list.append(name) 15 print(name) 16 #1、打开js2.html文件,读取内容(包含特殊语法) 17 #2、xxxooo = [11,22,333,44] 读取内容(包含特殊语法) 18 #3、得到新的字符串 19 #4、将得到新的字符串返回给用户 20 self.render('js2.html',xxxooo = input_list) 21settings = { 22 'template_path':'template',#路径配置,就是自动取到该路径下寻找文件 23 'static_path':'static',#静态文件配置,需要特殊处理 24 'static_url_prefix':'/sss/',#标记文件开始的名字 25} 26 27 28#路由映射,根据不同url对应到不同的类里面 29application = tornado.web.Application([ 30 (r"/index", MainHandler), 31],**settings) 32#第二个接收配置文件 33 34if __name__ == "__main__": 35 application.listen(8888)#监听端口 36 tornado.ioloop.IOLoop.instance().start()
利用tornado框架实现数据提交,,主要是修改上面s1的代码

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>你好</title> 6 <link rel="stylesheet" href="/sss/commons.css"> 7</head> 8<body> 9 10<P style="font-size: 50px"> js3</P> 11<h1>提交内容</h1> 12<form method="post" action="/index"> 13 <input type="text" name="jjj"> 14 <input type="submit" value="提交"> 15</form> 16 17 18<h1>显示内容</h1> 19<ul> 20 {% for item in xxxooo %} 21 <li>{{item}}</li> 22 {% end %} 23</ul> 24<!--<script src="sss/jayzhou.js"></script>--> 25</body> 26</html>
js2的html文件,写了一个表单

对于模板语言,主要有三类
模板语言分为三类:
{{}}表达式
{% if %}{% end %}
例如:
然后在上面的s1里面配置好设置文件
1settings = { 2 'template_path':'template',#路径配置,就是自动取到该路径下寻找文件 3 'static_path':'static',#静态文件配置,需要特殊处理 4 'static_url_prefix':'/sss/',#标记文件开始的名字 5 'ui_methods':uimethod#这个是我们导入的文件 6}
在js2.html文件里面的模板语言
1<ul> 2 {% for item in xxxooo %} 3 <li>{{item}}</li> 4 <h2>{{func(item)}}</h2> 5 {% end %} 6<h2>{{func(amp)}}</h2>


js2.html文件里面要这样写
1<ul> 2 {% for item in xxxooo %} 3 <li>{{item}}</li> 4 5 {% end %} 6<h2>{{func(amp)}}</h2> 7<h3>{% module custom() %}</h3> 8 <!--调用自定义的custom模块--> 9 10</ul>



1# wsgiref在py2中运行正常, 在py3中会报错 2 3# 当我们将执行的index()和news()功能函数放进Controllers业务逻辑处理模块, \ 4# 将返回结果ret改为文件读写后的内容, 并将该文件放置到Views或者Template模块中, \ 5# 就形成了最基础版本的MVC和MTV框架 6 7from wsgiref.simple_server import make_server 8 9def index(): 10 return "This is index " 11 12def news(): 13 return "welcome to news " 14 15URLS = { 16 '/index': index, 17 '/news': news, 18} 19 20def RunServer(rq, rp):#RunServer(rq, rp) 该方法中rq封装了请求信息, rp封装了响应信息 21 rp('200 OK', [('Content-Type', 'text/html')]) 22 url = rq['PATH_INFO']#获取请求的url连接地址 23 if url in URLS.keys(): 24 ret = URLS[url]()#根据请求的url执行对应的函数 25 else: 26 ret = '404' 27 return ret 28 29 30if __name__ == '__main__': 31 http = make_server('', 8000, RunServer)#这里创建socket服务端, 并传入业务逻辑功能函数RunServer(rq, rp) 32 http.serve_forever()#启动服务端, 阻塞进程等待客户端访问, 一旦有访问则执行RunServer(rq, rp)方法
使用wsgiref再加上自己写的业务逻辑自定义一个web框架


1.body { 2 margin: 0; 3 background-color: cornflowerblue; 4}
commons.css文件内容

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>S1</title> 6 <link rel="stylesheet" href="../static/commons.css"> 7</head> 8<body> 9<form method="post"> 10 <input type="text" name="name"> 11 <input type="submit" value="提交"> 12</form> 13<h1>内容展示</h1> 14<ul> 15 {% for item in contents %} 16 <li>{{item}}</li> 17 {% end %} 18</ul> 19</body> 20</html>
index.html文件内容

1import tornado.web, tornado.ioloop 2 3# 客户端第一次访问调用的是`MyHandle`类中的`get(self, *args, **kwargs)`方法, 服务端向客户端返回`index.html`文件 4# - 客户端浏览器接受到`index.html`文件之后, 在输入框中输入内容并提交之后会调用`post(self, *args, **kwargs)`, 并将输入的内容追加到 5# - `self.get_argument('name')` 获取指定参数的内容 6# `CONTENTS_LIST`中, 服务端返回`index.html`, 返回过程中`Toranado` 7# 会将`CONTENTS_LIST` 的内容渲染到`index.html`之后才会发给客户端浏览器 8 9class MyHandle(tornado.web.RequestHandler): 10 def get(self, *args, **kwargs): 11 self.render("index.html", contents=CONTENTS_LIST) 12 13 def post(self, *args, **kwargs): 14 CONTENTS_LIST.append(self.get_argument('name')) 15 self.render('index.html', contents=CONTENTS_LIST) 16 17 18if __name__ == '__main__': 19 CONTENTS_LIST = []#为存放的是输入框输入的内容 20 settings = { #字典表示的是配置文件 21 'template_path': 'template',#模板文件的存放位置 22 'static_path': 'static', #静态文件的存放位置, 静态文件必须声明, 否则浏览器无法找到静态文件 23 'static_url_prefix': 'static/', #静态文件前缀, 减少每个文件引入都要加前缀的麻烦 24 } 25 26 application = tornado.web.Application([ 27 (r"/index", MyHandle) 28 ], **settings) 29 application.listen(800)#设置服务端的监听端口 30 tornado.ioloop.IOLoop.instance().start()#阻塞服务端进程, 等待客户端的访问
index.py文件内容

模板引擎的使用:

1def test_uimethod(self): 2 return "uimethod"
uimethod

1from tornado.web import UIModule 2 3class MyClass(UIModule): 4 def render(self, *args, **kwargs): 5 return "uimodule"
uimodule.py文件如下

1import tornado.web, tornado.ioloop 2import uimethod as ut 3import uimodule as ud 4 5 6class MyHandle(tornado.web.RequestHandler): 7 def get(self, *args, **kwargs): 8 self.render("index.html", ag="this is ag", contents=CONTENTS_LIST) 9 10 def post(self, *args, **kwargs): 11 CONTENTS_LIST.append(self.get_argument('name')) 12 self.render('index.html', contents=CONTENTS_LIST) 13 14 15if __name__ == '__main__': 16 CONTENTS_LIST = [] 17 settings = { 18 'template_path': 'template', 19 'static_path': 'static', 20 'static_url_prefix': 'static/', 21 'ui_methods': ut, 22 'ui_modules': ud 23 } 24 25 application = tornado.web.Application([ 26 (r"/index", MyHandle) 27 ], **settings) 28 application.listen(80) 29 tornado.ioloop.IOLoop.instance().start()
index.py文件如下

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>S1</title> 6 <link rel="stylesheet" href='{{static_url("commons.css")}}'> 7</head> 8<body> 9<h1>{{ag}}</h1> 10<h1>{{test_uimethod()}}</h1> 11<h1>{%module MyClass()%}</h1> 12<form method="post"> 13 <input type="text" name="name"> 14 <input type="submit" value="提交"> 15</form> 16<h1>内容展示</h1> 17<ul> 18 {% for item in contents %} 19 <li>{{item}}</li> 20 {% end %} 21</ul> 22<hr> 23</body> 24</html>
index.html文件如下

- 模板引擎中的
{{key}}表示取key对应的值, 当key为函数时候执行该函数并取该函数结果. 例如index.html文件中的<h1>{{ag}}</h1>实际上取得是index.py的self.render("index.html", ag="this is ag", contents=CONTENTS_LIST)中的参数ag的值 <h1>{{test_uimethod()}}</h1>这里执行的是自定义函数, 我们将这个自定义函数写在uimethod.py文件中, 并且在index.py文件中导入, 然后将index.py文件中的settings配置增加一行'ui_methods': ut, 该行内容表示模板引擎可执行自定义函数- 模板引擎中的
{%%}可用于循环语句和条件语言以及自定义类的执行,{% for item in contents %}此处正是用于循环遍历contents中的内容 <h1>{%module MyClass()%}</h1>此处表示模板引擎执行自定义类, 该类的文件对应的是uimodule.py文件, 我们需要在index.py的settings中增加一行'ui_modules': ud, 改行表示模板引擎可使用自定义类- 注意, 我们将
index.html文件引入css的方式改为了<link rel="stylesheet" href='{{static_url("commons.css")}}'>,static_url()是模板引擎内置的自定义函数, 用该函数引入css文件时候, 仅当css文件内容发生变化时候, 浏览器才会重新缓存该css文件
以下tornado_cookie:


1import tornado.ioloop 2import tornado.web 3import time 4class IndexHandler(tornado.web.RequestHandler): 5 def get(self, *args, **kwargs): 6 self.render("index.html") 7 8class ManagerHandler(tornado.web.RequestHandler): 9 def get(self, *args, **kwargs): 10 co = self.get_cookie("auth") 11 if co == "1": 12 self.render("manager.html") 13 else: 14 self.redirect("/login") 15 16 17class LoginHandler(tornado.web.RequestHandler): 18 def get(self, *args, **kwargs): 19 self.render("login.html",status_text="") 20 21 def post(self, *args, **kwargs): 22 username = self.get_argument("username",None) 23 pwd = self.get_argument("password",None) 24 check = self.get_argument("auth",None) 25 if username == "alex" and pwd == "sb": 26 if check: 27 # self.get_secure_cookie()#原生cookie 28 self.set_cookie("username",username,expires_days=7) 29 self.set_cookie("auth","1",expires_days=7) 30 else: 31 r = time.time() + 10#当前时间加10秒 32 self.set_cookie("auth","1",expires=r) 33 self.set_cookie("username",username,expires=r) 34 self.redirect("/manager") 35 else: 36 self.render("login.html",status_text="登录失败") 37 38class LogoutHandler(tornado.web.RequestHandler): 39 def get(self, *args, **kwargs): 40 self.set_cookie("auth","1",expires=time.time()) 41 self.redirect("/login") 42 43settings = { 44 "template_path": "views", 45} 46 47application = tornado.web.Application([ 48 (r"/index", IndexHandler), 49 (r"/login",LoginHandler), 50 (r"/manager",ManagerHandler), 51 (r"/logout",LogoutHandler), 52],**settings) 53 54if __name__ == "__main__": 55 application.listen(8888) 56 tornado.ioloop.IOLoop.instance().start()
tornodo_cookie

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="auth" value="1">7天免登录 12 <input type="submit" value="登录"> 13 <span style="color:red;">{{status_text}}</span> 14 </form> 15</body> 16</html>
login

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>你的当前银行卡余额:-1000</h1> 10</body> 11</html>
manager

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>
index

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6</head> 7<body> 8 9 <input type="text" name="username" /> 10 <input type="password" name="password" /> 11 <input type="checkbox" name="auth" value="1">7天免登录 12 <input type="button" value="登录"> 13<script src="{{static-url('jquery-3.3.1')}}"></script> 14<script> 15 function SubmitForm(){ 16 $.post('/login',{'username':$('#user').val(), 'password':$('#pwd').val()},function(callback){ 17 console.log(callback); 18 }) 19 } 20</script>
jquery发送请求

1start.py文件: 2 3from controllers import home 4import tornado.web 5import tornado.ioloop 6settings = { 7 'template_path': 'views',#模板路劲配置 8 'static_path':'statics',#静态文件 9} 10 11application = tornado.web.Application([ 12 # (r'/index',home.IndexHandler), 13 (r'/index/(?P<num>\d*)/(?P<nid>\d*)',home.IndexHandler), 14],**settings) 15 16 17if __name__ == '__main__': 18 application.listen(8880) 19 tornado.ioloop.IOLoop.instance().start() 20 21 22 23home.html文件: 24 25import tornado.web 26class IndexHandler(tornado.web.RequestHandler): 27 def get(self, nid,num): 28 print(nid,num) 29 self.write("ok")
正则路由匹配

路由系统:
在Tornado中支持两种路由系统, 正则路由系统以及二级域名路由系统
1# 默认路由系统, 根据url的不容调用不同的类 2application = tornado.web.Application([ 3 (r"/index/(?P<page>\d*)", home.IndexHandle), 4], **settings) 5 6#二级路由匹配 7application.add_handlers("test.ming.com",[ 8 (r"/index/(?P<page>\d*)", home.IndexHandle) 9])
(r"/index/(?P<page>\d*)", home.IndexHandle)这里我们访问时候需要以类似http://127.0.0.1/index/2的方式访问, 在get或者post接受处理请求的函数应有page参数来接受访问地址最后的整数(我们稍后将根据这个做一个分页的demo)- 当我们加入二级域名时候, 默认访问网站执行第一个默认路由系统, 仅当我们访问设置的二级域名才会调用对应的处理器(当前代码指的是
test.ming.com的域名)
接下来我们使用基于正则的路由系统来实现网页分页功能.


1#利用全局变量模拟数据库所有内容 2USER_LIST= [ 3 {'username': 'test', 'email': 'test@163.com'} 4] 5 6#利用循环生成多条数据来模拟大量数据依次便于实现分页效果 7for i in range(300): 8 tmp = {'username': "test - " + str(i), 'email': str(i) + "@vip.com"} 9 USER_LIST.append(tmp)
all.py文件如下

1# 单独用于实现分页功能的类 2class Page: 3 # current_page表示当前页数, all_item表示总的数据条目 4 # 初始化时候将当前页数current_page与总页数all_page加入到对象中 5 def __init__(self, current_page, all_item): 6 # all_page表示总页数, 每页显示5条数据, more表示余数, 如果大于0则表示应多加一页才能显示完所有的数据 7 all_page, more = divmod(all_item, 5) 8 if more > 0: 9 all_page += 1 10 self.all_page = all_page 11 12 # 捕捉异常, 防止传入非法字符冒充页数, 一旦发生异常则直接将当前页current_page设置为1, 表示默认显示第一页 13 try: 14 current_page = int(current_page) 15 except Exception as e: 16 current_page = 1 17 18 # 如果前传入的页数小于1, 则直接默认为第一页 19 if current_page < 1: 20 current_page = 1 21 self.current_page = current_page 22 23 # 根据当前页在每个页面显示11个页码, 此处是开始页码 24 @property 25 def start_page(self): 26 return (self.current_page - 1) * 5 27 28 # 结束页码 29 @property 30 def end_page(self): 31 return self.current_page * 5 32 33 # 显示的页码对应的html字符串, base_url表示可定制的url跳转路径 34 def page_str(self, base_url): 35 # 定义list_page列表用来暂时存储所有的页码字符串 36 list_page = [] 37 38 # 如果总页数小于11页, 则直接显示所有页数 39 if self.all_page < 11: 40 s = 0 41 e = self.all_page 42 43 # 总页数大于11页时候 44 else: 45 # 当前页数小于6则直接显示1到11页 46 if self.current_page <= 6: 47 s = 1 48 e = 11 49 50 # 当前页数大于6页时候 51 else: 52 # 当前页加上5也之后就大于总页数则直接显示倒数11页 53 if self.current_page + 5 > self.all_page: 54 s = self.all_page - 10 55 e = self.all_page 56 57 # 当前页数大于6页并且加上5页并不超过总页数时候, 显示当前页前后5页以及当前页 58 else: 59 s = self.current_page - 5 60 e = self.current_page + 5 61 62 # 首页设置 63 first_page = '<a href="https://my.oschina.net/%s/1">首页</a>' % (base_url) 64 list_page.append(first_page) 65 66 # 上一页设置 67 # 当前页小于等于第一页时候, 点击上一页不做任何操作(这里理论上是不会有小于的情况) 68 if self.current_page <= 1: 69 pre_page = '<a href="javascript:void(0);">上一页</a>' 70 71 # 当前页大于第一页, 点击上一页则直接跳转到上一页 72 else: 73 pre_page = '<a href="https://my.oschina.net/%s/%s">上一页</a>' % (base_url, self.current_page - 1) 74 list_page.append(pre_page) 75 76 # 根据上边条件过滤后的页码起始位置s以及页码终止位置e来生成对应的11条页码对应的html字符串 77 for p in range(s, e + 1): 78 if p == self.current_page: 79 tmp = '<a class="active" href="https://my.oschina.net/index/%s">%s</a>' % (p, p) 80 else: 81 tmp = '<a href="https://my.oschina.net/%s/%s">%s</a>' % (base_url, p, p) 82 list_page.append(tmp) 83 84 # 下一页设置 85 # 下一页要大于或者等于最大页数时候, 不做任何操作 86 if self.current_page >= self.all_page: 87 next_page = '<a href="javascript:void(0);">下一页</a>' 88 else: 89 next_page = '<a href="https://my.oschina.net/%s/%s">下一页</a>' % (base_url, self.current_page + 1) 90 list_page.append(next_page) 91 92 # 尾页设置 93 last_page = '<a href="https://my.oschina.net/%s/%s">尾页</a>' % (base_url, self.all_page) 94 list_page.append(last_page) 95 96 # 页面跳转 97 jump_page = """<input type="text" /><a onclick='JumpTo("%s",this)'>GO</a>""" % base_url 98 # 页面跳转的js代码, 本质就是location.href的使用 99 jspt = """<script> 100 function JumpTo(base_url,th){ 101 var val=th.previousElementSibling.value; 102 if(val.trim().length>0){ 103 location.href="https://my.oschina.net/"+base_url+"/"+val 104 } 105 } 106 </script>""" 107 list_page.append(jump_page) 108 list_page.append(jspt) 109 return "".join(list_page)
pager.py文件如下

1import tornado.web 2from commons.all import USER_LIST 3from commons.pager import Page 4 5class IndexHandle(tornado.web.RequestHandler): 6 def get(self, c_page): 7 # c_page表示url传递过来的当前页数, len(USER_LIST)表示总共有多少条数据 8 pg = Page(c_page, len(USER_LIST)) 9 #开始页数 10 start = pg.start_page 11 #尾页 12 end = pg.end_page 13 #当前显示的数据片段 14 current_list = USER_LIST[start:end] 15 #传入index并返回对应的下面分页部分的html代码 16 str_page = pg.page_str('index') 17 #将当前显示的数据片段, 当前页数以及分页的html代码返回给浏览器 18 self.render('index.html', list_info=current_list, current_page=pg.current_page, str_page=str_page) 19 20 def post(self, c_page): 21 #获取传入的username的值 22 username = self.get_argument('username', None) 23 #获取传入的email的值 24 email = self.get_argument("email", None) 25 #生成临时的字典对象, 表示一个完整的数据片段 26 tmp = {'username': username, 'email': email} 27 #将该数据片段加入的全局变量USER_LIST, 这里用全局变量模拟数据库获取的数据 28 USER_LIST.append(tmp) 29 self.redirect('/index/' + c_page)
home.py文件如下

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .pager a{ 8 display: inline-block; 9 padding: 5px; 10 margin: 3px; 11 background-color: aquamarine; 12 } 13 .pager a.active { 14 background-color: crimson; 15 color: aliceblue; 16 } 17 </style> 18</head> 19<body> 20<h1>提交数据</h1> 21<form method="post" action="/index/{{current_page}}"> 22 <input name="username" type="text"> 23 <input name="email" type="text"> 24 <input type="submit" value="提交"> 25</form> 26<h1>显示数据</h1> 27<table border="1"> 28 <thead> 29 <tr> 30 <th>用户名</th> 31 <th>邮箱</th> 32 </tr> 33 </thead> 34 <tbody> 35 {%for tmp in list_info%} 36 <tr> 37 <td>{{tmp['username']}}</td> 38 <td>{{tmp['email']}}</td> 39 </tr> 40 {%end%} 41 </tbody> 42</table> 43<div class="pager"> 44 <!--加raw 以此来直接执行原始的字符串, 不做转义--> 45 {%raw str_page%} 46</div> 47</body> 48</html>
index.html文件如下

1import tornado.web,tornado.ioloop 2from controllers import home 3 4if __name__ == '__main__': 5 settings = { 6 # 模板路径配置 7 'template_path': 'views', 8 } 9 10 application = tornado.web.Application([ 11 (r"/index/(?P<c_page>\d*)", home.IndexHandle), 12 ], **settings) 13 application.listen(80) 14 tornado.ioloop.IOLoop.instance().start()
start.py文件如下


模板引擎:
1基本使用 2 继承,extends 页面整体布局用继承 3 导入,include 如果是小组件等重复的那么就用导入
在Tornado框架中, 模板引擎能带给我们很多方便, 它是便捷展现页面的极佳方式. 在上一节中我们介绍了模板引擎对于{{}}以及对于 {%%}的用法. 我们简单回顾一下:
**{{}}使用: **
- 直接取服务端在
render()函数中传递参数的值, 例如服务端中有self.render('index.html', contents=CONTENTS_LIST), 在html文件中有{{contents}}则表示在html中取服务端的CONTENTS_LIST的内容 - 我们通过配置
'ui_methods': 需要执行的自定义python模块,之后, 我们可以在html文件中通过{{自定义python模块中的函数名()}}来执行对应的函数取得该函数的返回结果以此来显示
**{%%}的使用: **
{%for tmp in iterable%}用于循环语句, 注意要加上{%end%}结束语句{%if condition%}用于条件判断, 同样同上需要结束语句- 通过配置
ui_modules : 需要执行的python模块之后, 我们可以在文件中通过{%module python模块名()%}来直接执行该模块中对应的方法, 注意该模块需要继承tornado.web.UIModule
以上有不懂的请参照上一篇博客(Tornado框架01-入门总概)中的具体实例实现后再对应解释来理解
接下来我们老规矩, 先使用一下模板引擎的继承之后, 再详细介绍


1import tornado.web 2 3 4class IndexHandle(tornado.web.RequestHandler): 5 def get(self, *args, **kwargs): 6 self.render("extend/index.html") 7 8 9class AccountHandle(tornado.web.RequestHandler): 10 def get(self, *args, **kwargs): 11 self.render("extend/account.html")
home

1{%extends "../template/master.html"%} 2 3<!--自定义css具体内容--> 4{%block tm_css%} 5 <style type="text/css"> 6 .page-content{ 7 background-color: green; 8 } 9 </style> 10{%end%} 11 12<!--#自定义的文本内容--> 13{%block tm_content%} 14 <h1>This is Account</h1> 15{%end%} 16 17<!--#自定义的js文件--> 18{%block tm_js%} 19 <script type="text/javascript"> 20 console.log('This is Account') 21 </script> 22{%end%}
acount.html

1{%extends "../template/master.html"%} 2 3<!--对应的自定义css的具体内容--> 4{%block tm_css%} 5 <style type="text/css"> 6 .page-content{ 7 background-color: yellow; 8 } 9 </style> 10{%end%} 11 12<!--自定义的文本内容--> 13{%block tm_content%} 14 <h1>This is Index</h1> 15{%end%} 16 17<!--自定义的js文件--> 18{%block tm_js%} 19 <script type="text/javascript"> 20 console.log('This is Index') 21 </script> 22{%end%}
index.html

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Master</title> 6 <style type="text/css"> 7 * { 8 margin: 0px; 9 padding: 0px; 10 } 11 .page-header{ 12 height: 50px; 13 background-color: red; 14 } 15 .page-content { 16 height: 200px; 17 background-color: azure; 18 } 19 .page-footer{ 20 height: 50px; 21 background-color: black; 22 } 23 </style> 24 25 <!--为后边自定义的css命名并占位置--> 26 {%block tm_css%}{%end%} 27</head> 28<body> 29 <div class="page-header"></div> 30 <div class="page-content"> 31 <!--自定义的内容, 命名并占位--> 32 {%block tm_content%}{%end%} 33 </div> 34 <div class="page-footer"></div> 35 36 <!--自定义的js文件位置--> 37 {%block tm_js%}{%end%} 38</body> 39</html>
master.html

1import tornado.web, tornado.ioloop 2from controllers import home 3 4if __name__ == '__main__': 5 CONTENTS_LIST = [] 6 settings = { 7 'template_path': 'views', 8 9 } 10 11 application = tornado.web.Application([ 12 (r"/index", home.IndexHandle), 13 (r"/account", home.AccountHandle), 14 ], **settings) 15 16 application.listen(80) 17 tornado.ioloop.IOLoop.instance().start()
start.py

- 从运行结果来看, 两个网页的主体结构相同, 只是里边包含的
css具体样式, 具体内容以及js文件不同 - 要继承模板文件来使用我们要在当前文件首行写上
{%extends "../template/master.html"%}, 这里表示当前文件以master.html来进行渲染
{%block tm_css%}
<style type="text/css"> .page-content{ background-color: yellow; } </style>{%end%}```
在index.html的这部分其实就是master.html中tm_css的具体内容
- 在
master.html文件中{%block tm_css%}{%end%}相当与为后面具体要写入的内容做一个占位符, 并且起名为tm_css.
使用模板的继承可以重复使用相同结构的模板, 可以大大减少代码量. 但是有时候并不是所有的网页结构都是我需要的, 我们会想要单独包含所有网页都有的相同的一小部分内容. 此时就需要模板文件的包含来实现

1<form> 2 <input type="text"> 3 <input type="submit" value="提交"> 4</form>

1{%extends "../template/master.html"%} 2 3<!--对应的自定义css的具体内容--> 4{%block tm_css%} 5 <style type="text/css"> 6 .page-content{ 7 background-color: yellow; 8 } 9 </style> 10{%end%} 11 12<!--自定义的文本内容--> 13{%block tm_content%} 14 <h1>This is Index</h1> 15 {%include "../include/form.html"%} 16 {%include "../include/form.html"%} 17 {%include "../include/form.html"%} 18{%end%} 19 20<!--自定义的js文件--> 21{%block tm_js%} 22 <script type="text/javascript"> 23 console.log('This is Index') 24 </script> 25{%end%}
将上面的index.html修改如下

cookie:
cookie:在浏览器端保存键值对, 特性:每次http请求都会附加在请求中并发送给服务器端


1import tornado.web 2 3 4class IndexHandle(tornado.web.RequestHandler): 5 def get(self): 6 username = self.get_argument('u', None) 7 if not username: 8# 设置未加密的cookie, 键为'name', 值为test 9 self.set_cookie('name', 'test') 10 #设置加密cookie, 键为'user', 值为test. 11# 设置加密cookie我们需要在配置中添加自定义的加密串(俗称对加密结果加盐)"cookie_secret": 'test-secret,' 12 self.set_secure_cookie('user', 'test') 13 self.redirect('/admin') 14 15 def post(self): 16 pass 17 18 19class AdminHandle(tornado.web.RequestHandler): 20 def get(self, *args, **kwargs): 21#获取指定key未加密的cookie的值 22 name = self.get_cookie('name', None) 23#获取指定key的加密后的cookie的值 24 user = self.get_cookie('user', None) 25 print('name: ', name, "\nuser: ", user) 26 27 28# 对于set_cookie()和set_secure_cookie()都用以下常见参数 29# name 表示传入cookie的键 30# value 表示传入cookie的name对应的值 31# domain=None 表示域名 32# expires=None 设置过期时间, 这里单位为秒 33# path="/" 表示当前的cookie在那些路径下有效, /表示当前域名下所有的路径均有效 34# expires_days=None 设置过期时间, 单位为天
home

1import tornado.web, tornado.ioloop 2from controllers import home 3 4if __name__ == '__main__': 5 settings = { 6 # 模板路径配置 7 'template_path': 'views', 8 "cookie_secret": 'test-secret,' 9 } 10 11 application = tornado.web.Application([ 12 (r"/index", home.IndexHandle), 13 (r"/admin", home.AdminHandle), 14 ], **settings) 15 application.listen(803) 16 tornado.ioloop.IOLoop.instance().start()
start

加密cookie的加密和解密原理:

- 解密时候将加密
cookie中的base64(test)也就是加密后的值和时间戳再加上cookie_secret生成新的加密串和加密cookie中的加密串比较, 若相同则合法验证通过, 然后再通过反解加密base64(test)取其本来的值
JavaScript操作Cookie
由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie。

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title></title> 6</head> 7<body> 8 <h1>adasd</h1> 9 <script> 10 /* 11设置cookie,指定秒数过期 12 */ 13function setCookie(name,value,expires){ 14 var temp = []; 15 var current_date = new Date(); 16 current_date.setSeconds(current_date.getSeconds() + 5); 17 document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString(); 18} 19 </script> 20</body> 21</html> 22 23 24然后在浏览器中访问 25setCookie("k22=11",5) 设置cookie,超时时间为5秒 26undefined 27document.cookie 查看cookie 28"k1=999; k22=11= 5" 29document.cookie 5秒后查看 30"k1=999" 31 32 33 34 35 36<!--toUTCString() 方法可根据世界时 (UTC) 把 Date 对象转换为字符串,并返回结果--> 37<!--设置cookie,指定秒数过期--> 38 <!--current_date.getSeconds() 获取当前秒--> 39 <!--current_date.setSeconds 设置秒--> 40 <!--data.setDate(data.getDate()+7),表示获取超过现在7天的时间--> 41 <!--current_date 当前时间+5秒--> 42 <!--toUTCString() 当前统一时间-->
HTML代码
对于参数
- domain 指定域名下的cookie
- path 域名下指定url中的cookie
- secure https使用
jquery中设置cookie
要用需要下载 这里
11、 导入jquery 22、 导入jQuery Cookie Plugin v1.4.1 3注意点: 4 如果用jquery导入的时候expires这里如果为数字的时候,表示天数 5 如果不想用天数,那么要用,这里的超时时间必须要用toUTCString()统一时间 6 7current_date.setSeconds(current_date.getSeconds() + 5); 用天数,然后用字符串拼接的方式";expires="+ current_date.toUTCString() 8等来设置时间,js数组的.join方法是吧数组变成字符串 9 $.cookie(“k1”,”22”,{“path”:””,”domin”:””,expires=1}) 10 上面的cookie中的数组,在内部用了join方法分割成了字符串
tornado支持两种方式
-
一、简单的方式
-
二、签名的方式
首先服务端让浏览器端生成cookie的时候会经过base64加密,首先生成加密串, 注意这里的当前时间是
import time time.time() 1491471613.5271676 --->生成的这个值就是当前时间
加密串 =v1(value)+当前时间+内部自定义字符串 之后生成的这个cookie就是k1(key)=v1|加密串|当前时间
如何验证这个cookie有没有被篡改: 客户端向浏览器端发送请求:会把v1和加密串和当前时间发送给浏览器,浏览器内部会经过md5生成一个新的加密串
自定义字符串+发送过来的时间+v1等于新的加密串,然后加密串进行对比,如果一致就能通过

1import tornado.ioloop 2import tornado.web 3 4class IndexHandler(tornado.web.RequestHandler): 5 #这里判断判断用户登录 6 def get(self): 7 if self.get_argument("u",None) in ["aa","eric"]: 8 self.set_cookie("name",self.get_argument("u")) 9 # self.set_secure_cookie("name",self.get_argument("u")) 10 else: 11 self.write("请登录") 12 13class ManagerHandler(tornado.web.RequestHandler): 14 #如果有cookie的时候就登录 15 def get(self): 16 if self.get_cookie("name",None) in ["aa","eric"]: 17 self.write("欢迎登录:"+self.get_cookie("name")) 18 else: 19 self.redirect("/index") 20 21settings={ 22 "template_path":"views", 23 "static_path":"statics" 24} 25application=tornado.web.Application([ 26 (r"/index",IndexHandler), 27 (r"/manager",ManagerHandler) 28],**settings) 29 30if __name__=="__main__": 31 application.listen(8000) 32 tornado.ioloop.IOLoop.instance().start() 33 34# 上面就是用一种简单的模式登录,登录的时候 35# 在浏览器中输入 36# http://127.0.0.1:8000/index?u=aa 37# 之后就会执行IndexHandler方法中的get方法首先存入用户输入的cookie,对比后台,然后访问manager网站的时候,判断,如果有对应的cookie那么就会出现欢迎登录
基于cookie实现用户验证

1import tornado.ioloop 2import tornado.web 3 4class IndexHandler(tornado.web.RequestHandler): 5 #这里判断判断用户登录 6 def get(self): 7 if self.get_argument("u",None) in ["alex","eric"]: 8# 这里设置加密的cookie 9 self.set_secure_cookie("user",self.get_argument("u")) 10 else: 11 self.write("请登录") 12 13 14class ManagerHandler(tornado.web.RequestHandler): 15 #如果有cookie的时候就登录 16 def get(self): 17# 获取加密的cookie 18 if str(self.get_secure_cookie("user",None),encoding="utf-8") in ["alex","eric"]: 19 self.write("欢迎登录:"+str(self.get_secure_cookie("user"))) 20 else: 21 self.redirect("/index") 22 23settings={ 24 "template_path":"views", 25 "static_path":"statics", 26# 这必须设置配置 27 "cookie_secret":"hello", 28} 29application=tornado.web.Application([ 30 (r"/index",IndexHandler), 31 (r"/manager",ManagerHandler) 32],**settings) 33 34if __name__=="__main__": 35 application.listen(8000) 36 tornado.ioloop.IOLoop.instance().start() 37 38# 设置加密的cookie用set_secure_cookie()方法,如果获取cookie的时候用get_secure_cookie() 39# 注意这里获取加密cookie 40# 注意:这里获取的cookie是byte类型,所以必须要转换一下类型
下面是带签名的cookie
1Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息, 2你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 3 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 4你可以把它作为一个关键词参数传入应用的设置中 5 6签名Cookie的本质是: 7 8写cookie过程: 9 10将值进行base64加密 11对除值以外的内容进行签名,哈希算法(无法逆向解析) 12拼接 签名 + 加密值 13读cookie过程: 14 15读取 签名 + 加密值 16对签名进行验证 17base64解密,获取值内容 18注:许多API验证机制和安全cookie的实现机制相同
session:
我们将许多信息放在cookie中势必会造成浏览器端的臃肿, 此时便需要在服务端保存原本在浏览器端的那些键值对. 在浏览器端只需存储一个表示身份的随机加密字符串, 当浏览器端访问服务端时候携带该字符串, 经过比较, 验证合法之后便可以取该用户在服务端存储的相应信息. 但是在Tornado中并没有session的模块, 我们需要自定义来实现.
session其实就是定义在服务器端用于保存用户回话的容器,其必须依赖cookie才能实现。
所有的web框架都是session[key]=value的方法实现的
优化后代码:
在Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作
这里的initialize就是钩子函数

1#定义tornado中的钩子函数和反射函数来优化下面的类 2class BaseHandler(tornado.web.RequestHandler): 3 def initialize(self): 4 self.session=Session(self) 5 6class IndexHandler(BaseHandler): 7 #这里判断判断用户登录,get方法是被反射调用的getattr 8 def get(self): 9 if self.get_argument("u",None) in ["aa","eric"]: 10 self.session.set_value("is_login",True) 11 self.session.set_value("name",self.get_argument("u",None)) 12 else: 13 self.write("请登录") 14 15class ManagerHandler(BaseHandler): 16 def get(self): 17 val=self.session.get_value("is_login") 18 if val: 19 self.write(self.session.get_value("name")) 20 else: 21 self.write("请重新登录")
两个类继承一个共同的父类,利用tornado内置的钩子函数来优化代码

1#!/usr/bin/env python 2# -*- coding:utf-8 -*- 3import tornado.ioloop 4import tornado.web 5 6#这个字典必须定制成为全局变量用来保存用户的信息,如果是局部变量, 7# 那么http请求断开下次用户登录这个用户信息就会消失 8container={} 9 10#把Sesson封装起来 11class Session: 12 def __init__(self,handler): 13 self.handler=handler 14 self.random_str=None #用户连接初始化随机数 15 def __genarate_random_str(self): #创建随机字符串 16 import hashlib 17 import time 18 #首先通过md5生成随机数据,电脑中的数据都是16进制保存的 19 obj=hashlib.md5() 20 ## 加入自定义参数来更新md5对象 21 obj.update(bytes(str(time.time()),encoding="utf-8")) 22 # 得到加严后的十六进制随机字符串来作为用户的索引 23 random_str=obj.hexdigest() 24 return random_str 25 26 def __setitem__(self,key,value): 27 #这里判断如果服务端没有随机数 28 if not self.random_str: #用户连接,首先服务端没有随机数,那么去客户端拿随机数 29 random_str=self.handler.get_cookie("__kakaka__") #去客户端中拿随机数 30 if not random_str: #如果客户端也没有随机数,那么服务端就自己创建随机数 31 random_str=self.__genarate_random_str() #创建随机数 32 container[random_str]={} #清空随机数字典中的内容 33 else: 34 if random_str in container.keys(): #如果客户端有随机数,并且为真那么就直接登录成功 35 pass 36 else: #如果客户端到的随机数是伪造的,那么服务端就自己创建随机数 37 random_str=self.__genarate_random_str() 38 container[random_str]={} 39 self.random_str=random_str #最后把上面判断出来的随机数传递给类 40 container[self.random_str][key]=value 41 #这里是为写超时时间做准备 42 self.handler.set_cookie("__kakaka__",self.random_str) #设置cookie给浏览器,这里可以设置超时时间 43 44 45 def __getitem__(self,key): #获取值, 注意加密方式返回的cookie的值是bytes类型的 46 random_str=self.handler.get_cookie("__kakaka__") 47 # 如果客户端没有随机字符串(索引值为空表示当前用户是新用户,直接返回空,),就结束,程序到此终止 48 if not random_str: 49 return None 50 user_info_dict=container.get(random_str,None)#客户端有随机字符串,但是内容服务器不匹配,就退出 51 if not user_info_dict: 52 return None 53 value=user_info_dict.get(key,None) #前面如果都满足,有值就拿值,没有就为None 54 return value 55 56#定义tornado中的钩子函数和反射函数来优化下面的类 57class BaseHandler(tornado.web.RequestHandler): 58 def initialize(self): 59 self.session=Session(self) 60 61class IndexHandler(BaseHandler): 62 #这里判断判断用户登录,get方法是被反射调用的getattr 63 def get(self): 64 if self.get_argument("u",None) in ["aa","eric"]: 65 self.session["is_login"]=True 66 self.session["name"]=self.get_argument("u",None) 67 # self.session.set_value("is_login",True) 68 # self.session.set_value("name",self.get_argument("u",None)) 69 else: 70 self.write("请登录") 71 72class ManagerHandler(BaseHandler): 73 def get(self): 74 val=self.session["is_login"] 75 if val: 76 self.write(self.session["name"]) 77 else: 78 self.write("请重新登录") 79 80 81settings={ 82 "template_path":"views", 83 "static_path":"statics", 84 "cookie_secret":"hello", 85} 86application=tornado.web.Application([ 87 (r"/index",IndexHandler), 88 (r"/manager",ManagerHandler) 89],**settings) 90 91if __name__=="__main__": 92 application.listen(8003) 93 tornado.ioloop.IOLoop.instance().start()
__getitem__/__setitem__优化后

1用户如果直接连接manager会提示必须登录,主要原因是浏览器cookie中没有登录信息 21、 用户访问index网页的时候就是访问IndexHandler这个类,用户连接,服务器内部就会初始化随机数 32、 服务器就会执行set_value方法,并且传入参数is_login参数,首先为了判断用户是否第一次登陆,所以用if not self.random_str,没有就用get_cookie()方法去客户端中拿随机数,这里需要判断,如果客户端也没有随机数,那么服务端就要自己创建随机数,并且把这个随机数传递给服务器这个类;如果客户端有随机数,要判断这个随机数是否是伪造的,如果是伪造的,服务器需要自己创建随机数,并且把这个随机数传递给服务器这个类;之后把is_login参数替代key传递给session这个字典求出来value这个值,并且设置一下这个cookie传递给浏览器;然后设置key为name的cookie 43、 用户访问manager这个网站,会执行get方法,并且获取浏览器随机数,如果浏览器中没有随机数或者浏览器的随机数是伪造的,那么就会退出,如果经过了2这个步骤,那么就能登录成功并且得到设置cookie中key为name的值
流程详解
验证码:
1验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面。 2这个验证码是放在tornado的session里面的 3验证码机制:服务器首先创建验证码,并且把验证码放入到随机数这个字典里面,用户通过get方法接收到验证码,然后用户输入验证码和账户信息发送给服务器,服务器通过对比用户发来的验证码和自己产生的验证码,(这里要创建不分辨大小写,可以让用户输入的和自己产生的转成全部大写或者全部小写)对比,如果一样那么就显示登录成功,如果没有一样,那么就显示输入的验证码错误。并且在前端添加一个点击事件,只要用户一点击那么验证码就会刷新pip3 install pillow 安装图像处理模块

1<!DOCTYPE html> 2<html> 3<head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <style> 7 .aa{ 8 cursor: pointer; 9 } 10 </style> 11</head> 12<body> 13 <h1>请输入登录信息</h1> 14 <form action="/login" method="post"> 15 <p><input name="user" type="text" placeholder="用户名" /></p> 16 <p><input name="pwd" type="password" placeholder="密码" /></p> 17 <p> 18 <input name='code' type="text" placeholder="验证码" /> 19 <img class="aa" src="/check_code" onclick='ChangeCode();' id='imgCode'> 20 </p> 21 <input type="submit" value="提交"/><span style="color: red">{{status}}</span> 22 </form> 23 <script type="text/javascript"> 24 25 function ChangeCode() { 26 var code = document.getElementById('imgCode'); 27 //url后面只能添加问号,添加问号就是改变地址 28 code.src += '?'; 29 } 30 </script> 31</body> 32</html>
login.html

1#/usr/bin/env python 2#-*- coding:utf-8-*- 3import tornado.ioloop 4import tornado.web 5import tornado.httpserver 6import tornado.ioloop 7import tornado.process 8import tornado.web 9# 10 11 12#这个字典必须定制成为全局变量用来保存用户的信息,如果是局部变量, 13# 那么http请求断开下次用户登录这个用户信息就会消失 14container={} 15 16 #把Sesson封装起来 17class Session: 18 def __init__(self,handler): 19 self.handler=handler 20 self.random_str=None #用户连接初始化随机数 21 def __genarate_random_str(self): #创建随机字符串 22 import hashlib 23 import time 24 #首先通过md5生成随机数据,电脑中的数据都是16进制保存的 25 obj=hashlib.md5() 26 obj.update(bytes(str(time.time()),encoding="utf-8")) 27 random_str=obj.hexdigest() 28 return random_str 29 def __setitem__(self,key,value): 30 #这里判断如果服务端没有随机数 31 if not self.random_str: #用户连接,首先服务端没有随机数,那么去客户端拿随机数 32 random_str=self.handler.get_cookie("__kakaka__") #去客户端中拿随机数 33 if not random_str: #如果客户端也没有随机数,那么服务端就自己创建随机数 34 random_str=self.__genarate_random_str() #创建随机数 35 container[random_str]={} #清空随机数字典中的内容 36 else: 37 if random_str in container.keys(): #如果客户端有随机数,并且为真那么就直接登录成功 38 pass 39 else: #如果客户端到的随机数是伪造的,那么服务端就自己创建随机数 40 random_str=self.__genarate_random_str() 41 container[random_str]={} 42 self.random_str=random_str #最后把上面判断出来的随机数传递给类 43 container[self.random_str][key]=value 44 #这里是为写超时时间做准备 45 self.handler.set_cookie("__kakaka__",self.random_str) #设置cookie给浏览器,这里可以设置超时时间 46 47 48 def __getitem__(self,key): #获取值 49 random_str=self.handler.get_cookie("__kakaka__") 50 if not random_str:#如果客户端没有随机字符串,就结束 51 return None 52 user_info_dict=container.get(random_str,None)#客户端有随机字符串,但是内容服务器不匹配,就退出 53 if not user_info_dict: 54 return None 55 value=user_info_dict.get(key,None) #前面如果都满足,有值就拿值,没有就为None 56 return value 57 58#定义tornado中的钩子函数和反射函数来优化下面的类 59class BaseHandler(tornado.web.RequestHandler): 60 def initialize(self): 61 self.session=Session(self) 62 63class IndexHandler(BaseHandler): 64 #这里判断判断用户登录,get方法是被反射调用的getattr 65 def get(self): 66 if self.get_argument("u",None) in ["aa","eric"]: 67 self.session["is_login"]=True 68 self.session["name"]=self.get_argument("u",None) 69 # self.session.set_value("is_login",True) 70 # self.session.set_value("name",self.get_argument("u",None)) 71 else: 72 self.write("请登录") 73 74class ManagerHandler(BaseHandler): 75 def get(self): 76 val=self.session["is_login"] 77 if val: 78 self.write(self.session["name"]) 79 else: 80 self.write("请重新登录") 81# class CheckCodeHandler(BaseHandler): 82# def get(self): 83# import io 84# import check_code 85# 86# mstream = io.BytesIO() 87# img, code = check_code.create_validate_code() 88# img.save(mstream, "GIF") 89# # self.session["CheckCode"] = code 90# self.write(mstream.getvalue()) 91class MainHandler(BaseHandler): 92 def get(self): 93 self.render('login.html',status="") 94 def post(self, *args, **kwargs): 95 user=self.get_argument("user",None) 96 pwd=self.get_argument("pwd",None) 97 code=self.get_argument("code",None) 98 #比较用户输入的验证码和服务器给出的验证码的值 99 check_code=self.session["CheckCode"] 100 if code.upper()==check_code.upper(): 101 self.write("验证码正确") 102 else: 103 # self.redirect("/login") 104 self.render("login.html",status="验证码错误") 105 106 107class CheckCodeHandler(BaseHandler): 108 def get(self, *args, **kwargs): 109 #生成图片并且返回 110 import io 111 import check_code 112 #建立内存级别文件,相当于一个容器 113 mstream = io.BytesIO() 114 #创建图片并且写入验证码 115 img, code = check_code.create_validate_code() 116 #将图片内容写入到IO中mstream 117 img.save(mstream, "GIF") 118 #为每个用户保存其对应的验证码 119 self.session["CheckCode"] = code 120 self.write(mstream.getvalue()) 121 122settings={ 123 'template_path': 'views', 124 'static_path': 'static', 125 "static_url_prefix":"/statics/", 126 "cookie_secret":"hello", 127 # "xsrf_cookies":True, 128} 129 130application=tornado.web.Application([ 131 (r"/index",IndexHandler), 132 (r"/manager",ManagerHandler), 133 # (r"/login",LoginHandler), 134 (r"/login",MainHandler), 135 (r"/check_code",CheckCodeHandler), 136 137 138],**settings) 139 140if __name__=="__main__": 141 application.listen(8000) 142 tornado.ioloop.IOLoop.instance().start()
python代码

下载下面源码之后,需要把check_code.py和Monaco.ttf导入到这个代码目录中(仅仅限制与python3.5)
验证码Demo源码下载:猛击这里
CSRF:
1会集群要会:分布式哈希haxi redis 2CSRF限制post请求的 3用户访问是先请求服务器调用get请求,然后发送post请求,之后服务器会给用户一个随机字符串,当用户离开后,下次再访问会带着这个随机字符串访问服务器,如果用户没有这个随机字符串,那么CSRF会阻止这个用户请求,这样可以使服务器免遭受恶意攻击造成服务器宕机
要加上CSRF:
1、在配置文件中加上配置文件”xsrf_cookies”:True
2、在前台代码中加上{% raw xsrf__form_html %}

1class CsrfHandler(BaseHandler): 2 def get(self, *args, **kwargs): 3 self.render("csrf.html") 4 def post(self, *args, **kwargs): 5 self.write("csrf.post") 6settings={ 7 'template_path': 'views', 8 'static_path': 'static', 9 "static_url_prefix":"/statics/", 10 "cookie_secret":"hello", 11 "xsrf_cookies":True, 这里加上配置文件 12} 13 14application=tornado.web.Application([ 15 (r"/index",IndexHandler), 16 (r"/manager",ManagerHandler), 17 # (r"/login",LoginHandler), 18 (r"/login",MainHandler), 19 (r"/check_code",CheckCodeHandler), 20 (r"/csrf",CsrfHandler) 21 22 23],**settings)
View Code

1<form action="/csrf" method="post"> 2 {% raw xsrf_form_html() %} 3 <p><input type="text" placeholder="用户"/></p> 4 <p><input type="text" placeholder="密码"/></p> 5 <p> 6 <input name="code" type="text" placeholder="验证码"/> 7 <!--<img src="https://my.oschina.net/check_code">--> 8 </p> 9 <input type="submit" value="Submit"/>
html
提交的是AJAX的post请求
如果你提交的是 AJAX 的 POST 请求,你还是需要在每一个请求中通过脚本添加上 _xsrf 这个值。下面是在 FriendFeed 中的 AJAX 的 POST 请求,使用了 jQuery 函数来为所有请求组添加 _xsrf 值:
1function getCookie(name) { 2 var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); 3 return r ? r[1] : undefined; 4} 5 6jQuery.postJSON = function(url, args, callback) { 7 args._xsrf = getCookie("_xsrf"); 8 $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", 9 success: function(response) { 10 callback(eval("(" + response + ")")); 11 }}); 12};
对于 PUT 和 DELETE 请求(以及不使用将 form 内容作为参数的 POST 请求) 来说,你也可以在 HTTP 头中以 X-XSRFToken这个参数传递 XSRF token。
如果你需要针对每一个请求处理器定制 XSRF 行为,你可以重写 RequestHandler.check_xsrf_cookie()。例如你需要使用一个不支持 cookie 的 API, 你可以通过将 check_xsrf_cookie() 函数设空来禁用 XSRF 保护机制。然而如果 你需要同时支持 cookie 和非 cookie 认证方式,那么只要当前请求是通过 cookie 进行认证的,你就应该对其使用 XSRF 保护机制,这一点至关重要。
11、cookie和session的区别 21)cookie是保存在客户端的,session是保存在服务端的,因为服务器端,表示可能在内存中,可能在数据库端,可能在缓存中统称为服务器端 32、session和cookie有什么联系?: 4答:有。session是通过cookie人为构建起来的,在web开发里面本身没有session这个东西的。在服务器端可以高层一个数据库,可以在内存中搞成一个字典,每一次用户来访问的时候,给用户发一对token,下一次,用户访问再带着这一对token来,服务器端就知道你是不是上一次的你。如果再问就来画一张图 5 63、分页 XSS 跨站脚本攻击 74、CSRF/别名XSRF工作方式: 8答:跨站请求伪造, 理解为:攻击者盗用了你的身份,以你的名义发送恶意请求 9验证:第一次请求的时候是get方式请求,防止没有经过验证就来post请求,造成大并发机器宕机 105、 Ajax 11为什么要有Ajax 12答:防止页面批量刷新 13 利用: 14 iframe 忽略 15 XMLHttpRequest 16 自己写 17 xhr 18 xhr.open() 19 xhr.onreadystatechange 20 xhr.send() 21 jQuery 22 会用下面的就会jquery,ajax 23 $.ajax({ 24 url: 25 type 26 data 27 dataType 28 success 29 error 30}) 316、 验证码、 327、 上传文件 33form标签 34 form标签 enctype=““form标签里面必须要有这个才能进行上传文件 35通过Ajax上传文件 36 利用formDate() 37 XMLHttpRequest 38 jQuery 39 iframe+form标签为了兼容性设计,ifram就相当于设置一个通道,form把数据提交到这个通道,然后不刷页面上传文件
tornado 结合前端进行文件上传:
在表单中我们获取用户提交的数据,使用的是get_argument,复选框使用的是get_arguments,但是文件的不一样,文件的使用request.files。
form文件上传

1<!DOCTYPE html> 2<html> 3<head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 5 <title>上传文件</title> 6</head> 7<body> 8 <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" > 9 <input name="fff" id="my_file" type="file" /> 10 <input type="submit" value="提交" /> 11 </form> 12</body> 13</html>
HTML
1注意: 2 3form文件上传,一定要在form表单上设置enctype的参数。enctype="multipart/form-data"。不然上传无法成功。

1import tornado.ioloop 2import tornado.web 3 4class MainHandler(tornado.web.RequestHandler): 5 def get(self): 6 self.render('index.html') 7 8 def post(self, *args, **kwargs): 9 file_metas = self.request.files["fff"] 10 # print(file_metas) 11 for meta in file_metas: 12 file_name = meta['filename'] 13 with open(file_name, 'wb') as up: 14 up.write(meta['body']) 15 16 17settings = { 18 'template_path': 'template', 19} 20 21application = tornado.web.Application([ 22 (r"/index", MainHandler), 23], **settings) 24 25if __name__ == "__main__": 26 application.listen(8003) 27 tornado.ioloop.IOLoop.instance().start()
PYthon
1说明: 2 31、代码中self.request封装了所有发送过来请求的内容。 4 52、self.request.files:可以获取上传文件的所有信息。此方法获取的是一个生成器,内部是由yield实现的,因此我们在利用此方法返回的对象的时候,不能通过下标获取里面的对象,只能通过迭代的方法。 6 73、迭代出来的对象的filename:就表示上传文件的文件名。 8 94、迭代出来的对象的body:表示上传文件的内容。获取的文件内容是字节形式的。
ajax上传文件
- 原生ajax
- jquery
原生ajax上传文件

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();" /> 10 <script> 11 function UploadFile(){ 12 var fileObj = document.getElementById("img").files[0]; 13 14 var form = new FormData(); 15 form.append("k1", "v1"); 16 form.append("fff", fileObj); 17 18 var xhr = new XMLHttpRequest(); 19 xhr.open("post", '/index', true); 20 xhr.send(form); 21 } 22 </script> 23</body> 24</html>
HTML
1说明: 2 3代码中利用原生的ajax进行文件上传。 4 5关键点: 6 71、获取文件对象,通过files[0],获取当前上传的文件对象。 8 92、通过FormData(),实例化一个对象form对象。 10 113、然后将要传递的参数,文件以键和值以逗号分隔的形式append到form对象中去。 12 134、然后将整个form对象发送到服务端。 14 15注意: 16 17后台代码和上面的代码一样,不变。注意接收的文件名要同步。
jquery文件上传:

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();" /> 10 <script> 11 function UploadFile(){ 12 var fileObj = $("#img")[0].files[0]; 13 var form = new FormData(); 14 form.append("k1", "v1"); 15 form.append("fff", fileObj); 16 17 $.ajax({ 18 type:'POST', 19 url: '/index', 20 data: form, 21 processData: false, // tell jQuery not to process the data 22 contentType: false, // tell jQuery not to set contentType 23 success: function(arg){ 24 console.log(arg); 25 } 26 }) 27 } 28 </script> 29</body> 30</html>
HTML
1说明: 2 31、和原生的一样,都是显得获取当前上传文件的对象。files[0];然后实例化form对象,将要传递的内容append到实例化的对象form中。 4 52、后台代码同前,注意字段名对应。 6 7关键点: 8 9processData:false和contentType:false。这2个是关键。 10 11默认的jquery会将我们上传的数据做部分处理。上面两段代码,就是告诉jquery不要处理我们的文件,不然会将我们的文件处理得不完整。
iframe文件上传
原生的ajax和jquery上传的时候,我们都是通过实例化一个form对象来进行文件的上传。但是实例化这个form的对象并不是所有的浏览器都存在,比如低版本的IE就可能没有合格FormData对象,那上面的方法就存在兼容性,没有form对象就不能发送。因此的使用一个兼容性更好的来进行操作,iframe。

1<!DOCTYPE html> 2<html> 3<head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6</head> 7<body> 8 <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" > 9 <div id="main"> 10 <input name="fff" id="my_file" type="file" /> 11 <input type="button" name="action" value="Upload" onclick="redirect()"/> 12 <iframe id='my_iframe' name='my_iframe' src="" class="hide"></iframe> 13 </div> 14 </form> 15 16 <script> 17 function redirect(){ 18 document.getElementById('my_iframe').onload = Testt; 19 document.getElementById('my_form').target = 'my_iframe'; 20 document.getElementById('my_form').submit(); 21 22 } 23 24 function Testt(ths){ 25 var t = $("#my_iframe").contents().find("body").text(); 26 console.log(t); 27 } 28 </script> 29</body> 30</html>
html
1关键点: 2 31、document.getElementById('my_form').target = 'my_iframe':这段代码就是获取iframe标签。 4 5 target就是目标,只要给form设置了target的话,form提交的时候,就会提交到这个target指定的目标上。所以上面的代码表示只要form提交,就会提交到iframe上去。 6 72、当iframe操作完后会执行Testt方法,Testt方法就是获取后台返回的信息,并打印。
Jsonp实现ajax跨域请求
同源策略
浏览器有一个很重要的概念——同源策略(Same-Origin Policy)。所谓同源是指,域名,协议,端口相同。不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。比较特别的是:由于同源策略是浏览器的限制,所以请求的响应和发送是可以进行的,只不过浏览器不支持罢了.
- 限制:XmlHttpRequest, AJax
- 不限制:img iframe script块等等具有src属性的标签
JSONP(JSON with Padding)
是JSON的一种”使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的script 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析
基本思路
利用script标签,src导入目标域名的接口,在文档数的head标签中添加一行script标签,得到内容后将scprit标签删除,返回的解析后的参数即为得到的数据.

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title></title> 6</head> 7<body> 8 <h1>Index</h1> 9 10 <input type="button" onclick="Ajax();" value="普通AJax"/> 11 <input type="button" onclick="Ajax2();" value="跨域普通AJax"/> 12 <input type="button" onclick="Ajax3();" value="跨域牛逼AJax"/> 13 <input type="button" onclick="Ajax4();" value="江西TV"/> 14 15 <script src="/static/jquery-2.1.4.min.js"></script> 16 <script> 17 // 原生ajax,测试无效 18 function Ajax(){ 19 $.ajax({ 20 url: '/get_data/', 21 type: 'POST', 22 data: {'k1': 'v1'}, 23 success: function (arg) { 24 alert(arg); 25 } 26 }) 27 } 28 // 使用ajax跨域请求,测试无效 29 function Ajax2(){ 30 $.ajax({ 31 url: 'http://wupeiqi.com:8001/api/',//需修改 32 type: 'GET', 33 data: {'k1': 'v1'}, 34 success: function (arg) { 35 alert(arg); 36 } 37 }) 38 } 39 40 // 利用script标签,得到数据 41 function Ajax3(){ 42 // script 43 // alert(api) 44 var tag = document.createElement('script'); 45 tag.src = 'http://wupeiqi.com:8001/api/';//需修改 46 document.head.appendChild(tag); 47 document.head.removeChild(tag); 48 } 49 function fafafa(arg){ 50 console.log(arg); 51 } 52 53 // 例子,获取江西卫视的节目单 54 function Ajax4(){ 55 // script 56 // alert(api) 57 var tag = document.createElement('script'); 58 tag.src = 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403'; 59 document.head.appendChild(tag); 60 document.head.removeChild(tag); 61 } 62 function list(arg){ 63 console.log(arg); 64 } 65 </script> 66</body> 67</html>
利用script标签实现跨域代码
JSONP实现ajax跨域
以上的代码其实也是jsonp的基本思路

1$.ajax({ 2 url:.. 3 type: 'GET', 4 dataType: 'jsonp', 5 //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) 6 jsonp: 'callback', 7 //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 8 jsonpCallback: 'list' 9}) 10 11function list(arg){ 12 13}
基本的jsonp写法
1jsonp: callback #发送给请求处理程序的,被请求端通过request.GET.get("callback"),获得jsonp回调函数的参数 2 3jsonpCallback: 'list' #定义回调函数的名称,然后后面通过list(...)来处理获取数据

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="Jsonp1();" value='提交'/> 11 </p> 12 13 <p> 14 <input type="button" onclick="Jsonp2();" value='提交'/> 15 </p> 16 17 <script type="text/javascript" src="jquery-1.12.4.js"></script> 18 <script> 19 function Jsonp1(){ 20 var tag = document.createElement('script'); 21 tag.src = "http://c2.com:8000/test/"; 22 document.head.appendChild(tag); 23 document.head.removeChild(tag); 24 25 } 26 27 function Jsonp2(){ 28 $.ajax({ 29 url: "http://c2.com:8000/test/", 30 type: 'GET', 31 dataType: 'JSONP', 32 success: function(data, statusText, xmlHttpRequest){ 33 console.log(data); 34 } 35 }) 36 } 37 38 39 </script> 40</body> 41</html>
生产示例-###基于JSONP实现跨域Ajax - Demo
JSONP不能发送POST请求
究其根源,通过script标签的src属性进行跨域请求,<script src='http://www.jxntv.cn/data/jmd-jxtv2.html?callback=qwerqweqwe&_=1454376870403'>最后全部都会转换成GET请求,哪怕是你把type改为POST.
其它例子参考
其他ajax跨站请求方式
需要顺带提一句的是,跨站请求还有另一种方式:cors,跨站资源共享,但此中方式对浏览器版本有要求,IE8以下的均不支持.
CORS与JSONP相比,无疑更为先进、方便和可靠。
1 1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。 2 3 2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。 4 5 3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS(这部分会在后文浏览器支持部分介绍)。
CORS(跨域资源共享)
背后的原理是:使用自定的HTTP头部与服务器进行沟通,从而由服务器决定响应是否成功。
它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
**只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
HEAD
GET
POST
2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain**
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
简单请求和非简单请求的区别?
简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
1预检: 2请求方式:OPTIONS 3- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息 4- 如何“预检” 5 => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过 6 Access-Control-Request-Method 7 => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过 8 Access-Control-Request-Headers
基于cors实现AJAX请求:
a、支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

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="submit" onclick="XmlSendRequest();" /> 11 </p> 12 13 <p> 14 <input type="submit" onclick="JqSendRequest();" /> 15 </p> 16 17 <script type="text/javascript" src="jquery-1.12.4.js"></script> 18 <script> 19 function XmlSendRequest(){ 20 var xhr = new XMLHttpRequest(); 21 xhr.onreadystatechange = function(){ 22 if(xhr.readyState == 4) { 23 var result = xhr.responseText; 24 console.log(result); 25 } 26 }; 27 xhr.open('GET', "http://c2.com:8000/test/", true); 28 xhr.send(); 29 } 30 31 function JqSendRequest(){ 32 $.ajax({ 33 url: "http://c2.com:8000/test/", 34 type: 'GET', 35 dataType: 'text', 36 success: function(data, statusText, xmlHttpRequest){ 37 console.log(data); 38 } 39 }) 40 } 41 42 43 </script> 44</body> 45</html> 46 47HTML
HTML

1class MainHandler(tornado.web.RequestHandler): 2 def get(self): 3 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 4 self.write('{"status": true, "data": "seven"}')
tornado
b、支持跨域,复杂请求
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
-
“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
-
“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
-
“预检”缓存时间,服务器设置响应头:Access-Control-Max-Age

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="submit" onclick="XmlSendRequest();" /> 11 </p> 12 13 <p> 14 <input type="submit" onclick="JqSendRequest();" /> 15 </p> 16 17 <script type="text/javascript" src="jquery-1.12.4.js"></script> 18 <script> 19 function XmlSendRequest(){ 20 var xhr = new XMLHttpRequest(); 21 xhr.onreadystatechange = function(){ 22 if(xhr.readyState == 4) { 23 var result = xhr.responseText; 24 console.log(result); 25 } 26 }; 27 xhr.open('PUT', "http://c2.com:8000/test/", true); 28 xhr.setRequestHeader('k1', 'v1'); 29 xhr.send(); 30 } 31 32 function JqSendRequest(){ 33 $.ajax({ 34 url: "http://c2.com:8000/test/", 35 type: 'PUT', 36 dataType: 'text', 37 headers: {'k1': 'v1'}, 38 success: function(data, statusText, xmlHttpRequest){ 39 console.log(data); 40 } 41 }) 42 } 43 44 45 </script> 46</body> 47</html>HTML

1class MainHandler(tornado.web.RequestHandler): 2 3 def put(self): 4 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 5 self.write('{"status": true, "data": "seven"}') 6 7 def options(self, *args, **kwargs): 8 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 9 self.set_header('Access-Control-Allow-Headers', "k1,k2") 10 self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") 11 self.set_header('Access-Control-Max-Age', 10)Tornado
c 、跨域获取响应头
默认获取到的所有响应头只有基本信息,如果想要获取自定义的响应头,则需要再服务器端设置Access-Control-Expose-Headers。

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="submit" onclick="XmlSendRequest();" /> 11 </p> 12 13 <p> 14 <input type="submit" onclick="JqSendRequest();" /> 15 </p> 16 17 <script type="text/javascript" src="jquery-1.12.4.js"></script> 18 <script> 19 function XmlSendRequest(){ 20 var xhr = new XMLHttpRequest(); 21 xhr.onreadystatechange = function(){ 22 if(xhr.readyState == 4) { 23 var result = xhr.responseText; 24 console.log(result); 25 // 获取响应头 26 console.log(xhr.getAllResponseHeaders()); 27 } 28 }; 29 xhr.open('PUT', "http://c2.com:8000/test/", true); 30 xhr.setRequestHeader('k1', 'v1'); 31 xhr.send(); 32 } 33 34 function JqSendRequest(){ 35 $.ajax({ 36 url: "http://c2.com:8000/test/", 37 type: 'PUT', 38 dataType: 'text', 39 headers: {'k1': 'v1'}, 40 success: function(data, statusText, xmlHttpRequest){ 41 console.log(data); 42 // 获取响应头 43 console.log(xmlHttpRequest.getAllResponseHeaders()); 44 } 45 }) 46 } 47 48 49 </script> 50</body> 51</html>
HTML

1class 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)
Tornado
d、跨域传输cookie
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
-
浏览器端:XMLHttpRequest的withCredentials为true
-
服务器端:Access-Control-Allow-Credentials为true
-
注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *

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="submit" onclick="XmlSendRequest();" /> 11 </p> 12 13 <p> 14 <input type="submit" onclick="JqSendRequest();" /> 15 </p> 16 17 <script type="text/javascript" src="jquery-1.12.4.js"></script> 18 <script> 19 function XmlSendRequest(){ 20 var xhr = new XMLHttpRequest(); 21 xhr.onreadystatechange = function(){ 22 if(xhr.readyState == 4) { 23 var result = xhr.responseText; 24 console.log(result); 25 } 26 }; 27 28 xhr.withCredentials = true; 29 30 xhr.open('PUT', "http://c2.com:8000/test/", true); 31 xhr.setRequestHeader('k1', 'v1'); 32 xhr.send(); 33 } 34 35 function JqSendRequest(){ 36 $.ajax({ 37 url: "http://c2.com:8000/test/", 38 type: 'PUT', 39 dataType: 'text', 40 headers: {'k1': 'v1'}, 41 xhrFields:{withCredentials: true}, 42 success: function(data, statusText, xmlHttpRequest){ 43 console.log(data); 44 } 45 }) 46 } 47 48 49 </script> 50</body> 51</html>HTML

1class MainHandler(tornado.web.RequestHandler): 2 3 def put(self): 4 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 5 self.set_header('Access-Control-Allow-Credentials', "true") 6 7 self.set_header('xxoo', "seven") 8 self.set_header('bili', "daobidao") 9 self.set_header('Access-Control-Expose-Headers', "xxoo,bili") 10 11 self.set_cookie('kkkkk', 'vvvvv'); 12 13 self.write('{"status": true, "data": "seven"}') 14 15 def options(self, *args, **kwargs): 16 self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 17 self.set_header('Access-Control-Allow-Headers', "k1,k2") 18 self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") 19 self.set_header('Access-Control-Max-Age', 10)Tornado