本篇主要讨论JSONP和CORS这两种技术,使用它们的原因是为了完成对资源的跨域访问,也就是如何绕过浏览器的同源策略Same-origin Policy。
那么什么是Same-origin Policy呢?简单地说,在一个浏览器中访问的网站不能访问另一个网站中的数据,除非这两个网站具有相同的Origin,也即是拥有相同的协议、主机地址以及端口。一旦这三项数据中有一项不同,那么该资源就将被认为是从不同的Origin得来的,进而不被允许访问。
特别的:由于同源策略是浏览器的限制,所以请求的发送和响应是可以进行,只不过浏览器不接受罢了。
浏览器同源策略并不是对所有的请求均制约:
- 制约: XmlHttpRequest
- 不叼: img、iframe、script等具有src属性的标签
解决方案:
1、-requests发请求时,跨域无限制
2、- ajax发请求时,浏览器限制【是否可以绕过限制?】
-JSONP
-CORS
requests模块当然可以通过跨域:
1 #####服务端(urls.py)###### 2 3 urlpatterns = [ 4 url(r'^admin/', admin.site.urls), 5 url(r'^get_data.html/$', views.get_data), 6 ]
1 ######服务端(views.py)###### 2 from django.shortcuts import render,HttpResponse 3 4 def get_data(request): 5 return HttpResponse(‘机密文件’)
1 #####客户端(views.py)###### 2 3 def index(request): 4 # 方式一:requests模块 5 import requests 6 response=requests.get("http://127.0.0.1:8000/get_data.html/") 7 return render(request,"index.html",{"response":response})
1 #####客户端(index.html)####### 2 3 <!DOCTYPE html> 4 <html lang="en"> 5 <head> 6 <meta charset="UTF-8"> 7 <meta http-equiv="x-ua-compatible" content="IE=edge"> 8 <meta name="viewport" content="width=device-width, initial-scale=1"> 9 <title>Title</title> 10 <script src="/static/jquery-3.2.1.js"></script> 11 </head> 12 <body> 13 <h1>JIAのHOMESITE</h1>
14{{ response.text }}
15 </body>
16 </html>
当然本篇主要讨论JSONP和CORS这两种跨域技术。
一、JSONP
依据:带有 src 属性的标签不受同源策略的影响(img, script, iframe等)。
我们可以通过使用 script 标签来获取内容,但 script 中 src 获取内容后,获得到的字符串(类似于调用python中的 eval 或 exec)会被 JS 执行,所以我们可以定义一个函数,然后让服务端返回的内容外面包裹一层这个函数名,前端在访问的时候把这个函数名发送过去,并提前定义好该函数。
当然,这种方法拥有一个显著的缺点,那就是只支持GET操作。
手动实现:
服务端返回的数据
1 from django.shortcuts import render,HttpResponse 2 3 4 def get_data(request): 5 return HttpResponse('func("机密文件")')
客户端定义及获取数据
1 urlpatterns = [ 2 url(r'^admin/', admin.site.urls), 3 url(r'^index.html/$', views.index), 4 url(r'^cors.html/$', views.cors), 5 ]
1 from django.shortcuts import render 2 3 4 def index(request): 5 return render(request,"index.html")
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Title</title> 8 <script src="/static/jquery-3.2.1.js"></script> 9 </head> 10 <body> 11 <h1>JIAのHOMESITE</h1> 12 <input type="button" value="获取数据" onclick="getInfo()" /> 13 14 <script> 15 var url = 'http://127.0.0.1:8000/get_data.html/'; // 服务端路径 16 var $script; // 模拟使用创建的标签名 17 18 function getInfo() { 19 $script = document.createElement('script'); // 创建一个 script 标签 20 $script.setAttribute('src',url); // 将需要请求数据的地址放入 script 的 src 中 21 document.head.appendChild($script); // 将标签放入到 head 中 22 } 23 24 function func(data) { // 数据返回后用来接收的函数 25 console.log(data); 26 document.head.removeChild($script); // 接收完数据后,从页面删除刚才使用的标签 27 } 28 </script> 29 </body> 30 </html>
通过JSONP自动完成 - 上面是它的原理
服务端返回的数据
1 from django.shortcuts import render,HttpResponse 2 3 4 def get_data(request): 5 # 根据后端发送过来的名字来决定返回时,数据外面套的内容 6 funcName = request.GET.get('callback') 7 return HttpResponse('%s("机密文件")'%funcName)
客户端定义及获取数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Title</title> 8 <script src="/static/jquery-3.2.1.js"></script> 9 </head> 10 <body> 11 <h1>JIAのHOMESITE</h1> 12 13 <script> 14 function func(data) { 15 console.log(data); 16 } 17 18 $(function () { 19 var url = 'http://127.0.0.1:8000/get_data.html/'; 20 $.ajax({ 21 url:url, 22 type:'GET', 23 dataType:'JSONP', 24 jsonp:'callback', 25 jsonpCallback:'func' 26 }); 27 }) 28 </script> 29 </body> 30 </html>
二、CORS
随着技术的发展,现在的浏览器可以支持主动设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是设置响应头,使得浏览器允许跨域请求。
CORS将导致跨域访问的请求分为三种:Simple Request,Preflighted Request以及Requests with Credential。
简单请求 和 非简单请求的区别
简单请求只发送一次,直接发送数据;
非简单请求则会发送两次数据,第一次发送 opption 请求(预检),为的是查看真正的数据是否可以被接收(数据头和请求方式是否符合要求), 如果符合要求,服务端可以把内容加入到头文件中来告诉浏览器;
简单请求 和 非简单请求的判断依据
条件: 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
基于cors实现AJAX请求:
a、支持跨域,简单请求
在使用cors的时候,客户端几乎不用修改,只需要按照普通的ajax的请求方式发送;
在服务端返回数据的时候,只要在返回的时候,加上一个响应头就可以解决这个问题了
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
*的话,代表允许所有的请求
服务端返回的数据
1 from django.shortcuts import render,HttpResponse 2 3 4 def get_data(request): 5 if request.method == 'GET': 6 response=HttpResponse('机密文件') 7 response['Access-Control-Allow-Origin'] = '*' 8 return response
客户端定义及获取数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Title</title> 8 <script src="/static/jquery-3.2.1.js"></script> 9 </head> 10 <body> 11 <h1>JIAのHOMESITE</h1> 12 13 <script> 14 $(function () { 15 var url = 'http://127.0.0.1:8000/get_data.html/'; 16 $.ajax({ 17 url:url, 18 type:'GET', 19 dataType:'text', 20 success:function (data,statusText,xmlHttpRequest) { 21 console.log(data); 22 } 23 }) 24 }) 25 26 </script> 27 </body> 28 </html>
b、支持跨域,复杂请求
对于复杂请求,会先发一次预检(OPTIONS)请求,如果服务端允许,那么再发送一次正式请求(如PUT等,总之就是真正的请求)。
这个时候,我们需要在后端进行判断,如果允许用户获取数据,那么当预检(OPTIONS)过来的时候,我们需要返回允许访问的请求。
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
- “预检”缓存时间,服务器设置响应头:Access-Control-Max-Age
服务端返回的数据
1 from django.shortcuts import render, HttpResponse 2 3 4 def get_data(request): 5 6 7 if request.method == 'OPTIONS': 8 response = HttpResponse() // 返回的内容可以为空,主要需要返回请求头 9 response['Access-Control-Allow-Origin'] = '*' 10 response['Access-Control-Allow-Methods'] = 'PUT' // 允许的复杂请求方式为 PUT 请求; 11 response['Access-Control-Allow-Headers'] = 'k1' // 复杂请求还有一种情况就是定制请求头,这种情况下,我们在返回的响应中应该设置该响应头,代表允许发送请求头的key是什么
12 return response 13 14 if request.method == 'PUT': 15 response = HttpResponse('机密文件') 16 response['Access-Control-Allow-Origin'] = '*' 17 return response
客户端定义及获取数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Title</title> 8 <script src="/static/jquery-3.2.1.js"></script> 9 </head> 10 <body> 11 <h1>JIAのHOMESITE</h1> 12 13 <script> 14 $(function () { 15 var url = 'http://127.0.0.1:8000/get_data.html/'; 16 $.ajax({ 17 url:url, 18 type:'PUT', 19 dataType:'text', 20 headers:{'k1':'v1'}, 21 success:function (data,statusText,xmlHttpRequest) { 22 console.log(data); 23 24 //获取响应头 25 console.log(xmlHttpRequest.getAllResponseHeaders()); //Content-Type: text/html; charset=utf-8 26 } 27 }) 28 }) 29 30 </script> 31 </body> 32 </html>
如果我们不想每次都经过“预检”这个环节的话,那么我们可以在服务器的响应头中增加一组:Access-Control-Max-Age的响应头,可以写成
1 response['Access-Control-Allow-Headers'] = 10 // 默认单位为秒
c、跨域获取响应头
默认获取到的所有响应头只有基本信息,如果想要获取自定义的响应头,则需要在服务器端设置Access-Control-Expose-Headers。
d、跨域传输cookie
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
- 浏览器端:XMLHttpRequest的withCredentials为true
- 服务器端:Access-Control-Allow-Credentials为true
- 注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *
服务端返回的数据
1 from django.shortcuts import render, HttpResponse 2 3 4 def get_data(request): 9 10 if request.method == 'OPTIONS': 11 response = HttpResponse() 12 response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8888' 13 response['Access-Control-Allow-Methods'] = 'PUT' 14 response['Access-Control-Allow-Headers'] = 'k1' 15 response['Access-Control-Allow-Credentials'] = 'true' #加在了这里 16 return response 17 18 if request.method == 'PUT': 19 response = HttpResponse('机密文件') 20 response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8888' 21 response['Access-Control-Allow-Credentials'] = 'true' #加在了这里 22 return response
客户端定义及获取数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="x-ua-compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Title</title> 8 <script src="/static/jquery-3.2.1.js"></script> 9 </head> 10 <body> 11 <h1>JIAのHOMESITE</h1> 12 13 <script> 14 $(function () { 15 var url = 'http://127.0.0.1:8000/get_data.html/'; 16 $.ajax({ 17 url:url, 18 type:'PUT', 19 dataType:'text', 20 headers:{'k1':'v1'}, 21 xhrFields:{withCredentials:true}, //加在了这里 22 success:function (data,statusText,xmlHttpRequest) { 23 console.log(data); 24 25 //获取响应头 26 console.log(xmlHttpRequest.getAllResponseHeaders()); 27 } 28 }) 29 }) 30 31 </script> 32 </body> 33 </html>
参考博文:http://www.cnblogs.com/alwaysInMe/p/7686931.html