這是我學習nodejs以來做的第一個小例子,很簡單,就是在第一個頁面里輸入自己的名字,在第二個頁面(有圖片)中顯示。思路和很多地方都參考了http://www.cnblogs.com/giggle/p/6287931.html 這篇文章,感謝作者。 這篇文章中說得不對的地方希望大家指正。 ...
安裝配置的環節這裡就不說了。
這是我學習nodejs以來做的第一個小例子,很簡單,就是在第一個頁面里輸入自己的名字,在第二個頁面(有圖片)中顯示。思路和很多地方都參考了http://www.cnblogs.com/giggle/p/6287931.html 這篇文章,感謝作者。
這篇文章中說得不對的地方希望大家指正。
客戶端的步驟是進入127.0.0.1:8000/login進入login.html,填寫輸入框後進入mian.html。
伺服器端的步驟是:
1.通過瀏覽器傳來的url獲取路由,判斷進入login.html,
2.用戶提交表單,在表單action方法中進入main.js,此時使用post方法,攜帶參數,並根據html中的{name}占位,利用node動態替換。
3.在遇到圖片src時,處理圖片請求。
先貼上兩個html文件:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="author" content="Hx2"> <meta name="format-detection" content="telephone=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="Description" content="網頁描述"/> <meta name="Keywords" content="關鍵字"/> <title></title> <style> form { text-align:center; } </style> </head> <body> <form action="./deliver" method="post"> 賬戶:<input type="text" name="name"/><br/> 密碼:<input type="password" name="password"/><br/> <input type="submit" value="提交"/> </form> </body> </html>login.html
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="author" content="Hx2"> <meta name="format-detection" content="telephone=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="Description" content="網頁描述"/> <meta name="Keywords" content="關鍵字"/> <title></title> <style> body { text-align:center; } span { color: brown; } img{ width: 100%; } </style> </head> <body> <div> hello!<span>{name}</span> </div> <img src="./showImg" /> </body> </html>main.js
首先是主文件,創建一個服務:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var http = require('http'); 2 http.createServer(function(request,response) { 3 response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); 4 if(request.url!=="/favicon.ico"){ 5 6 response.end(''); 7 } 8 }).listen(8000); 9 console.log('Server running at http://127.0.0.1:8000/');View Code
這段中引入node的自帶對象http。
response.writeHead是協議頭。
然後用if清除對favicon.ico的訪問,否則刷新一次就會有兩次訪問,就避免了未知的錯誤。
response.end結束請求。
listen是監聽某個埠。
當一個請求來到伺服器的時候,他會獲取到url的路徑來判斷接下來的操作。所以需要添加url模塊,利用這個模塊來獲得url中的相關路徑並處理。就是這句:var url = require('url');
連接到router.js文件後,此時主文件中獲取路由,通過正則處理掉多餘的 “ / ” 以正確匹配router.js裡面的方法。同時,用try catch來處理這裡可能發生的異常。
完整的main.js文件如下:
var http = require('http'); var url = require('url'); var router = require('./router.js'); http.createServer(function(request,response) { if(request.url!=="/favicon.ico"){ pathname = url.parse(request.url).pathname; pathname = pathname.replace(/\//,''); try{//如果這一部分有錯誤,執行下麵catch router[pathname](request,response) }catch(err){ console.info('router錯誤' + err); response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); //協議頭 response.write(err.toString()); response.end(''); } } }).listen(8000); console.log('Server running at http://127.0.0.1:8000/');
根據需求,我們需要處理的是進入login頁面的login、傳遞名字的deliver、顯示圖片的showImg。所以在router.js文件中有了個基本的骨架:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
module.exports = { login : function(req,res){ }, deliver : function(req,res){ }, showImg : function(req,res){ } }View Code
在router.js裡面有三個操作,他們有個共同點,就是都會讀取伺服器的本地文件將它寫在客戶端。這樣,把他的操作都提取出來放在一個文件中,有利於管理和調用,這裡建個optfile.js存放操作。暫定optfile.js裡面有三個方法,分別是同步讀取:readfileSync、非同步讀取:readfile、讀取圖片:readImg。具體操作等會說。
在router.js里的login中,如果是同步的話可以這樣寫,他會一步一步向下執行:
login : function(req,res){ res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); optfile.readfileSync('./view/login.html',res); res.write(data); res.end(''); },
但是node.js的優勢是非同步,我們需要做的是非同步的。
非同步讀取文件的方法(在下麵會說)需要一個閉包來存儲此時的request和response,來保證readFile在非同步執行的時候,這裡的request和response不會在垃圾回收機制下被自動清除。並且在閉包中加入end()就不存在當非同步還沒有執行完,主程式先執行完了報錯這種情況。
在後面的readFile方法里只需要調用這個閉包就可以了。
這個閉包就是下麵的recall(),我們在閉包中對讀取的文件進行相關的操作。
login : function(req,res){ response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); function recall(data){ res.write(data); res.end(''); }; optfile.readfile('./view/login.html',recall) }
在操作比較多的項目中,每一個相同操作(這裡是顯示到客戶端)都要寫一個recall函數非常麻煩,所以封裝成getRecall方法:
function getRecall(req,res){ res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); function recall(data){ res.write(data); res.end(''); } return recall; }
這樣,login就變得很簡潔:
login : function(req,res){ recall = getRecall(req,res);//這個recall並不是getRecall,而是getRecall返回的閉包recall optfile.readfile('./view/login.html',recall) },
在router.js里的deliver中,需要使用post或get方法傳參,這裡使用post方法:
var post = ''; req.on('data',function(chunk){ post += chunk; }) req.on('end',function(){ post = querystring.parse(post); console.log('收到參數:' + post['name值'] + '\n'); }
這時的思路是把收到的參數顯示在main.html中,在main.js中接收參數(如果是多個就使用數組),而deliver方法就是帶著參數跳轉到main.js中。所以想到接下來的步驟應該是:
recall = getRecall(req,res);
optfile.readfile('./view/main.html',recall);
但是上面的post方法相對於上面的兩句是非同步的,非同步傳參,所以為了確保這兩句在post方法後執行,可以把他放在end事件中。
在recall函數里,他要做的就不僅僅是顯示在頁面那麼簡單,而是要尋找到帶{}標記的元素,把他替換為傳來的參數,重寫recall:
function recall(data){ dataStr = data.toString();//字元串 re = new RegExp('{name}','g');//new RegExp和/.../的區別是他可以在裡面直接用字元串拼接的形式表示 dataStr = dataStr.replace(re,post['name值']); res.write(dataStr); res.end(); } optfile.readfile('./view/main.html',recall);
如果需要傳輸多個參數,使用數組
arr = ['email','pwd']; function recall(data){ dataStr = data.toString(); for(var i = 0; i < arr.length; i++){ re = new RegExp('{' + arr[i] + '}','g'); dataStr = dataStr.replace(re,post[arr[i]]); } res.write(dataStr); res.end(); }
第三個showImg,協議頭改成請求圖片的協議頭,然後讀取圖片即可:
showImg : function(req,res){ res.writeHead(200,{'Content-Type':'image/jpeg'}); optfile.readImg('./src/xin2.jpg',res); }
完整的router.js:
var optfile = require('./models/optfile.js');
var url = require('url');
var querystring = require('querystring');//post需導入
function getRecall(req,res){
res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});
function recall(data){
res.write(data);
res.end('');
}
return recall;
}
module.exports = {
login : function(req,res){
recall = getRecall(req,res);//這個recall並不是getRecall,而是getRecall返回的閉包recall
optfile.readfile('./view/login.html',recall)
},
deliver : function(req,res){
var post = '';
req.on('data',function(chunk){
post += chunk;
})
req.on('end',function(){
post = querystring.parse(post);
console.log('收到參數:' + post['name'] + '\n');
console.log('收到參數:' + post['password'] + '\n');
function recall(data){
dataStr = data.toString();
re = new RegExp('{name}','g');
dataStr = dataStr.replace(re,post['name']);
res.write(dataStr);
res.end();
}
optfile.readfile('./view/main.html',recall);
})
},
showImg : function(req,res){
res.writeHead(200,{'Content-Type':'image/jpeg'});
optfile.readImg('./src/xin2.jpg',res);
}
}
來說optfile.js了!optfile.js暫時有三個操作,同、非同步讀文件和讀取圖片。
var fs = require('fs'); module.exports = { readfileSync : function(path){ var data = fs.readFileSync(path,'utf-8'); }, readfile : function(path,res){ fs.readFile(path,function(err,data){ if(err){ console.log('讀文件異常!'); }else{ console.log('讀好了'); } }) }, readImg : function(path,res){ ... } }
大概這麼寫,但是上面有說,如果想在readFile中添加一個操作,比如說需求中將data顯示在客戶端,此時可能的思路是把console.log('讀好了');替換為res.write(data) 之類。但是不對。因為readFile方法是非同步的,在非同步里,主線程執行過程中會有很多分線程來執行各種不同的操作,但分線程並不影響主線程的執行。在讀取文件的這個分線程中,如果直接執行res.write(data)會報錯,因為主線程此時可能已經執行完了。
所以這裡要調用到之前寫的閉包。雖然recall()在這個線程中執行,但原來的response並沒有被清除掉,還在等待著recall();當recall返回一個data時,原來的response接收到了,進行操作。這個閉包儲存了原來的req和res用來操作,並解決了分線程end的問題。
readfile : function(path,recall){ fs.readFile(path,function(err,data){ if(err){ console.log('讀取文件分線程錯誤:'+err); recall('文件不存在'); }else{ console.log('非同步讀取文件data為:' + data.toString()); recall(data); } }) console.info('非同步讀取文件方法執行了'); },
如果readFile這裡非同步線程出錯,主線程可以執行,非同步可以讀取出來,但如果這裡有錯誤並且沒有回調recall('文件不存在');的話,記載圖標會一直轉,所以這裡處理異常要使用recall('文件不存在'),其中包括錯誤提示和結束線程。
readImg和文件讀取差不多,就是圖片需要用二進位流'binary'的方式讀取。
完整的optfile.js:
var fs = require('fs'); module.exports = { readfileSync : function(path){ var data = fs.readFileSync(path,'utf-8'); console.info('同步讀取文件執行完畢,data為:' + data); }, readfile : function(path,recall){ fs.readFile(path,function(err,data){ if(err){ console.log('讀取文件分線程錯誤:'+err); recall('文件不存在'); }else{ recall(data); } }) console.info('非同步讀取文件方法執行了'); }, readImg : function(path,res){ fs.readFile(path,'binary',function(err,imgdata){ if(err){ console.log('讀圖片分線程錯誤:'+ err); return; }else{ res.write(imgdata,'binary'); res.end(''); } }) } }