交換資料 - 表單、Ajax、XMLHttpRequest、CORS、JSONP


Posted by stella572322 on 2020-10-27

API 是什麼?

API 就是一段網址,只要照著對方要求的規格來輸入正確的網址,就能串接對方提供的資料。配合 HTTP 協定的規範,就可以達成更多資料交換的功能。

用 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() {
    // => callback
}

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 」,重點是因為 瀏覽器 才會有同源政策,主要是因為安全性的考量,不讓你的 JavaScript 拿到並且傳回錯誤。

CORS 跨來源資源共用

全名為 Cross-Origin Resource Sharing

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

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

如果想開啟跨來源 HTTP 請求的話,Server 必須在 Response 的 Header 裡面加上Access-Control-Allow-Origin
當瀏覽器收到 Response 之後,會先檢查Access-Control-Allow-Origin裡面的內容,如果裡面有包含現在這個發起 Request 的 Origin 的話,就會允許通過,讓程式順利接收到 Response。

Content-Type: application/json
Content-Length: 71
Connection: keep-alive
Server: nginx
Access-Control-Allow-Origin: * //這行是重點
Cache-Control: no-cache, no-store, must-revalidate, private
Expires: 0
Pragma: no-cache
Twitch-Trace-Id: e316ddcf2fa38a659fa95af9012c9358
X-Ctxlog-Logid: 1-5920052c-446a91950e3abed21a360bd5
Timing-Allow-Origin: https://www.twitch.tv

*星號就代表萬用字元,意思是任何一個 Origin 都接受。所以當瀏覽器接收到這個 Response 之後,比對目前的 Origin 符合* 這個規則,檢驗通過,允許我們接受跨來源請求的回應。

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

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

第二層防護 - Preflight Request

CORS 會把 Request 分成兩種

  • 簡單請求:

    • ( 非 GET/POST/HEAD || 帶 Header 參數 )沒有加任何自定義的 Header,而且又是 GET/POST/HEAD 的話,就是簡單請求
    • 屬於簡單請求的 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 就是 利用 sr 不受同源限制的特性,直接載入一隻帶參數的js,當作是發 request,用一個 function 包裝起來。
  • 下圖透過你帶過去的callback這個參數當作函式名稱,把 JavaScript 物件整個傳到 Function 裡面,你就可以在 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

補充:

資料格式 XML & JSON

XML

Extensible Markup Language
跟 HTML 有點像、都是標記語言 Markup Language,特色是「 內容用前後標籤包起來 」,因為此格式檔案較大、且不好閱讀,所以現代開發比較少人用 XML。

JSON

JavaScript Object Notation
乍看跟 JavaScript 的物件很像,但 key 值必須要用雙引號 "key" 包起來
JSON 字串可以包含 陣列 ( 用中括號 [ ] ) 以及 物件 ( 用大括號 { } )

  • 小規定:
    JSON 格式不能使用註解
    Vaule 值沒有辦法放 function









Related Posts

[BE201] 、超簡易留言板(下)

[BE201] 、超簡易留言板(下)

Typescript express 打造Twitter專案 Part1

Typescript express 打造Twitter專案 Part1

有空的話來學一下 Tailwind CSS - Week 2

有空的話來學一下 Tailwind CSS - Week 2


Comments