[ 筆記 ] 交換資料 - XMLHttpRequest、CORS、JSONP


Posted by krebikshaw on 2020-07-22

用 node.js 呼叫 API 與在網頁上呼叫的根本差異是什麼?

Node.js:

  • 使用 Node.js 來呼叫 API,Node 會直接讓電腦向 Server 發送 request,並將 response 完整呈現出來,不會檢查 response 的安全性,所以存在安全風險。

瀏覽器:

  • 從瀏覽器上呼叫 API 會先透過 「 瀏覽器 」處理,再讓電腦發送 request 給 Server,回傳回來的 response 會經過瀏覽器檢查,確保資料安全才會呈現出來。

傳送資料 - 方法1: 表單 form

  • 跟 JavaScript 並沒有太大關係,是瀏覽器提供的一種發送 request 的方法
  • 流程:
    • 瀏覽器發送一個 帶上參數 的 request 到一個新的頁面
    • 然後將 Server 回傳的 「 response 渲染在頁面上 」
  • 瀏覽器會「換頁」顯示結果
<form method="POST" action="https://google.com">
    ...
</form>
  • GET 方法:參數就會直接被放在 URL 上面
  • POST 方法: 參數會放在 request 的 body 裡面

傳送資料 - 方法2: Ajax

  • 全名為 Asynchronous JavaScript and XML
  • 流程:
    • 瀏覽器發送一個 帶上參數 的 request 到 一個新的頁面
    • 然後將 Server 回傳的 「 response 傳給頁面上的 JS 」
  • 不像表單一樣有換頁問題

  • Asynchronous 這個單字指的是「非同步」

    • 「同步」的情況,發送完 request 與等待 Response 之間的過程,整個 JavaScript 引擎是不會執行任何東西的!你點任何有牽涉到 JavaScript 的東西,都不會有反應,因為 JavaScript 還在等 Response 回來。
    • 「非同步」的情況,發送完 request 之後就不管它了,不等結果回來就繼續執行下一行
// 假設有個發送 Request 的函式叫做 sendRequest
var result = sendRequest('https://api.twitch.tv/kraken/games/top?client_id=xxx');

// 上面 Request 發送完之後就執行到這一行,所以 result 不會有東西
// 因為 Response 根本沒有回來
console.log(result);
  • 非同步的 Function 不能直接透過 return 把結果傳回來」,為什麼?因為像上面這個例子,它發送 Request 之後就會執行到下一行了,這個時候根本就還沒有 Response

  • 非同步在發送 Request 之後,不用等 Response 回來,可以繼續執行其他程式碼,等
    Response 回來之後,會透過 Callback Function 把資料帶進來。

// 假設有個發送 Request 的函式叫做 sendRequest
sendRequest('https://api.twitch.tv/kraken/games/top?client_id=xxx', callMe);

function callMe (response) {
  console.log(response);
}

// 或者寫成匿名函式
sendRequest('https://api.twitch.tv/kraken/games/top?client_id=xxx', function (response) {
  console.log(response);
});

XMLHttpRequest

  • JavaScript 提供的 API,主是要拿來發送 Request 與接收 response
  • 要發送 Request 的話,就要透過瀏覽器幫我們準備好的一個物件,叫做XMLHttpRequest,範例程式碼如下:
var request = new XMLHttpRequest();
request.open('GET', `https://api.twitch.tv/kraken/games/top?client_id=xxx`, true);
request.onload = function() {
  if (request.status >= 200 && request.status < 400) {

    // Success!
    console.log(request.responseText);
  }
};
request.send();

指令:

  • const request = new XMLHttpRequest;實例物件
  • request.open()設定 Request
    • 設定 Method & URL & 同步 | 非同步 & 使用者 & 密碼
      前兩項為必填、後三項可選
request.open(<method>, <url>, <async:b>, <user:s>, <password:s>)
  • request.setRequestHeader()設定 Request header
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // => 設定 Content-Type
request.setRequestHeader('Client-ID', token); // => 設定 client-ID
  • request.onload監聽 load 事件
  • 當瀏覽器拿到 response 時,會觸發 onload 事件綁定的 callback function
    如果是用 addEventListener 方法監聽,事件名稱是 load
request.onload = function() { 
    // => callback
}
  • request.onerror監聽 error 事件
request.onerror = function() {
    // => calback
}
  • request.send()發送 Request
function sendRequest() {
  const request = new XMLHttpRequest();
  const url ='https://dvwhnbka7d.execute-api.us-east-1.amazonaws.com/default/lottery';
  const errorMessage = "系統不穩定,請再試一次!";

  request.open('GET', url, true);
  request.onload = () => {
    if (request.status >= 200 && request.status < 400) {
      let data;
      try {
        data = JSON.parse(request.response)
      } catch(err) {
        alert(errorMessage);
        console.log(err);
        return;
      }

      if (!data.prize) {
        alert(errorMessage);
        return;
      }

    } else {
      alert(errorMessage);
    }
  }

  request.onerror = () => {
    alert(errorMessage);
  }

  request.send();
}

同源政策 Same Origin Policy

只要瀏覽器發的 request 位置 跟 Server 端的 位置是不同源(不同網域 ), request 就會被瀏覽器擋掉。

相同 domain 就是同源,但同網域的 http & https 也是不同源,所以如果你是接別人 API 的話,大多數情形都是不同源的。

值得注意的一點是:「 Request 還是有發出去,而且瀏覽器也確實有收到 Response 」,重點是因為 瀏覽器 才會有同源政策,主要是因為安全性的考量,不把 Response 傳回給你的 JavaScript。

CORS 跨來源資源共用

  • 全名為 Cross-Origin Resource Sharing
  • 其中做了兩層防護:
    • 第一層:不隨意讓使用者拿資料,需設定 Access-Control-Allow-Origin
    • 第二層:不隨意讓使用者更改資料,需通過 Preflight Request 檢查

第一層防護 - 不隨意讓使用者拿資料

  • 如果想開啟跨來源 HTTP 請求的話,Server 必須在 Response 的 Header 裡面加上Access-Control-Allow-Origin
access-control-allow-origin: *
  • 星號就代表萬用字元,意思是任何一個 Origin 都接受。所以當瀏覽器接收到這個 Response 之後,比對目前的 Origin 符合 * 這個規則,檢驗通過,允許我們接受跨來源請求的回應。

  • 除了這個 Header 以外,其實還有其他的可以用,例如說 Access-Control-Allow-Headers 跟 Access-Control-Allow-Methods,就可以定義接受哪些 Request Header 以及接受哪些 Method。

  • 需要確保 Server 端有加上Access-Control-Allow-Origin,不然 Response 會被瀏覽器給擋下來並且顯示出錯誤訊息。

第二層防護 - Preflight Request

CORS 會把 Request 分成兩種
簡單請求(simple requests):

  • 沒有加任何自定義的 Header,而且又是 GET 的話,就是簡單請求
  • 屬於簡單請求的 Request 不用經過預檢

非簡單請求:

  • 避免使用者任意更改資料庫內容,須先經過預檢
  • 會先送出一個 Request 叫做 Preflight Request,中文翻作「預檢請求」,因為非簡單請求可能會更改資料庫內容,因此會先透過 Preflight Request 去確認後續的請求能否送出。
  • 如果這個 Preflight Request 沒有過的話,真的 Request 也就不會發送了,這就是預檢請求的目的。

傳送資料 - 方法3: JSONP

  • 全名叫做 JSON with Padding
  • 有鑒於 Ajax 一定會受到同源政策影響,所以有個另類的方式拿到 Response 資料,就是 JSONP。
  • 網頁上有些標籤不受同源政策的限制,像是圖片 <img>,因為沒有安全性的問題,所以可以存取跨來源的圖片。還有像是引入 JS 的標籤 <script> 也可以引入其他 domain 的 js 進來, 等我們拿到 src 的內容 (response) ,再進行解析!所以簡單來說 JSONP 就是 利用 src 不受同源限制的特性,直接載入一隻帶參數的js,當作是發 request,用一個 function 包裝起來。
<script src="https://api.twitch.tv/kraken/games/top?client_id=xxx&callback=receiveData&limit=1"></script>
<script>
  function receiveData (response) {
    console.log(response);
  }
</script>
  • 但 JSONP 的缺點就是你要帶的那些參數「 永遠都只能用附加在網址上的方式(GET)帶過去,沒辦法用 POST 」
  • 使用 JSONP 傳送資料,也要 Server 端有提供 JSONP 的方法( 意指用 callback function 包起來 )才行,不然回傳的 Response 就只是字串而已,沒有辦法取得資料
  • 如果能用 CORS 的話,還是應該優先考慮 CORS

#XMLHttpRequest







Related Posts

MTR04_0823

MTR04_0823

[BE201] 後端中階:middleware (上)

[BE201] 後端中階:middleware (上)

你是不是也被這個網路現象愚弄了?揭露發錯答案背後的科學真相!

你是不是也被這個網路現象愚弄了?揭露發錯答案背後的科學真相!


Comments