如果你熟悉.NET或其他類似平台的Web開發,你可能會像,建立一個Web服務器有什麼,在Visual Studio中建立一個Web工程,點擊運行即可。事實的確是這樣,但請不要忘記,這樣的代價是,比如果說,你是用.NET開發Web應用,你就使用了完整的IIS作為你的Web服務器基礎,這樣當你的應用發布時就只能用IIS了。而如果使用獨立服務器(利用System.Web.Hosting自己構建的話),你則必須處理各種HttpListener和相應線程,則會比較麻煩,畢竟.NET不是專注於面向Web的。Node.js在這方面提供了方便且可定制的途徑,你可以在其基礎上構建精巧且完全面向你應用的服務平台。
一、建立簡單的Web服務器涉及到Node.js的一些基本知識點:
1、請求模塊
在Node.js中,系統提供了許多有用的模塊(當然你也可以用JavaScript編寫自己的模塊,以後的章節我們將詳細講解),如http、url等。模塊封裝特定的功能,提供相應的方法或屬性,要使用這些模塊,需要先請求模塊獲得其操作對象。
例如要使用系統的http模塊,可以這樣寫:
復制代碼 代碼如下:
var libHttp = require('http'); //請求HTTP協議模塊
這樣,以後的程序將可以通過變量libHttp訪問http模塊的功能。本章例程中使用了以下系統模塊:
http:封裝http協議的服務器和客戶端實現;
url:封裝對url的解析和處理;
fs:封裝對文件系統操作的功能;
path:封裝對路徑的解析功能。
有了這些模塊,我們就可以站在巨人的肩膀上構建自己的應用。
2、控制台
為了更好的觀察程序的運行,方便在異常時查看錯誤,可以通便變量console使用控制台的功能。
復制代碼 代碼如下:
console.log('這是一段日志信息');
計時並在控制台上輸出計時信息:
//開始計時
console.timeEnd('計時器1'); //開始名稱為“計時器1”的計時器
...
...
...
//結束計時,並輸出到控制台
console.timeEnd('計時器1'); //結束稱為“計時器1”的計時器並輸出
3、定義函數
在Node.js中定義函數的辦法與普通JavaScript中完全相同,不過我們推薦的寫法如下,即使用一個變量為函數命名,這樣可以比較方便明確的將函數作為參數傳遞給其他函數:
復制代碼 代碼如下:
//定義一個名為showErr的函數
var showErr=function(msg){
var inf="錯誤!"+msg;
console.log(inf+msg);
return msg;
}
4、創建Web服務器並偵聽訪問請求
創建Web服務器最重要的是提供Web請求的響應函數,它有兩個參數,第一個代表客戶端請求的信息,另一個代表將要返回給客戶端的信息。在響應函數中應解析請求信息,依據請求,組裝返後內容。
復制代碼 代碼如下:
//請求模塊
var libHttp = require('http'); //HTTP協議模塊
//Web服務器主函數,解析請求,返回Web內容
var funWebSvr = function (req, res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<html><body>');
res.write('<h1>*** Node.js ***</h1>');
res.write('<h2>Hello!</h2>');
res.end('</body></html>');
}
//創建一個http服務器
var webSvr=libHttp.createServer(funWebSvr);
//開始偵聽8124端口
webSvr.listen(8124);
5、解析Web請求
對於簡單的Web網頁訪問請求,重要的信息包含在請求信息參數的url裡,我們可以使用url解析模塊解析url中的訪問路徑,並利用path模塊,將訪問路徑組裝為要訪問的實際文件路徑用於返回。
復制代碼 代碼如下:
var reqUrl=req.url; //獲取請求的url
//向控制台輸出請求的路徑
console.log(reqUrl);
//使用url解析模塊獲取url中的路徑名
var pathName = libUrl.parse(reqUrl).pathname;
//使用path模塊獲取路徑名中的擴展名
if (libPath.extname(pathName)=="") {
//如果路徑沒有擴展名
pathName+="/"; //指定訪問目錄
}
if (pathName.charAt(pathName.length-1)=="/"){
//如果訪問目錄
pathName+="index.html"; //指定為默認網頁
}
//使用路徑解析模塊,組裝實際文件路徑
var filePath = libPath.join("./WebRoot",pathName);
6、設置返回頭
由於是Web請求,需要在返回內容中包含http返回頭,這裡重點是依據要訪問的文件路徑的文件擴展名,設置http返回頭的內容類型。
復制代碼 代碼如下:
var contentType="";
//使用路徑解析模塊獲取文件擴展名
var ext=libPath.extname(filePath);
switch(ext){
case ".html":
contentType= "text/html";
break;
case ".js":
contentType="text/javascript";
break;
...
...
default:
contentType="application/octet-stream";
}
//在返回頭中寫入內容類型
res.writeHead(200, {"Content-Type": contentType });
7、向返回對象中寫入訪問的文件內容
有了需要訪問的文件實際路徑,有了文件對應的內容類型,就可以利用fs文件系統模塊讀取文件流並返回給客戶端。
復制代碼 代碼如下:
//判斷文件是否存在
libPath.exists(filePath,function(exists){
if(exists){//文件存在
//在返回頭中寫入內容類型
res.writeHead(200, {"Content-Type": funGetContentType(filePath) });
//創建只讀流用於返回
var stream = libFs.createReadStream(filePath, {flags : "r", encoding : null});
//指定如果流讀取錯誤,返回404錯誤
stream.on("error", function() {
res.writeHead(404);
res.end("<h1>404 Read Error</h1>");
});
//連接文件流和http返回流的管道,用於返回實際Web內容
stream.pipe(res);
}
else { //文件不存在
//返回404錯誤
res.writeHead(404, {"Content-Type": "text/html"});
res.end("<h1>404 Not Found</h1>");
}
});
二、測試及運行
1、完整源碼
以下100行左右的JavaScript就是建立這樣一個簡單web服務器的全部源碼:
復制代碼 代碼如下:
//------------------------------------------------
//WebSvr.js
// 一個演示Web服務器
//------------------------------------------------
//開始服務啟動計時器
console.time('[WebSvr][Start]');
//請求模塊
var libHttp = require('http'); //HTTP協議模塊
var libUrl=require('url'); //URL解析模塊
var libFs = require("fs"); //文件系統模塊
var libPath = require("path"); //路徑解析模塊
//依據路徑獲取返回內容類型字符串,用於http返回頭
var funGetContentType=function(filePath){
var contentType="";
//使用路徑解析模塊獲取文件擴展名
var ext=libPath.extname(filePath);
switch(ext){
case ".html":
contentType= "text/html";
break;
case ".js":
contentType="text/javascript";
break;
case ".css":
contentType="text/css";
break;
case ".gif":
contentType="image/gif";
break;
case ".jpg":
contentType="image/jpeg";
break;
case ".png":
contentType="image/png";
break;
case ".ico":
contentType="image/icon";
break;
default:
contentType="application/octet-stream";
}
return contentType; //返回內容類型字符串
}
//Web服務器主函數,解析請求,返回Web內容
var funWebSvr = function (req, res){
var reqUrl=req.url; //獲取請求的url
//向控制台輸出請求的路徑
console.log(reqUrl);
//使用url解析模塊獲取url中的路徑名
var pathName = libUrl.parse(reqUrl).pathname;
if (libPath.extname(pathName)=="") {
//如果路徑沒有擴展名
pathName+="/"; //指定訪問目錄
}
if (pathName.charAt(pathName.length-1)=="/"){
//如果訪問目錄
pathName+="index.html"; //指定為默認網頁
}
//使用路徑解析模塊,組裝實際文件路徑
var filePath = libPath.join("./WebRoot",pathName);
//判斷文件是否存在
libPath.exists(filePath,function(exists){
if(exists){//文件存在
//在返回頭中寫入內容類型
res.writeHead(200, {"Content-Type": funGetContentType(filePath) });
//創建只讀流用於返回
var stream = libFs.createReadStream(filePath, {flags : "r", encoding : null});
//指定如果流讀取錯誤,返回404錯誤
stream.on("error", function() {
res.writeHead(404);
res.end("<h1>404 Read Error</h1>");
});
//連接文件流和http返回流的管道,用於返回實際Web內容
stream.pipe(res);
}
else { //文件不存在
//返回404錯誤
res.writeHead(404, {"Content-Type": "text/html"});
res.end("<h1>404 Not Found</h1>");
}
});
}
//創建一個http服務器
var webSvr=libHttp.createServer(funWebSvr);
//指定服務器錯誤事件響應
webSvr.on("error", function(error) {
console.log(error); //在控制台中輸出錯誤信息
});
//開始偵聽8124端口
webSvr.listen(8124,function(){
//向控制台輸出服務啟動的信息
console.log('[WebSvr][Start] running at http://127.0.0.1:8124/');
//結束服務啟動計時器並輸出
console.timeEnd('[WebSvr][Start]');
});
既然要建立Web服務器,我們需要創建一個WebRoot目錄來存放實際的網頁和圖片資源,“WebRoot”的目錄名在以上源碼中被用於組裝實際文件路徑。
在命令行中輸入:
node.exe WebSvr.js
我們的Web服務器就運行起來了,這時,可以通過浏覽器對其進行訪問,運行效果如下:
利用Node.js我們可以方便建立相對獨立的Web服務器,其事件驅動的特性避免繁瑣的線程保護,其基礎模塊更降低了開發難度。本章建立的Web服務器只是一個簡單的樣本,沒有過多的考慮模塊化、安全性等問題,但可以從中掌握Node.js開發的一些基本的知識。
作者:汪峰 www.otlive.cn