前言
瀏覽器出於防止潛在安全風險的考慮,使用了同源策略,這一方面保證了我們資料的安全, 另一方面卻又限制了我們的手腳,基於此,開發者們與標準制定組織提供了不同的解決方案, 這裡主要說說CORS與jsonp。
什麼是同源策略
同源指的是兩個域需要協議,子域名,主域名與埠號都保持一致,四者有一個不同,即屬於跨域。 注意: http://localhost:8080與http://127.0.0.1:8080不屬於同源,也就是說,即使IP地址一致,但是一個是域名,一個是IP地址,也不屬於同源。
CORS的使用與注意點
CORS:跨域資源共享,是W3C制定的一個草案,定義了在必須訪問跨源資源時,瀏覽器和伺服器該怎麼溝通。
CORS的實現原理是,瀏覽器發出請求報文中會額外包含一個Origin頭部,該頭部的值是當前頁面的源資訊(協議,域名和埠),伺服器收到請求報文後,如果同意這個跨源請求,就在響應報文的頭部新增Access-Control-Allow-Origin,值與請求報文中的Origin頭部的值一致,如果響應報文中沒有這個頭部或者有,但是值不一致,這次的跨源請求就會失敗。
瀏覽器原生支援CORS,但是不同的瀏覽器支援的方式不同。 IE8及以上版本引用了XDR(XDomainRequest)型別,這個物件和XHR物件類似,使用這個物件,可以實現安全可靠的跨域通訊。 XDR的使用與XHR類似,使用過程如下:
- 例項化XDR
var xdr = new XDomainRequest();
複製程式碼
- 呼叫open(),open()接收兩個引數,請求所用的方法以及URL,所有的XDR都是非同步執行的,所有不需要第三個引數。
xdr.open('get','#');
複製程式碼
- 呼叫send(),如果使用的是get方法,傳入null,如果是post方法,傳入字串。
xdr.send(null);
複製程式碼
- 為xdr繫結事件處理函式。xdr支援的事件包括load,error,timeout。需要說明一點的是,這部分的事件監聽器需要在呼叫open()之前宣告,這裡只是行文需要。 4.1 load事件 在接收到響應後,你可以訪問響應的原始文字,但是沒有辦法確定響應的狀態碼,只有在響應有效的/情況下才會觸發load事件,如果接收到的響應中不包含Access-Control-Allow-Origin頭部的話,則會觸發error事件。
xdr.onload=function(){
console.log(xdr.resonseText);
}
複製程式碼
4.2 error事件 導致XDR請求失敗的因素很多,所有為每個xdr物件繫結該事件的處理函式是很有必要的,但是該事件丟擲的資訊有限,我們只能確定請求失敗了,並不能得知請求失敗的原因。
xdr.onerror=function(){
console.log('get a error');
}
複製程式碼
4.3 timeout事件 xdr物件有一個timeout屬性,該屬性表明請求自發出多久後超時,當為其賦值後,在給定的時間內還沒接受到響應,就會觸發timeout事件。
xdr.ontimeout=function(){
alert('time is too long');
}
複製程式碼
使用XDR需要注意的點:
- cookie不會隨請求傳送,也不會隨請求返回。
- 不能訪問響應頭部資訊,這意味著xdr物件沒有getResponseHeader()和getAllResponseHeaders()。
- 只支援get和post方法。
- 當使用post方法時,xdr物件有一個contentType屬性,這個屬性可以表示要傳送的資料的格式,這是xdr物件能夠影響頭部資訊的唯一方法。
其他瀏覽器對CORS的實現 其他主流瀏覽器通過XHR物件原生支援CORS,在嘗試開啟不同來源的資源時,XML可以自動觸發這個行為,有一點不同的是,open方法中的URL引數需要傳入絕對路徑。
使用XHR物件進行跨域通訊的注意點:
- 不能使用setRequestHeader()設定自定義頭部。
- 不能傳送和接收cookie。
- 呼叫getAllResponseHeaders()返回空字串,呼叫getResponseHeader()報錯。
- 由於同源請求和跨源請求都使用同樣的介面,因此對於本地資源,使用相對URL,對跨源的資源,使用絕對路徑,這樣可以避免跨源請求時的限制(見1,2)。
這個給出一個例子,本地檔案是index.html,服務端檔案為server.js,需要注意的是,我們需要使用http-server(其他的也可以)搭建一個靜態資源伺服器,關於http-server的使用,點選這裡,這樣可以使用http協議訪問index.html檔案,使用file協議無法使用CORS。
// index.html
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=(req,res)=>{
if(xhr.readyState==4){
if((xhr.status>=200&& xhr.status<300)||xhr.status==304) {
console.log(xhr.responseText);
console.log(xhr.getResponseHeader('Connection'));
} else {
console.log('Request was unsuccessful: '+xhr.statusText);
}
}
};
xhr.open('get','http://127.0.0.1:3000/',true);
xhr.send(null);
</script>
複製程式碼
//server.js
const express = require("express"); //記得安裝express
const app = express();
app.get("/", function (req, res) {
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080');
res.end('hello world!');
});
app.listen(3000, function () {
console.log("app is listening 3000");
});
複製程式碼
CORS的高階使用技巧
CORS支援在跨域請求的過程中,使用自定義的頭部資訊,post和GET之外的方法,不同型別的主體內容和提高憑據(cookie)。在使用這些高階選項傳送請求時,瀏覽器會首先傳送一個Prefight請求,這種請求使用options方法,這是一種透明的伺服器驗證機制,開發者不需要做這些。 該請求這需要包含以下的頭部:
- Origin:與簡單的請求相同。
- Access-Control-Allow-Method:請求自身使用的方法(是指我們主動發起的跨域請求的方法,不是options)。
- Access-Control-Request-Headers:(可選)自定義的頭部資訊,多個頭部已逗號隔開。 除了這些頭部資訊外,xhr有withCredentials屬性,將該屬性設定未true,可以在請求過程中攜帶cookie。
伺服器在接收到這個請求後,如果同意這次跨域請求,就會在響應中設定響應的頭部資訊。 這些頭部資訊包括:
- Access-Control-Allow-Origin:與簡單的請求相同。
- Access-Control-Allow-Methods:允許的方法,多個方法以逗號分隔。
- Access-Control-Allow-Headers:允許的頭部,多個頭部以逗號隔開。
- Access-Control-Max-Age:應該將這個Preflight請求快取多長事件(以秒錶示)。 5.Access-Control-Allow-Credentials:布林值。
這裡給一個完整的例子:
//index.html
<script>
var xhr = new XMLHttpRequest();
document.cookie = "name=wang"; //BOM提供的介面,用於設定當前頁面所在的域的cookie
xhr.withCredentials = true; //允許在這次請求中攜帶cookie
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status >= 200) & (xhr.status < 300) || xhr.status == 304) {
console.log(xhr.responseText);
console.log('name:'+xhr.getResponseHeader("name"));
} else {
console.log("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("PUT", "http://localhost:4000/getData", true); //使用put方法
xhr.setRequestHeader("age", 12);
xhr.send(null);
</script>
複製程式碼
//server1.js
//將該檔案與index.html放在一個目錄下
const Koa = require('koa');
const app = new Koa();
const server = require('koa-static');
const home = server(__dirname);
app.use(home);
app.listen(3000,()=>{
console.log('app is runnint at port 3000');
})
//執行該程式碼後,可以在http://localhost:3000/index.html訪問到index.html。
複製程式碼
//server2.js
const Koa = require('koa');
const router = require('koa-router')();
const app = new Koa();
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method}: ${ctx.request.url}`)
await next();
})
app.use(async (ctx, next) => {
ctx.response.set('Access-Control-Allow-Origin', 'http://localhost:3000');
ctx.response.set('Access-Control-Allow-Headers', "age");
ctx.response.set('Access-Control-Allow-Methods', "PUT");
ctx.response.set('Access-Control-Allow-Credentials', true);
ctx.response.set('Access-Control-Allow-Max-Age', 6);
ctx.response.set('Access-Control-Expose-Headers', 'name');
if (ctx.method === 'OPTIONS') {
ctx.body = 'OPTIONS';
};
await next();
})
router.put('/getDate',async (ctx,next)=>{
console.log(ctx.request.header.age);
ctx.response.set('name','wang');
ctx.body='put';
await next();
})
app.use(router.routes());
app.listen(5000, () => {
console.log('app is listening at port 5000');
});
複製程式碼
執行以上server1.js,server2.js檔案,訪問http://localhost:3000/index.html。
從以上的圖片中,可以看到,在進行跨域請求時,攜帶了cookie,並且瀏覽器在後臺替我們傳送了一個options方法的請求。 **總結:**CORS的高階用法其實就是在真正與伺服器進行跨域通訊時,瀏覽器會先傳送一個options方法的請求,幫我們跟伺服器進行‘溝通’,基於溝通結果,決定我們真正需要的跨域通訊的成功或失敗。###jsonp
jsonp利用script標籤可以不受限制的從其他域載入資源的能力,進行跨域通訊。 jsonp由兩部分組成:回撥函式和資料。回撥函式是響應帶來時,應該呼叫的函式,它需要在URL中指定;資料就是伺服器返回給瀏覽器的響應。
jsonp的使用:
- 建立一個script元素。
- 宣告一個回撥函式。
- 為script指定src屬性的值,需要將回撥函式作為URL的查詢字串,形式為:'callback=functionName' 這裡給出一個完整的例子
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
var script = document.createElement('script');
//回撥函式
var blog = function(str){
console.log(str);
}
script.src='http://localhost:3000/?callback=blog';
document.body.appendChild(script);
</script>
</body>
</html>
複製程式碼
//server.js
const Koa = require('koa');
const app = new Koa();
const router = require('koa-router')();
router.get('/',async(ctx,next)=>{
var name = ctx.request.querystring.split('=')[1];
console.log(name);
var value='hello world!';
ctx.body = `${name}('${value}')`;
})
app.use(router.routes());
app.listen(3000,()=>{
console.log('app is running at port 3000');
})
複製程式碼
其實jsonp的內在邏輯很簡單,在script標籤中宣告的函式是屬於全域性的,當伺服器返回字串後,這個字串會被當做JavaScript程式碼執行,也就是呼叫之前宣告的函式。 ##總結 CORS屬於瀏覽器原生支援,支援所有型別的HTTP請求,是跨域通訊的根本解決方案。 jsonp是開發者們為了繞開同源策略的權宜之計,雖然只支援get方法,但是使用簡單。
總結
CORS屬於瀏覽器原生支援,支援所有型別的HTTP請求,是跨域通訊的根本解決方案。 jsonp是開發者們為了繞開同源策略的權宜之計,雖然只支援get方法,但是使用簡單。
參考
- 高程
- 九種跨域方式實現原理