node.js是什么似乎已經(jīng)不需要我多為贅述了,非阻塞的服務(wù)器語言、JS書寫的后臺(tái)代碼,無數(shù)的文章已經(jīng)很好的展示了node的魅力與展望。關(guān)于node.js的安裝,大家不妨參考博客園聶微東的http://www.cnblogs.com/Darren_code/archive/2011/10/31/nodejs.html (node.js初體驗(yàn)),這篇文章很好的綜述了node.js的一個(gè)基礎(chǔ)(從安裝到體驗(yàn)到模塊的一個(gè)入門,個(gè)人感覺是一篇很好的文章),相信通過東哥的這篇文章大家可以對(duì)node有一個(gè)初步的了解。
node是一門很有意思的框架,它能夠讓一個(gè)長(zhǎng)期執(zhí)迷于前端開發(fā)的攻城濕(忘記了還有一種語言叫后端語言。。。)能夠覺得很舒適(編碼習(xí)慣都一樣),但是也同樣會(huì)讓一個(gè)新手覺得無所適從(為什么么有從入門到精通哩?參考書在哪里???)。這是一個(gè)前端高手為之一亮(也許只亮了一眼o(╯□╰)o),新手眼前一暈(if you want to find more information,please read the source(╯□╰)o)的框架。為了讓和我一樣的新手能夠多多少少摸到一點(diǎn)門路,我以身試法,來為新手找一條路~
本文旨在新手入門,所學(xué)尚淺,代碼水準(zhǔn)有限,這也僅僅只是一個(gè)基本入門的筆記,高手可以笑一笑然后點(diǎn)關(guān)閉了。。。
首先我假設(shè)你已經(jīng)安裝好了node(http://nodejs.org/#download,0.6.14已經(jīng)很成熟了),那么首先我們來進(jìn)行一個(gè)入門的編碼分析。
在進(jìn)行分析之前,我們先來想一下以前的服務(wù)器端語言框架的工作原理。首先,用戶通過瀏覽器來訪問我們的應(yīng)用。然后服務(wù)器通過對(duì)端口的監(jiān)聽,來接收網(wǎng)絡(luò)端的request請(qǐng)求,并進(jìn)行相應(yīng)的處理。最后再將處理的結(jié)果返回客戶端。
好了,那么我們可以開始了。首先是服務(wù)器,要是沒有辦法啟動(dòng)服務(wù)器,那么一切都是閑的。我們也許用過很多服務(wù)器端的語言(PHP、JAVA、ASP等等),接收HTTP請(qǐng)求并提供Web頁面的過程似乎不用我們來做,apache和IIS似乎都會(huì)幫我們來完成。但是在node里,這一步必須你自己做。我們來實(shí)現(xiàn)的不僅僅是一個(gè)應(yīng)用,還需要實(shí)現(xiàn)HTTP處理的服務(wù)器。
好像很復(fù)雜的樣子,但對(duì)于node這并不是什么復(fù)雜的東西。我們即將進(jìn)行一個(gè)HTTP服務(wù)器的初步學(xué)習(xí),但是在學(xué)習(xí)之前我們需要溫習(xí)(預(yù)習(xí)?)一下node的模塊機(jī)制。
node采取的是模塊機(jī)制(和JS差不多),通過對(duì)模塊的導(dǎo)入我們可以聲明變量并將導(dǎo)入的模塊的實(shí)例化對(duì)象賦給變量并加以使用。具體各個(gè)模塊的使用方法以及用途請(qǐng)參考API,這里就不說了。。。
好了,我們回來看看HTTP服務(wù)器的構(gòu)成。首先,我們先對(duì)HTTP模塊進(jìn)行一次請(qǐng)求:
var http = require("http");
在node中require方法用于對(duì)各個(gè)模塊的引入,在這里我們需要對(duì)http模塊加以使用時(shí)就可以導(dǎo)入。導(dǎo)入后將之賦予http變量,然后對(duì)http變量進(jìn)行操作。讓我們以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時(shí)內(nèi)心都會(huì)有一絲變態(tài)的快意。。。
讓我們來回憶一下剛才的問題:實(shí)現(xiàn)一個(gè)http的服務(wù)器。其實(shí)這個(gè)很簡(jiǎn)單,http模塊自帶的createServer方法就可以完成。該方法只有一個(gè)參數(shù),類型為函數(shù),在接口文檔中的定義是“始終接收request事件”。而server.listen方法(server就是剛才通過createServer創(chuàng)建的server)的參數(shù)是(port, [hostname], [callback]),第一個(gè)是監(jiān)聽的端口號(hào),第二個(gè)和第三個(gè)是可選項(xiàng),第二個(gè)是主機(jī)名稱,第三個(gè)回調(diào)函數(shù)。這個(gè)函數(shù)只在綁定端口后進(jìn)行調(diào)用。
接下來是回調(diào)函數(shù),回調(diào)函數(shù)的參數(shù)有兩個(gè),第一個(gè)是客戶端發(fā)送的request,第二個(gè)是服務(wù)器端的response。這段代碼進(jìn)行了一個(gè)簡(jiǎn)單的響應(yīng)操作。首先是書寫了一個(gè)響應(yīng)頭,響應(yīng)頭參數(shù)包括狀態(tài)碼、原因描述以及頭部?jī)?nèi)容。狀態(tài)碼即http狀態(tài)碼,例如200、404、500之類的。原因描述與頭部?jī)?nèi)容可選,具體的就參見網(wǎng)絡(luò)報(bào)頭的書寫了,這里就不多說了(其實(shí)我也不會(huì)。。。)。writehead方法必須寫在end方法之前,這個(gè)是肯定的。。。
response的write方法就和JS的write所做的工作一樣,就是向頁面寫入數(shù)據(jù),雖然原理不盡相同,但目前沒有準(zhǔn)備去鉆研這部分源碼的我們可以忽略了。。。最后是end方法,它有兩個(gè)可選參數(shù),分別是data與encoding。該方法用于所有的響應(yīng)頭與響應(yīng)正文輸出之后,進(jìn)行響應(yīng)的終結(jié),并將管道流中的所有響應(yīng)數(shù)據(jù)輸出。簡(jiǎn)單地說就是在響應(yīng)最后加上去的東西,它執(zhí)行后會(huì)將響應(yīng)執(zhí)行。如果該方法帶有參數(shù),那么就相當(dāng)于先調(diào)用了response.write(data, encoding)方法,之后再調(diào)用無參數(shù)的end方法。
好了,最簡(jiǎn)單的一個(gè)http服務(wù)器已經(jīng)工作起來了。當(dāng)用戶訪問127.0.0.1的12345端口時(shí)服務(wù)器會(huì)監(jiān)聽到這一端口的request請(qǐng)求并書寫報(bào)頭與最簡(jiǎn)單的helloworld于頁面上,用戶得到響應(yīng)之后會(huì)在瀏覽器中顯示響應(yīng)的內(nèi)容,也就是helloworld。這個(gè)最簡(jiǎn)單的服務(wù)器已經(jīng)搭好了,但我們不能只滿足于這一點(diǎn)。
在繼續(xù)下一步的學(xué)習(xí)之前,我想給所有沒有使用過JS或者不怎么使用的同學(xué)大體的講述一下一個(gè)也許你們會(huì)略微奇怪的參數(shù)傳遞方法——函數(shù)傳遞。
在JS中,函數(shù)與數(shù)字、字符串等都是以var定義的,在參數(shù)傳遞的過程中所接受的參數(shù)也是var這種弱類型的。而function類型也是作為弱類型傳遞,當(dāng)我們將一個(gè)函數(shù)進(jìn)行傳遞時(shí),所得到的不是該函數(shù)的返回值,而是這個(gè)函數(shù)本身。也就是說,這個(gè)函數(shù)在運(yùn)行時(shí)會(huì)變成傳遞到的函數(shù)的本地變量(自己都覺得好亂。。。)。
讓我們回憶一下剛才的例子,在creatServer方法中我們使用了一個(gè)匿名函數(shù)作為參數(shù),現(xiàn)在我們把這個(gè)匿名函數(shù)提出來:
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對(duì)象,在node中可以作為全局變量的賦予。也就是說它一般用來定義全局變量的,多用于模塊間的變量傳遞。在此我需要簡(jiǎn)單說一下JS的模塊機(jī)制,JS中的模塊多用閉包進(jìn)行包裹(我也不知道這么說對(duì)不對(duì)),而在閉包中定義的局部變量則無法在全局展開使用,也就是說別的地方調(diào)用這個(gè)模塊時(shí)不能將其中的局部變量單獨(dú)的進(jìn)行使用。而exports則可以在載入模塊后將該函數(shù)載入全局變量的作用鏈中。
說到這大家也應(yīng)該明白了,我們要進(jìn)行一次模塊引用。將這段代碼存入serverRequest.js中,然后建立一個(gè)index.js文件,然后引用serverRequest模塊:
1 var server = require(“./serverRequest”);
2 server. serverRequest();
這樣我們就進(jìn)行了一個(gè)最基本的小模塊的搭建,也初步的了解了一下node的模塊體系。那么下一步我們就要進(jìn)行下連個(gè)個(gè)非常重要的模塊的學(xué)習(xí),也就是url模塊與path模塊。
url模塊的作用是從請(qǐng)求中獲取請(qǐng)求的url并進(jìn)行處理,它有著幾個(gè)常用的方法:
1 url.parse(string).pathname;
2 url.parse(string).query;
第一個(gè)方法的作用是獲取url請(qǐng)求部分的域名之后的路徑名稱,第二個(gè)方法獲取的則是通過get向服務(wù)器傳遞的參數(shù)。
而path模塊的作用是解決文件路徑問題,我們這次先學(xué)習(xí)三個(gè)方法:
1 path.extname(p);
2 path.join([path1], [path2], [...]);
3 path.exists(p, [callback]);
第一個(gè)方法是獲取擴(kuò)展名的方法,參數(shù)是url路徑。第二個(gè)方法是做路徑拼接使用,用來標(biāo)準(zhǔn)化最終路徑,參數(shù)是需要拼接的路徑。第三個(gè)方法是檢驗(yàn)路徑存在與否,第一個(gè)參數(shù)是標(biāo)準(zhǔn)化的路徑,第二個(gè)是可選的回調(diào)函數(shù),無論路徑存在與否都會(huì)被調(diào)用,函數(shù)有一個(gè)exist參數(shù),標(biāo)示路徑是否存在。
好了,現(xiàn)在我們就可以通過這兩個(gè)模塊進(jìn)行一個(gè)簡(jiǎn)單的路徑服務(wù)器的搭建了。通過這個(gè)服務(wù)器的搭建,我們可以對(duì)本地的靜態(tài)網(wǎng)站進(jìn)行部署,對(duì)于頁面以及網(wǎng)頁所需要載入的各種資源進(jìn)行尋址,最后對(duì)請(qǐng)求的資源進(jìn)行反饋。
1 //請(qǐng)求模塊
2 var http = require('http');
3 var url=require('url');
4 var fs = require("fs"); //在這里先導(dǎo)入文件模塊,僅僅做一個(gè)簡(jiǎn)單的操作,具體有關(guān)文件模塊的學(xué)習(xí)在之后的文件服務(wù)器上會(huì)進(jìn)行進(jìn)一步的學(xué)習(xí)。
5 var path = require("path");
6 //創(chuàng)建一個(gè)http服務(wù)器
7 var server=http.createServer(start).listen(12345);
8 //依據(jù)路徑獲取返回內(nèi)容類型字符串,用于http返回頭
9 var getContentType=function(filePath){
10 var contentType="";
11 //使用路徑解析模塊獲取文件擴(kuò)展名
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; //返回內(nèi)容類型字符串
39 }
40 //Web服務(wù)器主函數(shù),解析請(qǐng)求,返回Web內(nèi)容
41 var funWebSvr = function (req, res){
42 //獲取請(qǐng)求的url
43 var url=req.url;
44 //使用url解析模塊獲取url中的路徑名
45 var pathName = url.parse(reqUrl).pathname;
46 if (path.extname(pathName)=="") {
47 //如果路徑?jīng)]有擴(kuò)展名
48 if (pathName.length<2) {//如果是默認(rèn)域名
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"; //指定為默認(rèn)網(wǎng)頁
63 }
64 var filePath = pathName;
65 //使用路徑解析模塊,組裝實(shí)際文件路徑
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 //在返回頭中寫入內(nèi)容類型
73 res.writeHead(200, {"Content-Type": funGetContentType(filePath) });
74 //創(chuàng)建只讀流用于返回
75 var stream = libFs.createReadStream(filePath, {flags : "r", encoding : null});
76 //指定如果流讀取錯(cuò)誤,返回404錯(cuò)誤
77 stream.on("error", function() {
78 res.writeHead(404);
79 res.end("<h1>404 Read Error</h1>");
80 });
81 //連接文件流和http返回流的管道,用于返回實(shí)際Web內(nèi)容
82 stream.pipe(res);
83 }
84 else {//文件不存在
85 //返回404錯(cuò)誤
86 res.writeHead(404, {"Content-Type": "text/html"});
87 res.end("<h1>404 Not Found</h1>");
88 }
89 });
90 }
這是當(dāng)時(shí)對(duì)著一篇大牛的博文敲的例子,后來發(fā)現(xiàn)只能載入單個(gè)網(wǎng)頁,而其他資源不能很好的載入,就進(jìn)行了一次較大的改正,主要添加了對(duì)不同pathname的尋址以及載入。本例的css、js以及image文件夾都與頁面所在的html文件夾在同一目錄下。
相信通過這個(gè)例子大家已經(jīng)能簡(jiǎn)單的讓一個(gè)靜態(tài)網(wǎng)站在我們的服務(wù)器上支持起來了。我們下一次將會(huì)簡(jiǎn)單的部署一個(gè)文件系統(tǒng),希望大家能繼續(xù)關(guān)注。新手上道,文章代碼寫的都比較粗糙,希望大家指正。
數(shù)據(jù)庫de教程
在前一篇博文中,我們簡(jiǎn)單的分心了node,用node建立了一個(gè)文件解析服務(wù)器,并且在服務(wù)器中進(jìn)行了WEB尋址的操作。通過這些操作,我們已經(jīng)可以把一個(gè)簡(jiǎn)單的靜態(tài)網(wǎng)站搭設(shè)在服務(wù)器之上了(http://www.cnblogs.com/xiao-yao/archive/2012/03/30/2425716.html)。
當(dāng)然,僅僅完成這樣的一個(gè)步驟還是遠(yuǎn)遠(yuǎn)不夠的,我們需要的不是通過node搭設(shè)一個(gè)靜態(tài)網(wǎng)站,而是通過node搭設(shè)一個(gè)完整的應(yīng)用。那么我們可以想想下一步操作應(yīng)該做什么了。沒錯(cuò),我們來嘗試一下數(shù)據(jù)庫的簡(jiǎn)單操作。
在嘗試之前,我們需要來學(xué)習(xí)一個(gè)新的模塊:querystring模塊。
querystring模塊的主要用途是對(duì)字符串的處理,我們暫時(shí)先學(xué)習(xí)它的兩個(gè)方法:
querystring.stringify(obj, sep='&', eq='=');querystring.parse(str, sep='&', eq='=');
前一個(gè)方法是將對(duì)象向字符串的處理,后一個(gè)方法是將字符串的處理(是不是想到了JSON?)。前一個(gè)方法的參數(shù)是待處理對(duì)象、鍵值對(duì)之間的分隔符號(hào)以及鍵值之間的分割符號(hào);后一個(gè)方法的參數(shù)與前一個(gè)相同,只不過處理的過程是相反的而已。
好了,我們了解了這個(gè)方法之后,便可以進(jìn)行下一步的工作了。首先是對(duì)參數(shù)的捕獲,前端將數(shù)據(jù)傳遞給后臺(tái)時(shí),后臺(tái)進(jìn)行接收并處理,數(shù)據(jù)的傳遞似乎就這么簡(jiǎn)單。
往往讓人莫名痛苦的就是這些簡(jiǎn)單的東西,比如數(shù)據(jù)如何傳遞、node如何處理、如何接收傳遞過來的參數(shù)。好的,我們一個(gè)一個(gè)來解決。
首先是數(shù)據(jù)從前端的傳遞方式,這個(gè)本不該是這里的內(nèi)容,不過說說也無所謂。前端傳遞參數(shù)有很多方法,比如表單傳遞、AJAX傳遞,但歸根結(jié)底就是兩種傳遞方法,post傳遞或者get傳遞。
兩者的區(qū)別就是get傳遞是通過url后面附加參數(shù)的傳遞方法,而post傳遞是通過表單的數(shù)據(jù)體附加提交。其他的區(qū)別與node無關(guān),這里就不贅述了。
首先是get方法,通過url傳遞的參數(shù)的獲取非常簡(jiǎn)單,記得前面曾學(xué)習(xí)過一個(gè)url.parse(string).query方法嗎?這個(gè)方法獲取的就是get方法下所傳遞的參數(shù)。
然后就是對(duì)參數(shù)的處理了,還記得前面的querystring.parse方法吧,這里我們就可以簡(jiǎn)單的使用了:
var name=querystring.parse(url.parse(req.url).query)['name'];
這樣就獲取了前端get方法傳遞的name屬性的值了,方法很容易。
那么post呢?它可不在url中啊。處理post參數(shù),我們需要另一個(gè)模塊: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:\n\n'); 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);
讓我們簡(jiǎn)單的分析一下這個(gè)文件上傳:var form = new formidable.IncomingForm();一句通過簡(jiǎn)單的引用formidable的IncomingForm方法來捕獲fields與files的信息,之后使用util模塊(以前的sys模塊)的inspect方法來返回post對(duì)象的結(jié)構(gòu)信息。通過這樣的方式獲取到post的對(duì)象后,便可以對(duì)對(duì)象進(jìn)行操作了。
好了,現(xiàn)在我們分析了兩種不同的參數(shù)傳遞以及接收的方式,現(xiàn)在該講講如何去對(duì)數(shù)據(jù)庫進(jìn)行操作了。關(guān)于數(shù)據(jù)庫我采用的是mongo數(shù)據(jù)庫,這種語法類似JS的NO-SQL數(shù)據(jù)庫無非是前端攻城濕所欣賞的一種數(shù)據(jù)庫了。至于mongo的基本操作我建議參考園內(nèi)大牛一線碼農(nóng)的8天mongo系列,確實(shí)是入門的一部好系列。
在這里我們先進(jìn)行做簡(jiǎn)單的find查找,其他的我們以后再去討論。
首先我們先安裝并引入mongo模塊,具體的行為我們這里不再進(jìn)行贅述了。對(duì)于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]);
我們采用最簡(jiǎn)單的方法來進(jìn)行一次嘗試:
var db = new mongo.Db("test", new mongo.Server('localhost', 27017, {}), {}); db.open(function() { // 打開名為user的表 db.collection("user", function(err, collection) { // select * from products 相當(dāng)于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,我們通過對(duì)get方法傳遞的參數(shù)進(jìn)行提取,并將之與user表中的name與pwd字段進(jìn)行比較,若有該項(xiàng)則返回1,否則返回0。
我們已經(jīng)寫好了find方法,但是我不想只有一種數(shù)據(jù)庫操作,所以我需要一個(gè)類似路由的方法去尋址。方法如下:
var mongord = require("./mongord"), //mongo數(shù)據(jù)庫讀取模塊 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;
這樣,我們就獲得了一個(gè)近乎路由表的東西,由每次傳遞參數(shù)的value項(xiàng)去判斷執(zhí)行方法。
最后附上今天的全部代碼:
在上次的文件中加入:
if (libUrl.parse(req.url).query!=undefined) {router.find_router(req,res);}
然后路由表文件名為router:
var mongord = require("./mongord"), //mongo數(shù)據(jù)庫讀取模塊 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;
最后是數(shù)據(jù)庫模塊,取名為mongord:
var http = require("http"), mongo = require("mongodb"), events = require("events"), url=require('url'), querystring = require("querystring");function read_collection(req, res) { // 創(chuàng)建到test數(shù)據(jù)庫的鏈接。相當(dāng)于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 相當(dāng)于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;
好的,今天所講的全部?jī)?nèi)容就到這里了,希望對(duì)于node的新手能有一個(gè)較好的幫助,謝謝大家。
聯(lián)系客服