CORS
跨域jsonp
跨域postMessage
document.domain
通常狀況下咱們使用比較多的就是 Cross-Origin
和 jsonp
這兩種方式。postMessage
和 document.domain
使用的不是不少。下面我會介紹他們如何使用和使用的場景還有一些不常見的問題。全拼 cross-origin resource sharing
,意思是跨域資源共享。咱們先看看瀏覽器的兼容性,以下圖: html
注意: 因此要使用 CORS
進行跨域的話,必須注意客戶端和服務器必須同時支持。前端
瀏覽器將 CORS
請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request),咱們看看如何區分html5
1.請求方法是如下三種方法之一:json
HEAD
GET
POST
2.HTTP的頭信息不超出如下幾種字段:segmentfault
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限於三個值 application/x-www-form-urlencoded
、multipart/form-data、text/plain
對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否贊成此次請求。api
若是 Origin
指定的源,不在許可範圍內,服務器會返回一個正常的 HTTP
迴應。瀏覽器發現,這個迴應的頭信息沒有包含 Access-Control-Allow-Origin
字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被 XMLHttpRequest
的 onerror
回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於 HTTP
迴應的狀態碼有多是200。(若是咱們使用的 fetch
,那麼 fetch
會自動觸發一個錯誤,那麼這個時候咱們能夠經過 promise.catch()
方法可捕獲這個錯誤信息)。跨域
若是 Origin
指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段promise
// 該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求
Access-Control-Allow-Origin: http://api.bob.com
// 該字段可選。它的值是一個布爾值,表示是否容許發送Cookie,若是須要發送cookie那麼就設置爲true,不然不會含有這個字段
// 若是要發送cookie,那麼還須要設置 var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
Access-Control-Allow-Credentials: true
// 該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:
// Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
// 若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定
// getResponseHeader('FooBar')能夠返回FooBar字段的值。
Access-Control-Expose-Headers: FooBar
複製代碼
須要注意的是,若是要發送 Cookie
,Access-Control-Allow-Origin
就不能設爲*
,必須指定明確的、與請求網頁的域名一致。瀏覽器
非簡單請求的 CORS
請求,會在正式通訊以前,增長一次 HTTP
查詢請求,稱爲 預檢請求(preflight)。緩存
預檢請求 用的請求方法是 OPTIONS
,表示這個請求是用來詢問的。頭信息裏面,關鍵字段是 Origin
,表示請求來自哪一個源。
除了 Origin
字段,"預檢"請求的頭信息包括兩個特殊字段
// 該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
Access-Control-Request-Method: PUT
// 該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段
Access-Control-Request-Headers: X-Custom-Header
複製代碼
Origin、Request-Method
和 Request-Headers
字段之後,確認容許跨源請求,就能夠作出迴應。若是瀏覽器否認了"預檢"請求,會返回一個正常的 HTTP
迴應,可是沒有任何 CORS
相關的頭信息字段。這時,瀏覽器就會認定,服務器不一樣意預檢請求,所以觸發一個錯誤,被 XMLHttpRequest
對象的 onerror
回調函數捕獲// 該字段必需, 字段也能夠設爲星號,表示贊成任意跨源請求
Access-Control-Allow-Origin: *
// 該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。
// 注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。
Access-Control-Allow-Methods: GET, POST, PUT
// 若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。
// 它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。
Access-Control-Allow-Headers: X-Custom-Header
// 該字段可選
// 該字段與簡單請求時的含義相同
Access-Control-Allow-Credentials: true
// 該字段可選,用來指定本次預檢請求的有效期,單位爲秒。
// 即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。
Access-Control-Max-Age: 1728000
複製代碼
一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的 CORS
請求,就都跟簡單請求同樣,會有一個 Origin
頭信息字段。服務器的迴應,也都會有一個 Access-Control-Allow-Origin
頭信息字段。
咱們以 koa
爲列,在demo中這麼設置就能夠了
// 下面是對cors進行的設置,在返回的headers中添加以下字段,也可使用 koa2-cors
app.use(async (ctx, next) => {
// 容許來自全部域名請求,請求攜帶了cookie就不能設置爲 *
ctx.set("Access-Control-Allow-Origin", "http://10.105.16.162:3000");
// 設置所容許的HTTP請求方法
ctx.set("Access-Control-Allow-Methods", "GET,POST");
// 它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段.
ctx.set("Access-Control-Allow-Headers", "x-requested-with,accept,origin,content-type");
// 該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。
// 當設置成容許請求攜帶cookie時,須要保證"Access-Control-Allow-Origin"是服務器有的域名,而不能是"*";
ctx.set("Access-Control-Allow-Credentials", true);
await next();
});
複製代碼
jonsp跨域的原理是經過動態建立script的標籤的形式來實現跨域的,由於script不受同源策略的影響。可是jsonp只能接受get方法。 看看下面的代碼是如何封裝的,並作了超時設置。
const defaultOptions = {
timeout: 15000,
uid: 0,
perfix: '_jsp',
name: 'callBack',
}
const jsp = (url, params, options = {}) => {
let timer = null;
const options = Object.assign({}, defaultOptions, options);
const callBackName = `${options.perfix}_${options.name}_${options.uid++}`;
const paramsKeys = Object.entries(params);
url += `?${callback}=${callBackName}`;
url = paramsKeys.reduce((initUrl, [k, v]) => {
return `${initUrl}&${k}=${v}`;
}, url);
const body = document.getElementByTagName(body)[0];
const script = document.createElement('script');
script.src = url;
body.appendChild(script);
const clean = () => {
if (timer) {
clearInterval(timer);
timer = null;
body.removeChild(script);
window[callBackName] = null;
}
};
return new Promise((resolve, reject) => {
window[callBackName] = function(data) {
clear();
return resolve(data);
};
timer = setTimeout(() => {
clear();
return reject('請求超時了');
}, options.time);
});
}
複製代碼
它可用於解決如下方面的問題:
使用方法:postMessage(data, origin)
方法接受兩個參數 data
html5規範支持任意基本類型或可複製的對象,但部分瀏覽器只支持字符串,因此傳參時最好用 JSON.stringify()
序列化。IE10纔開始支持對象形式。 origin
協議+主機+端口號,也能夠設置爲 *
,表示能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲 /
。
// 發送窗口代碼:
const ifr = document.createElement('iframe');
ifr.src = 'http://localhost:3001';
ifr.display = 'none';
document.body.append(ifr);
ifr.onload = function() {
const doc = ifr.contentWindow;
// 目標源 能夠限定具體的 協議 + 域名 + 端口 或者是 *
const targetOrigin = 'http://localhost:3001';
// 發送消息 doc能夠是window.open() 也能夠是嵌套的iframe
// 發送的消息能夠是 string 或者 object(IE10才支持)
doc.postMessage('hello world', targetOrigin);
}
// 接收窗口代碼:
window.addEventListener('message', (e)=> { // 必須是window不能是document監聽
// 數據
const data = e.data;
// 消息是從哪一個源發送來的
const origin = e.origin;
// 發送消息窗口
const source = e.source;
// 經過source回傳消息 一樣在發送消息的窗口也須要監聽
source.postMessage('hi shenxuxiang', 'http://localhost:3000');
},false)
複製代碼
此方案僅限主域相同,子域不一樣的跨域應用場景。實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。 以後就能夠共享window下的屬性。
// www.a.com/a.html頁面中的代碼
document.domain = 'a.com';
const ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = 'none';
document.body.append(ifr);
ifr.onload = function() {
const doc = ifr.contentWindow;
console.log(doc.name); // shenxuxiang
}
// www.script.a.com/b.html頁面中的代碼
document.domain = 'a.com';
window.name = 'shenxuxiang';
複製代碼
上面一共介紹了4種前端跨域的方法,jsonp
和 cors
能夠和後臺進行跨域,而 postMessage
和 domain
適合頁面間的通信。頁面間的跨域還有可使用 window.name | location.hash
。若是想了解的能夠點擊這裏