跨域
什么是跨域
比如一個鏈接:http://www.baidu.com(端口默認是80端口),
如果再來一個鏈接是這樣:http://api.baidu.com,這個就算是跨域了(因為域名不同)
再來一個:https://www.baidu.com,這個也是跨域了(因為協議不同,用的https)
再來一個http://www.baidu.com:8888,這個也算跨域,端口號不同
舉個實際的例子:
- API接口數據部署在baidu.com上;
- Ajax文件部署在cnblogs.com上,Ajax文件會向API (baidu.com) 發送請求,返回數據;
- 用戶通過bing.com訪問cnblogs.com的Ajax文件,請求數據
以上過程就發生了跨域訪問。如果直接使用Ajax來請求就會失敗,就像Chrome提示的:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
跨域直白點就是,請求協議,域名(IP),端口號,三個其中任何一個不同都算跨域
CORS
CORS(跨域資源共享,Cross-Origin Resource Sharing)是一種跨域訪問的機制,可以讓Ajax實現跨域訪問
那么如果跨域是什么樣的呢?比如,我這里有個restframework項目作為服務器,項目名為drfversion,測試的app名叫testapp,url如下:

view:

啟動項目,直接訪問這個API接口測試:

在django的templates目錄下新建一個test.html,簡單的建立了一個vue和axios的項目,作為前端的異步請求,寫入以下參數:

點那個谷歌瀏覽器圖標,然后直接谷歌瀏覽器打開,這里這個功能是pycharm給我們提供的功能,此時這個作為客戶端,訪問:

里面的這個參數提示就是跨域了,瀏覽器默認有個同源策略,他檢測到這個http://localhost:63342的url與我們后端啟動的DRF項目url:http://127.0.0.1:8000/test/不同源,也就是跨域了,所以報錯。
再看接口,看其實后端沒有錯誤,已經給我們返回了數據:

所以跨域請求的根本原因是瀏覽器的同源策略造成的,要解決這個跨域請求就可以在這方面研究了
而跨域分簡單請求和復雜請求
簡單請求
HTTP方法是只能是HEAD, GET和POST
HTTP頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type
而且Content-Type只能是下列類型:
- application/x-www-from-urlencoded
- multipart/form-data
- text/plain
復雜請求
除了簡單請求的都是復雜請求了,所以如果傳輸json數據,Content-Type類型就是json了,所以一定是復雜請求了
復雜請求會先發出一個預請求,又叫預檢,OPTIONS請求
常見的解決跨域的方法
1.協議和域名,端口都是用同一個就行
2.利用jsonp解決跨域問題
因為看代碼相信你也看到了,那個script腳本標簽導入的兩個js文件,一個用的vue,一個用的axios,然后用到的是各自的cdn,url也都不一樣:

其實也算跨域了,但是瀏覽器並沒有給我們攔截,所以我們是否也可以直接用script的src屬性來訪問,當然可以:

刷新剛才那個前端客戶端頁面,發現真的可以這樣
那么再大膽一點,發一個函數名怎么樣?然后前端先定義好這個函數名,試試看:

后端的視圖函數里返回這個函數調用的字符串:

前端訪問,可行,確實打印了

但是,這只能做GET啊,如果做POST,PUT啥的請求呢?就沒辦法了,而且在調用函數時,前端和后端必須商量好函數名,必須統一才行,所以這jsonp還是有很大的弊端的
2.修改Django中的views.py文件
修改views.py中對應API的實現函數,允許其他域通過Ajax請求數據:
def myview(_request):
response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
4.在后端做處理——Python是利用django中間件添加響應頭
GET:
現在什么都不做,只是get請求,再看下提示的是什么:

注意這句話:No 'Access-Control-Allow-Origin' header is present on the requested resource.
好,在后端寫一個中間件:

配置文件里應用上:

重啟項目,前端再次訪問,這次終於沒有紅色的報錯了

POST:
再來個POST請求看看:

后端的view:

其他后端代碼不變,前端訪問,又變紅了

注意這句話:Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
好的中間件做如下修改:

重啟訪問,有了:

OPTIONS(PUT,DELETE):
好如果是put或者delete請求呢?這個按前面說的簡單請求與復雜請求的說法,這兩個請求方式肯定是復雜請求了,即那個OPTIONS了,搞一個put的請求看看,還是先看看它報什么錯:

view:

重啟訪問,報錯提示的字段跟前面的POST請求字段好像是一個說法對吧

但是我們只加了post的,並沒加put或者delete的,修改中間件:

重啟訪問,確實已解決

然后,其實中間件還是稍微優化一下,把簡單請求和復雜請求做個判斷處理,復雜請求可以預檢嘛,節省下資源:

重啟訪問,沒有任何問題:

相關代碼:
中間件middle:
from django.utils.deprecation import MiddlewareMixin class CorsMiddle(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'] = 'PUT,DELETE' return response
view:
from rest_framework.views import APIView from rest_framework.views import Response class TestView(APIView): def get(self, request): return Response('跨域測試') def post(self, request): return Response('post接口測試') def put(self,request): return Response('put請求測試')
....(還有個delete請求就省略了,跟put請求一樣的)
settings:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middle.CorsMiddle', ]
前端代碼test.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> // function PrintTest() { // console.log('jsonp test'); // } </script> <script src="http://127.0.0.1:8000/test/"></script> </head> <body> <div id="app"></div> <script> const app = new Vue({ el: '#app', mounted() { axios.request({ url: 'http://127.0.0.1:8000/test/', method: 'GET',// "POST","PUT","DELETE" //data:{ // "name":"jack", //} }).then(function (data) { console.log(data) }) } }) </script> </body> </html>
以上4個方法,已經可以解決大部分的跨域請求問題,但是由於我后期再次遇到過更強大的問題:以上4個方法針對火狐瀏覽器沒法,火狐瀏覽器安全性比較高,一樣會報跨域請求,即使你在django的中間件添加了response處理
下面這個使用第三方庫django-cors-headers的方法就可以完美解決火狐瀏覽器問題
5.使用第三方庫django-cors-headers
在Django中,有人開發了CORS-header的middleware,github:傳送門
只需在settings.py中做一些簡單的配置即可,其他不用作任何修改,我們也不用自己手動的創建中間件對response處理了,直接用以下配置即可, 現在用起來服務器端完全開放,開啟CORS,沒有任何跨域煩惱
安裝django-cors-headers:
pip install django-cors-headers
在settings.py中增加:
INSTALLED_APPS = ( ... 'corsheaders', ... ) ... MIDDLEWARE_CLASSES = ( ... 'corsheaders.middleware.CorsMiddleware', # 注意順序,必須放在Comon前面 'django.middleware.common.CommonMiddleware', ... )
#以下直接賦值放在settings.py里即可解決 CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_WHITELIST = ( '*' ) CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', )
總結:
- 針對跨域請求,其實本質上是瀏覽器對返回的結果response的攔截
- 最常見的解決辦法就是在后端返回結果response時做數據處理,讓瀏覽器不攔截
- 大殺招使用django-cors-headers庫,秒殺一切,簡直6得不行
