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/blob/master/index.js

时间: 2024-10-02 01:53:04

nodejs学习之实现简易路由的相关文章

nodejs学习笔记&lt;三&gt;关于路由(url)

在网站开发中,路由的设置非常关键.nodejs对路由处理封装了一个比较全面的模块. 来认识下url模块 1)在命令行(cmd)可以直接 node —> url 可直接查看url模块的所有方法. 2)在js中通过require调用url模块.var url = require('url'); url包括:parse,resolve,resolveObject,format,Url 五个方法. 1)parse: parse用来解析url地址.同样可以直接用命令行(cmd)调用,也可以在js中通过ur

Nodejs学习笔记(三)——一张图看懂Nodejs建站

前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试Nodejs>之后,代码编写环境就从Sublime转战到Eclipse下,感觉顺手多了.于是就跟着Scott老师学起了Nodejs建站的课程(推荐大家点进去看看),踏上了未爬先走的路子. 作为一个白里透白的小白来说,今天主要记录下如何用Nodejs搭建一个小小的网站,以及自己对于这种Nodejs建站的运

NodeJS学习笔记(一)——搭建开发框架Express,实现Web网站登录验证

JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS,浏览器充当了解析器的角色.而对于需要独立运行的JS,NodeJS就是一个解析器.每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情.例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象.而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs.http等内置对象.E

nodejs学习资料

NodeJS基础 什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS,浏览器充当了解析器的角色.而对于需要独立运行的JS,NodeJS就是一个解析器. 每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情.例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象.而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS

[Todo] Nodejs学习及Spider实验(包括php入门学习、React入门学习)

/Users/baidu/Documents/Data/Interview/Web-Server开发 深入浅出Node.js-f46c http://blog.csdn.net/u012273376/article/details/52736906 利用nodejs做爬虫 http://www.runoob.com/nodejs/nodejs-callback.html nodejs学习之路 http://www.runoob.com/php/php-tutorial.html php学习之路

NodeJS学习之2:express版的Hello World

接着 NodeJS学习之1:express安装 的结果: 我们继续应用程序的编写. 新建一个 app.js 文件 $ touch app.js copy 进去这些代码 // 这句的意思就是引入 `express` 模块,并将它赋予 `express` 这个变量等待使用. var express = require('express'); // 调用 express 实例,它是一个函数,不带参数调用时,会返回一个 express 实例,将这个变量赋予 app 变量. var app = expre

nodeJs学习

nodejsStudy 阅读<nodejs开发指南>来学习node.js的一个github仓库. 仓库里面放了这本书的电子版以及书籍的源代码 还放了node.js的最新win32的版本 node-v0.10.29 x86.msi 最后就是studyCase文件夹,存放着自己阅读书籍然后敲下来的学习案例 nodeJs学习,布布扣,bubuko.com

nodejs学习四 Node.js NPM

什么是NPM? 不知道大家注意没有,windows平台下的Node.js安装包大小才区区4M多,真可以用短小精悍来形容它,作为一种编程语言,像java一个SDK 就几十M,为什么node.js的运行环境这么小呢?这其中的微妙之处在于,它拥有一个庞大的第三方软件库. 在Node本身提供的包(原生)中没有我们要实现的功能模块的时候,我们可以去寻找下是否已经有人实现了这种功能.毕竟重复造轮子这种事情,很多人都不想干. 去哪里寻找我们想要的包呢?如果你还不知道报的名字,你可以去https://www.n

nodejs学习一 Holle World

安装包下载:http://www.nodejs.org/download/ Windows 操作系统安装方法: 第一步:下载安装包: 第二步:运行node-v0.10.28-x86.msi,选择要安装的地址. 第三步:安装完后,运行cmd ,输入node,如图: 说明安装成功.这样你就进入nodejs 世界. 闪烁的光标等你输入. 第一个程序Hello World 就这么简单,你完成了人生第一次nodejs体验.console.log . 我们在换种方式来体验一下. 我们建立我们将要学习的一个目