建立一個 server.js
文件,寫入:php
//最簡單的 http 服務例子 var http = require("http"); http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/html"}); response.write("<h1>Hi NodeJs</h1>"); response.end(); }).listen(8080); console.log("成功的提示:httpd start @8080");
打開 http://localhost:8080/
你會看到驚喜~html
tipsnode
執行:
node server.js
啓動服務。
按 Ctrl + c 結束 剛剛建立的服務。git
//自帶的http 模塊 var http = require("http"); function onRequest(request, response) { //console.log("請求來了,事件響應"); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("<h1>Hi NodeJs</h1>"); response.end(); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080");
Felix Geisendörfer 的 Understanding node.js (理解NodeJS)github
//console.log("請求來了,事件響應");
啓動 server.jsvar http = require("http"); function start() { function onRequest(request, response) { //console.log("請求來了,事件響應"); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hi NodeJS"); response.end(); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } //nodejs中exports對象,理解 **module.exports** 和 **exports** exports.start = start;
理解 module.exports 和 exports :
exports 獲取的全部的屬性和方法,都會傳遞給 Module.exports
可是 Module.exports 自己不具有任何屬性和方法。若是, Module.exports 已經具有某些屬性或方法,那麼 exports 傳遞的屬性或者方法會被忽略(失敗)。shell代碼舉例
// a.js exports.words = function() { console.log('Hi'); }; // b.js var say = require('./a.js'); say.words(); // 'Hi' //----分割線---- // aa.js module.exports = 'Wellcome'; exports.words = function() { console.log('Hi'); }; // bb.js var say = require('./aa.js'); say.words(); // TypeError: Object Wellcome has no method 'words'
建立 index.js 寫入:npm
var server = require("./server"); server.start();
啓動: node index.js
看看吧!編程
做爲 ThinkPHP 的玩家,確定能想到 TP 的路由: 經過實例化對象來實現路由選擇;
如今來看看 node 是怎麼來實現的:瀏覽器
1 、提取出請求的URL以及GET/POST參數,這裏須要額外的NodeJS模塊:URL 、 querystring服務器
仔細看下圖:
url.parse(string).query | url.parse(string).pathname | | | | | ----- ------------------ http://localhost:8080/start?foo=bar&hello=world --- ----- | | | | querystring(string)["foo"] | | querystring(string)["hello"]
2 、幫助 onRequest()函數 找出瀏覽器請求的URL路徑
先新建一個 router.js 寫入,可以輸出當前請求的路徑名稱
function route(pathname) { console.log("請求路徑是:" + pathname); } exports.route = route;
再擴展 server.js
var http = require("http"), //URL模塊能夠讀取URL、分析諸如hostname、port之類的信息 url = require("url"); //傳入 route (回調函數) function start(route) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; //console.log("請求 "+pathname+" ,事件"); route(pathname); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hi NodeJS"); response.end(); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
最後擴展 index.js
var server = require("./server"); var router = require("./router"); server.start(router.route);
啓動,輸入 http://localhost:8080/a
結果
$ node index.js 成功的提示:httpd start @8080 請求路徑是:/a 請求路徑是:/favicon.ico
擴展:
Martin Fowlers 關於依賴注入的大做
注重:數學本質、抽象本質。
重要的概念:循環能夠沒有(描述如何解決問題),遞歸(描述這個問題的定義)是不可或缺;
面向對象編程是 傳遞對象;而在函數式編程中,傳遞的是函數(更專業的叫:叫作高階函數)
高階函數:a、接受一個或多個函數輸入;b、輸出一個函數
其餘, 行爲驅動執行 (BDD) 和 測試驅動開發(TDD)
函數編程擴展閱讀:
示例,建立一個 requestHandlers.js
模塊
function start() { console.log("處理請求 'start' 開啓."); } function upload() { console.log("處理請求 'upload' 開啓."); } exports.start = start; exports.upload = upload;
將一系列請求處理程序經過一個對象來傳遞,而且須要使用鬆耦合的方式將這個對象注入到 route() 函數中
一、先將這個對象引入到主文件 index.js 中
var server = require("./server"), router = require("./router"), requestHandlers = require("./requestHandlers"); var handle = {}; handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; server.start(router.route, handle);
二、把額外的傳遞參數 handle 給服務器 server.js
var http = require("http"), url = require("url"); function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("請求 "+pathname+" 響應"); route(handle, pathname); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hi NodeJS"); response.end(); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
三、修改 router.js
function route(handle, pathname) { console.log("route 請求路徑:" + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](); } else { console.log("找不到路徑 " + pathname); } } exports.route = route;
四、運行結果: http://localhost:8080/start
$ node index.js 成功的提示:httpd start @8080 請求 /start 響應 route 請求路徑:/start 處理請求 'start' 開啓. 請求 /favicon.ico 響應 route 請求路徑:/favicon.ico 找不到路徑 /favicon.ico
瀏覽器須要對請求做出響應。
一、將 requestHandler.js 修改成
function start() { console.log("處理請求 'start' 開啓."); return "Hello Start"; } function upload() { console.log("處理請求 'upload' 開啓."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
二、將 router.js 修改成
function route(handle, pathname) { console.log("route 請求路徑:" + pathname); if (typeof handle[pathname] === 'function') { return handle[pathname](); } else { console.log("找不到路徑 " + pathname); return '404 Not Found' } } exports.route = route;
三、須要對 server.js 進行重構,以使得它可以將請求處理程序經過請求路由返回的內容 響應 給瀏覽器
var http = require("http"), url = require("url"); function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("請求 "+pathname+" 響應"); response.writeHead(200, {"Content-Type": "text/plain"}); var content = route(handle, pathname); response.write(content); response.end(); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
四、運行結果:當輸入 /start 是瀏覽器顯示 hello start ……
缺點:
當將來有請求處理程序須要進行 非阻塞 的操做的時候,咱們的應用就「掛」了。
在請求處理程序中加入阻塞操做的用例 requestHandlers.js:
function start() { console.log("處理請求 'start' 開啓."); function sleep (milliSeconds) { var startTime = new Date().getTime(); while (new Date().getTime() < startTime + milliSeconds); } sleep(10000); return "Hello Start"; } function upload() { console.log("處理請求 'upload' 開啓."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
這樣,當 start() 被調用的時候,Node.js會先等待10秒,以後纔會返回 「Hello Start」 。當調用 upload() 的時候,會和此前同樣當即返回。
可是,當你打開兩個瀏覽器窗口或者標籤頁,
第一個輸入 http://localhost:8080/start 可是先不「回車」;
第二個輸入 http://localhost:8080/upload 一樣先不「回車」
接下來在第一個窗口中 /start 按下回車,快速的切到第二個窗口再按下回車……
會看到: /start 花了10s加載url, upload 竟然也是!
緣由就是 start() 包含了阻塞操做。形象的說就是「它阻塞了全部其餘的處理工做」。
tips
一、Node.js是單線程的。它經過事件輪詢(event loop)來實現並行操做
二、避免阻塞操做,多使用非阻塞操做————回調( callbackFunction() )
修改 requestHandlers.js 中的 start 請求處理程序
//child_process 能夠建立多進程,利用多核計算資源。 var exec = require("child_process").exec; function start() { console.log("處理請求 'start' 開啓."); var content = "empty"; exec("ls -lah", function (error, stdout, stderr) { content = stdout; }); return content; } function upload() { console.log("處理請求 'upload' 開啓."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
上述代碼建立了一個新的變量content(初始值爲「empty」),執行 「ls -lah」 命令,將結果賦值給 content ,最後將content返回。
接下來 重啓服務 訪問 http://localhost:8080/start 結果是 empty ,
這時,exec() 在非阻塞這塊發揮了做用。它能夠執行很是耗時的 shell 操做而無需網頁應用等待該操做。
可是代碼是同步執行的,這就意味着在調用 exec() 以後,Node.js會當即執行 return content;
在這個時候,content仍然是 「empty」 ,由於傳遞給 exec() 的回調函數還未執行到————由於 exec() 的操做是異步的……
tips:
child_process 模塊提供四個建立子進程的函數: spawn,exec,execFile和fork
其中 spawn 是最原始的建立子進程的函數,其餘三個都是對 spawn 不一樣程度的封裝。 spawn 只能運行指定的程序,參數須要在列表中給出,至關於 execvp 系統函數,而 exec 能夠直接運行復雜的命令
例子:運行ls -lh /usr
//使用 spawn spawn('ls', ['-lh', '/usr']) //使用 exec exec('ls -lh /usr')
exec 是啓動了一個系統 shell 命令來解析參數,能夠執行復雜的命令,包括管道和重定向。
exec 還能夠直接接受一個回調函數做爲參數,回調函數有三個參數,分別是 err, stdout, stderr
實現方案: 函數編程(函數傳遞)
目前的結果:經過應用各層之間傳遞值的方式(請求處理程序 -> 請求路由 -> 服務器)將請求處理程序返回的內容(請求處理程序最終要顯示給用戶的內容)傳遞給HTTP服務器。
新實現方式:將服務器「傳遞」給內容的方式。從實踐角度來講,就是將response對象(從服務器的回調函數 onRequest() 獲取)經過請求路由傳遞給請求處理程序。 隨後,處理程序就能夠採用該對象上的函數來對請求做出響應。
一、 server.js
var http = require("http"), url = require("url"); function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("請求 "+pathname+" 響應"); route(handle, pathname, response); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
再也不從 route() 函數獲取返回值,而將 response 對象做爲第三個參數傳遞給 route() 函數,而且,移除 onRequest() 中有關 response 的函數調用,這部分讓 route() 函數完成。
二、 router.js
function route(handle, pathname, response) { console.log("route 請求路徑:" + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](response); } else { console.log("找不到路徑 " + pathname); response.writeHead(404, {"Content-Type": "text/plain"}); response.write("404 Not found"); response.end(); } } exports.route = route;
相對此前從請求處理程序中獲取返回值,此次取而代之的是直接傳遞 response 對象。
三、 requestHandler.js
var exec = require("child_process").exec; function start(response) { console.log("處理請求 'start' 開啓."); exec("ls -lah", function (error, stdout, stderr) { response.writeHead(200, {"Content-Type": "text/plain"}); response.write(stdout); response.end(); }); } function upload(response) { console.log("處理請求 'upload' 開啓."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
start 處理程序在 exec() 的匿名回調函數中作請求響應的操做
upload 處理程序此次是使用 response 對象。
四、運行結果: 運行很好
如何 證實 /start 處理程序中耗時的操做不會阻塞對 /upload 請求做出當即響應?
修改 requestHandlers.js
var exec = require("child_process").exec; function start(response) { console.log("處理請求 'start' 開啓."); exec("find /", { timeout: 10000, maxBuffer: 2000*1024}, function (error, stdout, stderr) { response.writeHead(200, {"Content-Type": "text/plain"}); response.write(stdout); response.end(); } ); } function upload(response) { console.log("處理請求 'upload' 開啓."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
結果: /upload 時會當即響應,無論 /start 是否還在處理中
簡單的例子: 用 post 請求提交給服務器 文本區 中的內容; requestHandlers.js
function start(response) { console.log("處理請求 'start' 開啓."); var body = '<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html; '+ 'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value=" 提 交 " />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response) { console.log("處理請求 'upload' 開啓."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
由於 POST 請求會比較大(用戶可能會填大量內容),爲了使整個過程非阻塞, NodeJS 會將數據拆分紅不少小數據塊,而後經過觸發特定的事件,將這些小數據塊傳遞給回調函數。這裏的特定的事件有 data 事件(表示新的小數據塊到達了)以及 end事件(表示全部的數據都已經接收完畢)。
經過在 request 對象上註冊監聽器(listener)來實現這些事件的觸發,以及回調;request對象是每次接收到HTTP請求時候,都會把該對象傳遞給 onRequest 回調函數。
request.addListener("data", function(chunk) { // called when a new chunk of data was received }); request.addListener("end", function() { // called when all chunks of data have been received });
這部分的邏輯應該寫在哪裏?
由於獲取全部來自請求的數據,而後將這些數據給應用層處理,應該是HTTP服務器要作的事情,因此能夠在服務器中處理 POST 數據,而後將最終的數據傳遞給請求路由和請求處理器,讓他們來進行進一步的處理。
一、 server.js
var http = require("http"), url = require("url"); function start(route, handle) { function onRequest(request, response) { var postData = '', pathname = url.parse(request.url).pathname; console.log("請求 "+pathname+" 響應"); request.setEncoding('UTF-8'); request.addListener('data', function(postDataChunk){ postData += postDataChunk; console.log("收到數據塊 ‘" + postDataChunk + "’.") }) request.addListener('end', function(){ route(handle, pathname, response, postData); }); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
以上代碼作了三件事:
二、接下來須要在 /upload 頁面展現用戶輸入的內容,將 postData 傳遞給請求處理程序 router.js
function route(handle, pathname, response, postData) { console.log("route 請求路徑:" + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](response, postData); } else { console.log("找不到路徑 " + pathname); response.writeHead(404, {"Content-Type": "text/plain"}); response.write("404 Not found"); response.end(); } } exports.route = route;
三、 requestHandlers.js
function start(response, postData) { console.log("處理請求 'start' 開啓."); var body = '<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html; '+ 'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value=" 提 交 " />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("處理請求 'upload' 開啓."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("你寫的:" + decodeURIComponent(postData)); response.end(); } exports.start = start; exports.upload = upload;
四、運行結果:
在 /start 中輸入「 你好 」 ,在 /upload 輸出 你寫的:text=你好
缺點: 其實通常狀況下須要的只是 text 字段;那麼,方法是調用 querystring 模塊
修改 requestHandlers.js
//原生自帶,包括4個方法,看 tips var querystring = require('querystring'); function start(response, postData) { console.log("處理請求 'start' 開啓."); var body = '<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html; '+ 'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value=" 提 交 " />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("處理請求 'upload' 開啓."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("你寫的:<br>" + querystring.parse(decodeURIComponent(postData).text)); response.end(); } exports.start = start; exports.upload = upload;
tips
querystring 類包含4個方法
一、querystring.stringify(obj, [sep], [eq]) 將對象轉換成字符串
二、querystring.parse(str, [sep], [eq], [options]) 將字符串轉換成對象
三、querystring.escape 參數編碼
四、querystring.unescape 參數解碼
效果:容許用戶上傳圖片,並將該圖片在瀏覽器中顯示出來。
須要用到外部模塊: Felix Geisendörfer 開發的 node-formidable 模塊
用 NPM 包管理器安裝
npm install formidable
完成後
一、 requestHandlers.js 添加圖片展現模塊,修改上傳模塊
var querystring = require('querystring'), //可將文件讀取到服務器中 fs = require('fs'); //解析上傳文件數據 formidable = require("formidable"); function start(response) { console.log("處理請求 'start' 開啓."); var body = '<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html; '+ 'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" enctype="multipart/form-data" '+ 'method="post">'+ '<input type="file" name="upload">'+ '<input type="submit" value=" 上 傳 " />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, request) { console.log("處理請求 'upload' 開啓."); var form = new formidable.IncomingForm(); console.log("加載解析"); form.parse(request, function(error, fields, files) { console.log("解析完成"); var is = fs.createReadStream(files.upload.path); var os = fs.createWriteStream("./tmp/test.png"); is.pipe(os); is.on('end',function(){ fs.unlinkSync(files.upload.path); }); //fs.renameSync(files.upload.path, "./tmp/test.png"); response.writeHead(200, {"Content-Type": "text/html"}); response.write("收到圖片:<br/>"); response.write("<img src='/show' />"); response.end(); }); } function show(response) { console.log("處理請求 'show' 開啓."); fs.readFile("./tmp/test.png", "binary", function(error, file) { if(error) { response.writeHead(500, {"Content-Type": "text/plain"}); response.write(error + "\n"); response.end(); } else { response.writeHead(200, {"Content-Type": "image/png"}); response.write(file, "binary"); response.end(); } }); } exports.start = start; exports.upload = upload; exports.show = show;
二、 index.js 添加新的請求處理到 路由映射表
var server = require("./server"), router = require("./router"), requestHandlers = require("./requestHandlers"); var handle = {}; handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; handle["/show"] = requestHandlers.show; server.start(router.route, handle);
三、 server.js 移除對postData的處理以及request.setEncoding (這部分node-formidable自身會處理),轉而採用將request對象傳遞給請求路由的方式:
var http = require("http"), url = require("url"); function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("請求 "+pathname+" 響應"); route(handle, pathname, response, request); } http.createServer(onRequest).listen(8080); console.log("成功的提示:httpd start @8080"); } exports.start = start;
四、 router.js 不須要傳遞postData了,而要傳遞request對象
function route(handle, pathname, response, request) { console.log("route 請求路徑:" + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](response, request); } else { console.log("找不到路徑 " + pathname); response.writeHead(404, {"Content-Type": "text/plain"}); response.write("404 Not found"); response.end(); } } exports.route = route;
小坑:
//fs.renameSync(files.upload.path, "./tmp/test.png") //報錯以下 fs.js:543 return binding.rename(pathModule._makeLong(oldPath), ^ Error: EXDEV, cross-device link not permitted 'C:\User…… ……
修改爲以下代碼便可:
var is = fs.createReadStream(files.upload.path); var os = fs.createWriteStream("./tmp/test.png"); is.pipe(os); is.on('end',function(){ fs.unlinkSync(files.upload.path); }); //fs.renameSync(files.upload.path, "./tmp/test.png");
五、效果演示:(樓主以 .gif爲例)
注:本文筆記來自於 Manuel Kiessling寫的 Node入門 一本全面的Node.js教程
其餘: Node.js community wiki——NodeJS社區
本次擴展: NodeJS+Mongodb+Express作CMS博客系統(符合MVC)