處理跨域請求


瀏覽器具有同源策略,會禁止向與當前頁面不同的域發送請求,只要是協議,域名,端口中有任何一個不同,都被當作是不同的域,這雖然是一種保護數據的機制,但是對我們開發來說確是個麻煩,解決辦法有很多,這里介紹一下JSONP和CORS

先來理解一下瀏覽器這個同源策略是什么,就是瀏覽器禁止掉向其他域名發送的請求,其實是在請求回來的時候被禁掉的

哪些操作會受同源策略,哪些不會呢?

  requests模塊不受影響,因為沒有經過瀏覽器

  ajax發請求時,瀏覽器會限制(我們就是要解決這個問題)

  有src屬性的都不受同源策略限制   -img,script,iframe

    但是注意,script中的src,拿到的數據會當作js代碼執行,所以不能直接把數據放到src中

 

JSONP

  jsonp是一種機智的方式,本質就是在遠程發送數據的時候,在數據外層套一個函數名,把數據以函數的形式返回這樣才能被script識別

  在本地先定義一個函數,當遠程發送過來數據,本地就當成一個函數執行真實數據就相當於參數

 

  jsonp本質就是創建一個script標簽,把url放到src屬性中,就是相當於發送了一個get請求,事實上jsonp只能發送get請求

 

用一個簡單的示例來理解一下jsonp的本質

客戶端的地址是:localhost:8000,要向服務端 localhost:8888 發送請求,獲取數據,

利用jsonp來避開同源策略:

客戶端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<button onclick="jsonp('http://127.0.0.1:8888/get_data?callback=func')">獲取數據</button>{#通過參數向服務端發送函數名,這樣就能實現動態生成函數名#}
<script>
    function func(arg) {
        console.log(arg);
        document.head.removeChild(tag);
    }

    function jsonp(url) {
        tag = document.createElement('script');//創建一個script標簽,用來發送請求,注意是一個全局的變量,以便在func函數中刪除
        tag.src = url;
        document.head.appendChild(tag); //在html的頭文件中添加這個script標簽
    }
</script>
</body>
</html>

 

服務端:

from django.shortcuts import HttpResponse

def get_data(request):
func_name = request.GET.get('callback')#從請求頭中獲取函數名
return HttpResponse('%s(數據)'%func_name)#返回一個函數,把數據當作參數

1.jsonp就是利用script的src發送請求不會被受同源策略限制,然后通過src屬性向服務端發送請求

2.script會把請求來的數據當成是js代碼,所以服務端返回的數據不能直接是數據,會報錯

3.把返回值封裝成一個函數的形式,真實數據放在函數的參數中,客戶端等收到后,從函數中拿到數據就行了

4.這個函數名最好是動態生成的,不然每次發送一個請求就要前后端商量好函數名,太麻煩

 

以上就是jsonp的原理

所以使用jsonp,本地需要定義一個函數,而遠程需要把數據封裝成一個函數

再來看看ajax如何實現跨域請求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<button onclick="jsonp()">獲取數據</button>
<script>
    function func(arg) {
        console.log(arg);
        document.head.removeChild(tag);
    }

    {# 這種實際上就是內部幫我們實現創建一個script標簽#}
    function jsonp() {
        $.ajax({
            url:'http://127.0.0.1/get_data',
            type:'GET',
            dataType:'JSONP',
            jsonp:'callback',
            jsonCallback:'func'{# 這兩行就是自動在url后面添加?callback=func #}


        })
    }
</script>

 

jsonp只能發送get請求,但是可不是所有的數據都能封裝在參數中,想要發送post請求,還得用CORS


 

CORS跨站資源共享(Cross-Origin Resource Sharing)

再來看一下跨域請求時,瀏覽器的提示信息:

之所以ajax發送請求,返回的數據拿不到,是因為少了一點東西。提示缺少一個響應頭,所以CORS的原理就是添加上這個響應頭

注意一定要理解這個‘響應頭’,響應頭是通過響應對象response來設置的

只需要修改視圖函數 

from django.shortcuts import HttpResponse

def get_data(request):
    response = HttpResponse('數據')
    response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8888'#這樣表示允許這個地址訪問
    #response['Access-Control-Allow-Origin'] = '*'#這樣表示允許所有地址訪問
    return response

這樣就可以了,這是最簡單的情況,本地不用做任何事,遠程設置一個響應頭

 

 

 

還有一種復雜點的情況:非簡單請求

簡單請求&非簡單請求:

  簡單請求的條件:  

    1.請求方式為 HEAD,GET,POST

    2.請求頭中Content-Type的值是下面這三個中的一個

      application/x-www-form-urlencoded

      multipart/form-data

      text/plain

 

    同時滿足以上兩個條件時,才是簡單請求,有一個不滿足就是復雜請求

 


對於復雜請求,以PUT請求為例,會先發一個options請求,用來預檢,對於預檢請求,也要返回響應頭

遠程視圖函數要這樣處理:

def data(request):
    if request.method == 'OPTIONS':
        #預檢
        response = HttpResponse()
        response['Access-Control-Allow-Origin'] = '*'
        response['Access-Control-Allow-Methods'] = 'PUT'#允許這個請求頭  
        return response
    elif request.method == 'PUT':
        #預檢通過后真正的請求
        response = HttpResponse('數據')
        response['Access-Control-Allow-Origin'] = '*'
        
        return response

如果GET請求自定義請求頭,也變成了復雜請求,也需要設置一下,允許這個請求頭

比如自定義了一個請求頭:xxx:yyyy

那遠程代碼中就要允許這個請求頭

def data(request):
    if request.method == 'OPTIONS':
        #預檢
        response = HttpResponse()
        response['Access-Control-Allow-Origin'] = '*'  #設置為星號表示同意任何跨域請求,還可以寫某一個地址(協議+域名+端口)
        # response['Access-Control-Allow-Methods'] = 'PUT,POST,DELETE'
        response['Access-Control-Allow-Headers'] = 'Content-Type,application/json'#允許這個請求頭 

  return response

elif request.method == 'PUT': #預檢通過后真正的請求
  response = HttpResponse('數據')
  response[
'Access-Control-Allow-Origin'] = '*'

  return response
雖然復雜請求可以解決
但是,要盡量避免復雜請求,會增加服務器壓力
 
還有一些設置:
   Access-Control-Max-Age    該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
   Access-Control-Allow-Credentials      它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為 true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為 true如果服務器不要瀏覽器發送Cookie,刪除該字段即可。
 
  Access-Control-Expose-Headers    CORS請求時, XMLHttpRequest對象的 getResponseHeader()方法只能拿到6個基本字段: Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必須在 Access-Control-Expose-Headers里面指定。

 

 

 
發送cookie
CORS請求默認不發送Cookie和HTTP認證信息。如果要把Cookie發到服務器,一方面要服務器同意, 即指定Access-Control-Allow-Credentials :true
 
另一方面,開發者必須在AJAX請求中打開withCredentials屬性。
 
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否則,即使服務器同意發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理

但是,如果省略withCredentials設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials   xhr.withCredentials = false;

 
需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取服務器域名下的Cookie。
 
 

總結,解決跨域請求,常用的有三種方式:requests模塊,cors,jsonp
后兩種直接從前端拿數據,不需要經過服務器,requests模塊是先把請求發送到服務器,服務器向目標發送請求,再返回數據到本地
 
jsonp和cors相比,兼容性更好一點
 
參考博客:http://www.cnblogs.com/wupeiqi/articles/5703697.html
    http://www.ruanyifeng.com/blog/2016/04/cors.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM