zoukankan      html  css  js  c++  java
  • nodejs学习之实现简易路由

      此前实现了个数据转发功能,但是要建本地服务器,还需要一个简易的路由功能。因为只是用于本地服务器用于自己测试用,所以不需要太完善的路由功能,所以也就不去使用express框架,而是自己实现一个简易路由,可以针对自己的需求来定制路由功能。

      在制作路由功能之前,我先写了一张路由表,表明了自己大概想要实现的四种路由转换效果,这四种效果也正是自己项目需要的: 

    {
        "/my/**/*":"func:testFun",
    
        "index":"url:index.html",
    
        "test?v=*":"url:my*.html",
    
        "/public/bi*/**/*":"url:public/**/*"
    }

      第一种:只要我的地址是/my/**/*的格式,**/*意思就是my目录下任意目录目录的任意文件都会触发testFun这个方法。比如/my/test/index.html或者/my/1/2/3/index.html都会触发testFun,因为会触发这个方法,所以路由不会进行页面输出。

      第二种:就是常规的,当我访问/index时,将index.html页面输出。

      第三种:如果我输入为test?v=index,输出的页面则为myindex.html,两边的*即数值相同。

      第四种:用于静态资源的获取,当我访问/public/bi*/**/*时,就会将public下的任意文件输出。比如我的请求路径为/public/biz009/stylesheets/css/main.css,那么路由转换出的文件路径即为:public/stylesheets/css/main.css

      抱着实现这四种效果的目的,就开始了自己的实现。

      第一段代码,mimes里的内容比较长,所以就用...代替了。

      首先把正则写好,正则主要用于替换**和*,替换成相应的正则字符串。

      然后实现Router的构造函数,对传入的参数进行简易处理,传入的参数可以直接为上面的对象,也可以为json文件的路径,构造函数中会用eval转换成对象,之所以不用JSON.parse是因为其对json格式的要求比较严,不方便书写。

      后面再继承一下事件类,方便外部调用事件绑定。

    "use strict";
    var fs = require("fs");
    var url = require("url");
    var events = require("events");
    var util = require("util");
    var path = require("path");
    
    var mimes = '...'.split(",");
    var ALL_FOLDER_REG = //**//g;
    var ALL_FOLDER_REG_STR = '/([\w._-]*/)*';  //匹配XXX/XXX/XX/
    var ALL_FILES_REG = /*+/g;
    var ALL_FILES_REG_STR = '[\w._-]+';  //匹配XX
    var noop = function () {};
    
    var Router = function (arg) {
        this.methods = {};
    
        if ((typeof arg == "object") && !(arg instanceof Array)) {
            this.maps = arg;
        } else if (typeof arg == "string") {
            try {
                var json = fs.readFileSync(arg).toString();
                this.maps = eval('(' + json + ')');
            } catch (e) {
                console.log(e);
                this.maps = {};
            }
        } else {
            this.maps = {};
        }
    
        this.handleMaps();
    };
    
    //继承事件类
    util.inherits(Router, events.EventEmitter);
    
    var rp = Router.prototype;
    
    rp.constructor = Router;

      上面代码中再构造函数里还执行了一个handleMaps方法,该方法是用于将路由表中的路由地址和目标地址进行处理后,再放到数组里保存起来。__A__代表**,__B__代表*,这两个也对应了上面写的正则字符串:ALL_FOLDER_REG_STR  和  ALL_FILES_REG_STR

    rp.handleMaps = function () {
        this.filters = [];  //存放路由地址
        this.address = [];  //存放目标地址
    
        for (var k in this.maps) {
            var fil = trim(k);
            var ad = trim(this.maps[k]);
    
            fil = fil.charAt(0) == "/" ? fil : ("/" + fil);
    
            ad = ad.replace(ALL_FOLDER_REG, '__A__').replace(ALL_FILES_REG, '__B__');
            fil = fil.replace(/?/g , "\?").replace(ALL_FOLDER_REG, '__A__').replace(ALL_FILES_REG, '__B__');
    
            this.filters.push(fil);
            this.address.push(ad);
        }
    };

      然后还要实现一个保存function的方法,因为要根据路由表执行方法,所以有了set方法:

    rp.set = function (name, func) {
        if (!name)return;
    
        this.methods[name] = (func instanceof Function) ? func : noop;
    };

      前面的都实现好后,就要实现具体的路由方法,这段代码相对比较简单,当发生请求时,跟据请求地址,遍历上面保存的路由地址,并将路由地址中的__A__和__B__转成相应正则字符串,再通过RegExp实现正则实例,对请求地址进行匹配。如果匹配成功,当前索引 i 即为目标地址中的索引。

      然后对字符串进行分割,判断如果是url则进行相应的url处理,如果是function则执行保存的方法,并且传入req,res。

    rp.route = function (req, res) {
        var urlobj = url.parse(req.url);
        var pathname = urlobj.pathname;
    
        var i = 0;
        var match = false;
        var fil;
    
        for (; i < this.filters.length; i++) {
            fil = this.filters[i];
            var reg = new RegExp("^" + fil.replace(/__A__/g, ALL_FOLDER_REG_STR).replace(/__B__/g, ALL_FILES_REG_STR) + "$");
    
            if (reg.test(fil.indexOf("?") >= 0 ? (pathname = urlobj.path) : pathname)) {
                match = true;
                break;
            }
        }
    
        if (match) {
            var ad = this.address[i];
            var array = ad.split(':' , 2);
    
            if(array[0] === "url"){
                //如果是url则查找相应url的文件
                var filepath = getpath(fil , array[1] , pathname);
    
                this.emit("match", filepath , pathname);
    
                this.routeTo(res , filepath);
            }else if(array[0] === "func" && (array[1] in this.methods)){
                //如果是func则执行保存在methods里的方法
                this.methods[array[1]].call(this , req , res , pathname);
            }else {
                throw new Error("route Error");
            }
        }else {
            this.emit("notmatch");
    
            this.error(res);
        }
    };

      上面代码中有个getpath方法,该方法就是将**和*映射为实际地址,也即使将/public/biz009/stylesheets/css/main.css 转换为public/stylesheets/css/main.css 的逻辑。

    function getpath(fil , ad , pathname){
        var filepath = ad;
        if(/__(A|B)__/g.test(fil) && /__(A|B)__/g.test(ad)){
            var ay = fil.split("__");
            var dy = ad.split("__");
    
            var index = 0;
            for(var k=0;k<ay.length;k++){
                if(!ay[k]) continue;
    
                var reg;
                if (ay[k] === 'A' || ay[k] === 'B') {
                    reg = new RegExp(ay[k] === 'A' ? ALL_FOLDER_REG_STR : ALL_FILES_REG_STR);
    
                    //扫描路径,当遇到AB关键字时处理,如果两者不相等,停下dy的扫描,继续执行对ay的扫描,直至遇到相等数值
                    while(index < dy.length){
                        if(dy[index] === 'A' || dy[index] === 'B'){
                            if(dy[index] === ay[k]){
                                dy[index] = pathname.match(reg)[0];
                                index++;
                            }
                            break;
                        }
                        index++;
                    }
                } else {
                    reg = new RegExp(ay[k]);
                }
    
                pathname = pathname.replace(reg, '');
            }
    
            filepath =  dy.join("");
        }
    
        filepath = path.normalize(filepath);
        filepath = filepath.charAt(0) == path.sep ? filepath.substring(1,filepath.length):filepath;
    
        return filepath;
    }

      说说实现原理:先将路由地址和目标地址转成数组

    /public/bi*/**/* ==> ['/public/bi','B','','A','','B']
    public/**/*      ==> ['public','A','','B']

      而当我请求/public/biz009/stylesheets/css/main.css 的时候,即要将

    ['/public/bi','B','','A','','B']  ==>  ['/public/bi','z009','','/stylesheets/css/','','main.css']

      然后再跟上面的['public','A','','B']对应,即

    ['public','A','','B'] ==> ['public','/stylesheets/css/','','main.css']

      实现逻辑为:

      请求的pathname还是为/public/biz009/stylesheets/css/main.css ,扫描['/public/bi','B','','A','','B']:

      扫描第一个即'/public/bi'时,将/public/bi转成正则,通过匹配将/public/biz009/stylesheets/css/main.css 变为:z009/stylesheets/css/main.css

      扫描第二个即B,因为B所以用上面的ALL_FILES_REG_STR 即 [w._-]+匹配,将从而获取到了B对应的z009,同时将pathname变成/stylesheets/css/main.css,此时再扫描 ['public','A','','B'],扫描到A或B的时候,发现是A而不是对应的B,因此不更新扫描索引,所以上面没有进行index++,而是直接break,继续下一步。

      扫描第三个''所以不管继续扫描

      扫描第四个为A,同上,获取到/stylesheets/css/,并将pathname变成main.css,此时再扫描['public','A','','B'],扫描索引还停留在A上,所以再进行判断,结果两者都是A,因此将['public','A','','B']中的A替换成了/stylesheets/css/,即变成了['public','/stylesheets/css/','','B']

      然后同上继续扫描直至扫描完,就会将['public','A','','B']变成['public','/stylesheets/css/','','main.css'];

      最后再join出来的结果:public/stylesheets/css/main.css就是转换出来的最终路径,也就是匹配的文件路径。

      还有两个方法一个是输出文件内容,一个就是404了,比较简单,就不作赘述

    rp.routeTo = function(res , filepath){
        var that = this;
        fs.stat(filepath , function(err , stats){
            if(err || !stats.isFile()){
                that.emit("error" , err || (new Error("path is not file")));
    
                that.error(res);
                return;
            }
    
            var fileKind = filepath.substring((filepath.lastIndexOf(".")+1)||0 , filepath.length);
            var readstream = fs.createReadStream(filepath);
    
            var index = mimes.indexOf('.'+fileKind);
            var options = {
                'Cache-Control':'no-cache',
                'Content-Type': mimes[index+1]+';charset=utf-8',
                'Content-Length':stats.size
            };
            res.writeHead(200, options);
            readstream.pipe(res);
        })
    }
    
    rp.error = function(res){
        res.writeHead(404);
        res.end("404 not found");
    }

      该源码放在github上

      https://github.com/whxaxes/easy-router

  • 相关阅读:
    Code First数据库迁移
    创建静态报表
    JavaScript prototype
    把事务封装成类似Serializable用法的特性
    我的开发框架(WinForm)2
    使用 NPC,NPCManager 在 XNA 中创建 NPC
    ExtJs控件属性配置详细
    Python+Django+Eclipse 在Windows下快速开发自己的网站
    C++ const && 二叉树合集
    验证视图状态 MAC 失败,解决方法
  • 原文地址:https://www.cnblogs.com/axes/p/4491983.html
Copyright © 2011-2022 走看看