Django跨域请求--JSONP

一 需求分析

之前我们学习的都是在本站内进行数据的获取,创建Django web1服务,简单程序代码如下:

urls.py

urlpatterns = [
    path('web1/', views.web1),
]

views.py

def web1(request):
    return HttpResponse('web1数据')

访问结果如下:

image_thumb31

补充:我们也可以在终端通过requesrts模块进行数据获取

image_thumb1

那么我们怎么实现跨域请求呢?现在我试着通过目前掌握的知识进行如下尝试:

二 小试牛刀

现在在原有基础上新建一个Django web2服务:

urls.py

urlpatterns = [
    path('web2/', views.web2),
]

views.py

def web2(request):
    return render(request,'web2.html')

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
</body>
</html>

 访问结果:

image_thumb5

因为同时开启两个web服务端,所以需要注意端口号的修改

接下来该怎么处理呢?

方式一:通过后端请求

views.py

def web2(request):
    import requests
    res = requests.get('http://127.0.0.1:8000/web1/').text
    return render(request,'web2.html',{'res':res})

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <P>{{ res }}</P>
</body>
</html>

结果:

image_thumb6

访问简图如下:

image_thumb3

方式二:浏览器通过Ajax请求

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $.ajax({
            url:'http://127.0.0.1:8000/web1/',
            type:'GET',
            success:function(arg){
                var $tag = $('<p>');
                $tag.text(arg);
                $('h3').append($tag);
            }
        })
    </script>
</body>
</html>

当然,以上Ajax部分我们也可以通过原生Ajax实现。

通过谷歌浏览器访问出现以下错误:

image_thumb9

再通过火狐浏览器访问:

image_thumb11

这是为什么呢?

这是因为浏览器存在同源策略(Same origin policy)机制,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略,它是由Netscape提出的一个著名的安全策略。
现在所有支持JavaScript的浏览器都会使用这个策略。
所谓同源是指域名协议端口相同。
当一个浏览器的两个tab页中分别打开来百度和谷歌的页面,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收(百度百科)。

访问简图如下:

image_thumb51

怎么绕过浏览器的同源策略?这里我们就需要引入JSONP了。

三 JSONP实现跨域请求

浏览器同源策略并不是对所有的请求均制约:

  • 制约: XmlHttpRequest
  • 不制约: img、iframe、script等具有src属性的标签

JSONP(JSONP - JSON with Padding是JSON的一种“使用模式”),利用<script>标签的src属性(浏览器允许script标签跨域)完成跨域。代码如下:

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
</body>
    <script src="/static/jquery-3.2.1.js"></script>
    <script src="http://127.0.0.1:8000/web1/"></script>
</html>

执行结果如下:

image_thumb13

image_thumb15

根据上面的结果,证明数据我们已经拿到了,但是没有渲染在页面上,报错提醒该值未定义:这是因为我们使用<script>标签取到的数据只是一个单纯的文本格式,加载了js代码中,并没有声明变量去接收它,所以在js中则会报错。

其实质上是在内存中加入了返回的字符串,在前端的结果为:

<script src="http://127.0.0.1:8000/web1/">
    web1数据
</script>

所以,只是单纯的返回一个文本并没有什么意义,我们需要的是的数据。解决如下:

web1服务:

views.py

def web1(request):
    l = 'a = "web1数据"'
    return HttpResponse('%s' % (l))

web2服务:

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <script src="/static/jquery-3.2.1.js"></script>
    <script src="http://127.0.0.1:8000/web1/"></script>
</body>
</html>

访问结果如下:

image_thumb8

这样有一个问题,是什么呢?我们不知道第三方何时将数据返回,要是有一种机制,第三方返回后,前端浏览器自动执行就好了,方法如下:

web1服务:

views.py

def web1(request):
    import json
    # return HttpResponse("func('web1数据')")
    l = {'name':'joe1991','age':18}       #返回数据可以是文本,也可以是字典及其他格式
    return HttpResponse("func('%s')" % json.dumps(l))  #返回一个func()字符串,客户端的js中存在func函数,自然会去执行func函数,arg是传的形参。 

web2服务:

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        //创建一个函数,当第三方返回请求数据数据时,自动执行。当然,第三方需返回一个与之名称相同且包含数据的字符串
        function func(arg) {
             var $tag = $('<p>');
                $tag.text(arg);
                $('h3').append($tag);
        }
    </script>
    <script src="http://127.0.0.1:8000/web1/"></script>
</body>
</html>

执行结果如下:

image_thumb18

image_thumb17

这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。

将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。

于此同时,我们希望这个<script>标签能够动态的调用,同时,为了更加灵活,我们可以在访问第三方地址时将回调函数的函数名传送给第三方,第三方则会返回以我们自定义的回调函数名的方法,将获取的json数据传入这个方法完成回调:

web1服务:

views.py

def web1(request):
    import json
    func_name = request.GET.get('callback')    #获取回调函数名称
    # return HttpResponse("%s('web1数据')"%func_name)
    l = {'name':'joe1991','age':18}
    return HttpResponse("%s('%s')" %(func_name,json.dumps(l)))

web2服务:

web2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <input type="button" onclick="f1()" value="发送JSONP请求"></input>
 
    <script src="/static/jquery-3.2.1.js"></script>
 
    <script>
        function func(arg) {
             var $tag = $('<p>');
                $tag.text(arg);
                $('h3').append($tag);
        }
        function f1() {
            jsonp('http://127.0.0.1:8000/web1/?callback=func')
        }
        //js实现<script>动态化
        function jsonp(url) {
            var tag = document.createElement('script');
            tag.setAttribute("type","text/javascript");
            tag.src = url;
            document.body.appendChild(tag);
            document.body.removeChild(tag);
        }
        //jQuery实现<script>动态化
//        function jsonp(url) {
//           var $tag = $("<script>");
//           $tag.attr("src",url);
//           $tag.attr("id","scr1");
//           $("body").append($tag);
//          $("body").remove($tag);
//        }
    </script>
</body>
</html>

四 jQuery对JSONP的实现

jQuery框架当然也支持JSONP,可以使用$.getJSON(url,[data],[callback])方法

web2中web2.html做如下修改:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>web2数据</h3>
    <input type="button" onclick="f1()" value="发送JSONP请求"></input>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        function f1() {
            $.getJSON('http://127.0.0.1:8000/web1/?callback=?', function(arg) {
                var $tag = $('<p>');
                $tag.text(arg);
                $('h3').append($tag);
            });    //function为匿名函数
        }
    </script>
</body>
</html>

注意:在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个?是内部自动生成的一个回调函数名。

image_thumb20

另外,如果我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名,我们可以使用$.ajax方法来实现。

其他代码一致,仅function f1() 不同,f1()代码如下:

function f1() {
	$.ajax({
		url:'http://127.0.0.1:8000/web1/',
		type:'GET',                 //可省略
		dataType:'JSONP',           //必须有,告诉第三方,这次访问要的是一个jsonp的结果
		jsonp: 'callback',          //键
		jsonpCallback: 'func',      //函数的名字,url中为:.../?callback=func,此时需创建func函数
	})
}
function func(arg) {
	var $tag = $('<p>');
		$tag.text(arg);
		$('h3').append($tag);
}

当然,最简单的形式还是通过回调函数来处理:

代码一:

function f1() {
	$.ajax({
		url:'http://127.0.0.1:8000/web1/?callback=?',   //函数名自动生成,也可以在下面单独列举
		type:'GET',            //可省略
		dataType:'JSONP',      //必须有,告诉第三方,这次访问要的是一个jsonp的结果
		success:function(arg){
			var $tag = $('<p>');
			$tag.text(arg);
			$('h3').append($tag);
		}
	})
}

代码二:

function f1() {
	$.ajax({
		url:'http://127.0.0.1:8000/web1/',
		type:'GET',            //可省略
		dataType:'JSONP',      //必须有,告诉第三方,这次访问要的是一个jsonp的结果
		jsonp: 'callback',     //后台获取request.GET.get('callback'),url中为:.../?callback=?
		success:function(arg){
			var $tag = $('<p>');
			$tag.text(arg);
			$('h3').append($tag);
		}
	})
}

jsonp: 'callback' 就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名'func',server端接受callback键对应值后就可以在其中填充数据打包返回了;

jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,前端就可以用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。

五 总结

说明:JSONP只是一种方式,目的是解决跨域问题,其他语言也存在。

5.1 浏览器与第三方服务端约定

客户端:

1.通过GET方式向后台发送回调函数(可指定或自动生成)—URL/?callback=xxx

2.需创建function xxx(arg){},或使用回调函数

第三方服务端:

1.获取函数名称:funcname = request.GET.get(callback)

2.返回:funcname(‘数据’)

5.2 JSONP的使用

1.自已动态创建script(第三部分)

2.通过jQuery实现(第四部分)

5.3 JSONP优缺点

优点:

1.通过jsonp的传输方式我们可以绕开浏览器同源策略

2.页面不用刷新而获得数据

缺点:

1.第三方服务端需要将数据封装在函数中

2.本地浏览器需要定义被封装的函数

3.JSONP只能发送GET请求

一句话概述JSONP:通过<script>标签的跨域特性绕过同源策略(创建一个回调函数,然后在第三方服务上得到这个函数名称,并将返回数据json化后作为这个函数的形参返回给前端,完成回调)。

posted @ 2018-11-04 15:21  Joe1991  阅读(203)  评论(0)    收藏  举报