[Node.js] 請求與響應 首先介紹在上一 Node.js 系列文章中出現的 req
及 res
物件。
Request Object 將上一筆記中的 server.js 第四行 console.log('request made');
改為 console.log(req);
1 2 3 4 5 6 7 8 9 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
執行並在瀏覽器連上 localhost:3000
可以看到 req
物件包含了很多東西,例如標頭,標頭是有關請求類型的 meta data 元數據預期的響應類型…等等。
我們可以再改成如下
1 2 3 4 5 6 7 8 9 10 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req.url); console .log(req.method); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
執行結果
req.url
為 /
,不是 localhost:3000
是因為它以此為根,所以才顯示 /
,試著將網址後面添加 /about
可以看到結果如下
req.method
表示是一個使用 GET 方法的請求
Response Object 在前一章的筆記中,連上了 localhost:3000
卻顯示找不到網頁的訊息是因為我們沒有做響應的處理,接下來就要透過 response 物件處理這部分。
現在我們想提出某種類型的內容回應,要制定相對應的 response header 響應頭給予瀏覽器對於將接收的響應更多信息。
1 2 3 4 5 6 7 8 9 10 11 12 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req.url); console .log(req.method); res.setHeader('Content-Type' , 'text/plain' ); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
Content-Type
表示內容類型,可以是 text, HTML, 或是 json,例如 'text/plain'
即發送純文本給瀏覽器。
設置完 response header,接著實際發送數據到瀏覽器,使用 res.write()
,還要加上 res.end()
表示響應結束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req.url); console .log(req.method); res.setHeader('Content-Type' , 'text/plain' ); res.write('Hello, Joe!' ); res.end(); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
接著執行並且連上 localhost:3000
,出現以下內容
我們可以開啟「開發人員工具」的 Network 標籤 ( F12 或是 Ctrl + shift + I 開啟)
按 Ctrl + R 刷新
點擊 localhost
選擇 Headers 標籤可以看到 Response Headers 當中的內容類型為 text/plain,與我們在伺服器設定的一樣
若是我們想回應一些 HTML 內容,而不是純文本呢?
首先更改響應標頭,將內容類型改為 'text/html'
1 res.setHeader('Content-Type' , 'text/html' );
接著修改內容
1 2 res.write('<p>Hello, Joe!</p>' ); res.write('<p>Hello again, Joe!</p>' );
目前的 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req.url); console .log(req.method); res.setHeader('Content-Type' , 'text/html' ); res.write('<p>Hello, Joe!</p>' ); res.write('<p>Hello again, Joe!</p>' ); res.end(); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
執行
可以檢查這些內容發現為 p 元素 當然也可以將內容添加 head 等其它元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const http = require ('http' );const server = http.createServer(( req, res ) => { console .log(req.url); console .log(req.method); res.setHeader('Content-Type' , 'text/html' ); res.write('<head><link rel="stylesheet" href="#"></head>' ); res.write('<p>Hello, Joe!</p>' ); res.write('<p>Hello again, Joe!</p>' ); res.end(); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
雖然這樣可以回應 HTML 給瀏覽器,但如果我們像這樣添加更多的內容會非常的混亂,所以這不是一個好方法。
我們應該創建 HTML 在一個單獨的文件中,然後使用 Node.js 讀取這些文件並發送給瀏覽器,所以要再次使用上個章節介紹的 fs 檔案系統模組。
回傳 HTML 頁面 首先建立一個資料夾名為 views ,用於存放要回應的 HTML 檔案
index.html 內容
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Node.js Test</title> </head> <body> <h1>Home</h1> <p>This is a test!</p> </body> </html>
接著修改 server.js,新增
1 const fs = require ('fs' );
內容類型不變,使用 fs.readFile()
讀取檔案並在沒有 err 的情況下響應到瀏覽器
1 2 3 4 5 6 7 8 9 10 res.setHeader('Content-Type' , 'text/html' ); fs.readFile('./views/index.html' , (err, data ) => { if (err) { console .log(err); res.end(); } else { res.write(data); res.end(); } })
全部的 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const http = require ('http' );const fs = require ('fs' );const server = http.createServer(( req, res ) => { res.setHeader('Content-Type' , 'text/html' ); fs.readFile('./views/index.html' , (err, data ) => { if (err) { console .log(err); res.end(); } else { res.write(data); res.end(); } }); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ) });
接著執行,重新整理 localhost:3000
頁面,結果顯示了 index.html
另外如果只寫入一樣東西到響應,可以寫在 res.end()
即可,它仍會執行相同的操作
若是要編寫多個內容,則寫多個 res.write()
,最後 res.end()
以上就是一個將 HTML 頁面發送到瀏覽器的方式。
基礎路由 現在不論你在 localhost:3000/
後面加什麼內容
所引導的響應都一樣會是 index.html
但我們想根據訪問的路線來響應不一樣的頁面,所以我們需要一種方法找到發出請求的 URL,並根據此 URL 響應相對應的頁面。 (前面有使用 req.url 顯示)
首先在 views 資料夾新增 about.html 及 404.html
404.html 內容,用於使用者訪問不存在的路線時響應的頁面
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Node.js Test</title> </head> <body> <h1>404 哎呀!找不到該頁面!</h1> <p>該頁面不存在喔!</p> </body> </html>
about.html 內容,用於路徑為 /about
時,響應該頁面
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Node.js Test</title> </head> <body> <h1>About</h1> <p>This is an about page.</p> </body> </html>
接著修改 server.js ,path
為要響應的檔案路徑,使用 switch
來判斷使用者輸入了何種路徑
1 2 3 4 5 6 7 8 9 10 11 12 let path = "./views/" ; switch (req.url) { case '/' : path += 'index.html' ; break ; case '/about' : path += 'about.html' ; break ; default : path += '404.html' ; break ; }
讀取檔案路徑改為讀取 path
1 fs.readFile(path, (err, data ) => {
全部的 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const http = require ('http' );const fs = require ('fs' );const server = http.createServer(( req, res ) => { let path = "./views/" ; switch (req.url) { case '/' : path += 'index.html' ; break ; case '/about' : path += 'about.html' ; break ; default : path += '404.html' ; break ; } res.setHeader('Content-Type' , 'text/html' ); fs.readFile(path, (err, data ) => { if (err) { console .log(err); res.end(); } else { res.end(data); } }); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ); });
現在執行並輸入各種路徑觀察顯示首頁
顯示 about 頁面
隨便輸入路徑,顯示 404 頁面
以上可以讓我們自由的根據 URL 響應多個不同的頁面。
Status Codes 狀態代碼 在連到找不到網頁的情況下
開啟開發人員工具的 Network,注意到 123 的 status 為 200,此為現在要介紹的 status codes 狀態代碼
響應的狀態代碼,描述了響應發送到瀏覽器的狀態,常見有以下:
200 - OK 一切正常
301 - Resource moved 資源已永久移動到某處,表示請求資源的 URL 已被改變
404 - Not found 找不到該頁面
500 - Internal server error 內部的伺服器錯誤
有更多的狀態代碼,通常分布於 100, 200, 300, 400, 500 的範圍
資訊回應 (Informational responses, 100–199),
成功回應,按計畫進行 (Successful responses, 200–299),
重定向 (Redirects, 300–399),
用戶或客戶端的錯誤 (Client errors, 400–499),
伺服器端的錯誤 (Server errors, 500–599).
現在添加這些狀態代碼到我們的響應中,在 switch
中對 res.statusCode
進行設置, '/'
或 '/about'
都是正常的狀態,除此之外則為 404 找不到網頁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 switch (req.url) { case '/' : path += 'index.html' ; res.statusCode = 200 ; break ; case '/about' : path += 'about.html' ; res.statusCode = 200 ; break ; default : path += '404.html' ; res.statusCode = 404 ; break ; }
全部的 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const http = require ('http' );const fs = require ('fs' );const server = http.createServer(( req, res ) => { let path = "./views/" ; switch (req.url) { case '/' : path += 'index.html' ; res.statusCode = 200 ; break ; case '/about' : path += 'about.html' ; res.statusCode = 200 ; break ; default : path += '404.html' ; res.statusCode = 404 ; break ; } res.setHeader('Content-Type' , 'text/html' ); fs.readFile(path, (err, data ) => { if (err) { console .log(err); res.end(); } else { res.end(data); } }); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ); });
執行,依舊連到 /123
,發現狀態為 404 了(favicon.ico 與我們無關,這裡不管)
可以連上 /about
或是 /
,狀態都為 200
Redirects 重新定向 假設我們有一個網站,其中一個路徑為 '/about-me'
,但後來決定將該路徑改為 '/about'
,依照前面的做法將 switch
中的 URL 從 '/about-me'
改成 '/about'
即可,但萬一我的網站很受歡迎,有成千上萬的人點擊過去的連結,則會連到 404 的頁面(因為該頁面已不在),所以我們應該偵測 '/about-me'
的 URL,並將其重新導向 '/about'
的頁面。
在 switch
新增 '/about-me'
的 case,雖然也可以用 path
指向 about.html 的方式,但這裡要做的是「重新定向」,首先設定 statusCode
為 301,表示進行了永久的重新定向,接著設定標頭,第一個參數是 Location
屬性,第二個參數則是重新定向到哪裡,這裡即 '/about'
,最後要加上該次的響應結束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 switch (req.url) { case '/' : path += 'index.html' ; res.statusCode = 200 ; break ; case '/about' : path += 'about.html' ; res.statusCode = 200 ; break ; case '/about-me' : res.statusCode = 301 ; res.setHeader('Location' , '/about' ); res.end(); break ; default : path += '404.html' ; res.statusCode = 404 ; break ; }
全部的 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const http = require ('http' );const fs = require ('fs' );const server = http.createServer(( req, res ) => { let path = "./views/" ; switch (req.url) { case '/' : path += 'index.html' ; res.statusCode = 200 ; break ; case '/about' : path += 'about.html' ; res.statusCode = 200 ; break ; case '/about-me' : res.statusCode = 301 ; res.setHeader('Location' , '/about' ); res.end(); break ; default : path += '404.html' ; res.statusCode = 404 ; break ; } res.setHeader('Content-Type' , 'text/html' ); fs.readFile(path, (err, data ) => { if (err) { console .log(err); res.end(); } else { res.end(data); } }); }); server.listen(3000 , 'localhost' , () => { console .log('listening for requests on port 3000' ); });
現在執行,輸入以下網址
頁面會自動引導到 /about
頁面
開啟開發人員工具 Network 標籤,觀察到 about-me 狀態為 301
觀看其 Headers 可以看到 Location 的部分
以上為伺服器響應網頁的基礎,但隨著網站越來越大,變得更複雜,要處理許多不同類型的請求,如發布、刪除請求以及有關數據庫的邏輯… 等等,這些以上面所學的方式處理會有些混亂、難維護,但幸運的是有名為 Express 的第三方框架,可以幫助我們更輕鬆的處理、管理以上問題。
即使如此我們也該先了解 Node.js 在這中間的過程,再學習 Express 會對於為什麼這麼做比較有感受及輕鬆。
參考資料 The Net Ninja | Node.js Crash Course Tutorial #4 - Requests & Responses