Django 跨域请求处理 同源策略(Same origin policy) 基于json 的跨域请求方式  基于 cors 的跨域请求 django-cors-headers 组件

是一种约定安全策略,浏览器自带的安全功能

Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现

同源 

域名 , 协议  , 端口

策略

当一个浏览器的两个tab页中分别打开来 即检查是否同源,只有同源的脚本才会被执行。
如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

简单的跨域请求测试

场景

新建两个项目 obj1obj2 ,两个项目分别 端口号为 80068008

视图传回数据分别为 "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)
             }
         })
     })

验证结果

无法取出,同源策略确实无法允许跨域的请求

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

通过 obj2 的后端打印确实可以看到函数被执行了,说明拦截发生在前端浏览器进行的操作

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

基于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 后台执行

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

但是前端报错是 变量名没有被声明,最起码证明了一点数据确实被传过来了

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

简单的测试1阶段总结

验证 成功,scirpt 的 src 属性可以通过向目的url 请求并拿回数据放在 标签内容 里面

简单的测试2阶段

测试目的

在证实了script 标签的 src 属性请求可以实现跨域请求后,

进一步探索传递数据的可行性

测试分析 

既然拿到的数据是变量名的形式无声明报错,那解决途径两种

1. 不让传回变量名 :浏览器对标签内容的处理必然是 去除 "" 双引号,绕不过去。无解

2. 声明变量 : 那就提前声明一个变量

obj1.  index.html  在页面声明一个和请求数据相同的变量

<script>
    var yangtuo
</script>

测试结果

解决报错问题

但是并没有什么卵用

对每次请求的数据设计出变量那我除非提前知道我要请求什么

我都知道了我还请求个p

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

延伸

既然传过来的是个变量。如果这个变量是个方法我加个"()" 不就可以执行了?

方法比单纯的数据变量可做的事情就多了去了!

简单的测试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>

测试结果 

拿到了预期的数据并可以进行相应的操作 ,基本实现了我们的预期要求

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

延伸

对于日常处理的数据在网络间传输必然是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>

测试结果 

什么鬼。不应该传过来是一个字符串吗?怎么直接是对象了。

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

延伸 

经查阅,新版本的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>

测试结果

成功

 Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

延伸

完善1 

存在一个问题。如果你点击了标签不消除的话标签会一直在,

毕竟我们用完这标签就没用了,因此还需要将它消除掉,不然用多了全都是这标签了‘

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

完善后的代码

<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})")

以上都是简单的测试

以下为最终版本!

 原理就是基于 上面的测试(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})") # 关于方法名是什么无所谓,只是中间值而已

 基于 cors 的跨域请求

用火狐浏览器支持中文提示会告知是缺少了东西导致, 缺了那就加上即可

但是这是在服务端的操作

比起jsonp要简单很多, 只需要将要通过的 url 添加进去表示允许即可

Django 跨域请求处理
同源策略(Same origin policy)
基于json 的跨域请求方式
 基于 cors 的跨域请求
django-cors-headers 组件

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 组件

既然有需求自然就会有人对此进行封装,

django-cors-headers 就是基于cors 方式的跨域解决方案组件

官方 点击这个

安装

pip install django-cors-headers

配置

全部都在 settings.py 中完成后就可以, 非常的简单方便

无需再手动写入请求头以及自建中间件

app 加载

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

中间件加载

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',    # 此中间件要写在 csrf 中间件之前
    'django.middleware.common.CommonMiddleware',
    ...
]

配置字段添加

# 设置跨域请求
CORS_ORIGIN_ALLOW_ALL = True