是一種約定安全策略,瀏覽器自帶的安全功能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