第二百七十四节,同源策略和跨域Ajax
同源策略和跨域Ajax
什么是同源策略
URL | 结果 | 原因 |
http://store.company.com/dir2/other.html | 是 | |
http://store.company.com/dir/inner/another.html | 是 | |
https://store.company.com/secure.html | 不是 | 协议不相同 |
http://store.company.com:81/dir/etc.html | 不是 | 端口号不相同 |
http://news.company.com/dir/other.html | 不是 | 主机名不相同 |
127.0.0.1 www.fwd.com
127.0.0.1 www.khd.com
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="提交" onclick="doajax();"/> <script> function doajax() { $.ajax({ url: 'http://www.fwd.com:8001/fwd', type: 'POST', data:{'k1':'v1'}, success: function (responseText, statusText) { }, error: function (event, errorText, errorType) { } }); } </script> </body> </html>
http://www.fwd.com:8001/fwd
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> <script type="text/javascript" src='{{static_url("jquery.form.js")}}' charset="UTF-8"></script> </head> <body> 服务端 <script> </script> </body> </html>
此时点击按钮发起ajax请求后,可以看到请求已被浏览器的同源策略阻止
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="跨域提交" onclick="doajax();"/> <script> //利用原生js自己创建一个跨域请求 function doajax() { //当点击提交按钮是执行函数 var tag = document.createElement('script'); //创建一个script标签 tag.src = 'http://www.fwd.com:8001/fwd'; //设置script标签的src地址,为要请求数据的地址,因为src是不受同源策略 document.head.appendChild(tag); //将script标签添加到head标签里面 document.head.removeChild(tag); //添加后会立即发送请求,所以这里可以将添加的script标签删除了 } //响应网站返回,shuju({'k1':'v1'}); 也就相当于返回了一个带有参数的函数名称 function shuju(shj) { //自定义执行函数,和响应网站返回的名称相同,也就会执行自定义函数,而参数就是响应网站返回的数据 for(var i in shj){ //循环响应网站返回的数据 alert(i + ':' + shj[i]); //打印出数据的键和值 } } </script> </body> </html>
响应端
import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 # self.render("fwd.html") self.write("shuju({'k1':'v1'});") #返回数据 def post(self): self.write("post请求成功") settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="跨域提交" onclick="doajax();"/> <script> function doajax() { //当点击提交按钮是执行函数 $.ajax({ type: 'GET', url: 'http://www.fwd.com:8001/fwd', dataType: 'jsonp', //JSONP,数据类型 jsonpCallBack:'shj' //接收响应页面带有参数的函数名称 }); } //响应网站返回,shuju({'k1':'v1'}); 也就相当于返回了一个带有参数的函数名称 function shuju(shj) { //自定义执行函数,和响应网站返回的名称相同,也就会执行自定义函数,而参数就是响应网站返回的数据 for (var i in shj) { //循环响应网站返回的数据 alert(i + ':' + shj[i]); //打印出数据的键和值 } } </script> </body>
响应端
#!/usr/bin/env python #coding:utf-8 import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 # self.render("fwd.html") self.write("shuju({'k1':'v1'});") #返回数据 def post(self): self.write("post请求成功") settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()
简单请求和非简单请求的区别?
简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
条件:
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
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
self.set_header()方法,在响应端的逻辑处理的get或者post里使用,功能:给返回数据加上响应头标识告诉浏览器允许跨域请求
使用方法:两个参数,参数1,响应头标识,参数2,允许跨域请求的域名(多个域名,号隔开),(*代表所有域名支持跨域请求)
self.set_header('Access-Control-Allow-Origin','http://www.jxiou.com/')
self.set_header('Access-Control-Allow-Origin','*')
响应端
#!/usr/bin/env python #coding:utf-8 import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 pass def post(self): self.set_header('Access-Control-Allow-Origin','*') self.write("{'k1':'v1'}") # 返回数据 settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()
请求页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="跨域提交" onclick="doajax();"/> <script> function doajax() { //当点击提交按钮是执行函数 $.ajax({ type: 'POST', url: 'http://www.fwd.com:8001/fwd', data: {"a":'b'}, success: function (response, status, xhr) { alert(response); //打印返回数据 } }); } </script> </body> </html>
复杂请求
复杂请求:两次请求,在发送数据之前会先发一次OPTIONS请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") 预检请求或者数据请求时允许跨域,允许跨域的域名(多个逗号隔开)(*标识所有域名允许)
self.set_header('Access-Control-Allow-Headers', "k1,k2") 预检请求时允许请求页面ajax设置请求头headers属性跨域,允许跨域的headers属性请求头(多个逗号隔开)
self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") 预检请求时允许请求页面的请求方式跨域,允许跨域的请求方式(多个逗号隔开)
self.set_header('Access-Control-Max-Age', 10) 预检请求时设置预检有效时间,如设置10,表示这次预检后10秒后的请求再次预检
响应端
#!/usr/bin/env python #coding:utf-8 import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 pass def options(self): #接收预检的options请求 self.set_header('Access-Control-Allow-Origin', '*') #预检请求或者数据请求时允许跨域,允许跨域的域名(多个逗号隔开)(*标识所有域名允许) self.set_header('Access-Control-Allow-Methods', 'PUT') #预检请求时允许请求页面的请求方式跨域,允许跨域的请求方式(多个逗号隔开) self.set_header('Access-Control-Allow-Headers', "k1,k2") #预检请求时允许请求页面ajax设置请求头headers属性跨域,允许跨域的headers属性请求头(多个逗号隔开) def put(self): self.set_header('Access-Control-Allow-Origin','*') self.write("{'k1':'v1'}") # 返回数据 settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()
请求页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="跨域提交" onclick="doajax();"/> <script> function doajax() { //当点击提交按钮是执行函数 $.ajax({ type: 'PUT', //复杂请求 url: 'http://www.fwd.com:8001/fwd', headers: {'k1': 'qqtou'}, //设置请求头 data: {"a":'b'}, success: function (response, status, xhr) { alert(response); //打印返回数据 } }); } </script> </body> </html>
跨域传输cookie
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
浏览器端ajax属性:xhrFields的withCredentials为true
服务器端的预检请求和数据请求:Access-Control-Allow-Credentials为true
注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *
响应端
/usr/bin/env python #coding:utf-8 import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 pass def options(self): #接收预检的options请求 self.set_header('Access-Control-Allow-Credentials', "true") #传递Cookie头以及用户的SSL证书 self.set_header('Access-Control-Allow-Origin', 'http://www.khd.com:8002') #预检请求或者数据请求时允许跨域,允许跨域的域名 self.set_header('Access-Control-Allow-Methods', 'PUT') #预检请求时允许请求页面的请求方式跨域,允许跨域的请求方式(多个逗号隔开) self.set_header('Access-Control-Allow-Headers', "k1,k2") #预检请求时允许请求页面ajax设置请求头headers属性跨域,允许跨域的headers属性请求头(多个逗号隔开) def put(self): self.set_header('Access-Control-Allow-Credentials', "true") # 传递Cookie头以及用户的SSL证书 self.set_header('Access-Control-Allow-Origin','http://www.khd.com:8002') self.write("{'k1':'v1'}") # 返回数据 self.set_cookie('kkkkk', 'vvvvv') #写入cookie print(self.get_cookie('kkkkk')) #获取cookie settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()
请求页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src='{{static_url("jquery.min.js")}}' charset="UTF-8"></script> </head> <body> <input type="button" value="跨域提交" onclick="doajax();"/> <script> function doajax() { //当点击提交按钮是执行函数 $.ajax({ type: 'PUT', //复杂请求 url: 'http://www.fwd.com:8001/fwd', headers: {'k1': 'qqtou'}, //设置请求头 xhrFields:{withCredentials: true}, //传递Cookie头以及用户的SSL证书 data: {"a":'b'}, success: function (response, status, xhr) { alert(response); //打印返回数据 } }); } </script> </body> </html>
self.set_header('xxoo', "bili") 设置响应头第一个是响应头名称,第二个是响应头值
self.set_header('Access-Control-Expose-Headers', "xxoo,bili") 允许设置的响应头,请求端获取
#!/usr/bin/env python #coding:utf-8 import tornado.ioloop import tornado.web #导入tornado模块下的web文件 class fwdHandler(tornado.web.RequestHandler): def get(self): #接收get请求 pass def options(self): #接收预检的options请求 self.set_header('Access-Control-Allow-Credentials', "true") #传递Cookie头以及用户的SSL证书 self.set_header('Access-Control-Allow-Origin', 'http://www.khd.com:8002') #预检请求或者数据请求时允许跨域,允许跨域的域名 self.set_header('Access-Control-Allow-Methods', 'PUT') #预检请求时允许请求页面的请求方式跨域,允许跨域的请求方式(多个逗号隔开) self.set_header('Access-Control-Allow-Headers', "k1,k2") #预检请求时允许请求页面ajax设置请求头headers属性跨域,允许跨域的headers属性请求头(多个逗号隔开) def put(self): self.set_header('Access-Control-Allow-Credentials', "true") # 传递Cookie头以及用户的SSL证书 self.set_header('Access-Control-Allow-Origin','http://www.khd.com:8002') self.set_header('xxoo', "bili") #设置响应头第一个是响应头名称,第二个是响应头值 self.set_header('Access-Control-Expose-Headers', "xxoo,bili") #允许设置的响应头,请求端获取 self.write("{'k1':'v1'}") # 返回数据 self.set_cookie('kkkkk', 'vvvvv') #写入cookie print(self.get_cookie('kkkkk')) #获取cookie settings = { #html文件归类配置,设置一个字典 "template_path":"views", #键为template_path固定的,值为要存放HTML的文件夹名称 "static_path":"statics", #键为static_path固定的,值为要存放js和css的文件夹名称 } #路由映射 application = tornado.web.Application([ #创建一个变量等于tornado.web下的Application方法 (r"/fwd", fwdHandler), ],**settings) #将html文件归类配置字典,写在路由映射的第二个参数里 if __name__ == "__main__": #内部socket运行起来 application.listen(8001) #设置端口 tornado.ioloop.IOLoop.instance().start()