如何用Node去写一个Web应用框架

第一步,用node输出一个hello world

var http=require('http');
http.createServer(function(req,res){
    var urlPares=url.parse(req.url);
    var query=querystring.parse(urlPares.query);
    res.end('hello world');
}).listen(80);

大部分的node教程在这里会告诉你,我们很容易的建立的一个服务器。但是在实际使我们通常使用的是express.(f**k,难道Node必须要用express吗?自己实现一个Web应用框架真的很难吗?)其实并不是。

那么既然打算自己写我们首先要知道我们要做哪些事情。 1.路由或者智能路由 2.静态文件输出 3.session/cookie 4.模版渲染 5.数据库处理 6.文件上传

第二步,路由

路由好高大上的名字,它是干啥的?url对应具体方法就是它该做的事情。 那么我们为什么不让url对应xxx文件的xx方法。 例如:/user/login能不能自动对应到user.js的login方法上。实现起来很难么?其实只需要几句代码

var fs = require("fs");
module.exports=function(req,res){
    var query=req.query;
    var urlPares=req.urlPares;
    var pathname=urlPares.pathname;
    var arr=pathname.split("/");
    req.arr=arr;
    //start 这段代码处理默认行为。可以先忽略
    if(arr.length==0||arr.length==1){
        arr=["","index","index"];
    }else if(arr.length==2){
        arr.push("index");
    }
    if(arr[1]==""){
        arr[1]="index";
    }
    if(arr[2]==""){
        arr[2]="index";
    }
    //end 这段代码处理默认行为。可以先忽略
    if (fs.existsSync(APP_PATH+'/controller/'+arr[1]+'.js')){
        var controller=require('./controller/'+arr[1]);
        if(controller[arr[2]]){
            controller[arr[2]](req,res);
        }else{
            res.writeHead(404,{'Content-Type': 'text/plain' });
            res.end("你访问的控制器不存在指定方法");
        }
    }else{
        res.writeHead(404,{'Content-Type': 'text/plain' });
        res.end("你访问的路径不存在");
    }
}

通过fs判断文件是否存在。然后去require它就行了。APP_PATH是个全局变量表示程序入口的路径。

第三步,静态文件输出

静态文件输出我们需要一个库MIME

var url = require("url");
var fs = require("fs");
var mime = require('mime');
/**
 * [[检测是否为静态资源]]
 * @param   {Object}   req [[Description]]
 * @param   {[[Type]]} res [[Description]]
 * @returns {bool} [[Description]]
 */
module.exports = function (req, res) {
    //正则表达式检测文件后缀
    var url_resource_reg = /.*\.(html|htm|gif|jpg|jpeg|bmp|webp|htc|swf|png|ico|txt|js|css)/;
    if (!url_resource_reg.test(req.url)) {
        return false;
    }
    var urlPares = url.parse(req.url);
    var pathname = urlPares.pathname;
    var fileUrl = APP_PATH + "/static" + pathname;

    if (fs.existsSync(fileUrl)) {
        var contentType = mime.lookup(fileUrl);
        res.setHeader('Content-Type', contentType || "text/plain");

        var fileStream = fs.createReadStream(fileUrl);
        fileStream.pipe(res);
        fileStream.on('end', function () {
            res.end();
        });
        return true;
    } else {
        return false;
    }
}

第四步,session/cookie

这里稍微有点。但是代码量也不多

var sessions = {};
var sessionKey = 'session_key';
var EXPIRES = 30 * 60 * 1000;
function randString(size) {
    var result = '';
    var allChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    size = size || 1;
    while (size--) {
        result += allChar.charAt(rand(0, allChar.length - 1));
    }
    return result;
}
var generate = function () {
    var session = {};
    session.id = Date.now() + randString(12);
    session.cookies = {
        expire: Date.now() + EXPIRES
    }
    sessions[session.id] = session;
    return session;
}
var parseCookie= function (cookie) {
    var cookies = {};
    if (!cookie) {
        return cookies;
    }
    var list = cookie.split(";");
    for (var i = 0; i < list.length; i++) {
        var pair = list[i].split("=");
        cookies[pair[0].trim()] = pair[1];
    }
    return cookies;
}
var serializeCookies = function (cookies) {
    var arr = [];
    for (var key in cookies) {
        arr.push(serialize(key, cookies[key]));
    }
    return arr;
}
var serialize = function (name, value, option) {
    var pairs = [name + '=' + encodeURI(value)];
    //设置cookie默认共用"/"路径
    option = option || {
        path: "/"
    };
    if (option.maxAge) pairs.push('Max-Age=' + option.maxAge);
    if (option.domain) pairs.push('Domain=' + option.domain);
    if (option.path) pairs.push('Path=' + option.path);
    if (option.expires) pairs.push('Expires=' + option.expires);
    if (option.httpOnly) pairs.push('HttpOnly');
    if (option.secure) pairs.push('Secure');
    return pairs.join('; ');
}
module.exports = function (req, res) {
    req.cookies = parseCookie(req.headers.cookie);
    var id = req.cookies[sessionKey];
    if (!id) {
        req.session = generate();
    } else {
        var session = sessions[id];
        if (session) {
            if (session.cookies.expire > Date.now()) {
                session.cookies.expire = Date.now() + EXPIRES;
                req.session = session;
            } else {
                delete sessions[id];
                req.session = generate();
            }
        } else {
            req.session = generate();
        }
    }
    for (var key in sessions) {
        if (sessions[key].cookies.expire < Date.now()) {
            delete sessions[key];
        }
    }
    var writeHead = res.writeHead;
    res.writeHead = function () {
        delete req.cookies[ham_sessionKey];
        var sessionStr = serialize(ham_sessionKey, req.session.id);
        res.setHeader('Set-Cookie', serializeCookies(req.cookies).concat(sessionStr));
        return writeHead.apply(res, arguments);
    }
}

第五步,模版渲染

这是最简单的。因为我用https://github.com/aui/artTemplate ,自己用自己喜欢的模块组件就行了

第六步,数据库处理

这里可以是用一些ORM框架。例如https://github.com/dresende/node-sql-query

第七步,文件上传,post

这里只需要一个组件https://github.com/felixge/node-formidable

第八步,就是你把上面的代码组织起来。

可以参考我的实现 https://coding.net/u/as3long/p/today/git/tree/master/node_modules/ham 代码比较乱,见谅。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang测试技术

本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,...

3706
来自专栏哈雷彗星撞地球

Xcode 调试技巧 --常用命令和断点

Xcode 中的调试技巧与我们的日常开发息息相关,而这些调试技巧在我们解决Bug时,常常有事半功倍的作用,经常会用到的有各种断点 和 命令。而这些调试技巧也经常...

962
来自专栏Golang语言社区

Go 的浏览器集成测试

最近从 Ruby 转到 Go. 新项目 QOR 需要浏览器集成测试,一番搜索后发现了 agouti, 试用一下发现基本算是 Go 版本的 Capybara,正好...

4596
来自专栏iOS技术

YYWebImage 源码剖析:线程调度与缓存策略

在 iOS 开发中,异步网络图片下载框架可以说是很大的解放了生产力,通常情况下开发者只需要简单的代码就能将网络图片异步下载并显示到手机屏幕上,并且还带有缓存优化...

1394
来自专栏恰同学骚年

ASP.Net MVC开发基础学习笔记:一、走向MVC模式

  在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/blog/index.aspx的URL,那么我们的WebFo...

1003
来自专栏简单聊聊Spark

Spark内核分析之Shuffle操作流程(非常重要)

        如题,我们来分析一下spark的shuffle操作原理;为什么说其非常重要,是因为shuffle操作是我们在Spark调优中非常重要的一环,对s...

1073
来自专栏Ken的杂谈

ASP.NET(C#) 发送邮件帮助类Mailhelper

1121
来自专栏Golang语言社区

Golang测试技术

本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,...

3787
来自专栏用户2442861的专栏

Settings.xml文件详解

如果在Eclipse中使用过Maven插件,想必会有这个经验:配置settings.xml文件的路径。

741
来自专栏刘望舒

Android开发的几个插件和工具,提升你的开发效率

对于一个Android开发,面对以下问题会非常痛苦。 重复的findById。 将复杂的Json字符串,解析成javaBean。 调试接口的时候打断点十分麻烦。...

3279

扫码关注云+社区