项目讲完了,但要及时的复习
今天就来讲一下jsonp,
先来说一下同源策略。
同源策略
一个源的定义:如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
一般的http协议会走默认的走80端口,https默认走443端口。域名会翻译成ip地址。
举个例子:
下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://a.xyz.com/dir2/other.html |
成功 | |
http://a.xyz.com/dir/inner/another.html |
成功 | |
https://a.xyz.com/secure.html |
失败 | 不同协议 ( https和http ) |
http://a.xyz.com:81/dir/etc.html |
失败 | 不同端口 ( 81和80) |
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。
意思就是当我们在访问两个网页时,这两个网页之间的文件数据是不能互相读取的。
比如说我们在这个页面发送一个Ajax请求去请求另外一
不受同源策略限制的:
1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等。
举个例子:
我们手写两个Django demo:
demo1:(端口设为8000)
url.py
from app01 import views urlpatterns = [ url(r'^abc/', views.abc), ]
view.py
def abc(request):return HttpResponse('alex')
demo2:(端口设为8005)
url.py
from app02 import views urlpatterns = [ url(r'^xyz/', views.xyz), ]
views.py
def xyz(request): return render(request,'xyz.html')
xyz.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button type="button" id="btn">点我</button> <script>
$('#btn').click(function () {
$.ajax({
url:'http://127.0.0.1:8000/',
type:'get',
success:function (res) {
console.log(res)
}
})
})
</script> {#<script src="http://127.0.0.1:8000/abc/"></script>#} <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
现在,打开使用浏览器打开http://127.0.0.1:8000/xyz/,点击页面上的 '点我' 按钮,会在console页面发现错误信息如下:
为什么报错呢?因为同源策略限制跨域发送ajax请求。
细心点的同学应该会发现我们的demo1项目其实已经接收到了请求并返回了响应,是浏览器对非同源请求返回的结果做了拦截。
但是我们在页面中的script标签中写入src的cdn方式就可以跨域请求,这就是一个漏洞,我们可以利用这点来搞些事情。我们可以不用在demo2进行点击事件,可以这样
<script src="http://127.0.0.1:8000/abc"></script>
这样就会出现这样的报错。
说是alex没有定义。我们在上面定义一个alex,就不会报错了,var alex = ‘sb’,既然定义一个变量可以,那么定义一个函数名叫alex同样也是可以的,在demo1返回一个alex(),就可以执行demo2中的这个函数。
//回调函数
function alex() {
console.log(‘我是大帅逼’)
}
这样就可以实现跨域请求了。
既然可以执行函数,那么当然可以加参数了。在demo1试图函数中添加一个参函数。(因为添加的是一个字典,所以要导入json进行序列化)
def abc(request): res = {'code':0,'data':[0,1,2,3]} data_str = json.dumps(res) return HttpResponse('alex({})'.format(data_str))
在demo2中就在函数中加一个参数(也要注意函数要定义在script标签之前,不然会报alex为定义的错误。)
//回调函数
function alex(res) {
console.log(res)
}
这就实现了在demo2的端口拿到了demo1端口的数据。
现在是页面刷新就发送请求,那么怎么实现点击之后在发送请求呢?我们有这样一个思路就是在我们点击按钮时,插入一个script标签。
$('#btn').click(function(){ var scriptEle = document.createElement('script') // 创建标签 $(scriptEle).attr('src','http://127.0.0.1:8000/abc') //添加属性 $('body').append(scriptEle) //将script标签添加到body $(scriptEle).remove() //发送请求后删除 }
这样同样可以拿到另一个页面的数据,这其实就是jsonp的原理。利用script标签绕过同源策略的限制,拿到数据。
那么当demo1中有两个试图函数呢?我们怎么实现去访问这两个试图的数据。这当然是写两个点击事件呗。
function addscriptag(url) { var scriptEle = document.createElement('script'); $(scriptEle).attr('src',url); $('body').append(scriptEle); $(scriptEle).remove(); } $('#btn').click(function () { addscriptag('http://127.0.0.1:8000/abc/') }); $('#btn2').click(function () { addscriptag('http://127.0.0.1:8000/cba/') });
这里是将创建script标签写成了函数。这样就能获取到两个视图函数的数据了。
那我这样写:
demo2中的script标签:
function addscriptag(url) { var scriptEle = document.createElement('script'); $(scriptEle).attr('src',url); $('body').append(scriptEle); $(scriptEle).remove(); } $('#btn').click(function () { addscriptag('http://127.0.0.1:8000/abc/?func=alex') }); $('#btn2').click(function () { addscriptag('http://127.0.0.1:8000/cba/?func=yangbo') });
这个代码也可以这样写:
function addscriptag(url,func) {
var scriptEle = document.createElement('script');
$(scriptEle).attr('src',url + '?func=' + func);
$('body').append(scriptEle);
$(scriptEle).remove();
}
$('#btn').click(function () {
addscriptag('http://127.0.0.1:8000/abc/','alex')
});
$('#btn2').click(function () {
addscriptag('http://127.0.0.1:8000/cba/','yangbo')
});
demo1中的视图函数:
def abc(request): func = request.GET.get('func') res = {'code':0,'data':[0,1,2,3]} data_str = json.dumps(res) return HttpResponse('{}({})'.format(func,data_str)) def cba(request): func = request.GET.get('func') res = {'name':'alex','age':20} data_str = json.dumps(res) return HttpResponse('{}({})'.format(func,data_str))
当我们点击btn的时候,走的是http://127.0.0.1:8000/abc/?func=alex地址,在demo1的abc视图函数中,get取到func的值,作为参数,并且和数据一起返回,点击btn2的时候同理。
这样我们就实现了地址的动态变化,只需要你改func后面的内容,就能拿到不同视图函数中的数据。
但是这样写还是很麻烦。
jQuery中getJSON方法
demo2中的js代码:
$('#btn').click(function () { $.getJSON('http://127.0.0.1:8000/abc/?callback=?',function (res) { console.log(res) }) })
demo1的视图函数:
def abc(request): func = request.GET.get('callback') res = {'code':0,'data':[0,1,2,3]} data_str = json.dumps(res) return HttpResponse('{}({})'.format(func,data_str))
这样是不是就是很简单。格式比较固定都不需用写回调函数。callback后面的那个?是jQuery内部自动生成的一个回调函数名。
但是如果我们就想实现执行我们自定义的回调函数名,就要用到ajax。
//回调函数 function yangbo(res) { $.each(res,function (k,v) { console.log(k,v) }) } $('#btn').click(function () { $.ajax({ url:'http://127.0.0.1:8000/abc', dataType:'jsonp', //此次请求是jsonp格式 jsonp:'callback', //回调函数url的参数名 jsonpCallback:'yangbo' // 回调函数 }) })
那能不能再优化一下呢?
$('#btn').click(function () { $.ajax({ url:'http://127.0.0.1:8000/abc', dataType:'jsonp', success:function (res) { console.log(res) } }) })
JSONP应用:
$('#btn').click(function () { $.ajax({ url:'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403', dataType:'jsonp',
jsonp:'callback' jsonpCallback:'list', success:function (res) { console.log(res)
console.log(res.data)
接下来就进行一些dom操作
} })
有几个关键点:回调函数和回调函数url的参数名。
老师完整代码:
// 跨域请求示例 $("#show-tv").click(function () { $.ajax({ url: "http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403", dataType: 'jsonp', jsonp: 'callback', jsonpCallback: 'list', success: function (data) { var weekList = data.data; var $tvListEle = $(".tv-list"); $.each(weekList, function (k, v) { var s1 = "<p>" + v.week + "列表</p>"; $tvListEle.append(s1); $.each(v.list, function (k2, v2) { var s2 = "<p><a href='" + v2.link + "'>" + v2.name + "</a></p>"; $tvListEle.append(s2) }); $tvListEle.append("<hr>"); }) } }) });