網頁與伺服器的溝通


Posted by ericcch24 on 2020-10-16

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

  • 在網頁上呼叫 API,是透過瀏覽器發送 request 與接收 response,而瀏覽器會加上一些特殊的資訊或是限制某些行為,反之在 node.js 則沒有這些額外東西。

傳送資料的方式

表單 form -- 最基本的方式

  • action 是要提交資料的頁面,瀏覽器發送post的 request 到 action(新的頁面),而瀏覽器會直接 render action 傳回來的 response,此時網頁在提交 request 後會換頁。
  • 比起與 server 的資料交換,此方式比較像是要帶上一些參數的資料到 action 這個頁面。
  • 與 JS 無關。

AJAX (Asynchronous JavaScript And XML)

  • 可以讓我們在瀏覽器上透過 Javascript 交換資料的技術,其重點在於==非同步的處理方式==,可以讓 Javascript 發送 request 之後,不等 reponse 回傳就繼續執行接下來的程式。而在 Response 回傳之後,就可以透過 Callback Function把回傳的資料帶進來。
  • 與表單發送資料的差異是 Ajax 可以在拿取資料的時候,將回傳的結果透過 Javascript 來處理顯示在頁面的形式,而不是像表單直接更新整個頁面。
    ```javascript=
    const request = new XMLHttpRequest();
    // 類似我要發一個新的 request 的意思
    request.onload = function() {
    // request 拿到結果時觸發 onload
    if (request.status >= 200 && request.status < 400) {
    // 表示 request 成功
    console.log(request.responseText);
    // 得到 response
    // 不一定每個 response 都有 responseText,有時候會是空物件
    } else {
    console.log('err');
    }
    }

// 也可以用監聽事件老方法
request.addEventListener('load', function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText);
} else {
console.log('err');
}
})

request.onerror = function() {
// request 有錯誤的時候
console.log('error');
}

request.open('GET', 'https://reqres.in/api/users', true);
// 發 request 的 method、位置、要不要非同步處理
// (同步的話會等待 response 回傳然後整個瀏覽器卡住)

request.send(); // 把 request 送出

* 從伺服器資源抓取資料後,下一步就是送出資料給伺服器,有些情況下,傳輸資料可能會影響返回的資料,例如為了取得某部門的員工,Ajax 請求可以將部門識別字送到伺服器。其他情況下,發送的資料需通過伺服器的驗證,例如使用者名稱是否存在;或觸發伺服器回應,例如張貼評論置留言板上。
* 將資料作為請求的一部份送給伺服器有兩種方法:
  1. ==將資料附加到 URL 後==:以「名稱 = 值」為資料結構配對,然後配對間用「&」隔開,為保證請求資料的安全性,可將其封裝至 ==`encodeURIComponent()`== 呼叫中
        ```javascript=
        ajax.open('GET', 'http://www.example.com/somepage.php?id=' +     encodeURIComponent(id), true);
  1. 把資料作為 send() 方法(代替 null)的唯一參數
       var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password);
       ajax.open('POST', 'http://www.example.com/somepage.php', true);
       ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');   //指定合適的資料編碼
       ajax.send(data);
    
    參考資料:DOMO MEMO

Same Origin Policy 同源政策

  • 當現在這個網站的跟你要呼叫的 API 的網站「不同源」的時候,瀏覽器一樣會幫你發 Request,但是會把 Response 給擋下來,不讓你的 JavaScript 拿到並且傳回錯誤,這是瀏覽器為了安全性考量的限制,node.js 就完全沒有。
  • 什麼是不同源呢?只要是 Domain 不一樣就是不同源,或者是一個用 http 一個用 https 也是不同源,端口號不一樣也是不同源。
  • 「跨網域」這個問題基本上只會在瀏覽器發生,因為安全性的關係。這是我們第四週在自己的電腦上用自己寫的程式發 request,跟第八週透過瀏覽器來發 request 最大最大的差別。
    參考資料:輕鬆理解 Ajax 與跨來源請求
    ### CORS: Cross-Origin Resource Sharing 跨來源資源共享
  • 如果要在不同 origin 之間傳輸資料的話,也就是想開啟跨來源 HTTP 請求的話,Server 必須在 Response 的 Header 裡面加上==Access-Control-Allow-Origin==。
  • 當瀏覽器收到 Response 之後,會先檢查==Access-Control-Allow-Origin==裡面的內容,如果裡面有包含現在這個發起 Request 的 Origin 的話,就會允許通過,讓程式順利接收到 Response。
  • 除了這個 Header 以外,也有其他方式,例如Access-Control-Allow-HeadersAccess-Control-Allow-Methods,可以定義接受哪些 Request Header 以及接受哪些 Method。
  • 總結,如果想要發起跨來源 HTTP 請求並且順利收到回應的話,需要確保 Server 端有加上Access-Control-Allow-Origin,不然 Response 會被瀏覽器給擋下來並且顯示出錯誤訊息。

同源政策範例:

公司內部有一個 API 是拿來刪除文章的,只要把文章 id 用 POST 帶過去即可刪除。

舉例來說:POST https://lidemy.com/deletePost 並帶上 id=13,就會刪除 id 是 13 的文章。

公司前後端的網域是不同的,而且後端並沒有加上 CORS 的 header,因此小明認為前端用 ajax 會受到同源政策的限制,request 根本發不出去,所以前端沒辦法利用 ajax 呼叫這個 API 刪除文章。

請問小明的說法是正確的嗎?如果錯誤,請指出錯誤的地方。

答案:錯誤。

這題考的是你對同源政策的理解程度。

首先,==同源政策限制的對象有兩種,簡單請求跟非簡單請求==,前者像是 GET 跟 POST(還必須沒有加上任何 custom header),後者像是 DELETE 之類的。這邊可以自己去找 MDN 的參考資料來看。

簡單請求跟非簡單請求的差別在於:

==簡單請求限制的是「拿到 response」而不是「發出 request」,這是超級無敵重要的一點,但我想很多人都會搞混==

==非簡單請求會先發出一個 OPTIONS 的 request 去查看後端是否允許非同源的 request,如果不允許,則不會把請求發出去==。舉例來說,使用 DELETE 方法就會先發出 OPTIONS 的 request,確認後端有帶 CORS 的 header 才會把 DELETE 的 request 發出去。

所以,以這題的狀況來說,儘管後端沒有加上 CORS 的 header,刪除文章的 request 依舊發的出去,只是前端的 JS 拿不到 response,然後 console 會出現錯誤。所以文章還是被刪掉了,只是前端不知道到底有沒有成功。

參考資料:
輕鬆理解 Ajax 與跨來源請求
第十五週網站前後端開發基礎測試


綜合示範:抓取資料並顯示

  • 先切好要抓取的資料的 css 版面
  • 透過 Ajax 抓取資料
  • 然後用 innerHTML 插入處理好的 HTML 樣式,然後用${}插入抓取到的動態資料。
  • 最後使用 appendChild 來顯示到頁面上
  • 這種方式叫==client side rendering==,用 Javascript 去動態新增網頁內容,所以在檢視網頁原始碼的時候 body 內會沒東西,因為是透過 js 動態新增內容。
    ```javascript=

    const request = new XMLHttpRequest();
    const container = document.querySelector('.app')
    request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
      const response = request.responseText;
      const json = JSON.parse(response);
      const users = json.data;
      for (let i = 0; i < users.length; i += 1) {
        const div = document.createElement('div')
        div.classList.add('profile');
        div.innerHTML = `
        <div class='profile__name' >${users[i].first_name} ${users[i].last_name}</div>
        <img class='profile__img' src='${users[i].avatar}' />
        `
        container.appendChild(div);
      }
    } else {
      console.log('err');
    }
    
    }

request.onerror = function() {
console.log('error');
}
request.open('GET', 'https://reqres.in/api/users', true);
// 如果 url 有變數,可以用'網址' + 變數
// 例如:'https://api.twitch.tv/kraken/streams/?game='+ data.top[0].game.name
request.send()

</script>
```

tags: Week8

#week8







Related Posts

利用 Elm 製作 Chrome Extension

利用 Elm 製作 Chrome Extension

JavaScript 學習筆記 - Operator

JavaScript 學習筆記 - Operator

871. Minimum Number of Refueling Stops

871. Minimum Number of Refueling Stops


Comments