問題
今天在聯調介面的時候,發現了一個奇怪的錯誤Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response
OPTIONS
請求,獲得瀏覽器支援的請求型別,那麼何時會進行預檢請求呢?
只有在瀏覽器傳送非簡單請求的情況下,才會傳送預檢請求
那麼什麼樣叫做非簡單請求,什麼又是簡單請求呢?
只要同時滿足以下兩大條件,就屬於簡單請求。
- 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
- HTTP的頭資訊不超出以下幾種欄位:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求。
這是該問題所引出的相關名詞,本著好奇的原則,繼續往下走...
跨域
跨域是針對瀏覽器端而產生的,伺服器端的通訊並不會產生這樣的問題,這就引出了第一個解決跨域的思路----代理伺服器(瀏覽器請求本地起的伺服器—同源伺服器,再由後者請求外部服務)並且跨域並非瀏覽器限制了發起跨站請求,而是跨站請求可以正常發起,但是返回結果被瀏覽器攔截了。
代理伺服器
我們利用node可以實現代理伺服器,筆者是利用了http-proxy-middleware
var proxy = require('http-proxy-middleware');
var apiProxy = proxy('/api', {target: 'http://www.example.org', changeOrigin: true});
複製程式碼
在這裡changeOrigin: true
引起了我的興趣,思考為什麼會存在該項配置?這裡提到了虛擬主機與獨立主機的概念
在我們ping baidu.com的時候會得到IP地址,瀏覽器通過這個IP地址是可以訪問網站的(獨立主機),在我們ping apple.com的時候得到的IP地址是不能通過瀏覽器的方式訪問的(共享主機或者虛擬主機) 虛擬主機是一種在單一主機或主機群上,實現多網域服務的方法,可以執行多個網站或服務的技術,因此它可以共享伺服器的記憶體、CPU等資源
虛擬主機主要有以下幾種型別
- 網址名稱對應(Name-based)
- IP地址對應(IP-based)
- Port埠號對應
在http-proxy-middleware
中使用的changeOrigin
配置項正式由於使用了網址名稱對應的虛擬主機。
跨域資源共享(CORS)
在本篇部落格中,該種方式會進行詳細介紹,利用AJAX進行跨域資源共享的時候,我們就會引出文章剛開始所遇到的問題,簡單請求和非簡單請求。
- 簡單請求: 在這裡為了模仿跨域的請求利用到express框架,先建立兩個檔案,一個後端介面檔案backend.js。
'use strict';
var express = require('express'),
app = express();
app.get('/auth/:id/', function (req, res) {
res.send({ id:req.params.id });
});
app.listen(3000);
複製程式碼
然後利用backend.js
指向一個html
檔案,在html
裡面傳送ajax
請求,模仿跨域(埠號不同)
'use strict';
var express = require('express'),
app = express();
app.use(express.static("./"))
app.get('/', function (req, res) {
res.render(__dirname + '/index.html');
});
app.listen(4000);
複製程式碼
最後是html
檔案,引入了axios
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.get('http://localhost:3000/auth/1')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
複製程式碼
當發起請求時,我們發現
即使在返回結果是200的情況下,仍然時跨域提醒,這就與前面的觀點是一致的 跨域並非瀏覽器限制了發起跨站請求,而是跨站請求可以正常發起,但是返回結果被瀏覽器攔截了。 伺服器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭資訊沒有包含Access-Control-Allow-Origin
欄位(詳見下文),就知道出錯了,從而丟擲一個錯誤。
那麼解決方法也很簡單,在後端加上允許跨域請求。
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
複製程式碼
這樣就可以正常訪問
- 非簡單請求
依舊利用上面的程式碼,在傳送請求POST時,當頭部
Content-Type
為application/json
時,程式碼如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.post('http://localhost:3000/auth', {
name: 'gao'
}, {
headers: {
'Content-Type': 'application/json'
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
複製程式碼
會傳送如圖所示的預檢請求:
當頭部Content-Type
為application/x-www-form-urlencoded
時,程式碼如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.post('http://localhost:3000/auth', {
name: 'gao'
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
複製程式碼
會發現,瀏覽器不會傳送預檢請求並且返回成功
那麼如何保證在傳送預檢請求時,瀏覽器不會報錯呢?後端程式碼如下:
'use strict';
var express = require('express'),
app = express();
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "content-type");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.get('/auth/:id/', function (req, res) {
res.send({ id:req.params.id });
});
app.post('/auth', function (req, res) {
res.send({ id:req.params.id });
});
app.listen(3000);
複製程式碼
會發現加入了這兩行程式碼即可。
res.header("Access-Control-Allow-Headers", "content-type"); // 這裡的content-type是預設的,根據客戶端請求來設定
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
複製程式碼
此時POST會傳送兩次請求:
當預檢請求通過後,再次傳送POST
請求。
其他
像其他的技術,包括JSONP、LocalStorage、cookie、postMessage在此不多做介紹,可以參考阮大的部落格進行參考。
奇淫技巧
在有些時候,後端的介面由於各種不能滿足我們所需要的跨域支援,那麼就需要前端自己下功夫,我們思考一下,既然瀏覽器由於安全策略阻止了返回的結果,那麼就可以在瀏覽器的安全策略上進行修改。
設定Chrome瀏覽器的 disable-web-security
, 實現跨域訪問後端的介面。
windows
"C:\Users\UserName\AppData\Local\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir
mac
open -a "Google Chrome" --args --disable-web-security --user-data-dir
linux
chromium-browser --disable-web-security