用 node.js 呼叫 API 與在網頁上呼叫的根本差異
- 在網頁上呼叫 API,是透過瀏覽器發送 request 與接收 response,而瀏覽器會加上一些特殊的資訊或是限制某些行為,反之在 node.js 則沒有這些額外東西。
傳送資料的方式
表單 form -- 最基本的方式
action
是要提交資料的頁面,瀏覽器發送post
的 request 到action
(新的頁面),而瀏覽器會直接 renderaction
傳回來的 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);
- 把資料作為 send() 方法(代替 null)的唯一參數
參考資料:DOMO MEMOvar 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);
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-Headers
跟Access-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>
```