我也是前段时间才接触到nodejs,感觉写起来挺爽的,大致学了下,就自己顺手写个简单(准确应该说是简陋)的MVC框架,当是练习了。
废话不多说,直接开上。
0.整体目录结构
大致说下,run.js是服务器监听入口,通过命令“node run”就可以开启服务了,server.js是HTTP服务器的创建代码,application.js则是MVC框架程序的入口,其他的就不一一介绍了。
1.搭建一个HTTP服务器
这部分就相当简单了,直接来官网的代码基本也可以搞定,我自己的代码如下:
exports.class = function(port){
this._port = port;
}
exports.class.prototype = {
runApplication: function(appClass) {
http.createServer(function(req, res) {
console.log('[' + req.method + ']', req.url);
var _postData = '';
//提取POST数据
req.on('data', function(data) {
_postData += data;
console.log('[Received]' + data.length);
});
req.on('end', function() {
//保存POST数据
req.post = querystring.parse(_postData);
//交由dispatcher
new appClass(req, res);
});
}).listen(this._port);
console.log('Server is running at port ' + this._port);
}
}
这里不仅创建了一个HTTP服务器监听,并针对POST参数进行了处理。(注意我这里使用了exports,即构建了一个nodejs模块[关于模块,请参考相关nodejs教程],并将该类作为该模块的一个属性class,要引用该类的话,就使用像这样的代码:var Server = require('./server').class,后面的类定义也将遵循此格式。)在此,我们也得改变以往的过程式代码书写,转变思维为“事件驱动”型,熟悉前端JS/Jquery的童鞋都应该比较了解:
req.on('data', function(data) {
//TODO
});
这种书写格式吧(即这些代码并不是立即就顺序执行的),当然,nodejs的高性能也在于此,“事件驱动”使得各个方面的IO都是无阻塞的,从而能够最大化利用资源。上面的代码通过on('data',function(){})来监听'data'事件,该事件即是浏览器向服务器POST数据时触发的事件,我们就只需要_postData += data;这样将数据一部分一部分保存到内存即可,一旦POST数据接收完毕,就会触发'end'事件,这时我们通过nodejs的内置库querystring来解析POST参数。(注意:这里仅仅是简单的解析了键值对(表单是application/x-www-form-urlencoded)这种形式的POST参数,如果有文件上传(表单是multipart/form-data)的话,那么要处理文件数据,格式就完全不一样了,具体可以参考:http://cnodejs.org/blog/?p=2207)。处理完毕后,我们将new appClass(req, res)创建一个application(即我们这里的MVC框架应用),用它来handle这些请求。
2.一个Application类(相当于MVC应用的入口)
这里Application的构造函数如下:
exports.class = function(req, res) {
this._req = req, this._res = res;
this.dispatcher();
}
初始化完成后就直接调用了dispatcher函数,dispatcher函数主要的工作就是判断该请求是请求一个“动态资源”还是一个“静态资源(图片、css什么的)”(怎么判断的?这里我模仿apache的rewrite功能,使用正则来匹配,配置暂时写死在了core/route.js中),如果是请求“静态资源”,则我们直接读取该文件,然后输出。后面我们会着重讲“动态资源”(也就是请求动态网页了)。
3.完成C的部分
如果请求的是“动态资源”,那么就相当于是在调用我们的Controller部分了,这里我实现了一个Controller基类,所有的controller都将继承自这个基类,里面主要是一个vendor()方法,用于渲染网页。
至于如何定位controller及它下面的action,看看下面代码:
var classPath = path.join(CONTROLLER_PATH, actionInfo.controller);
var classRef = require(classPath).class; //类的一个引用
var c = new classRef(this._req, this._res);
if (typeof(c[actionInfo.action]) != 'function') {
actionInfo.action = config.defaultAction; //使用默认action
if(typeof(c[actionInfo.action]) != 'function') {
throw new Error('No callable action');
}
}
actionInfo是通过route.getActionInfo(this._req)解析而来,它包含了最终我们理解出来的controller和action以及GET参数,然后通过这些信息来定位具体的controller文件,初始化并执行它的action。
4.完成V的部分
这部分我主要实现了一个简陋的Template用于解析并渲染html模板,上面C的vendor函数就是调用的这个Template,而这个Template就相当简陋了,主要代码如下:
var content = fs.readFileSync(file, 'utf8');
//替换变量
if(content) {
result = content.replace(/\{\$(\w+)?\}/g, function() {
return assign[arguments[1]]; //取第一个匹配项 即为 变量名
});
}
说白了就是一个正则替换,在html模板里面,我们就可以这样“{$var}”调用变量了:
<hr />This is:{$var1} <br />
是不是很简单:-)
5.完成M的部分
由于时间关系,这部分还没完成,主要也就是数据库连接与操作相关的了。。
结语
nodejs现在发展挺好的,相信你试用了之后,也会感觉到她的强大与舒适,js到时前后通吃了,呵呵。
上面项目的所有代码可以在http://code.google.com/p/ihttp-framework/ 获取,本人才疏学浅,有不对的地方还望提出、指点指点。