不到50行代碼搞懂跨域(CORS)

目標

使用 Hapi 框架在實例 CORS 場景下測試首部字段做用。這裏不須要你掌握 Hapi 框架的使用,以及任何 Node 知識。前端

準備工做

你須要懂得哪方面的知識?git

  • 會使用 XMLHttpRequest(Ajax) / Fetch 發起 HTTP 請求
  • 基本的 HTTP 知識
  • 理解基本的 CORS 知識

下面咱們會對照這 MDN 上 CORS 部分的講解(下面簡稱 講解),使用具體代碼來測試首部字段的做用。github

代碼部分

主體結構咱們按照 Hapi 官網的示例,修改路由部分。json

const Hapi = require('@hapi/hapi')

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: 'localhost'
  })
  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      console.log(request.info)
      console.log(request.headers)
      return {
        code: 200,
        data: {
          success: true
        }
      }
    }
  })
  await server.start()
  console.log('Server running on %s', server.info.uri)
}

process.on('unhandledRejection', err => {
  console.log(err)
  process.exit(1)
})

init()
複製代碼

這樣咱們就有了一個本地 3000 端口的服務,並有一個 / 路徑的 API。下面咱們使用 Fetch 發起一個跨域請求,使用 Chrome 打開任意網站,打開 開發者工具,在 Console 下進行測試。後端

fetch('http://localhost:3000')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });
複製代碼

收到如下錯誤:api

Access to fetch at 'http://localhost:3000/' from origin 'developer.mozilla.org' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.跨域

直接訪問 http://localhost:3000 是能夠看到結果的。說明瀏覽器限制從腳本內發起的跨源HTTP請求瀏覽器

下面增長容許跨域源的字段服務器

path: '/',
+++ options: {
+++ cors: {
+++ origin: ['*']
+++ }
+++ },
複製代碼

以上代碼等同於在 response headers 中增長 access-control-allow-origin 字段爲 *,容許任何源的跨域請求。cookie

再次訪問,成功獲取到內容。對照 MDN 講解並查看 Chrome Network 面板中 headers 部分和服務日誌。

簡單請求 與 預檢請求

對照講解理解 「簡單請求」 和 」預檢請求「 的區別。

下面發起一個 POST JSON 的請求,並在服務端接收。

--- method: ['GET']
+++ method: ['GET', 'POST'],

// 輸出請求體
+++ console.log(request.payload)
複製代碼
fetch('http://localhost:3000', {
	method: 'POST',
	body: JSON.stringify({'user': 'kenny'}),
	headers: new Headers({
    'Content-Type': 'application/json'
  })
})
複製代碼

Network 中會看到有 2 次請求發起,由於咱們修改了除規定之外的首部字段,因此首先發起了一個 options 的預檢請求。

OPTIONS 是 HTTP/1.1 協議中定義的方法,用以從服務器獲取更多信息。該方法不會對服務器資源產生影響。

一樣咱們查看 Network 中的 headers 部分進行對比和理解。

帶 Cookie 的跨域請求

Fetch 與 CORS 的一個有趣的特性是,能夠基於 HTTP cookies 和 HTTP 認證信息發送身份憑證。通常而言,對於跨域 XMLHttpRequest 或 Fetch 請求,瀏覽器不會發送身份憑證信息。若是要發送憑證信息,須要設置 XMLHttpRequest 的某個特殊標誌位。

首先增長一個 domain 爲 localhost 的 cookie 在測試域下。而後增長下面代碼。

// 攜帶憑證
fetch('http://localhost:3000', {
    credentials: 'include'
})
複製代碼
origin: ['*']
+++ credentials: true


// 輸出 cookie
+++ console.log(request.state)
複製代碼

執行後會發現仍是跨域錯誤。這是由於對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin 的值爲「*」。

因此修改 origin

--- origin: ['*'],
+++ origin: ['https://developer.mozilla.org'],
複製代碼

如今執行,能夠在日誌中看到剛剛增長的 cookie。

反過來,咱們在服務端設置 cookie,看前端可否生效。

h.state('user', 'kenny', {
    isSecure: false,
    isHttpOnly: false,
    isSameSite: 'false',
    domain: 'localhost'
})
複製代碼

一樣可行。

攜帶額外的首部信息 和 獲取前端的首部信息

Access-Control-Allow-Headers 其指明瞭實際請求中容許攜帶的首部字段。

Access-Control-Expose-Headers

在跨域訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,若是要訪問其餘頭,則須要服務器設置本響應頭。

首先咱們獲取服務器時間

先刪除多餘代碼

--- console.log(request.payload)
--- console.log(request.state)
--- h.state('user', 'kenny', {
--- isSecure: false,
--- isHttpOnly: false,
--- isSameSite: 'false',
--- domain: 'localhost'
--- })
複製代碼
fetch('http://localhost:3000')
  .then(function(response) {
    console.log(response.headers.get('Date'))
  })

// null
複製代碼

後端增長代碼,容許獲取額外的頭部。

+++ additionalExposedHeaders: ['Date']
複製代碼

再次測試後顯示接口獲取時服務器的時間。

如今前端發送一個自定義的頭(Region) 表明當前的地理位置(北京: 52),而後使用後端獲取它。

fetch('http://localhost:3000', {
    headers: new Headers({
        'Region': 52
    })
})
複製代碼
+++ additionalHeaders: ['Region']
複製代碼

能夠從後端日誌中看到 headers 含有 Region 字段

關於 Hapi 框架的 CORS 設置,能夠參考:Hapi route cors

所有代碼:

const Hapi = require('@hapi/hapi')

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: 'localhost'
  })
  server.route({
    method: ['GET', 'POST'],
    path: '/',
    options: {
      cors: {
        origin: ['https://*.mozilla.org'],
        credentials: true,
        additionalExposedHeaders: ['Date'],
        additionalHeaders: ['Region']
      }
    },
    handler: (request, h) => {
      console.log(request.info)
      console.log(request.headers)
      console.log(request.payload)
      console.log(request.state)
      // 設置 cookie
      // h.state('user', 'kenny', {
      // isSecure: false,
      // isHttpOnly: false,
      // isSameSite: 'false',
      // domain: 'localhost'
      // })
      return {
        code: 200,
        data: {
          success: true
        }
      }
    }
  })
  await server.start()
  console.log('Server running on %s', server.info.uri)
}

process.on('unhandledRejection', err => {
  console.log(err)
  process.exit(1)
})

init()

複製代碼

結語

跨域問題其實並不複雜,網上教程也很是多,其實對於同源策略和跨域的概念,只要閱讀 MDN 就能夠了,本身動手建立一個服務器,對照調試工具和後端日誌,查看 HTTP 請求和響應,加深理解。但願這個實例教程能幫助你們理解前端跨域 和 Hapi 框架的使用。

附上一個 整理和機翻的 Hapi 中文文檔,以爲有用的小夥伴能夠點點關注。

相關文章
相關標籤/搜索