前言
由于浏览器的同源策略,使得发送请求的时候,只能给本域发送请求,不能跨域发送请求。对于Ajax请求来说,跨域浏览器是不允许的。原因不是不能发送,Ajax跨域的时候,确实发送了请求,并且收到了另一个域名的响应。但是浏览器会报错,默认情况下,Ajax不允许跨域,而script,img, iframe 允许跨域, 基本上通过src引用去发送请求的,都可以跨域。src请求相当于发送了一个GET请求
那么如何让Ajax可以跨域请求呢?今天介绍两种方案
JSONP
什么是jsonp?jsonp是如何实现跨域请求的呢?
在前言中提到,对于Ajax提交的请求是不允许跨域,只能是本域请求,而浏览器允许具有src属性的元素跨域进行请求。那么是否可以借助src属性来帮助Ajax跨域呢?
答对了,这就是jsonp实现跨域请求的基本原理
优点:兼容性好,应用广泛
缺点:由于是src引用发送的请求,那么jsonp的请求只能是get请求,不能是其他的请求。
好了,说到这里,我们来看看,jsonp是如何跨域的
1 # Python write by yhy 2 import tornado.web 3 import tornado.ioloop 4 5 class IndexHandler(tornado.web.RequestHandler): 6 def get(self, *args, **kwargs): 7 self.write(‘func([11,22,33])‘) 8 9 # 说明一下这个‘static_url_prefix‘: ‘/statics/‘,的问题 10 settings = { 11 ‘template_path‘: ‘views‘, 12 ‘static_path‘: ‘statics‘, 13 ‘static_url_prefix‘: ‘/statics/‘, 14 } 15 16 application = tornado.web.Application([ 17 (r‘/index‘, IndexHandler) 18 ], **settings) 19 20 if __name__ == ‘__main__‘: 21 application.listen(8002) 22 tornado.ioloop.IOLoop.instance().start()
服务端tornado框架代码
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="/statics/jquery.js"></script> 7 </head> 8 <body> 9 <input type="button" value="Ajax" onclick="DoAjax();"> 10 <input type="button" value="JsonpAjax" onclick="DoJsonpAjax();"> 11 <script> 12 function func(arg) { 13 console.log(arg) 14 } 15 16 // Ajax 17 function DoAjax() { 18 $.ajax({ 19 url: ‘http://127.0.0.1:8002/index‘, 20 type: ‘POST‘, 21 data: {‘k1‘: ‘v1‘}, 22 success: function (arg) { 23 console.log(arg); 24 } 25 }) 26 } 27 28 // JsonAjax 29 function DoJsonpAjax() { 30 var tag = document.createElement(‘script‘); 31 tag.src = "http://127.0.0.1:8002/index"; 32 document.head.appendChild(tag); 33 document.head.removeChild(tag); 34 } 35 </script> 36 </body> 37 </html>
原生的AjaxJSONP
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="/statics/jquery.js"></script> 7 </head> 8 <body> 9 <input type="button" value="Ajax" onclick="DoAjax();"> 10 <input type="button" value="JsonpAjax" onclick="DoJsonpAjax();"> 11 <script> 12 // 这里的func函数就是等待被调用,等待跨域请求返回的调用函数的字符串调用,一旦调用就会在这个函数中拿到跨域请求的参数。 13 function func(arg) { 14 console.log(arg) 15 } 16 17 // Ajax 18 function DoAjax() { 19 $.ajax({ 20 url: ‘http://127.0.0.1:8002/index‘, 21 type: ‘POST‘, 22 data: {‘k1‘: ‘v1‘}, 23 success: function (arg) { 24 console.log(arg); 25 } 26 }) 27 } 28 29 // JsonAjax 30 function DoJsonpAjax() { 31 // 基于jquery的Ajax的跨域请求,只要指明dataType为‘jsonp‘,那么就相当于创建了一个带有src的script标签,跨域请求通常返回一个函数,这个函数包裹在<script> func(‘hello world’) <script> 标签里面。一旦返回到html页面的时候,将会调用提前在<script> <script> 标签中写好的函数。这个写好的<script> <script>标签中写好的函数就会拿到函数调用传递进来的参数‘hello world’。这样就实现了跨域的请求 32 $.ajax({ 33 url: ‘http://127.0.0.1:8002/index‘, 34 dataType: ‘jsonp‘, 35 jsonpCallBack: ‘func‘ 36 37 }) 38 } 39 </script> 40 </body> 41 </html>
基于jQuery的AjaxJSONP
** 思考:服务端的返回的func函数名称是否可以与客户端提前写好的回调函数名一致呢,如果一致的话,那么就不需要在服务端指定函数名了,客户端的回调函数叫什么名字,服务端就叫什么名字就好。
解决的方式:服务端拿到请求的url的参数,这个参数里面有一个callback参数,这个callback参数的值就是客户端请求的函数名称
那么服务端返回的代码就可以这么写:通过self.get_argument(‘callback‘)拿到客户端的回调函数名
服务端拿到返回的函数名
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <script src="../statics/jquery.js"></script> 8 </head> 9 <body> 10 <input type="button" onclick="AjaxJsonp();" value="Ajax" > 11 <input type="button" onclick="jQueryAjaxJsonp();" value="jqueryAjax"> 12 13 <script> 14 function getData(arg) { 15 console.log(arg); 16 } 17 function AjaxJsonp() { 18 var tag = document.createElement(‘script‘); 19 tag.src = ‘http://yhycn.com:8002/index?callback=getData‘; 20 document.head.appendChild(tag); 21 document.head.removeChild(tag); 22 } 23 function jQueryAjaxJsonp() { 24 $.ajax({ 25 url: ‘http://yhycn.com:8002/index‘, 26 dataType: ‘jsonp‘, 27 // 下面的jsonp: ‘callback‘,jsonCallBack: ‘getData‘, 相当于在请求的url中指定/index?callback=getData 28 jsonp: ‘callback‘, 29 jsonCallback: ‘getData‘, 30 }) 31 } 32 </script> 33 </body> 34 </html>
前端html代码
CORS:跨域资源共享(Cross-Origin Resource Sharing)
当客户端跨域请求时,服务端返回数据的时候,同时绑定一个标记,告诉浏览器这是跨域请求的数据,不要报错。当浏览器识别这个标示的时候,浏览器就不会报错了,这个表示就是加上一个响应头CORS。这样就实现了跨域请求。客户端请求与本域请求一样不变,服务端这是一个CORS响应头即可。
优点:客户端不需要修改原本的ajax跨域请求,只是服务端需要进行相应的设置即可
缺点:兼容性不好,由于IE浏览器对跨域资源共享的兼容性问题,因此cors应用不是很广泛
概念说明:
条件: 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 注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
1:简单的cors请求
对应简单的cors请求而言,与普通的本域请求一样,而服务端需要在set-header中设置运行的请求客户端地址
2:复杂的cors请求
对应复杂的cors请求而言,与普通的本域请求不一样的地方在于,请求方法可以是head, get , post之外的其他方法,可以自定义请求头,可以告诉客户端浏览器请求的时候携带cookie。客户端会先发送一个options自检请求,在发送一个type中指定的请求
而服务端对复杂的cors请求,首先会处理自检请求,在处理type中指定的请求
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="/statics/jquery.js"></script> 7 </head> 8 <body> 9 10 <input type="button" onclick="jQueryAjax();" value="jqueryAjax"> 11 <input type="button" onclick="jQueryAjaxComplecate();" value="jQueryAjaxComplecate" > 12 <script> 13 // TODO 简单的CORS请求, 请求的方法为 POST 14 function jQueryAjax() { 15 $.ajax({ 16 url: ‘http://yhycn.com:8002/cors‘, 17 type: ‘POST‘, 18 data: {‘k1‘: ‘v1‘}, 19 success: function (arg) { 20 console.log(arg) 21 } 22 }) 23 } 24 25 // TODO 复杂的cors, 请求的方式不是HEAD、GET、POST中的一种,满足复杂请求的要求, 可以自定义请求头,是否携带cookie 26 function jQueryAjaxComplecate() { 27 $.ajax({ 28 url: ‘http://yhycn.com:8002/cors‘, 29 type: ‘PUT‘, 30 // 设置一个请求头,自定义请求头, 自定义了headers也是属于复杂的请求,因此需要在后台允许这样的headers 31 headers: {‘h1‘: ‘hh1‘}, 32 // 指定在客户端跨域发送请求的时候,携带cookie 33 xhrFields:{withCredentials: true}, 34 data: {‘k1‘: ‘v1‘}, 35 success: function (arg) { 36 console.log(arg); 37 } 38 }) 39 } 40 </script> 41 </body> 42 </html>
请求代码
1 # Python write by yhy 2 # Python write by yhy 3 import tornado.web 4 import tornado.ioloop 5 6 class IndexHandler(tornado.web.RequestHandler): 7 def get(self, *args, **kwargs): 8 self.write(‘hello‘) 9 10 class CorsHandler(tornado.web.RequestHandler): 11 def get(self, *args, **kwargs): 12 self.write(‘{"status": 1, "message": "get"}‘) 13 14 def post(self, *args, **kwargs): 15 # 服务端设置一个允许跨域的响应头,响应头的key=‘Access-Control-Allow-Origin‘, value=客户端的域名, * 表示所有的客户端都可以 16 self.set_header(‘Access-Control-Allow-Origin‘, ‘*‘) 17 self.write(‘{"status": 1, "message": "post"}‘) 18 19 # 这个是浏览器自动发送的域检请求 20 def options(self, *args, **kwargs): 21 # 指定允许客户端发送PUT请求 22 self.set_header(‘Access-Control-Allow-Origin‘, ‘http://yhy.com:8001‘) 23 self.set_header(‘Access-Control-Allow-Methods‘, ‘PUT, DELETE‘) 24 # 允许自定义的请求头通过 25 self.set_header(‘Access-Control-Allow-Headers‘, ‘h1, h2‘) 26 self.set_header(‘Access-Control-Allow-Credentials‘, ‘true‘) 27 28 def put(self, *args, **kwargs): 29 # 给客户端设置cookie值,当客户端请求的时候,会携带cookie值, 30 # TODO 应该注意的是,如果客户端跨域请求的时候携带了cookie值的话,那么需要将Access-Control-Allow-Origin的值设置为客户端的域名地址URL 31 print(self.cookies) 32 self.set_cookie(‘k1‘, ‘kkkk‘) 33 self.set_header(‘Access-Control-Allow-Origin‘, ‘http://yhy.com:8001‘) 34 self.set_header(‘Access-Control-Allow-Credentials‘, ‘true‘) 35 self.write(‘put‘) 36 37 settings = { 38 ‘template_path‘: ‘views‘, 39 ‘static_path‘: ‘statics‘, 40 ‘static_url_prefix‘: ‘/statics/‘, 41 } 42 43 application = tornado.web.Application([ 44 (r‘/index‘, IndexHandler), 45 (r‘/cors‘, CorsHandler), 46 ], **settings) 47 48 if __name__ == ‘__main__‘: 49 application.listen(8002) 50 tornado.ioloop.IOLoop.instance().start()
服务端响应代码