国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

跨域解決方案

Scholer / 1250人閱讀

摘要:同源策略帶來兩個問題無法獲取非本域的資源無法調取接口無法傳遞以下提供三種方案以供參考。期間存在跨域,但沒有違背同源策略。

在進行前后端分離的開發中,跨域是一個不得不解決的問題。以下基于 Vue-Resource、PHP 及 Nginx 介紹跨域問題及其解決方案。

跨域問題 配置

Nginx 中的配置只是簡單的指向 PHP 代碼的所在目錄:

server{

    listen         80;
    server_name localhost;
    root         /mnt/apps;
    index         index.php index.html index.htm;

    location / {
        index       index.php index.html;
    }
    
    location ~ .php$ {
        fastcgi_pass     localhost:9000;
        fastcgi_index     index.php;
        fastcgi_param     SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include         fastcgi_params;
    }
}

PHP 只是輸出一個 JSON 數據,代碼如下:

// api.php

 "value"
];

echo json_encode($response);

Vue-Resource 為調用該接口,試圖獲取其中的數據:

this.$http.get("http://localhost:8088/api.php").then(res => {
    console.log(res);
})
測試

首先,我們在 Postman 中進行測試:

可以看到,這一接口是能夠返回預期值的。

但當我們刷新 Vue 頁面時,控制臺中卻沒有輸出想要的值,而是拋出了錯誤:

XMLHttpRequest cannot load http://localhost:8088/api.php. No "Access-Control-Allow-Origin" header is present on the requested resource. Origin "http://localhost:8080" is therefore not allowed access.
跨域問題

跨域問題基于瀏覽器的同源策略,簡而言之,就是腳本不能調用來自不同域名、不同協議、不同端口的資源。如上,來自 localhost:8080 的 JavaScript 代碼試圖獲取 localhost:8088 的 PHP 返回值(視為資源),便違背了同源策略,從而引發跨域問題。

有關同源策略的更多信息,可以參考知乎的這篇討論:對于瀏覽器的同源策略你是怎樣理解的呢?。

同源策略帶來兩個問題:

無法獲取非本域的資源(無法調取 API 接口);

無法傳遞 Cookie;

以下提供三種方案以供參考。

PS:有同學可能會有疑問,為什么在 Postman 中不會有跨域問題呢?注意,跨域問題針對的是腳本對資源的訪問限制,而 Postman 本身基于客戶端代碼,是 C/S 架構,自然不會有此問題。這就像是使用 curl 調用接口也不會受同源策略影響一樣。

方案一:Jsonp 方案

當我們談及獲取非本域資源時,可以發現并不是所有類型的資源都受同源策略限制的,比如圖片和 JavaScript、CSS 等。

這也使得我們可以轉換思路,采用一種取巧的方式獲得那些被同源策略拒絕的資源。比如,服務端動態地把數據放在 JavaScript 中,在前端請求時,將動態生成的 JavaScript 文件返回,文件中的內容包含相應的數據。

可是,單純的在 JavaScript 文件中包含數據是不能被前端獲取的。因為通過 JavaScript 代碼是不能讀取文件中的內容的。所以,我們的思路還需要轉換一下??紤]以下場景:




    


    
    
    

引用的遠程 JavaScript 文件為:


這樣一來,通過這種方式,我們便在本地獲取到了遠程的數據。期間存在跨域,但沒有違背同源策略。

也就是說,只要后臺能夠根據前臺的請求,動態的生成一個調用特殊函數的 JavaScript 文件就可以了。具體流程如下:

在這其中,有幾個問題需要解決:

JavaScript 代碼如何才能向后臺請求 JavaScript 文件?

前后端如何商議 handle 函數的名字?

先來看第一個問題。事實上,在 JavaScript 代碼中是不能直接向后臺索要 JavaScript 文件的。除非使用 DOM 操作創建一個 script 標簽,再將請求地址通過 src 填充到該標簽之中。可即使是這樣,讓后臺動態生成 JavaScript 文件的方案還是不合適,這無疑增加了后臺的負擔。

不生成文件,如何實現函數調用和調用時傳參呢?

在 JavaScript 中,我們可以使用 eval() 使得字符串具有特殊意義。如 eval("handle("data")") 可以使得中間的字符串變為 handle 函數的執行。這樣一來,后臺便不必再生成 JavaScript 文件,而只用發送字符串,再由前臺通過 eval 處理即可。

第二個問題相對容易解決,我們都知道,在進行 API 請求時,無論是 GET 還是 POST,都可以攜帶參數。也就是說,我們只需要把想要后臺使用的函數名通過參數傳遞即可,如 http://localhost:8088/api.php?callback=handle。后臺接收到請求后,取得 callback 參數即可獲得所需的函數名。

這樣一來,整個流程就變為了:

實現

采用原生方法實現時,我們需要準備一個接收函數(如 handle),以及在收到數據后使用 eval 將其包裹。這一過程實際上引入了很多與業務無關的代碼。

借助 Vue-Resource 或 jQuery 等庫,我們可以輕松地實現 jsonp:

將原先的 JavaScript 代碼改變為:

this.$http.jsonp("http://localhost:8088/api.php").then(res => {
    console.log(res);
})

并將 PHP 代碼修改為:

 "value"
];

$callback = $_GET["callback"];

$json = json_encode($data);

echo $callback."(".$json.")";
測試

此時,我們可以在看到如下結果:

如此,我們便得到了想要的數據。

注意這里的請求 URL,Vue-Resource 自動幫我們加上了 callback 參數,即接收函數的名字。

如果我們想要自行指定接收參數的名字,或者在請求時添加額外的參數,可以使用如下方式:

this.$http.jsonp("http://localhost:8088/api.php", {
    params: {
        param1: 1
    },
    jsonp: "callback"
}).then(res => {
    console.log(res);
})

在瀏覽器的控制臺中,我們可以找到此次請求的網絡傳輸過程:

可以看到,這里實際是發起了一次 GET 請求。

此外,由上圖可知,使用 jsonp 的方式,Cookie 值也是可以成功傳遞的。

但是,這種做法其實是存在一些問題。因為需要適配 jsonp 的需求,返回值實際上變成了接收函數與實際數據的字符串拼接:

這確實是解決了跨域的需求,但對于不跨域的請求,就需要另行處理了。

另外,由于本身不能指定請求類型,采用 jsonp 難以進行 RESTful 風格的 API 請求(除非使用請求頭方法覆蓋),因而對于愈發流行的 API 請求范式,這一方式也顯得有些過時。

方案二:服務端代碼中增加響應頭 方案

同源策略的目的是為了安全性,那么有沒有一種方法,使得客戶端和服務器之間彼此信任,從而同意對方跨域訪問呢?

以下討論通過添加響應頭的方式解決跨域問題。

實現 跨域解決

首先我們將 PHP 和 JavaScript 代碼還原:

header("Content-type : application/json");

$data = [
    "key" => "value"
];

echo json_encode($data);
this.$http.get("http://localhost:8088/api.php").then(res => {
    console.log(res);
})

這時瀏覽器又會提示出現跨域問題。

接著我們為 PHP 代碼增加一條語句:

header("Access-Control-Allow-Origin : *");

此時便可以得到期望的返回值了??梢宰⒁獾剑覀兇藭r并沒有修改前臺代碼。

PUT、DELETE 等復雜請求問題

下面,我們將代碼稍作修改,將前端請求方式改為 PUT:

this.$http.put("http://localhost:8088/api.php").then(res => {
    console.log(res);
})

這時,瀏覽器又拋出了跨域錯誤。為了解決這一問題,我們還需要給 PHP 代碼加入一個響應頭:

header("Access-Control-Allow-Methods : PUT");

當加入這一響應頭后,瀏覽器依然會拋出跨域問題,只不過這一問題現在變成了:

XMLHttpRequest cannot load http://localhost:8088/api.php. Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

提示我們,Content-type 這一請求頭不被允許。

針對這一問題,我們按照提示,在 PHP 代碼再增加一個響應頭:

header("Access-Control-Allow-Headers : Content-type");

此時,跨域問題便解決了。注意,這里不能使用 * 號,所需增加的內容需要根據實際情況。

為什么采用 GET 請求的時候,不需要這一條代碼呢?這是由于 HEAD、GET、POST 類型的請求為簡單請求,只需增加 Access-Control-Allow-Origin: * 即可。但除此之外的請求方式均為復雜請求。在復雜請求發起時,瀏覽器會首先發送 OPTIONS 類型的請求,詢問瀏覽器是否同意跨域,以及允許跨域的條件(OPTIONS 無需寫入 Access-Control-Allow-Methods 中),這一步被稱為預請求(preflight request)。

就上面的情況而言,因為服務端沒有設置是否允許復雜請求及具有特殊 Content-type 頭的請求進行跨域,所以請求被攔截了下來。

通過瀏覽器的控制臺我們可以更清晰地看到這些過程:

首先發送了一次 OPTIONS 請求:

然后才是真正的 PUT 請求:

同理,當使用 DELETE 等其他復雜請求時,只需修改響應頭即可。

關于這一點,這篇博客有著非常詳細的介紹:CORS 跨域 access-control-allow-headers 的問題 - CSDN。

emulateHTTP 與 emulateJSON

在 Vue-Resource 中,提供了這兩種參數。

其中,前者可以將 PUT、DELETE 和 PATCH 請求轉換為 POST,并通過 X-HTTP-Method-Override 請求頭標識真實的請求類型。這種做法可以兼容一些舊版本的協議。

而后者可以將請求的 body 使用 application/x-www-form-urlencoded 編碼。有關 x-www-form-urlencoded 可以參考 form-data、x-www-form-urlencoded、raw、binary的區別。

Cookie 問題

通過瀏覽器的控制臺,或是將后臺代碼改為:

header("Access-Control-Allow-Origin : *");
header("Access-Control-Allow-Methods : PUT");
header("Access-Control-Allow-Headers : Content-type");

header("Content-typ : application/json");

$data = [
    "key" => "value"
];

$data = $_COOKIE;

echo json_encode($data);

我們可以檢測到,這次的請求并沒有攜帶 Cookie 進行發送。而沒有 Cookie 會使得大量需求無法實現。那么該如何解決這一問題呢?

此時,我們需要對前端和后臺代碼同時進行修改:

修改 JavaScript 代碼為:

this.$http.put("http://localhost:8088/api.php",{}, {
    credentials: true,
}).then(res => {
    console.log(res);
})

注意,在 credentials: true 的前面還有一個 {},這是因為對于 PUT、POST、DELETE 等可以攜帶 body 體參數的請求而言,其第二個參數為 body 參數項,其他配置需要放在第三個參數中。

對于 GET 請求,我們則需要把它放到第二個參數中:

this.$http.get("http://localhost:8088/api.php",{
    credentials: true,
}).then(res => {
    console.log(res);
})

在 PHP 代碼中,我們需要增加一個響應頭:

header("Access-Control-Allow-Credentials: true");

此時,刷新瀏覽器可以發現又拋出了一個錯誤:

XMLHttpRequest cannot load http://localhost:8088/api.php. Response to preflight request doesn"t pass access control check: The value of the "Access-Control-Allow-Origin" header in the response must not be the wildcard "*" when the request"s credentials mode is "include". Origin "http://localhost:8080" is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

這提示我們,當使用 credentials 時,Access-Control-Allow-Origin 的響應頭不能設置為 *。此時,我們需要把原本的后臺設置改為:

$origin = $_SERVER["HTTP_ORIGIN"];

header("Access-Control-Allow-Origin : ".$origin);

即前端域名為什么,后臺便允許什么跨域。

這樣一來,我們便又可以正常的得到 Cookie 值了:

通過這種方式依然需要我們增加很多與業務無關的代碼。當然了,我們可以通過在后臺框架中增加中間件的方式為響應結果統一添加響應頭。雖然這種方式確實很方便,但當后臺返回的狀態碼不是 2xx,即后臺報錯或進行重定向時,瀏覽器收到的結果依然會變成跨域錯誤。這種情況使得我們無法在測試時準確的知道哪里出現了問題。

為了更進一步的改進跨域方案,我們試著將增加響應頭的工作交給服務器來做,如 Apache 或 Nginx。

方案三:使用 Nginx 配置的方式解決跨域

在修改 Nginx 配置前,我們先將 PHP 代碼還原:

header("Content-type : application/json");

$data = $_COOKIE;

echo json_encode($data);

注意,JavaScript 代碼與之前相同。如需考慮 Cookie 問題,還是需要在異步請求中中加入 credentials: true 的。

然后,我們修改 Nginx 配置為:

server{

    listen         80;
    server_name localhost;
    root         /mnt/apps;
    index         index.php index.html index.htm;

    location / {
        index       index.php index.html;
    }
    
        location ~ .php$ {
        add_header "Access-Control-Allow-Origin" "$http_origin";
        add_header "Access-Control-Allow-Credentials" "true";
        add_header "Access-Control-Allow-Methods" "PUT, DELETE";
        add_header "Access-Control-Allow-Headers" "Content-type";
        fastcgi_pass     localhost:9000;
        fastcgi_index     index.php;
        fastcgi_param     SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include         fastcgi_params;
    }

}

注意,這里主要的代碼在于通過 Nginx 為響應附加響應頭:

add_header "Access-Control-Allow-Origin" "$http_origin";
add_header "Access-Control-Allow-Credentials" "true";
add_header "Access-Control-Allow-Methods" "PUT, DELETE";
add_header "Access-Control-Allow-Headers" "Content-type";

而添加的響應頭類型均與之前在 PHP 代碼中的改動一致。

如此,也可以解決跨域問題。

靜態資源文件跨域問題

在進行前端開發時,很可能會使用字體圖標,當我們試圖獲取非本域的如 iconfont.eot 等資源時,很可能也會在控制臺中看到跨域拒絕的報錯信息。

諸如此類的問題都可以使用添加響應頭來實現。只不過對于這種靜態資源文件,由于它們都是 GET 請求,所以我們只需要在服務器中添加 Access-Control-Allow-Origin: *。如在 Nginx 中:

location ~* .(eot|otf|ttf|woff|svg)$ {
    add_header  "Access-Control-Allow-Origin" "*";
}

這樣,在前端請求這些后綴名的資源文件時,便不會出現報錯信息了。

后記:Laravel 的坑

最近在使用 Laravel 時,發現了一個詭異的現象:當在 Laravel 中使用中間件進行跨域時(代碼如下):

class CORSProtection
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        if(isset($_SERVER["HTTP_ORIGIN"])){
            $origin = $_SERVER["HTTP_ORIGIN"];
            $response->header("Access-Control-Allow-Origin", $origin);
        }
        $response->header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
        $response->header("Access-Control-Allow-Credentials", "true");
        $response->header("Access-Control-Allow-Methods", "POST, PUT, DELETE, OPTIONS");
        return $response;
    }
}

來自前端的跨域請求:GET 和不帶參數的 POST 都可以正常發送。而一旦 POST 中攜帶了參數,瀏覽器就會輸出跨域錯誤。

通過查看瀏覽器的網絡,發現在 POST 請求前確實發送了一次 OPTIONS 請求,但該請求的響應并沒有按我們之前所說的攜帶上允許跨域的信息頭。

通過查閱資料發現,Laravel 會對 OPTIONS 請求自動返回 200 狀態碼而無視中間件或其他形式的響應頭附加。詳見參考:關于 Laravel 下 Cors 跨域 POST 請求的一種實現方法。

此時,我們需要在路由中強制加入對 OPTIONS 類請求的響應,以使得 OPTIONS 探測請求能夠正確的響應我們想要的跨域允許信息:

Route::options("{any}", function ($any) {
    return response("ok");
})->middleware("cors");

由于 Laravel 中似乎沒有缺省路由,這里需要根據請求 URL 的層級添加不同的路由。

但是,為什么 GET(無論帶參與否)以及不帶參的 POST 請求都沒有出現這一問題呢?還記得前面提到的復雜請求和簡單請求嗎?GET 和 POST 雖然都是簡單請求,但當 POST 攜帶參數時,由于大多數前端 HTTP 請求框架的默認 POST 帶參請求頭 Content-Type 都是 application/json,不屬于簡單請求的類型,因而觸發了 OPTIONS 探測。而 Laravel 會默認對 OPTIONS 請求返回 200 狀態,而不是攜帶我們定義好的那些響應頭,于是就出現了上述詭異的情況。

這里要說明的是,使用 Nginx 方案是不會遇到這一問題的。此外,在請求中添加請求頭,強制 Content-Type 為 x-www-form-urlencoded 或其他簡單請求類型時(如 Vue-Resource 中使用 emulateJSON: true),也可以跨過這一問題。

參考

JSON數據的HTTP Header應該怎么標記? - segmentfault

Vue2.0 vue-source.js jsonp demo vue跨域請求 - 博客園

說說JSON和JSONP,也許你會豁然開朗 - 博客園

CORS 跨域 access-control-allow-headers 的問題 - CSDN

跨域(CORS) 解決方案中,為什么 Access-Control-Allow-Methods 不起作用? - segmentfault

使用withCredentials發送跨域請求憑據 - iteye

PHP Ajax 跨域問題最佳解決方案 - 博客園

Nginx CORS實現JS跨域 - CSDN

關于Laravel下Cors跨域POST請求的一種實現方法

PHP and laravel知識點小小積累 - 博客園

關于 Content-Type:application/x-www-form-urlencoded 和 Content-Type:multipart/related - 博客園

四種常見的 POST 提交數據方式

Laravel-Ajax-AcrossDoamin (跨域) Post傳Json - 博客園

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83935.html

相關文章

  • ajax跨域,這應該是最全的解決方案

    摘要:關于,強烈推薦閱讀跨域資源共享詳解阮一峰另外,這里也整理了一個實現原理圖簡化版如何判斷是否是簡單請求瀏覽器將請求分成兩類簡單請求和非簡單請求。 前言 從剛接觸前端開發起,跨域這個詞就一直以很高的頻率在身邊重復出現,一直到現在,已經調試過N個跨域相關的問題了,16年時也整理過一篇相關文章,但是感覺還是差了點什么,于是現在重新梳理了一下。 個人見識有限,如有差錯,請多多見諒,歡迎提出iss...

    ytwman 評論0 收藏0
  • 20K前端大佬面試(關于如何回答ajax跨域問題)

    摘要:在接觸前端開發起,跨域這個詞就一直以很高的頻率在我們學習工作中重復出現,最近在工作中遇到了跨域的相關問題,這里我把它總結記錄一下。 在接觸前端開發起,跨域這個詞就一直以很高的頻率在我們學習工作中重復出現,最近在工作中遇到了跨域的相關問題,這里我把它總結記錄一下。關于跨域,有N種類型,現在我只專注于ajax請求跨域(ajax跨域只是屬于瀏覽器同源策略中的一部分,其它的這里不做介紹),內容...

    Yangyang 評論0 收藏0
  • AJAX跨域完全講解

    摘要:跨域完全講解今天在慕課網上學習了跨域完全講解我在收集面試題的時候其實就已經有過跨域的問題的了,當時候知道了為什么會存在跨域,以及跨域解決的方案有哪些,今天隨著課程的學習,又加深了跨域的理解,以此記錄下來。 AJAX跨域完全講解 今天在慕課網上學習了AJAX跨域完全講解:https://www.imooc.com/learn/947 我在收集AJAX面試題的時候其實就已經有過AJAX跨域...

    alexnevsky 評論0 收藏0
  • AJAX跨域完全講解

    摘要:跨域完全講解今天在慕課網上學習了跨域完全講解我在收集面試題的時候其實就已經有過跨域的問題的了,當時候知道了為什么會存在跨域,以及跨域解決的方案有哪些,今天隨著課程的學習,又加深了跨域的理解,以此記錄下來。 AJAX跨域完全講解 今天在慕課網上學習了AJAX跨域完全講解:https://www.imooc.com/learn/947 我在收集AJAX面試題的時候其實就已經有過AJAX跨域...

    i_garfileo 評論0 收藏0

發表評論

0條評論

Scholer

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<