經常使用的前端跨域的幾種方式

前端跨域

前端跨域有多少種狀況

  • CORS 跨域
  • jsonp 跨域
  • postMessage
  • document.domain 通常狀況下咱們使用比較多的就是 Cross-Originjsonp 這兩種方式。postMessagedocument.domain 使用的不是不少。下面我會介紹他們如何使用和使用的場景還有一些不常見的問題。

CORS

全拼 cross-origin resource sharing,意思是跨域資源共享。咱們先看看瀏覽器的兼容性,以下圖: html

Cross-Origin兼容性

注意: 因此要使用 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-urlencodedmultipart/form-data、text/plain

簡單請求(simple request)

  • 對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否贊成此次請求。api

  • 若是 Origin 指定的源,不在許可範圍內,服務器會返回一個正常的 HTTP 迴應。瀏覽器發現,這個迴應的頭信息沒有包含 Access-Control-Allow-Origin 字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被 XMLHttpRequestonerror 回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於 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

複製代碼

須要注意的是,若是要發送 CookieAccess-Control-Allow-Origin就不能設爲*,必須指定明確的、與請求網頁的域名一致。瀏覽器

非簡單請求(not-so-simple request)

  • 非簡單請求的 CORS 請求,會在正式通訊以前,增長一次 HTTP 查詢請求,稱爲 預檢請求(preflight)。緩存

  • 預檢請求 用的請求方法是 OPTIONS,表示這個請求是用來詢問的。頭信息裏面,關鍵字段是 Origin,表示請求來自哪一個源。

  • 除了 Origin 字段,"預檢"請求的頭信息包括兩個特殊字段

// 該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
  Access-Control-Request-Method: PUT
  // 該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段
  Access-Control-Request-Headers: X-Custom-Header

複製代碼
  • 服務器收到預檢請求之後,檢查了 Origin、Request-MethodRequest-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 跨域

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 (這是HTML5提供的方法,IE8+才支持) 能夠實現兩個窗口之間的通訊,是否是同源不重要

它可用於解決如下方面的問題:

  • 頁面和其打開的新窗口的數據傳遞
  • 多窗口之間消息傳遞
  • 頁面與嵌套的iframe消息傳遞

使用方法: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)

複製代碼

document.domain

此方案僅限主域相同,子域不一樣的跨域應用場景。實現原理:兩個頁面都經過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種前端跨域的方法,jsonpcors 能夠和後臺進行跨域,而 postMessagedomain 適合頁面間的通信。頁面間的跨域還有可使用 window.name | location.hash。若是想了解的能夠點擊這裏

參考

Cross-Origin Resource Sharing MDN

相關文章
相關標籤/搜索