前端跨域真Easy,媽媽不再用擔憂了

本文主要介紹JSONP、CORS兩種跨域方式,後臺採用Koa模擬,真正的目標是理解整個跨域的流程。至於什麼是跨域和瀏覽器同源策略的問題,請同窗們自行百度。javascript

JSONP

JSONP 實際上是一種trick, 利用瀏覽器對帶有src標籤的能力實現訪問跨域數據的小技巧(像img、link標籤等不存在跨域問題)。css

<!DOCTYPE html>
<html>
<head>
<title>模擬JSONP跨域請求</title>
</head>
<body>
<script type="text/javascript">
  var message = 'hello world';
  function doSomething(data) {
    // 處理返回的數據
    document.write(data);
  }
</script>
<script src="http://127.0.0.1:3000/jsonp?callback=doSomething&msg=message"></script>
</body>
</html>
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => {
  ctx.body = 'Hello World!';
});

// jsonp跨域請求
router.get('/jsonp', (ctx, next) => {
  // 獲取參數
  const query = ctx.request.query;
  ctx.body = `${query.callback}(${query.msg})`
})

app
.use(router.routes())
.use(router.allowedMethods());

app.listen(3000);

當後端的請求完成以後,會回調callback函數,並傳入相應的message參數,執行doSomething函數。html

JSONP的優缺點

優勢:兼容性好java

缺點:ios

  • JSONP只支持GET請求
  • XMLHttpRequest相對於JSONP有着更好的錯誤處理機制

CORS

MDN:跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。git

須要注意的是,針對CORS,異步請求會被分爲簡單請求和非簡單請求,非簡單請求會先發起一次preflight,也就是咱們所說的預檢。github

簡單請求

使用下列方法之一:json

  • GET
  • HEAD
  • POST

HTTP請求頭僅限於如下:axios

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

Content-Type的值僅限於下列三者之一:後端

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

看上去十分複雜,咱們怎麼來理解?其實簡單請求就是HTML form原生表單不依賴腳本能夠發出的請求,咱們來看一下表單的enctype屬性:

enctype
  • application/x-www-form-urlencoded:未指定屬性時的默認值。
  • multipart/form-data:當表單包含 type=file 的input元素時使用此值。
  • text/plain

其實簡單請求還能夠分爲原生form請求(不依賴腳本)和經過腳本發起的簡單請求,咱們先來看一下原生的form請求:

<!DOCTYPE html>
<html>
<head>
<title>CORS-form</title>
</head>
<body>
    <form action="http://127.0.0.1:3000/cors/form-request" method="get" class="form-example">
      <div class="form-example">
        <label for="name">Enter your name: </label>
        <input type="text" name="name" id="name">
      </div>
      <div class="form-example">
        <label for="email">Enter your email: </label>
        <input type="email" name="email" id="email">
      </div>
      <div class="form-example">
        <input type="submit" value="Subscribe!">
      </div>
    </form>
</body>
</html>
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();

router.get('/', (ctx, next) => {
  ctx.body = 'Hello World!';
});

// CORS原生表單請求
router.get('/cors/form-request', (ctx, next) => {
  ctx.body = "Hello easy form!";
});

app
.use(router.routes())
.use(router.allowedMethods());

app.listen(3000);

image

咱們看到原生表單不存在跨域的狀況,咱們再來看下用腳原本模擬表單提交:

<!DOCTYPE html>
<html>
<head>
<title>cors</title>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
</head>
<body>
<script>
function easyRequest() {
    axios({
        method: 'get',
        url: 'http://127.0.0.1:3000/cors/form-request',
        params: {
        username: 'test',
        email: "test.com"
        },
        headers: {
        'Content-type': 'application/x-www-form-urlencoded'
        }
  }).then((res)=> {
    document.write(res.data)
  })
}
  easyRequest();
</script>
</body>

</html>

image

咱們會發現Request Headers頭裏面添加了Origin標籤。Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器會根據這個值,決定是否贊成此次請求。 若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。(注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。)

非簡單請求

下面咱們再來看一下非簡單請求:

<!DOCTYPE html>
<html>
<head>
<title>cors</title>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
</head>
<body>
<script>
function request() {
    axios({
        method: 'put',
        url: 'http://127.0.0.1:3000/cors/request',
        params: {
        msg: 'hello cors'
        },
        headers: {
        'Content-type': 'application/x-www-form-urlencoded'
        }
    }).then((res)=> {
    document.write(res.data)
    })
}

setInterval(request, 5000);
</script>

</body>
</html>
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();

// 設置CORS
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Methods', 'GET,POST,PUT');
ctx.set('Access-Control-Allow-Headers', 'x-requested-with, Content-Type');

ctx.set('Access-Control-Max-Age', 10);
    if (ctx.request.method == 'OPTIONS') {
      ctx.body = 200;
    } else {
      await next();
    }
});

// CORS跨域非簡單請求
router.put('/cors/request', (ctx, next) => {
  ctx.body = "Hello world!";
});

app
.use(router.routes())
.use(router.allowedMethods());

app.listen(3000);

image
image
咱們會發現多了一次OPTIONS請求,這個就是咱們所說的預檢請求。瀏覽器會詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP動詞和頭信息字段。只有獲得了確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。

若是Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。

  • Access-Control-Allow-Headers: 首部字段用於預檢請求的響應。其指明瞭實際請求中容許攜帶的首部字段。
  • Access-Control-Allow-Methods: 首部字段用於預檢請求的響應。其指明瞭實際請求所容許使用的 HTTP 方法。
  • Access-Control-Allow-Origin: 參數的值指定了容許訪問該資源的外域 URI

一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。咱們還能夠經過設置Access-Control-Max-Age來控制"預檢"請求的時效性。

至此跨域的實踐就所有結束了,咱們思考一下瀏覽器爲何要區分簡單請求和非簡單請求呢?

咱們來看一下賀師俊老師是怎麼解釋的:

預檢這種機制只能限於非簡單請求。在處理簡單請求的時候,若是服務器不打算接受跨源請求,不能依賴 CORS-preflight 機制。由於不經過 CORS,普通表單也能發起簡單請求,因此默認禁止跨源是作不到的。

既然如此,簡單請求發 preflight 就沒有意義了,就算髮了服務器也省不了後續每次的計算,反而在一開始多了一次 preflight。

有些人把簡單請求不須要 preflight 理解爲『向下兼容』。這也不能說錯。但嚴格來講,並非『爲了向下兼容』而不能發。理論上瀏覽器能夠區別對待表單請求和非表單請求 —— 對傳統的跨源表單提交不發 preflight,從而保持兼容,只對非表單跨源請求發 preflight。

但這樣作並無什麼好處,反而把事情搞複雜了。好比原本你能夠直接用腳本發跨源普通請求,儘管(在服務器默認沒有跨源處理的狀況下)你沒法獲得響應結果,可是你的需求可能只是發送無需返回,好比打個日誌。但如今若是服務器不理解 preflight 你就幹不了這個事情了。

並且若是真的這樣作,服務器就變成了默認容許跨源表單,若是想控制跨源,仍是得(跟本來同樣)直接在響應處理中執行跨源計算邏輯;另外一方面服務器又須要增長對 preflight 請求的響應支持,執行相似的跨源計算邏輯以控制來自非表單的相同跨源請求。服務器一般沒有區分表單/非表單差別的需求,這樣搞純粹是折騰服務器端工程師。

因此簡單請求不發 preflight 不是由於不能兼容,而是由於兼容的前提下發 preflight 對絕大多數服務器應用來講沒有意義,反而把問題搞複雜了。

參考文章

https://juejin.im/post/684490...
http://www.ruanyifeng.com/blo...

源碼地址

https://github.com/warplan/JS...

歡迎關注

碼字實屬不易,但願你們能關注一波公衆號,一塊兒學習,一塊兒Easy。
image.png

相關文章
相關標籤/搜索