網路基礎
HTTP
本篇為 [NET101] 網路基礎概論(搭配 JS 實作練習) 這門課程的學習筆記。如有錯誤歡迎指正。
學習目標:
知道網路背後大概的運作模式
知道什麼是 Request 跟 Response
知道什麼是 DNS 以及運作原理
知道 HTTP 與 HTTPS 的差異
知道 localhost 跟 127.0.0.1 是什麼
知道 GET 與 POST 的差別
知道常用的 HTTP Header
知道什麼是 API
會使用 node.js 寫出串接 API 的程式
知道 HTTP method 有哪些
知道基本的 HTTP statud code,像是 200、301、400、404、500
全名是 HyperText Transfer Protocol,中文翻作「 超文本傳輸協定」。
HTTP 是一套網路傳輸協定,為全球資訊網的資料通訊的基礎。也就是說,網頁前端和後端在溝通時,就是透過 HTTP 協定進行。
簡言之,協定就是一個「標準」。協定是為了讓彼此溝通而建立的規範,制定標準以統一格式,如此能夠進行規模化。
而透過協議溝通的兩端,通常可分為客戶端(Client)和伺服器端(Server)。
網頁上的資訊,其實就是由許多 request 跟 response 構成。且兩者內容均分成 header 與 body,分別帶著不同資訊:
我們可透過瀏覽器的開發者工具、或是 HTTP 抓包工具 Charles 來查看詳細資訊,過程大致如下:
全名是 Domain Name System,負責將域名轉換成 IP 位置。
補充:localhost 跟 127.0.0.1 是什麼?
localhost
:是一個域名,對應的的 IP 位址是 127.0.0.1127.0.0.1
是回送地址,指本地機,一般用來測試使用
Google 提供免費的 DNS 伺服器 8.8.8.8 和8.8.4.4,如此可透過搜尋引擎來蒐集大數據。
就是透過 DNS 把 Request URL(github.com) 轉換成實際 IP 位置(13.250.177.223)。
nslookup
指令在終端機輸入 nslookup '網址'
:可查詢 DNS 伺服器。有可能出現多個結果,代表對應到多個 server。
即使我們沒有瀏覽器,也能夠過其他方式發送 request 跟取得 response。
利用 Node.js 的一套 library:request - Simplified HTTP client,來模擬瀏覽器發送 request 的過程,步驟如下:
npm install request
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
const request = require('request');
request('https://github.com/Lidemy/mentor-program-4th-heidiliu2020', function (error, response, body) {
console.error('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('body:', body);
});
node index.js
可拿到 responsenode index.js > github.html
導入程式碼,一樣能打開網頁什麼是 HTTP Method
?在 HTTP 1.1 協定中,定義了八種方法,來以不同方式操作指定的資源。詳細可參考 HTTP 請求方法- HTTP | MDN:
這是為了讓 Server 能夠清楚辨別 request 的目的。
常用的幾個動作分別為:GET / POST / PUT / DELETE,正好對應到資料庫基本操作 CRUD 增刪查改:
一般的 API 可能會長這樣:
新增使用者:POST + /new_user
刪除使用者:POST + /delete_user
查詢使用者:GET + /user_data/:id
更改使用者:POST + /update_user
若以 RESTful API 風格開發:
新增使用者:POST + /users
刪除使用者:DELETE + /users/:id
查詢使用者:GET + /users/:id
更改使用者:PATCH + /users/:id
由此可見 RESTful 的寫法較一致,且語意化更好理解。透過把「動作」藏在 HTTP method 裡面,而有唯一的 URL(/users
)表示資源位置,藉此統一 API 接口。
如果用 HTML 來比喻,寫網頁時我們可以全部使用 div 標籤,但為了幫助理解,通常會使用 li、section、article 等具有語意的標籤,即可從結構看出內容的作用。
參考資料:API 是什麼? RESTful API 又是什麼? - Medium
當中又以 GET
和 POST
兩種,為最常使用的 HTTP 請求方法。兩者最直觀的區別是「資料傳遞方式」與「安全性」。
GET
:向指定的資源要求資料,類似於查詢操作。POST
:將要處理的資料提交給指定的資源,類似於更新操作。在最下方夾帶 request 資訊:
是 HTTP 用「3 位數字代碼」來表示回應狀態,通常以開頭的數字來進行區分。詳細可參考 HTTP狀態碼- 維基百科:
200 OK
:代表成功204 No Content
:伺服器成功處理了請求,沒有返回任何內容(例如發出 DELETE XXX 訊息,回傳 204 代表刪除成功)301 Moved Permanently
:資源「永久」移到新位置302 Found
:資源「暫時」移到其他位置400 Bad Request
:請求語法錯誤、資源太大、請求訊息無效等404 Not Found
:找不到資源500 Internal Server Error
:伺服器出錯。例如搶票時伺服器當機503 Service Unavailable
:由於臨時的伺服器維護或者過載,伺服器當前無法處理請求參考資料:
前面我們已經實作一個 Client 端,也就是利用 request 這個 library 來顯示出來。接下來我們要利用 Node.js 內建 library:http
來實作 Server 端:
server.js
,並輸入下列程式碼:let http = require('http'); // 引用 library: http
let server = http.createServer(handleRequest)
function handleRequest(req, res) {
console.log(req.url)
res.write('hello')
res.end()
}
server.listen(5000); // 監聽 5000 這個 port
執行 node server.js
:不會跑出任何東西。因為沒有 console.log
任何東西,所以是正常的。此時程式會一直執行到按 Crtl + C
才會退出,符合 server 端必須不斷運行來接收資訊。
在瀏覽器輸入網址 localhost:5000
會跑出下列畫面:
let http = require('http'); // 引用 library: http
let server = http.createServer(handleRequest)
function handleRequest(req, res) {
console.log(req.url)
if (req.url === '/') { // 若網址為根目錄
res.write('welcome!')
res.end()
return
}
if (req.url === '/hello') { // 若網址為 /hello
res.write('hello')
res.end()
return
}
if (req.url === 'redirect') {
}
}
server.listen(5000); // 監聽 5000 這個 port
localhost:5000
:回應 welcomelocalhost:5000/hello
:回應 helloredirect
部分和 res.writeHead(404)
,就完成一個較完整的 Server 端:let http = require('http'); // 引用 library: http
let server = http.createServer(handleRequest)
function handleRequest(req, res) {
console.log(req.url)
if (req.url === '/') {
res.write('welcome!')
res.end()
return
}
if (req.url === '/hello') {
res.write('hello')
res.end()
return
}
if (req.url === '/redirect') {
res.writeHead(200, {
'lidemy': 'good'
})
res.end()
return
}
res.writeHead(404)
res.end()
}
server.listen(5000); // 監聽 5000 這個 port
404 Not Found
:localhost:5000/redirect
,會在 Response Headers 會出現 'lidemy': 'good'
:302 Found
: if (req.url === '/redirect') {
res.writeHead(302, {
'Location': '/hello' // 指定要轉到哪裡
})
res.end()
return
}
'localhost:5000/redirect
,瀏覽器就會轉址到新位置:'Location': 'https://google.com'
:輸入網址 localhost:5000/redirect
就會直接連到 google.com
先前提到「從電腦發出 request 到 server」,在溝通訊息這段過程,其實需要經過非常多道手續。因此就有組織將網路標準化,將網路連接過程分成數個階層(layer),每個階層都有特別的獨立的功能。
分層的好處,是只要處理該層級的事情就好,因為每個階層的程式碼可以獨立撰寫,功能也不會相互干擾。
而著名的分層模型主要有兩種:
由於協定非常嚴謹,較偏向理論。
TCP/IP 是由 OSI 七層協定簡化而來,為目前網路通訊的基礎架構。以下為兩者之間的比較圖與常見的通訊協定:
參考資料:2.4 TCP/IP 的傳輸層相關封包與資料 - 鳥哥的Linux 私房菜
IP 的全名是 Internet Protocol,中文是「網際網路協定」。
可分為 IPv4、IPv6 這兩種協議,兩者最主要差異在於「IP 地址的不同」:
我們常聽到的 IP 地址,代表在網路上的地址。在網際網路上,每台電腦之間,是透過 IP 位址來通訊。當中又分為下列幾種類型:
理想情況下,是每一台電腦都有一個 IP 位址。固定的 IP 位址,適合架設網站,也因為不會變動,確保使用者能夠連上伺服器。例如:伺服主機、網路設備多使用固定 IP。
一般用戶大多是浮動 IP。浮動 IP 解決了「IP 不夠用」這個問題,因為每次連線的 IP 位址皆不同,也不會輕易被駭客攻擊。
只有在內網才能互相連接,外網找不到該地址,所以內網 IP 位址是可以重複的。但對外網而言,一定會有一個「固定 or 浮動」的 IP 位址。例如:公司使用內網溝通,藉由鎖 IP 位址,可提高安全性。
通常以
192.168
或10.0
開頭的,都是虛擬 IP。
參考資料:浮動 IP 與 固定 IP 有何不同?? 各有何優缺點??
在應用層當中,每台電腦主機 IP 位置(localhost),可能對應到不同應用程式。例如:HTTP、FTP、信件收發等服務。
而 Port 扮演網路通訊的端點,用來區分不同功能。如此即可辨認出,該連線要對應到哪個應用程式。
server.listen(5000)
:代表監聽 5000
這個 Port,輸入網址 localhost:5000
即可連到這個服務如果沒有輸入 Port,會有一些預設或常用值:
參考資料:[網路管理]常用 port 說明
傳輸層(Transport Layer)協定提供不同主機之間的資料傳輸及控制。根據不同需求,又分為「可靠的 TCP」和「速度較快的 UDP」兩種協定:
若以「傳紙條概念」比喻三次握手:
小明:安安,在嗎?
小美:在阿,你好。
小明:收到,太好了!
參考資料:
參考 Huli - 從傳紙條輕鬆學習基本網路概念,我們能夠透過傳紙條的例子,來試著理解網路通訊概念:
可參考 Huli - 從拉麵店的販賣機理解什麼是 API,或是這部影片:什麼是 API?來瞭解。
全名是 Application Programming Interface,中文是「應用程式介面」。簡言之,就是方便溝通、交換資料的管道。
API 是應用程式、裝置之間資料的交換,但不一定要透過網路才能有 API,例如:
以下為實際的運用例子:
透過 API 能夠達到省時、便利、營利等優點,因此我們也可以說,API 是一個「能讓生產者與消費者雙方溝通的介面」。
與先前「實做一個 Client 端發送 request」操作方式相同,是利用 library request 來模擬瀏覽器發送 request 的過程。
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('head:', response.headers);
console.log('body:', body);
});
將範本中的 google 網址改成 https://reqres.in/api/users
在終端機輸入 node index.js
,即可獲得 Regres API 所提供的資訊:
這樣其實就完成了簡單的 API 串接!透過丟一個 request 到網站,我們能夠獲取想要的資訊。
而透過 request 文件,我們能找到不同的 request.METHOD()
,瞭解如何使用各種方法:
request.get(): Defaults to method: "GET".
request.post(): Defaults to method: "POST".
request.put(): Defaults to method: "PUT".
request.patch(): Defaults to method: "PATCH".
request.del() / request.delete(): Defaults to method: "DELETE".
https://reqres.in/api/users/<userID>
/api/users/2
為例:方法一:直接將網址改成 https://reqres.in/api/users/2
方法二:使用 node.js 內建 library process
這個方法是利用 process.argv
達成帶入參數,我們可以先用 console.log
來查看 process.argv
是什麼:
const process = require("process");
console.log(process.argv);
會發現 process.argv
其實是一個陣列,利用 process.argv[2]
就可以拿到我們需要的參數:
const request = require('request');
const process = require('process');
request(
'https://reqres.in/api/users/' + process.argv[2],
function (error, response, body) {
console.log('body:', body);
}
);
輸入 node index.js 2
,就可獲得 id: 2
的使用者資料:
https://reqres.in/api/users/
const request = require('request');
request.post(
{
url: 'https://reqres.in/api/users',
form: {
name: 'Heidi',
job: 'web engineer'
}
},
function (error, response, body) {
const json = JSON.parse(body) // 轉成 JS 物件
console.log(json);
}
);
成功新增使用者資料:
註:這裡的操作只是測試用,並不會真的新增資料到網站。
https://reqres.in/api/users/<userID>
const request = require('request');
request.delete(
{
url: `https://reqres.in/api/users/2`, // 將使用者 id 帶入
},
function (error, response, body) {
// console.log(JSON.parse(body));
// 因為是刪除,所以不會回傳東西,因此轉印狀態碼
console.log('status code:', response.statusCode)
}
);
回傳 204
,代表成功刪除使用者資料:
https://reqres.in/api/users/<userID>
const request = require('request');
request.patch(
{
url: `https://reqres.in/api/users/2`,
form: { // 傳入要修改的資料
name: 'hello'
}
},
function (error, response, body) {
console.log(JSON.parse(body))
}
);
成功修改使用者資料:
Base URL: https://lidemy-book-store.herokuapp.com
說明 | Method | path | 參數 | 範例 |
---|---|---|---|---|
獲取所有書籍 | GET | /restaurants | _limit:限制回傳資料數量 | /restaurant?_limit=6 |
獲取單一書籍 | GET | /restaurants/:id | 無 | /restaurant/12 |
刪除書籍 | DELETE | /restaurants/:id | 無 | 無 |
新增書籍 | POST | /restaurants | name: 書籍名稱 | 無 |
更改書籍 | PATCH | /restaurants/:id | name: 書籍名稱 | 無 |
const request = require('request');
const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com';
request(`${API_ENDPOINT}/books?_limit=10`, (error, response, body) => {
// 判斷 status code 是否為 2 開頭:偵測回傳是否成功
if (response.statusCode >= 200 && response.statusCode < 300) {
let books = '';
// try catch:偵測處理資料這個動作是否出現錯誤
try {
books = JSON.parse(body);
// 列出前十筆資料
for (let i = 0; i < books.length; i += 1) {
console.log(`${books[i].id} ${books[i].name}`);
}
} catch (err) {
// 若 response 不是一個合法的 JSON 字串,會回傳錯誤
return console.log(err);
}
}
});
在發出 request 時,我們其實能透過兩種方式來帶入資料:
實際例子可參考Twitch API v5 文件,同樣支援這兩種方法:
Client-ID: XXXXX
)https://api.twitch.tv/kraken/users/44322889?client_id=XXXXX
)因此在串接之前,必須先確認該 API 支援哪種方式來發出請求。
在 API 實戰中的 response 資料其實就是「JSON 格式字串」。在談到如何整理資訊之前,先來介紹常用的資料格式:
前後標籤
包起來。
<firstName>John</firstName>
key
值要用雙引號 "key"
包起來[array]
、{object}
等;但 value 值
不能放 functionJSON.prase(<JSON>)
:將 JSON 格式字串轉成物件JSON.stringify(<object>)
:將物件轉成 JSON 格式字串參考資料:
同樣以串接 Reqres API 為例。得到 response 就是「JSON 格式的字串」,但這種資料並不易閱讀,也無法直接使用。
因此使用內建函式 JSON.parse()
做處理,就會轉成「JS 物件」:
const request = require('request');
request(
`https://reqres.in/api/users/2`,
function (error, response, body) {
console.log('原始格式,JSON 格式的字串----------');
console.log(body);
console.log('轉成 JS 物件--------------------');
console.log(JSON.parse(body));
}
);
如此就可以「物件」方式來取出想要的資料:
const request = require('request');
request(
`https://reqres.in/api/users/2`,
function (error, response, body) {
const json = JSON.parse(body) // JSON 物件
console.log(json.data.first_name); // 印出 Janet
}
);
反之,若想把「JS 物件」轉成「JSON 格式字串」,可以用: JSON.stringify(<object>)
curl
在 curl 官網下載,安裝方法可參考 Day14 - cURL 工具,即可在終端機使用。
curl 做法非常簡單,只要在終端機輸入指令就可支援發 HTTP request 、下載及上傳檔案的功能,基本格式為:
curl [options] [URL...]
GET
方法以使用 curl 能夠發 request 到 google 首頁為例:
crul 'http://www.google.com'
:即可下載該網頁程式碼crul '網址' > google.html
:可將回傳值導向其他檔案參考資料: