[ 筆記 ] 交換資料 - 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

User Story、流程圖練習、Scrum 練習

User Story、流程圖練習、Scrum 練習

Secure Apache Using Certbot with Let's Encrypt on Ubuntu 20.04

Secure Apache Using Certbot with Let's Encrypt on Ubuntu 20.04

Git 筆記 - 將該次 commit 變動的檔案打包壓縮

Git 筆記 - 將該次 commit 變動的檔案打包壓縮


Comments