node.js從入門到菜鳥——資源無法載入?你需要學會地址解析
node.js是什麼似乎已經不需要我多為贅述了,非阻塞的伺服器語言、JS書寫的後台代碼,無數的文章已經很好的展示了node的魅力與展望。關於node.js的安裝,大家不妨參考博客園聶微東的http://www.cnblogs.com/Darren_code/archive/2011/10/31/nodejs.html (node.js初體驗),這篇文章很好的綜述了node.js的一個基礎(從安裝到體驗到模塊的一個入門,個人感覺是一篇很好的文章),相信通過東哥的這篇文章大家可以對node有一個初步的了解。
node是一門很有意思的框架,它能夠讓一個長期執迷於前端開發的攻城濕(忘記了還有一種語言叫後端語言。。。)能夠覺得很舒適(編碼習慣都一樣),但是也同樣會讓一個新手覺得無所適從(為什麼么有從入門到精通哩?參考書在哪裡???)。這是一個前端高手為之一亮(也許只亮了一眼o(╯□╰)o),新手眼前一暈(if you want to find more information,please read the source(╯□╰)o)的框架。為了讓和我一樣的新手能夠多多少少摸到一點門路,我以身試法,來為新手找一條路~
本文旨在新手入門,所學尚淺,代碼水準有限,這也僅僅只是一個基本入門的筆記,高手可以笑一笑然後點關閉了。。。
首先我假設你已經安裝好了node(http://nodejs.org/#download,0.6.14已經很成熟了),那麼首先我們來進行一個入門的編碼分析。
在進行分析之前,我們先來想一下以前的伺服器端語言框架的工作原理。首先,用戶通過瀏覽器來訪問我們的應用。然後伺服器通過對埠的監聽,來接收網路端的request請求,並進行相應的處理。最後再將處理的結果返回客戶端。
好了,那麼我們可以開始了。首先是伺服器,要是沒有辦法啟動伺服器,那麼一切都是閑的。我們也許用過很多伺服器端的語言(PHP、JAVA、ASP等等),接收HTTP請求並提供Web頁面的過程似乎不用我們來做,apache和IIS似乎都會幫我們來完成。但是在node里,這一步必須你自己做。我們來實現的不僅僅是一個應用,還需要實現HTTP處理的伺服器。
好像很複雜的樣子,但對於node這並不是什麼複雜的東西。我們即將進行一個HTTP伺服器的初步學習,但是在學習之前我們需要溫習(預習?)一下node的模塊機制。
node採取的是模塊機制(和JS差不多),通過對模塊的導入我們可以聲明變數並將導入的模塊的實例化對象賦給變數並加以使用。具體各個模塊的使用方法以及用途請參考API,這裡就不說了。。。
好了,我們回來看看HTTP伺服器的構成。首先,我們先對HTTP模塊進行一次請求:
var http = require("http");
在node中require方法用於對各個模塊的引入,在這裡我們需要對http模塊加以使用時就可以導入。導入後將之賦予http變數,然後對http變數進行操作。讓我們以hello world作為例子:
1 http.createServer(function(request, response) {2 response.writeHead(200, {"Content-Type": "text/plain"});3 response.write("Hello World");4 response.end();5 }).listen(12345,」127.0.0.1」);
每每看到hello world時內心都會有一絲變態的快意。。。
讓我們來回憶一下剛才的問題:實現一個http的伺服器。其實這個很簡單,http模塊自帶的createServer方法就可以完成。該方法只有一個參數,類型為函數,在介面文檔中的定義是「始終接收request事件」。而server.listen方法(server就是剛才通過createServer創建的server)的參數是(port, [hostname], [callback]),第一個是監聽的埠號,第二個和第三個是可選項,第二個是主機名稱,第三個回調函數。這個函數只在綁定埠後進行調用。
接下來是回調函數,回調函數的參數有兩個,第一個是客戶端發送的request,第二個是伺服器端的response。這段代碼進行了一個簡單的響應操作。首先是書寫了一個響應頭,響應頭參數包括狀態碼、原因描述以及頭部內容。狀態碼即http狀態碼,例如200、404、500之類的。原因描述與頭部內容可選,具體的就參見網路報頭的書寫了,這裡就不多說了(其實我也不會。。。)。writehead方法必須寫在end方法之前,這個是肯定的。。。
response的write方法就和JS的write所做的工作一樣,就是向頁面寫入數據,雖然原理不盡相同,但目前沒有準備去鑽研這部分源碼的我們可以忽略了。。。最後是end方法,它有兩個可選參數,分別是data與encoding。該方法用於所有的響應頭與響應正文輸出之後,進行響應的終結,並將管道流中的所有響應數據輸出。簡單地說就是在響應最後加上去的東西,它執行後會將響應執行。如果該方法帶有參數,那麼就相當於先調用了response.write(data, encoding)方法,之後再調用無參數的end方法。
好了,最簡單的一個http伺服器已經工作起來了。當用戶訪問127.0.0.1的12345埠時伺服器會監聽到這一埠的request請求並書寫報頭與最簡單的helloworld於頁面上,用戶得到響應之後會在瀏覽器中顯示響應的內容,也就是helloworld。這個最簡單的伺服器已經搭好了,但我們不能只滿足於這一點。
在繼續下一步的學習之前,我想給所有沒有使用過JS或者不怎麼使用的同學大體的講述一下一個也許你們會略微奇怪的參數傳遞方法——函數傳遞。
在JS中,函數與數字、字元串等都是以var定義的,在參數傳遞的過程中所接受的參數也是var這種弱類型的。而function類型也是作為弱類型傳遞,當我們將一個函數進行傳遞時,所得到的不是該函數的返回值,而是這個函數本身。也就是說,這個函數在運行時會變成傳遞到的函數的本地變數(自己都覺得好亂。。。)。
讓我們回憶一下剛才的例子,在creatServer方法中我們使用了一個匿名函數作為參數,現在我們把這個匿名函數提出來:
1 var http = require("http"); 2 var serverhandel = function(request, response) { 3 response.writeHead(200, {"Content-Type": "text/plain"}); 4 response.write("Hello World"); 5 response.end(); 6 } 7 function serverRequest (){ 8 http.createServer(serverhandel).listen(12345); 9 }10 exports. serverRequest = serverRequest;
exports即module.exports對象,在node中可以作為全局變數的賦予。也就是說它一般用來定義全局變數的,多用於模塊間的變數傳遞。在此我需要簡單說一下JS的模塊機制,JS中的模塊多用閉包進行包裹(我也不知道這麼說對不對),而在閉包中定義的局部變數則無法在全局展開使用,也就是說別的地方調用這個模塊時不能將其中的局部變數單獨的進行使用。而exports則可以在載入模塊後將該函數載入全局變數的作用鏈中。
說到這大家也應該明白了,我們要進行一次模塊引用。將這段代碼存入serverRequest.js中,然後建立一個index.js文件,然後引用serverRequest模塊:
1 var server = require(「./serverRequest」);2 server. serverRequest();
這樣我們就進行了一個最基本的小模塊的搭建,也初步的了解了一下node的模塊體系。那麼下一步我們就要進行下連個個非常重要的模塊的學習,也就是url模塊與path模塊。
url模塊的作用是從請求中獲取請求的url並進行處理,它有著幾個常用的方法:
1 url.parse(string).pathname;2 url.parse(string).query;
第一個方法的作用是獲取url請求部分的域名之後的路徑名稱,第二個方法獲取的則是通過get向伺服器傳遞的參數。
而path模塊的作用是解決文件路徑問題,我們這次先學習三個方法:
1 path.extname(p);2 path.join([path1], [path2], [...]);3 path.exists(p, [callback]);
第一個方法是獲取擴展名的方法,參數是url路徑。第二個方法是做路徑拼接使用,用來標準化最終路徑,參數是需要拼接的路徑。第三個方法是檢驗路徑存在與否,第一個參數是標準化的路徑,第二個是可選的回調函數,無論路徑存在與否都會被調用,函數有一個exist參數,標示路徑是否存在。
好了,現在我們就可以通過這兩個模塊進行一個簡單的路徑伺服器的搭建了。通過這個伺服器的搭建,我們可以對本地的靜態網站進行部署,對於頁面以及網頁所需要載入的各種資源進行定址,最後對請求的資源進行反饋。
1 //請求模塊 2 var http = require("http"); 3 var url=require("url"); 4 var fs = require("fs"); //在這裡先導入文件模塊,僅僅做一個簡單的操作,具體有關文件模塊的學習在之後的文件伺服器上會進行進一步的學習。 5 var path = require("path"); 6 //創建一個http伺服器 7 var server=http.createServer(start).listen(12345); 8 //依據路徑獲取返回內容類型字元串,用於http返回頭 9 var getContentType=function(filePath){10 var contentType="";11 //使用路徑解析模塊獲取文件擴展名12 var extension=path.extname(filePath);13 switch(extension){14 case ".html":15 contentType= "text/html";16 break;17 case ".js":18 contentType="text/javascript";19 break;20 case ".css":21 contentType="text/css";22 break;23 case ".gif":24 contentType="image/gif";25 break;26 case ".jpg":27 contentType="image/jpeg";28 break;29 case ".png":30 contentType="image/png";31 break;32 case ".ico":33 contentType="image/icon";34 break;35 default:36 contentType="application/octet-stream";37 }38 return contentType; //返回內容類型字元串39 }40 //Web伺服器主函數,解析請求,返回Web內容41 var funWebSvr = function (req, res){ 42 //獲取請求的url43 var url=req.url; 44 //使用url解析模塊獲取url中的路徑名45 var pathName = url.parse(reqUrl).pathname; 46 if (path.extname(pathName)=="") {47 //如果路徑沒有擴展名48 if (pathName.length<2) {//如果是默認域名49 pathName+="/";50 }51 else{52 pathName+=".html";53 }54 }55 else{56 if (path.extname(pathName)!=".html"){57 pathName=".."+ pathName;58 }59 }60 if (pathName.charAt(pathName.length-1)=="/"){61 //如果訪問目錄62 pathName+="login.html"; //指定為默認網頁63 }64 var filePath = pathName;65 //使用路徑解析模塊,組裝實際文件路徑66 if (pathName.charAt(pathName.length).search(/./) == -1) {67 filePath = libPath.join("./html",pathName);68 };69 //判斷文件是否存在 70 libPath.exists(filePath,function(exists){71 if(exists){//文件存在72 //在返回頭中寫入內容類型73 res.writeHead(200, {"Content-Type": funGetContentType(filePath) });74 //創建只讀流用於返回75 var stream = libFs.createReadStream(filePath, {flags : "r", encoding : null}); 76 //指定如果流讀取錯誤,返回404錯誤77 stream.on("error", function() { 78 res.writeHead(404); 79 res.end("<h1>404 Read Error</h1>"); 80 }); 81 //連接文件流和http返迴流的管道,用於返回實際Web內容82 stream.pipe(res);83 } 84 else {//文件不存在85 //返回404錯誤86 res.writeHead(404, {"Content-Type": "text/html"});87 res.end("<h1>404 Not Found</h1>");88 }89 });90 }
這是當時對著一篇大牛的博文敲的例子,後來發現只能載入單個網頁,而其他資源不能很好的載入,就進行了一次較大的改正,主要添加了對不同pathname的定址以及載入。本例的css、js以及image文件夾都與頁面所在的html文件夾在同一目錄下。
相信通過這個例子大家已經能簡單的讓一個靜態網站在我們的伺服器上支持起來了。我們下一次將會簡單的部署一個文件系統,希望大家能繼續關注。新手上道,文章代碼寫的都比較粗糙,希望大家指正。
資料庫de教程
在前一篇博文中,我們簡單的分心了node,用node建立了一個文件解析伺服器,並且在伺服器中進行了WEB定址的操作。通過這些操作,我們已經可以把一個簡單的靜態網站搭設在伺服器之上了(http://www.cnblogs.com/xiao-yao/archive/2012/03/30/2425716.html)。
當然,僅僅完成這樣的一個步驟還是遠遠不夠的,我們需要的不是通過node搭設一個靜態網站,而是通過node搭設一個完整的應用。那麼我們可以想想下一步操作應該做什麼了。沒錯,我們來嘗試一下資料庫的簡單操作。
在嘗試之前,我們需要來學習一個新的模塊:querystring模塊。
querystring模塊的主要用途是對字元串的處理,我們暫時先學習它的兩個方法:
querystring.stringify(obj, sep="&", eq="=");querystring.parse(str, sep="&", eq="=");
前一個方法是將對象向字元串的處理,後一個方法是將字元串的處理(是不是想到了JSON?)。前一個方法的參數是待處理對象、鍵值對之間的分隔符號以及鍵值之間的分割符號;後一個方法的參數與前一個相同,只不過處理的過程是相反的而已。
好了,我們了解了這個方法之後,便可以進行下一步的工作了。首先是對參數的捕獲,前端將數據傳遞給後台時,後台進行接收並處理,數據的傳遞似乎就這麼簡單。
往往讓人莫名痛苦的就是這些簡單的東西,比如數據如何傳遞、node如何處理、如何接收傳遞過來的參數。好的,我們一個一個來解決。
首先是數據從前端的傳遞方式,這個本不該是這裡的內容,不過說說也無所謂。前端傳遞參數有很多方法,比如表單傳遞、AJAX傳遞,但歸根結底就是兩種傳遞方法,post傳遞或者get傳遞。
兩者的區別就是get傳遞是通過url後面附加參數的傳遞方法,而post傳遞是通過表單的數據體附加提交。其他的區別與node無關,這裡就不贅述了。
首先是get方法,通過url傳遞的參數的獲取非常簡單,記得前面曾學習過一個url.parse(string).query方法嗎?這個方法獲取的就是get方法下所傳遞的參數。
然後就是對參數的處理了,還記得前面的querystring.parse方法吧,這裡我們就可以簡單的使用了:
var name=querystring.parse(url.parse(req.url).query)["name"];
這樣就獲取了前端get方法傳遞的name屬性的值了,方法很容易。
那麼post呢?它可不在url中啊。處理post參數,我們需要另一個模塊:formidable。
讓我們來看一下formidable的demo吧:
var formidable = require("formidable"), http = require("http"), util = require("util");http.createServer(function(req, res) { if (req.url == "/upload" && req.method.toLowerCase() == "post") { // parse a file upload var form = new formidable.IncomingForm(); form.parse(req, function(err, fields, files) { res.writeHead(200, {"content-type": "text/plain"}); res.write("received upload:
"); res.end(util.inspect({fields: fields, files: files})); }); return; } // show a file upload form res.writeHead(200, {"content-type": "text/html"}); res.end( "<form action="/upload" enctype="multipart/form-data" "+ "method="post">"+ "<input type="text" name="title"><br>"+ "<input type="file" name="upload" multiple="multiple"><br>"+ "<input type="submit" value="Upload">"+ "</form>" );}).listen(8888);
讓我們簡單的分析一下這個文件上傳:var form = new formidable.IncomingForm();一句通過簡單的引用formidable的IncomingForm方法來捕獲fields與files的信息,之後使用util模塊(以前的sys模塊)的inspect方法來返回post對象的結構信息。通過這樣的方式獲取到post的對象後,便可以對對象進行操作了。
好了,現在我們分析了兩種不同的參數傳遞以及接收的方式,現在該講講如何去對資料庫進行操作了。關於資料庫我採用的是mongo資料庫,這種語法類似JS的NO-SQL資料庫無非是前端攻城濕所欣賞的一種資料庫了。至於mongo的基本操作我建議參考園內大牛一線碼農的8天mongo系列,確實是入門的一部好系列。
在這裡我們先進行做簡單的find查找,其他的我們以後再去討論。
首先我們先安裝並引入mongo模塊,具體的行為我們這裡不再進行贅述了。對於collection的API是這樣寫的:
db.open(function(err, db) { if(!err) { db.collection("test", function(err, collection) {}); db.collection("test", {safe:true}, function(err, collection) {}); db.createCollection("test", function(err, collection) {}); db.createCollection("test", {safe:true}, function(err, collection) {}); }});
而find的api則是這樣寫的:
find(query[, options][, callback]);
我們採用最簡單的方法來進行一次嘗試:
var db = new mongo.Db("test", new mongo.Server("localhost", 27017, {}), {}); db.open(function() { // 打開名為user的表 db.collection("user", function(err, collection) { // select * from products 相當於db.products.find() collection.find({name:querystring.parse(url.parse(req.url).query)["name"],pwd:querystring.parse(url.parse(req.url).query)["pwd"]},function(err, cursor) { cursor.toArray(function(err, items) { if (items != null&&items.length != 0) { res.writeHead(200); var obj = {value:1} res.end(JSON.stringify(obj)); } else{ res.writeHead(200); var obj = {value:0} res.end(JSON.stringify(obj)); } }); }); }); });
OK,我們通過對get方法傳遞的參數進行提取,並將之與user表中的name與pwd欄位進行比較,若有該項則返回1,否則返回0。
我們已經寫好了find方法,但是我不想只有一種資料庫操作,所以我需要一個類似路由的方法去定址。方法如下:
var mongord = require("./mongord"), //mongo資料庫讀取模塊 mongoinsert = require("./mongoinsert"), querystring = require("querystring");function find_router(req, res){ if (querystring.parse(url.parse(req.url).query)["num"] == 1) { mongord.read_collection(req,res); } else if(querystring.parse(url.parse(req.url).query)["num"] == 2){ mongoinsert.insert_collection(req,res); } else{}}
exports.find_router = find_router;
這樣,我們就獲得了一個近乎路由表的東西,由每次傳遞參數的value項去判斷執行方法。
最後附上今天的全部代碼:
在上次的文件中加入:
if (libUrl.parse(req.url).query!=undefined) {router.find_router(req,res);}
然後路由表文件名為router:
var mongord = require("./mongord"), //mongo資料庫讀取模塊 querystring = require("querystring");function find_router(req, res){ if (querystring.parse(url.parse(req.url).query)["num"] == 1) { mongord.read_collection(req,res); } else{ }}
exports.find_router = find_router;
最後是資料庫模塊,取名為mongord:
var http = require("http"), mongo = require("mongodb"), events = require("events"), url=require("url"), querystring = require("querystring");function read_collection(req, res) { // 創建到test資料庫的鏈接。相當於use test var db = new mongo.Db("test", new mongo.Server("localhost", 27017, {}), {}); db.open(function() { // 打開名為user的表 db.collection("user", function(err, collection) { // select * from products 相當於db.products.find() collection.find({name:querystring.parse(url.parse(req.url).query)["name"],pwd:querystring.parse(url.parse(req.url).query)["pwd"]},function(err, cursor) { cursor.toArray(function(err, items) { console.log(items); if (items != null&&items.length != 0) { res.writeHead(200); var obj = {value:1} res.end(JSON.stringify(obj)); } else{ res.writeHead(200); var obj = {value:0} res.end(JSON.stringify(obj)); } }); }); }); });}exports.read_collection = read_collection;
好的,今天所講的全部內容就到這裡了,希望對於node的新手能有一個較好的幫助,謝謝大家。
推薦閱讀:
※宋朝帝王陵墓地址表
※圖片加透明的FLASH效果的代碼和地址AAA-夢婷思竹-搜狐博客
※心酸-幾個長期需要衣物捐助的地址(已加圖)
※香港書店地址大全
※【資料】音樂下載地址大全(2)