相信大家對於nodejs應該不會陌生,如果真的比較陌生的請訪問:http://nodejs.org或者http://cnodejs.org/了解。
這個簡易MVC的結構如下圖:
首先需要一個http服務來監聽來自客戶端的請求,大致代碼如下:
var m_http = require('http'); var m_querystring = require('querystring'); var m_requestHandler = require('./requestHandler'); exports.run = function (port) { port || (port = 80); m_http.createServer(function (req, res) { req.setEncoding('utf8'); var postData = []; req.on('data', function (chunk) { postData.push(chunk); }).on('end', function () { req.post = m_querystring.parse(postData.join('')); m_requestHandler.handle(req, res); }); }).listen(port); console.log('服務器啟動!'); };
以上req.on('data', ...)內,使用的是一個postData的數組來保存請求的數據,但是當數據量大的時候,會出現一些問題,這個我們暫時不管,博友么可以自己去完善,^_^
接收了請求之后,需要通過請求的url和method去查找與之相匹配的Contoller,並調用Controller相應的Action,大致代碼如下:
var m_route = require('./route'); var m_controllerBase = require('./controllerBase'); var m_invalidHandler = require('./invalidHandler'); var m_getRequestArgs = { ... }; exports.handle = function (req, res) { var method = req.method ? req.method.toLowerCase() : 'get'; var route = m_route.find(req.url, method); var controller = require(m_util.format('./controllers/%s', route.controller)); if (controller[route.action]) { try { controller[route.action].call( new m_controllerBase(req, res), m_getRequestArgs.hasOwnProperty(method) ? m_getRequestArgs[method](req) : {}); } catch (e) { m_invalidHandler.handle500(req, res); } } else { m_invalidHandler.handle404(req, res); } };
在這里引用了另外3個模塊,route.js會根據請求的url和method去獲取對應的controller和action信息,代碼如下:
//緩存映射信息 var m_cache = {}; exports.addMap = function (map) { if (!(map && map.rule && map.controller)) return; var method = (map.method || 'get').toLowerCase(); m_cache[method] || (m_cache[method] = []); m_cache[method].push({ rule: map.rule, controller: map.controller, action: map.action || 'index' }); }; exports.find = function (url, method) { var route = { controller: null, action: null }; var routes; if (!(m_cache.hasOwnProperty(method) && (routes = m_cache[method]).length)) return route; for (var i = 0, r; r = routes[i]; i++) { if (r.rule.test(url)) { route.controller = r.controller; route.action = r.action; break; } } return route; };
而controllerBase相當於是所有Controller的基類,會提供諸如緩存,輸出html、json、script等方法,代碼如下:
function controllerBase(req, res) { this.req = req; this.res = res; }; //添加緩存 controllerBase.prototype.addCache = function (key, value) { m_cache[key] = value; }; //添加緩存 controllerBase.prototype.getCache = function (key) { return m_cache[key]; }; //返回html controllerBase.prototype.html = function (viewName) { //coding }; //根據模板生成的,可以使用其他模板來實現 controllerBase.prototype.template = function (viewName, obj) { //coding }; //返回json controllerBase.prototype.json = function (obj) { //coding }; module.exports = controllerBase;
但是在處理這些操作的事情,可能會出現錯誤,因此就需要一個invalidHandler來處理一些原因導致請求無法返回的情況,代碼大致如下:
//簡單實現了404和500 對於其他http狀態碼大家可以根據情況實現 exports.handle404 = function (req, res) { res.writeHead(404, {}); res.end(); }; exports.handle500 = function (req, res) { res.writeHead(500, {}); res.end(); };
當我做到這里的時候,我突然發現並不是所有的請求都會觸發Controller和Action,有的僅僅是請求一些如圖片、js、css等方面的靜態文件,所以我們在處理請求的時候,應該增加一個處理靜態文件的,代碼如下:
exports.handle = function (req, res) { var url = m_parseURL(req.url); //當請求的文件是網站圖標時,不映射到靜態文件夾下 var filePath = url.pathname == m_config.FAVICON ? m_path.join(__dirname, url.pathname) : m_path.join(__dirname, m_config.STATIC_FILE_DIR, url.pathname); m_fs.exists(filePath, function (exists) { if (!exists) { m_invalidHandler.handle404(req, res); return; } m_fs.readFile(filePath, FILE_ENCODING, function (err, file) { if (err) { m_invalidHandler.handle500(req, res, err); return; } var ext; ext = (ext = m_path.extname(filePath)) ? ext.slice(1) : 'html'; res.writeHead(200, { 'Content-Type': m_config.CONTENT_TYPE[ext] || m_config.CONTENT_TYPE.html }); res.write(file, FILE_ENCODING); res.end(); }); }); };
寫到這里已經將簡易的MVC完成了,最后再增加幾個功能來跑起來看看吧,代碼如下:
//base.js exports.index = function () { this.view('index.html'); }; exports.login = function (data) { this.addCache('user', data.name); this.json({ success: true }); }; //user.js exports.load = function () { var msg = this.getCache('user') || '未登錄'; this.template('main.html', { msg: msg }); }; //index.html <div>用戶名:<input id="txtName" type="text" value="admin" /></div> <input id="btn" type="button" value="登錄" /> //main.html 歡迎你來到建議MVC,{#msg}!
以上增加了controllers和對應的action以及html頁面,運行起來發現沒效果,檢查了代碼原來是還沒有配置對應route規則,於是我們再添加一些規則,代碼如下:
m_route.addMap({ rule: /^\/$/, controller: 'base' }); m_route.addMap({ method: 'post', rule: /^\/login/, controller: 'base', action: 'login' }); m_route.addMap({ rule: /^\/user\/load$/, controller: 'user', action: 'load' });
整個mvc的效果如下:
使用用戶名登錄
直接訪問/user/load
簡易mvc就完成啦,如有什么錯誤和建議請給我留言,^_^,源代碼在此