是一种约定安全策略,浏览器自带的安全功能css
Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现html
新建两个项目 obj1 和 obj2 ,两个项目分别 端口号为 8006 和 8008前端
视图传回数据分别为 "123456" 和 "654321"jquery
obj1.views.pygit
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.pygithub
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/" 路径请求数据ajax
$(".get_service").click(function () {
$.ajax({
url:"http://127.0.0.1:8008/service/",
success:function (data) {
console.log(data)
}
})
})
没法取出,同源策略确实没法容许跨域的请求django
经过 obj2 的后端打印确实能够看到函数被执行了,说明拦截发生在前端浏览器进行的操做json
若是全部的跨域请求拿不到,那是怎么导入外部的 js ,bootstrap ,之类的文件呢。bootstrap
很明显 script 标签的 src 属性是不被拦截的
在同源策略中也只限定了脚本的执行,对于标签src 属性并无干涉
所以基于 script 标签的 src 属性能够作些手脚
查看script 标签的 src 属性请求的可行性
便于测试咱们将 http://127.0.0.1:8008/service/ 返回的数据更改一下
obj2.views.py
def service(request): print("yangtuo") return HttpResponse("yangtuo")
script 标签的 src 属性请求能够实现跨域请求
同上述同样 obj2 后台执行
可是前端报错是 变量名没有被声明,最起码证实了一点数据确实被传过来了
验证 成功,scirpt 的 src 属性能够经过向目的url 请求并拿回数据放在 标签内容 里面
在证明了script 标签的 src 属性请求能够实现跨域请求后,
进一步探索传递数据的可行性
既然拿到的数据是变量名的形式无声明报错,那解决途径两种
1. 不让传回变量名 :浏览器对标签内容的处理必然是 去除 "" 双引号,绕不过去。无解
2. 声明变量 : 那就提早声明一个变量
obj1. index.html 在页面声明一个和请求数据相同的变量
<script> var yangtuo </script>
解决报错问题
可是并无什么卵用
对每次请求的数据设计出变量那我除非提早知道我要请求什么
我都知道了我还请求个p
既然传过来的是个变量。若是这个变量是个方法我加个"()" 不就能够执行了?
方法比单纯的数据变量可作的事情就多了去了!
利用声明方法的方式而后经过传入的参数拿到想要的数据
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 的跨域请求在这一基础上诞生
测试传递更复杂的数据
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 的请求方式来拿到数据
实现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>
毕竟不是只有一个数据要取,每一个标签都绑定事件写这么多很蠢
封装成函数在每一个要取的时候调用便可
<!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>
说了这么多的前提必须是两端的数据必须基于一个协商好的方法名字。
可不能够有什么放服务器知道个人方法而后基于方法直接给数据呢?
利用 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})")
以上都是简单的测试
原理就是基于 上面的测试(script 标签的 src 属性来进行跨域请求)
用 get 请求(必须是get)发生请求同时带一个随机生成的参数。
服务端无视参数直接数据处理方法,在请求端再调用方法处理数据
$(".get_service").click(function () { $.ajax({ url: "http://127.0.0.1:8008/service/", type: "get", dataType: "jsonp", // 伪造ajax 本质和ajax不要紧的,形式上模仿了ajax jsonp: 'callbacks', // 建立一个 get请求的参数的 键 //jsonpCallback:"alex", // 建立一个 get 请求参数的 值 ,若是不写会自动生成随机的字符串来表示函数的名字 // 若是不使用 jsonCallback参数 能够用直接在下面写本身的数据处理过程, // 写了jsonpCallback参数又在下面用success 会在随机字符串前面以 “ alex&=” 的形式拼接一下 success: function (data) { console.log(data) } }) });
def service(request): func = request.GET["callbacks"] # 获取请求者提供的方法名 data = {"name": "yangtuo", "age": 18} data = json.dumps(data) return HttpResponse(f"{func}({data})") # 关于方法名是什么无所谓,只是中间值而已
用火狐浏览器支持中文提示会告知是缺乏了东西致使, 缺了那就加上便可
可是这是在服务端的操做
比起jsonp要简单不少, 只须要将要经过的 url 添加进去表示容许便可
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
还能够用中间件的方式更方便的让全部的页面均可以支持跨域请求
新建一个py文件,而后再setting 中注册后
setting.py
MIDDLEWARE = [ ...'api.cors.CORSMiddleware', ]
cors.py
class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class CORSMiddleware(MiddlewareMixin): def process_response(self,request,response): # 添加响应头 # 容许你的域名来获取个人数据,简单请求这样作就够了 response['Access-Control-Allow-Origin'] = "*" # 复杂请求须要有一些要求 # 容许你携带自定义的 Content-Type 请求头 # 自定义请求头不能用 * 来代替全部。有多少写都少。 response['Access-Control-Allow-Headers'] = "Content-Type" # 容许你发送DELETE,PUT response['Access-Control-Allow-Methods'] = "DELETE,PUT" return response
既然有需求天然就会有人对此进行封装,
django-cors-headers 就是基于cors 方式的跨域解决方案组件
官方 点击这个
pip install django-cors-headers
所有都在 settings.py 中完成后就能够, 很是的简单方便
无需再手动写入请求头以及自建中间件
INSTALLED_APPS = ( ... 'corsheaders', ... )
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10 ... 'corsheaders.middleware.CorsMiddleware', # 此中间件要写在 csrf 中间件以前 'django.middleware.common.CommonMiddleware', ... ]
# 设置跨域请求 CORS_ORIGIN_ALLOW_ALL = True