同源策略(Same origin policy)
是一种约定安全策略,浏览器自带的安全功能
Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
同源 :域名 , 协议 , 端口策略 :当一个浏览器的两个tab页中分别打开来 即检查是否同源,只有同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
简单的跨域请求测试
场景:
新建两个项目 obj1 和 obj2 ,两个项目分别 端口号为 8006 和 8008
视图传回数据分别为 "123456" 和 "654321"
obj1.views.py
from django.shortcuts import render,HttpResponse # Create your views here. def index(request): return render(request,"index.html") def service(request): return HttpResponse("123456")obj2.views.py
from django.shortcuts import render,HttpResponse # Create your views here. def index(request): return render(request,"index.html") def service(request):
print("654321") return HttpResponse("654321")验证方式:
通过 obj1中的 index 页面通过 button 按钮标签绑定 ajax事件 的 url 向 obj2 的 "http://127.0.0.1:8008/service/" 路径请求数据
$(".get_service").click(function () { $.ajax({ url:"http://127.0.0.1:8008/service/", success:function (data) { console.log(data) } }) })
验证结果:
无法取出,同源策略确实无法允许跨域的请求
通过 obj2 的后端打印确实可以看到函数被执行了,说明拦截发生在前端浏览器进行的操作
基于json 的跨域请求方式
如果所有的跨域请求拿不到,那是怎么导入外部的 js ,bootstrap ,之类的文件呢。
很明显 script 标签的 src 属性是不被拦截的
在同源策略中也只限定了脚本的执行,对于标签src 属性并没有干涉
因此基于 script 标签的 src 属性可以做些手脚
简单的测试1阶段:
测试目的:
查看script 标签的 src 属性请求的可行性
测试分析:
便于测试我们将 http://127.0.0.1:8008/service/ 返回的数据更改一下
obj2.views.py
def service(request): print("yangtuo") return HttpResponse("yangtuo")测试结果 : script 标签的 src 属性请求可以实现跨域请求
同上述一样 obj2 后台执行
但是前端报错是 变量名没有被声明,最起码证明了一点数据确实被传过来了
简单的测试1阶段总结:
验证 成功,scirpt 的 src 属性可以通过向目的url 请求并拿回数据放在 标签内容 里面
简单的测试2阶段:
测试目的:
在证实了script 标签的 src 属性请求可以实现跨域请求后,
进一步探索传递数据的可行性
测试分析:
既然拿到的数据是变量名的形式无声明报错,那解决途径两种
1. 不让传回变量名 :浏览器对标签内容的处理必然是 去除 "" 双引号,绕不过去。无解
2. 声明变量 : 那就提前声明一个变量
obj1. index.html 在页面声明一个和请求数据相同的变量
<script> var yangtuo </script>测试结果 : 解决报错问题
但是并没有什么卵用:
对每次请求的数据设计出变量那我除非提前知道我要请求什么。我都知道了我还请求个p
延伸:
既然传过来的是个变量。如果这个变量是个方法我加个"()" 不就可以执行了?
方法比单纯的数据变量可做的事情就多了去了!
简单的测试3阶段:
测试目的:
利用声明方法的方式然后通过传入的参数拿到想要的数据
测试分析:
obj2 views.py 真正传递的数据通过预先一致的方法中的参数中传递
def service(request): print("yangtuo") data = "123" return HttpResponse(f"yangtuo({data})")obj1 index.html 通过预先一致的方法的参数执行取出来目标数据
<script> function yangtuo(arg) { alert(arg) } </script>测试结果 :
拿到了预期的数据并可以进行相应的操作 ,基本实现了我们的预期要求
延伸:
对于日常处理的数据在网络间传输必然是json的格式,因此基于 jsonp 的跨域请求在这一基础上诞生
简单的测试4阶段:
测试目的:
测试传递更复杂的数据
测试分析:
obj2 views.py 这次尝试一下传递字典,提前用json 处理成字符串形式
def service(request): print("yangtuo") data = {"name": "yangtuo", "age": 18} data = json.dumps(data) return HttpResponse(f"yangtuo({data})")obj1 index.html 查看传过来的数据类型并且使用一下数据看看
<script> function yangtuo(arg) { console.log(arg); console.log(typeof arg); var data = JSON.parse(arg); console.log(data); console.log(typeof data); } </script>测试结果 :
什么鬼。不应该传过来是一个字符串吗?怎么直接是对象了。
延伸 :
经查阅,新版本的js 中已经自动对数据进行了还原。不再需要自己还原数据了。所以测试结果是成功的。
但是这种拿数据的方法触发是 script 标签的 src 请求,执行必须要走整个页面的刷新,实在有点蠢
我们期待的是类似于 ajax 的请求方式来拿到数据
简单的测试5阶段:
测试目的:
实现AJAX方式的请求方式的跨域请求
测试分析:
html 的标签的在创建的时候自动会被渲染执行,
因此可以用ajax 的方式创建 script 标签从而控制执行时间
obj1 index.html 绑定一个点击事件,创建一个 src 属性为跨域请求的 script 标签
<script> $(".get_service").click(function () { var ele_script = $("<script>"); // 创建标签 ele_script.attr("src","http://127.0.0.1:8008/service/"); // 添加标签属性 $("body").append(ele_script) // 添加标签 }) </script>测试结果 :成功
延伸:
完善1 :
存在一个问题。如果你点击了标签不消除的话标签会一直在,
毕竟我们用完这标签就没用了,因此还需要将它消除掉,不然用多了全都是这标签了‘
完善后的代码
<script> $(".get_service").click(function () { var ele_script = $("<script>"); // 创建标签 ele_script.attr("src","http://127.0.0.1:8008/service/"); // 添加标签属性 ele_script.attr("id","jsonp"); // 添加标签属性 $("body").append(ele_script); // 添加标签 $("#jsonp").remove() // 删除标签 }) </script>完善2:
毕竟不是只有一个数据要取,每个标签都绑定事件写这么多很蠢
封装成函数在每个要取的时候调用即可
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h3>INDEX</h3> {#数据处理触发器#} <button class="get_service">啦啦啦你来点我啊~</button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> // 对数据的操作 function yangtuo(arg) { console.log(arg); console.log(typeof arg); } // 定义一个专门取数据的 方法 function get_jsonp_data(url) { var ele_script = $("<script>"); // 创建标签 ele_script.attr("src",url); // 添加标签属性 ele_script.attr("id","jsonp"); // 添加标签属性 $("body").append(ele_script); // 添加标签 $("#jsonp").remove() } </script> <script> // 点击事件 $(".get_service").click(function () { get_jsonp_data("http://127.0.0.1:8008/service/") }) </script> </body> </html>完善3 :
说了这么多的前提必须是两端的数据必须基于一个协商好的方法名字。
可不可以有什么放服务器知道我的方法然后基于方法直接给数据呢?
利用 request 里面的 get 请求传递方法名字即可,服务器基于拿到的名字来返回方法名执行
obj1 index.html
<script> // 点击事件 $(".get_service").click(function () { get_jsonp_data("http://127.0.0.1:8008/service/?callbacks=yangtuo") }) </script>obj2 views.py
def service(request): func = request.GET["callbacks"] # 获取请求者提供的方法名 data = {"name": "yangtuo", "age": 18} data = json.dumps(data) return HttpResponse(f"{func}({data})")以上都是简单的测试
以下为最终版本!
原理就是基于 上面的测试
用 get 请求(必须是get)发生请求同时带一个随机生成的参数。
服务端无视参数直接数据处理方法,在请求端再调用方法处理数据
$(".get_service").click(function () { $.ajax({ url: "http://127.0.0.1:8008/service/", type: "get", dataType: "jsonp", // 伪造ajax 基于script jsonp: 'callbacks', // 创建一个 get请求的参数的 键 //jsonpCallback:"alex", // 创建一个 get 请求参数的 值 success: function (data) { // 如果使用 jsonCallback参数 可以用直接在下面写自己的数据处理过程 console.log(data) } }) });
def service(request): func = request.GET["callbacks"] # 获取请求者提供的方法名 data = {"name": "yangtuo", "age": 18} data = json.dumps(data) return HttpResponse(f"{func}({data})") # 关于方法名是什么无所谓,至少中间值而已
基于 cors 的跨域请求
def service(request): # func = request.GET["callbacks"] # 获取请求者提供的方法名 # data = {"name": "yangtuo", "age": 18} # data = json.dumps(data) # return HttpResponse(f"{func}({data})") # 关于方法名是什么无所谓,至少中间值而已 info={"name":"egon","age":34,"price":200} response=HttpResponse(json.dumps(info)) response["Access-Control-Allow-Origin"]="http://127.0.0.1:8006" # response["Access-Control-Allow-Origin"]="*" return response