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
nodejs学习之实现简易路由的更多相关文章
- nodejs学习笔记<三>关于路由(url)
在网站开发中,路由的设置非常关键.nodejs对路由处理封装了一个比较全面的模块. 来认识下url模块 1)在命令行(cmd)可以直接 node —> url 可直接查看url模块的所有方法. ...
- NodeJS学习笔记 - Express4.x路由操作
一.为Express添加about路由 1.新建js文件,about.js 2.打开about.js,并输入以下代码: var express=require('express'); var rout ...
- Nodejs学习笔记(四)——支持Mongodb
前言:回顾前面零零碎碎写的三篇挂着Nodejs学习笔记的文章,着实有点名不副实,当然,这篇可能还是要继续走着离主线越走越远的路子,从简短的介绍什么是Nodejs,到如何寻找一个可以调试的Nodejs ...
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...
- Nodejs学习笔记(六)--- Node.js + Express 构建网站预备知识
目录 前言 新建express项目并自定义路由规则 如何提取页面中的公共部分? 如何提交表单并接收参数? GET 方式 POST 方式 如何字符串加密? 如何使用session? 如何使用cookie ...
- Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...
- Nodejs学习笔记(十六)--- Pomelo介绍&入门
目录 前言&介绍 安装Pomelo 创建项目并启动 创建项目 项目结构说明 启动 测试连接 聊天服务器 新建gate和chat服务器 配置master.json 配置servers.json ...
- [转]Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
本文转自:https://www.cnblogs.com/zhongweiv/p/nodejs_koa2_webapp.html 目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装 ...
- Nodejs学习笔记(十六)—Pomelo介绍&入门
前言&介绍 Pomelo:一个快速.可扩展.Node.js分布式游戏服务器框架 从三四年前接触Node.js开始就接触到了Pomelo,从Pomelo最初的版本到现在,总的来说网易出品还算不错 ...
随机推荐
- Html5拖拽复制
拖拽是一种常见的特性,即抓取对象以后拖到另一个位置. 在 HTML5 中,拖拽是标准的一部分,任何元素都能够拖拽. Html5拖拽非常常见的一个功能,但是大部分拖拽的案例都是一个剪切的过程, 项目中需 ...
- MySQL binlog 组提交与 XA(两阶段提交)
1. XA-2PC (two phase commit, 两阶段提交 ) XA是由X/Open组织提出的分布式事务的规范(X代表transaction; A代表accordant?).XA规范主要定义 ...
- Linux mke2fs 硬盘格式化
[root@whp6 ~]# cat /etc/filesystems ext4 ext3 ext2 nodev proc nodev devpts iso9660 vfat hfs hfsplus ...
- AngularJS基础概念
作用域.控制器.指令 作用域 应用的作用域是和应用的数据模型相关联的,同时作用域也是表达式执行的上下文.$scope对象是定义应用业务逻辑.控制器方法和视图属性的地方. 作用域是应用状态的基础.基于动 ...
- 一次Mutex死锁的原因探究
1.现象 最近项目中调出一个bug,某些时候程序会卡死不动,用windbg进行加载后用 ~*kb 命令列出所有的线程栈调用,发现有多个线程调用 WaitForMultipleObjects 在等 ...
- Android View和ViewGroup
View和ViewGroup Android的UI界面都是由View和ViewGroup及其派生类组合而成的. 其中,View是所有UI组件的基类,而 ViewGroup是容纳这些组件的容器,其本身也 ...
- hdu-5492 Find a path(dp)
题目链接: Find a path Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others ...
- AC日记——欧几里得的游戏 洛谷 P1290
题目描述 欧几里德的两个后代Stan和Ollie正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的.给定两个正整数M和N,从Stan开始,从其中较大的一个数,减去较小的数的正整数倍,当然,得到的数 ...
- u3d_Shader_effects笔记3 half diffuse 和 ramp texture
1.前面的心情 每次写博客,先写心情也好,就当是小日记了吧.现在已经懒到不想动笔和纸来写日记了.近两天公司的活较少,晚上直接回来了,没有留公司.在公司看代码,不做工,就困... 哎,小辉哥家的老房子后 ...
- Nginx负载均衡配置
1.yum安装nginx yum install nginx 2.启动nginx chkconfig nginx on service nginx start向web服务器中放入测试文件: < ...