一、一個源的定義
如果兩個頁面的協定,端口(如果有指定)和域名都相同,則兩個頁面具有相同的源。
舉個例子:
下表給出了相對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)
http://a.opq.com/dir/other.html 失敗 不同域名 ( xyz和opq)
二、同源政策是什麼?
同源政策是浏覽器的一個安全功能,不同源的用戶端腳本在沒有明确授權的情況下,不能讀寫對方資源。是以xyz.com下的js腳本采用ajax讀取abc.com裡面的檔案資料是會被拒絕的。
同源政策限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行互動。這是一個用于隔離潛在惡意檔案的重要安全機制。
三、基于jsonp實作的跨域請求
- 頁面中的連結,重定向以及表單送出是不會受到同源政策限制的。
- 跨域資源的引入是可以的。但是js不能讀寫加載的内容。如嵌入到頁面中的<script src="..."></script>,<img>,<link>,<iframe>等。
下面來分步舉例詳細闡述其中的奧妙:
1、先開兩個項目,
項目1(http://127.0.0.1:8000/)
項目2(http://127.0.0.1:8100/)
項目1
url:
url(r'index1/$',views.index1)
views:
def index1(request):
return HttpResponse('wangjifei')
項目2
url:
url(r'index2/$',views.index2)
views :
def index2(request):
return render(request,'index2.html')
index2.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
$('#btn').click(function () {
$.ajax({
url:"http://127.0.0.1:8000/index1/",
type:'get',
success:function (res) {
console.log(res)
}
})
})
</script>
</body>
</html>
現在,打開使用浏覽器打開
http://127.0.0.1:8100/index2/,點選頁面上的 '送出' 按鈕,會在console頁面發現錯誤資訊如下:

微信圖檔_20180823142349.png
為什麼報錯呢?因為同源政策限制跨域發送ajax請求。
細心點的同學應該會發現我們的demo1項目其實已經接收到了請求并傳回了響應,是浏覽器對非同源請求傳回的結果做了攔截。
再細心點的同學會發現,我們使用cdn方式引用的jQuery檔案也是跨域的,它就可以使用。
同樣是從其他的站點拿東西,script标簽就可以。那我們能不能利用這一點搞點事情呢?
2、把index2.html中的代碼改一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>
現在重新整理一下會出現如下錯誤:
微信圖檔_20180823143053.png
看來後端傳回的響應已經被拿到了,隻不過把wangjifei當成了一個變量來使用,但是該頁面上卻沒有定義一個名為wangjifei的變量。是以出錯了。
3、那我們就在index2.html中定義一個wangjifei變量看看:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script>
var wangjifei = 123
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>
重新整理發現不報錯了,
微信圖檔_20180823143436.png
4、我定義一個變量可以,那可不可以定義一個函數呢?
index2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script>
function wangjifei() {
console.log('出手就要專業')
}
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>
項目1中的views:也修改一下
def index1(request):
return HttpResponse('wangjifei()')
重新整理一下頁面顯示結果:
微信圖檔_20180823145716.png
結果分析:傳回的 wangjifei(),頁面上拿到這個響應之後直接執行了wangjifei函數!
5、那函數中可不可以傳遞參數呢?我們試一下!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script>
function wangjifei(res) {
console.log(res)
}
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script src="http://127.0.0.1:8000/index1/"></script>
</body>
</html>
項目1中的 views
from django.http import HttpResponse
import json
def index1(request):
ret={'code':1,'msg':[110,119,120,12306]}
res = json.dumps(ret)
return HttpResponse(f'wangjifei({res})')
重新整理之後顯示結果:
微信圖檔_20180823151309.png
果然傳遞參數也是可以的!我們通過script标簽的跨域特性來繞過同源政策拿到想要的資料了!!!
這其實就是JSONP的簡單實作模式,或者說是JSONP的原型:建立一個回調函數,然後在遠端服務上調用這個函數并且将JSON 資料形式作為參數傳遞,完成回調。
将JSON資料填充進回調函數,這就是JSONP的JSON+Padding的含義。
但是我們更多時候是希望通過事件觸發資料的擷取,而不是像上面一樣頁面一重新整理就執行了,這樣很不靈活。
6、我們可以通過javascript動态的建立script标簽來實作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
//自定義的函數
function wangjifei(res) {
console.log(res)
}
//jquery給button綁定點選事件
$('#btn').click(function () {
//建立一個script标簽
var scriptEle = document.createElement('script');
//給标簽添加src屬性,并添加對應的屬性值 http://127.0.0.1:8000/index1
$(scriptEle).attr('src','http://127.0.0.1:8000/index1');
//将建立好的标簽添加到頁面中,标簽添加後就會自動觸發get請求
$('body').append(scriptEle);
//将标簽移除
$(scriptEle).remove()
})
</script>
</body>
</html>
這樣當我們點選button按鈕的時候,會在頁面上插入一個script标簽,然後從後端擷取資料後再删除掉。
7、為了實作更加靈活的調用,我們可以把用戶端定義的回調函數的函數名傳給服務端,服務端則會傳回以該回調函數名,将擷取的json資料傳入這個函數完成回調。這樣就能實作動态的調用了。修改代碼如下:
index2.html代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
//自定義的函數
function xxx(res) {
console.log(res)
}
//jquery給button綁定點選事件
$('#btn').click(function () {
//建立一個script标簽
var scriptEle = document.createElement('script');
//給标簽添加src屬性,并添加對應的屬性值 http://127.0.0.1:8000/index1?callback=xxx
$(scriptEle).attr('src','http://127.0.0.1:8000/index1?callback=xxx');
//将建立好的标簽添加到頁面中,标簽添加後就會自動觸發get請求
$('body').append(scriptEle);
//将标簽移除
$(scriptEle).remove()
})
</script>
</body>
</html>
項目1中views:
from django.http import HttpResponse
import json
def index1(request):
ret={'code':1,'msg':[110,119,120,12306]}
res = json.dumps(ret)
callback = request.GET.get('callback')
return HttpResponse(f'{callback}({res})')
四、jQuery中getJSON方法介紹:
1、jQuery中有專門的方法實作jsonp。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
//jquery給button綁定點選事件
$('#btn').click(function () {
$.getJSON("http://127.0.0.1:8000/index1?callback=?",function (res) {
console.log(res)
})
})
</script>
</body>
</html>
要注意的是在url的後面必須要有一個callback參數,這樣getJSON方法才會知道是用JSONP方式去通路服務,callback後面的那個?是jQuery内部自動生成的一個回調函數名。
2、但是如果我們想自己指定回調函數名,或者說服務上規定了回調函數名該怎麼辦呢?我們可以使用$.ajax方法來實作:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
//jquery給button綁定點選事件
$('#btn').click(function () {
$.ajax({
//要通路的url
url:"http://127.0.0.1:8000/index1/",
//要處理的資料類型jsonp
dataType:'jsonp',
//自定義回調函數名必要參數
jsonp:'callback',
//自定義回調函數名,url中callback=後面的函數名
jsonpcallback:'wangjifei'
})
});
//回調函數
function wangjifei(res) {
console.log(res)
}
</script>
</body>
</html>
views:
from django.http import HttpResponse
import json
def index1(request):
ret={'code':1,'msg':[110,119,120,12306]}
res = json.dumps(ret)
callback = request.GET.get('callback')
return HttpResponse(f'wangjifei({res})')
3、用ajax技術通常将回調函數寫在成功回調函數的位置:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="btn">送出</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"> </script>
<script>
//jquery給button綁定點選事件
$('#btn').click(function () {
$.ajax({
//要通路的url
url:"http://127.0.0.1:8000/index1/",
//要處理的資料類型jsonp
dataType:'jsonp',
//success回調
success:function (res) {
console.log(res)
}
})
});
//回調函數
function wangjifei(res) {
console.log(res)
}
</script>
</body>
</html>
最後來一個jsonp的實際應用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同源政策</title>
</head>
<body>
<button id="show-tv">送出</button>
<div class="tv-list"></div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
$("#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;
console.log(weekList);
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>");
})
}
})
});
</script>
</body>
</html>
五、基于Core方法解決跨域請求
- 我們介紹了jsonp解決跨域請求問題,這種解決方式很好的诠釋了跨域請求的本質,但是略顯麻煩,是否還記得在我們不做任何處理的時候,跨域請求時候浏覽器給我們報的錯誤不?翻譯過來就是因為響應頭沒有指定Access-Control-Allow-Origin所允許原始的請求路徑,是以原始請求路徑http://127.0.0.1:8001不被允許通路。 基于上述的原因解釋,我們隻需要在響應的内容加入上述這樣的授權字段,便可解決。
-
簡單請求的定義:
隻要同時滿足以下兩大條件,就屬于簡單請求,不滿足就是複雜請求!!!
1.(1) 請求方法是以下三種方法之一:
2.(2)HTTP的頭資訊不超出以下幾種字段:-- HEAD,GET,POST
-- Accept
-- Accept-Language
-- Content-Language
-- Last-Event-ID
-- Content-Type:隻限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
由于django的所有請求響應都要走中間件,是以可以寫一個跨域的中間件來解決跨域問題
from django.utils.deprecation import MiddlewareMixin
class MyCore(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = "*" //簡單請求
if request.method == "OPTIONS":
# 複雜請求 預檢
response['Access-Control-Allow-Headers'] = "Content-Type"
response['Access-Control-Allow-Methods'] = "POST, DELETE, PUT"
return response