如何用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 代码比较乱,见谅。