跨域 和同源策略
用ajax 不能实现跨域
如
<script> $(".b1").click(function () { $.ajax({ url:"http://127.0.0.1:8002/ajax_send/",/本服务地址是8000浏览器的同源策略的原因,AJax无法发送跨域请求 success:function (data) { alert(data) } }) }); </script>
报错信息如下
同源策略与Jsonp
同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
示例:
http://127.0.0.1:8001项目的index
项目中有一个按钮绑定功能
$.ajax({
url:"http://127.0.0.1:8002/SendAjax/", 是跨域的

==================================http://127.0.0.1:8001项目的index <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="http://code.jquery.com/jquery-latest.js"></script> </head> <body> <button>ajax</button> {% csrf_token %} <script> $("button").click(function(){ $.ajax({ url:"http://127.0.0.1:8002/SendAjax/", type:"POST", data:{"username":"yuan","csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()}, success:function(data){ alert(123); alert(data) } }) }) </script> </body> </html> ==================================http://127.0.0.1:8001项目的views def index(request): return render(request,"index.html") def ajax(request): import json print(request.POST,"+++++++++++") return HttpResponse(json.dumps("hello"))
项目2:

==================================http://127.0.0.1:8002项目的index <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="http://code.jquery.com/jquery-latest.js"></script> </head> <body> <button>sendAjax</button> {% csrf_token %} <script> $("button").click(function(){ $.ajax({ url:"/SendAjax/", type:"POST", data:{"username":"yuan","csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()}, success:function(data){ alert(data) } }) }) </script> </body> </html> ==================================http://127.0.0.1:8002项目的views def index(request): return render(request,"index.html") from django.views.decorators.csrf import csrf_exempt @csrf_exempt def SendAjax(request): import json print("++++++++") return HttpResponse(json.dumps("hello2"))
当点击项目1的按钮时,发送了请求,但是会发现报错如下:
已拦截跨源请求:同源策略禁止读取位于 http://127.0.0.1:8002/SendAjax/ 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。
但是注意,项目2中的访问已经发生了,说明是浏览器对非同源请求返回的结果做了拦截。
Jsonp
jsonp是json用来跨域的一个东西。原理是通过script标签的跨域特性来绕过同源策略。
思考:这算怎么回事?
<script src="http://code.jquery.com/jquery-latest.js"></script>
在引入网络cdn的js文件也是跨域的请求,但是可以实现, 正式因为script标签有跨域的特性
借助script标签,实现跨域请求,示例:
# =============================http://127.0.0.1:8001/index <button>ajax</button> {% csrf_token %} <script> function func(name){ alert(name) } </script> <script src="http://127.0.0.1:8002/SendAjax/"></script> 他得到的就是 func("yuan") 是另一个8002项目返回的,所以就能够执行上边定义的函数
func() 从而将yuan显示出来
# =============================http://127.0.0.1:8002/ from django.views.decorators.csrf import csrf_exempt @csrf_exempt def SendAjax(request): import json print("++++++++") # dic={"k1":"v1"} return HttpResponse("func('yuan')") # return HttpResponse("func('%s')"%json.dumps(dic)) 将这个结果返回给上边发送跨域请求的script
这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。
将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。
一般情况下,我们希望这个script标签能够动态的调用,而不是像上面因为固定在html里面所以没等页面显示就执行了,很不灵活。我们可以通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务了。
<button onclick="f()">sendAjax</button> <script> function addScriptTag(src){ var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); document.body.removeChild(script); } function func(name){ alert("hello"+name) } function f(){ addScriptTag("http://127.0.0.1:7766/SendAjax/") } </script>
为了更加灵活,现在将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调:
将8001的f()改写为:
function f(){
addScriptTag("http://127.0.0.1:8002/SendAjax/?callbacks=func")
}
8002的views改为:
def SendAjax(request): import json dic={"k1":"v1"} print("callbacks:",request.GET.get("callbacks")) callbacks=request.GET.get("callbacks") return HttpResponse("%s('%s')"%(callbacks,json.dumps(dic)))
jQuery对JSONP的实现
getJSON
jQuery框架也当然支持JSONP,可以使用$.getJSON(url,[data],[callback])方法
8001的html改为:
<button onclick="f()">sendAjax</button> <script> function f(){ $.getJSON("http://127.0.0.1:8002/SendAjax/?callbacks=?",function(arg){ alert("hello"+arg) }); } </script>
# $.getJSON("http://127.0.0.1:8002/SendAjax/?callbacks=?" 这句话的功能就是将引号中的地址内容转换成script的
实际就是转换成成了 <script src="http://....../?callbacks=?"><?script> 这个
function 就是匿名回调函数 arg就是返回的值
8002的views不改动。 详见上文
结果是一样的,要注意的是在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。
此外,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?我们可以使用$.ajax方法来实现
$.ajax
8001的html改为:
<script> function f(){ $.ajax({ url:"http://127.0.0.1:8002/SendAjax/", dataType:"jsonp", #期待数据类型, 写jsonp 就是告诉服务器, ajax方法就能根据这个jsonp参数 来生成加<script>标签形式发送请求 jsonp: 'callbacks', jsonpCallback:"SayHi"#下边这两个参数就是让上边的url最后形成
#"http://127.0.0.1:8002/SendAjax/?callbacks=SayHi"这种形式所以还要有一个SayHi函数来接收参数 }); } function SayHi(arg){ #写好的函数,用于将上边的url 最后合成的 <script src="地址"></script> alert(arg); } </script>
#形成的地址到 另一个服务器后去的数据 来执行这个函数
8002的views不改动。
当然,最简单的形式还是通过回调函数来处理:*****重要 主要用这个
<script> function f(){ $.ajax({ url:"http://127.0.0.1:8002/SendAjax/", dataType:"jsonp", //必须有,告诉server,这次访问要的是一个jsonp的结果。 jsonp: 'callbacks', //jQuery帮助随机生成的:callbacks="wner"
//这里没有写jsonpCallback :URL就会形成 "http://......./?callbacks=231das4" 后是生成的随机字符串
success:function(data){ //返回的内容 比如返回的是 213das4('{"name":"alxe"}')
// 执行213das4('{"name":"alex"}') 123das4实际就是指的是success这个函数的内存空间,
4 //所以执行它就是执行success后边的匿名函数 alert("hi "+data) } }); } </script>
jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名'SayHi',server端接受callback键对应值后就可以在其中填充数据打包返回了;
jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。
注意 JSONP一定是GET请求
应用
跨域来实现将电视台的界面单返回出来
先查看这个连接能够得到什么内容 通过连接可以看到是得到的这些内容
$('.b3').click(function () {
$.ajax({url:'http://www.jxntv.cn/data/jmd-jxtv2.html?',
dataType:'jsonp',
jsonp:'callback',
jsonpCallback:'list' 这里这样写就是为了返回的函数是以 list(内容)的形式
所以在ajax这里边也不用写success的函数了 直接写下边的的函数的内容
})
});
js中的函数
function list(data) { #这个名字这样写就是为了接收上边返回回来的函数,并且执行 console.log(data.data);
#打印出来的数据如下var a=data.data; {# #} $.each($(a),function(key,i){ {# 用each循环将内容取出来 只取了其中的week字段 #} console.log(i["week"]);
{# 将取出的week中的字段拼接成一个标签,用js将内容返回给HTML页面y用于显示#} c='<h4>'+i["week"] +'</h4>'; $('.show').append(c); var b=i["list"];
$.each($(b),function (key,j) {
{#取出数据,将数据拼接在模板语言中返回给页面#} d='<span><a href='+j['link'] +'>'+ j['name']+'</a></span>'; $('.show').append(d); console.log(j['name'],j['link']) }) }) }
实现效果
实现代码

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h4>这是测试跨域的</h4> <button class="b3">点击3</button> <hr> <div class="show"> </div> <script src="/static/js/jquery-3.2.1.min.js/"></script> <script> $('.b3').click(function () { $.ajax({url:'http://www.jxntv.cn/data/jmd-jxtv2.html?', dataType:'jsonp', jsonp:'callback', jsonpCallback:'list' }) }); function list(data) { console.log(data.data); var a=data.data; {# #} $.each($(a),function(key,i){ {# #} console.log(i["week"]); c='<h4>'+i["week"] +'</h4>'; $('.show').append(c); var b=i["list"]; $.each($(b),function (key,j) { d='<span><a href='+j['link'] +'>'+ j['name']+'</a></span>'; $('.show').append(d); console.log(j['name'],j['link']) }) }) } </script> </body> </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 注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
* 简单请求和非简单请求的区别?
12简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
* 关于“预检”
1234567-
请求方式:OPTIONS
-
“预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
-
如何“预检”
=
> 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
Access
-
Control
-
Request
-
Method
=
> 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
Access
-
Control
-
Request
-
Headers
基于cors实现AJAX请求:
a.基本跨域:
被访问的页面的设置
def service(request): v1 = request.GET.get('v1') v2 = request.GET.get('v2') result = v1 + v2 obj = HttpResponse(result)
#这样设置是可以允许这两个地址来跨域访问此试图返回的内容 # obj['Access-Control-Allow-Origin'] = 'http://localhost:63342,http://localhost:63343,'
#允许所有的跨域请求访问
obj['Access-Control-Allow-Origin'] = '*' return obj
发送跨域请求的也页面端 通过ajax来发送
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h3>这是发送跨域请求的页面</h3> <button type="submit" onclick="a()"></button> <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script> <script> function a(){ $.ajax({ url:'http://127.0.0.1:8009/test/?v1=haha&v2=哼歌', type:'get', success:function (arg) { alert(arg) } } ) } </script> </body> </html>
b. 自定义请求头:先预检允许头,再发起数据相关请求。
被访问的页面的设置
def test(request): #当自定义请求头时 request.method为OPTIONS if request.method=="OPTIONS": print('进行预检') v1 = request.GET.get('v1') v2 = request.GET.get('v2') result = v1 + v2 obj = HttpResponse('nihhao a ') #设置允许请求头中带有k1字段 obj["Access-Control-Allow-Headers"] = 'k1' obj["Access-Control-Allow-Origin"] = 'http://localhost:63342' return obj v1 = request.GET.get('v1') v2 = request.GET.get('v2') result = v1 + v2 obj=HttpResponse('nihhao a ') obj["Access-Control-Allow-Origin"]='http://localhost:63342' return obj
发送跨域请求的也页面端 通过ajax来发送
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h3>这是发送跨域请求的页面</h3> <button type="submit" onclick="a()"></button> <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script> <script> function a(){ $.ajax({ url:'http://127.0.0.1:8009/test/?v1=haha&v2=哼歌', type:'get', // 这是自定义的请求头,定义他之后 请求的method会发生变化 headers:{"k1":"aaa"}, success:function (arg) { alert(arg) } } ) } </script> </body> </html>
c. 不寻常的方法:先预检允许方法,再发起数据相关请求。
被访问的页面的设置
def test(request): print(request.method) #当请求的type="PUT"的时候 request.method为OPTIONS if request.method=="OPTIONS": print('进行预检') print(request.method) v1 = request.GET.get('v1') v2 = request.GET.get('v2') result = v1 + v2 obj = HttpResponse('nihhaSSSSo a ') #设置允许请求方法为PUT的请求 obj["Access-Control-Allow-Methods"] = 'PUT' obj["Access-Control-Allow-Origin"] = 'http://localhost:63342' return obj v1 = request.GET.get('v1') v2 = request.GET.get('v2') result = v1 + v2 obj=HttpResponse('nihhao a ') obj["Access-Control-Allow-Origin"]='http://localhost:63342' return obj
发送跨域请求的也页面端 通过ajax来发送
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h3>这是发送跨域请求的页面</h3> <button type="submit" onclick="a()"></button> <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script> <script> function a(){ $.ajax({ url:'http://127.0.0.1:8009/test/?v1=haha&v2=哼歌', // 自定义请求方式 type:'PUT', success:function (arg) { alert(arg) } } ) } </script> </body> </html>
b、支持跨域,复杂请求
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
- “预检”缓存时间,服务器设置响应头:Access-Control-Max-Age
c、跨域获取响应头
默认获取到的所有响应头只有基本信息,如果想要获取自定义的响应头,则需要再服务器端设置Access-Control-Expose-Headers。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); // 获取响应头 console.log(xhr.getAllResponseHeaders()); } }; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); // 获取响应头 console.log(xmlHttpRequest.getAllResponseHeaders()); } }) } </script> </body> </html>

class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
d、跨域传输cookie
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
- 浏览器端:XMLHttpRequest的withCredentials为true
- 服务器端:Access-Control-Allow-Credentials为true
- 注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.withCredentials = true; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, xhrFields:{withCredentials: true}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>

class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Credentials', "true") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.set_cookie('kkkkk', 'vvvvv'); self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)