(本文同步發表於NodeJust.com)
在Hello World的例子裡,學習了Node.js的回應(Response)的基礎,今天來看一下請求(Request)的部分。以下是今天的學習重點:
1. Node.js的url模組;URL的結構:pathname與query。
2. 請求(Request)的初體驗:req.url。
3. 建立自己的模組,匯出模組中的函數:exports.外部引用名稱 = 內部函數名稱。
4. 理解路由的邏輯。
請求(Request)基本上就是處理URL以及GET/POST,我們先來看URL的部分。URL的處理就是路由(Routing)。首先我們要明白URL的結構,請看以下例子:
http://localhost:3000/blog?user=zack
我們只要看"/"之後,問號之前,也就是"blog"的部分,我們叫它:pathname。問號之後,也就是"user=zack"的部分,我們叫它:query。
這個命名是來自我們今天要用到的一個Node.js模組:url模組。以下是今天第一個例子(index.js):
var http = require("http");
var url = require("url");
function onRequest(req, res) {
var pathname = url.parse(req.url).pathname;
console.log("Request for " + pathname + " received.");
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Hello World");
}
http.createServer(onRequest).listen(3000);
console.log("Server has started to listen at port: 3000.");
我們引進url模組,並將之賦值給變數"url"var url = require("url"); 。
req.url裡包含整個URL,我們用url.parse(req.url).pathname 提取pathname的部分,並將之賦值給"pathname"變數。然後我們就可以根據pathname來決定該顯示什麼內容,例如,當pathname的值為"blog"的時候,我們就顯示部落格;當pathname為"about"的時候就顯示"關於我們"頁面。這個例子裡則是什麼都沒做,只是在console顯示所訪問的位置。因此,當你在瀏覽器輸入:localhost:3000/blog,畫面並不會變化,只有console會顯示:"Request for /blog received."
至於要怎麼處理路由呢?最簡單的方法就是用IF來進行判斷。這顯然不是一個好方法,特別是當網站變得越來越大,需要處理的路由越來越多的時候。雖然網站會變多大,我們想像不到;但至少我們可以把路由的處理獨立出來,作為一個函數,就算將來要改動路由,對主程式的影響也能降到最低。整個程式也會更清晰。
現在來想想route()函數的內容該怎樣。暫且設定三種情況要處理:
pathname為"/":顯示主頁;
pathname為"blog":顯示部落格;
pathname為其他:顯示404 Not Found。
我們可以建立一個handle[pathname]的變數,根據pathname的不同而執行不同的功能:
pathname為"/",即handle["/"]:顯示主頁;
pathname為"blog",即handle["blog"]:顯示部落格;
pathname為其他,即handle[pathname]不存在:顯示404 Not Found。
因此要先給handle["/"]跟handle["blog"]分別建立顯示用的函數,將這兩個函數放在同一個文件,以一個模組的形式來提供這個兩功能以供稍後的route()函數使用。方法是:新增一個文件,命名為showPage.js,內容如下:
function home() {
console.log("This is the home page.");
}
function blog() {
console.log("This is the blog page.");
}
exports.home = home;
exports.blog = blog;
最後兩句就是將本文件中的功能匯出,格式是:exports.外部引用名稱 = 內部函數名稱。這個文件其實就是一個獨立的模組。在下面的route()函數裡,我們會看到具體如何使用。
接著將這兩個函數賦值給handle[],為什麼是shwoPage.home?下面再解釋:
var handle = {}
handle["/"] = showPage.home;
handle["/blog"] = showPage.blog;
判斷handle[pathname]是否為有效的函數,是則執行;否則顯示404:
if (typeof handle[pathname] === 'function') {
handle[pathname]();
} else {
console.log("404 Not Found: " + pathname);
}
之前,我們將函數賦值給了handle[pathname],這裡只是加一對小括號就能調用該函數handlepathname; 。是不是非常方便?
整個邏輯清晰了,正式來建立route()函數。我們把route()放在一個獨立的文件裡,並在結尾將函數匯出,讓主程式(index.js)調用。新增一個route.js的文件,其內容如下:
var showPage = require("./showPage");
function route(pathname) {
var handle = {}
handle["/"] = showPage.home;
handle["/blog"] = showPage.blog;
if (typeof handle[pathname] === 'function') {
handle[pathname]();
} else {
console.log("404 Not Found " + pathname);
}
}
exports.route = route;
首先,我們用var showPage = require("./showPage"); ,將showPage.js文件(模組)引入並賦值給showPage變數,這樣我們就能用showPage.home 以及showPage.blog (不用加小括號)來調用裡面的兩個函數。下面我們就把這兩個函數分別賦值給handle[]變數。然後進行路由判斷。最後我們也將route()函數匯出exports.route = route; ,以便主程式index.js使用。
所以route.js也是一個獨立的模組。這樣,我們一共建立了兩個自訂模組:showPage.js與route.js。
現在的index.js變成這樣:
var http = require("http");
var url = require("url");
var router = require("./route");
function onRequest(req, res) {
var pathname = url.parse(req.url).pathname;
router.route(pathname);
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Hello World");
}
http.createServer(onRequest).listen(3000);
console.log("Server has started to listen at port: 3000.");
先是匯入了route.js模組,並賦值給router變數。然後增加了router.route(pathname); 這一行,讓router來幫我們處理所有的路由。
現在你用瀏覽器打開:localhost:3000,你會看到console裡輸出:"This is the home page."。
當打開:localhost:3000/blog,會看到console裡輸出:"This is the blog page."。
當打開:localhost:3000/xxx,會看到console裡輸出:"404 Not Found /xxx"。
另外你也會看到:"404 Not Found /favicon.ico",這是之前提過的對favicon的請求,暫時先不用理它。
到這裡為止,我們的路由只輸出到console,沒有真正輸出到網頁的部份。這是因為會涉及到Node.js非同步特性帶來的問題,所以下一篇再來詳細討論這個部份。
同樣來回顧今天的學習目標。
1. Node.js的url模組;URL的結構:pathname與query。
--匯入模組var url = require("url");
--取得某URL的pathname:url.parse(URL).pathname;
2. 請求(Request)的初體驗:
--req.url 包含完整URL,要使用上面的url模組來分解。
3. 建立自己的模組,匯出模組中的函數。
--以一個獨立文件的形式,結尾將內部功能匯出:exports.外部引用名稱 = 內部函數名稱
--要使用一個模組:var router = require("./route"); 不用加副檔名.js。
4. 理解路由的邏輯。
--理解handle[pathname] 跟handlepathname 的神奇之處。
參考資料:
[image credit: kamillehmann]