http-hash-router源码阅读

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:HttpHash对象维护这样一个routerNode树,结点结构定义如下:并提供了set,get两个方法插入结点逻辑:
var http = require('http');
var HttpHashRouter = require('http-hash-router');

var router = HttpHashRouter();

router.set('/health', function health(req, res) {
    res.end('OK');
});

var server = http.createServer(function handler(req, res) {
    router(req, res, {}, onError);

    function onError(err) {
        if (err) {
            // use your own custom error serialization.
            res.statusCode = err.statusCode || 500;
            res.end(err.message);
        }
    }
});
server.listen(3000);

http-hash

HttpHash对象维护这样一个routerNode树,结点结构定义如下:

function RouteNode(parent, segment, isSplat) {
    this.parent = parent || null; // 上级结点
    this.segment = segment || null; // 当前结点元素
    this.handler = null; // handler方法
    this.staticPaths = {}; // 静态路径
    this.variablePaths = null; // 可变路径
    this.isSplat = !!isSplat; // 是否带*号路径
    this.src = null; // 完整path
}

并提供了set,get两个方法

  • router.set(pathname, handler), 根据pathname, 遍历每层路径元素,不断在hash中插入路由结点
  • router.get(pathname), 同样通过可变路径优先找到访问的路径,调用相应结点的handler

set(pathname, handler)

function set(pathname, handler) {
    var pathSegments = pathname.split('/');
    var hash = this._hash;
    var lastIndex = pathSegments.length - 1;
    var splatIndex = pathname.indexOf('*');
    var hasSplat = splatIndex >= 0;

    if (hasSplat && splatIndex !== pathname.length - 1) {
        throw SplatError(pathname);
    }

    for (var i = 0; i < pathSegments.length; i++) {
        var segment = pathSegments[i];
        // 路径元素为空,遍历下一个
        if (!segment) {
            continue;
        }
        // 如果该路径元素是*号 且是最后一个元素 则增加可变路径结点
        if (hasSplat && i === lastIndex) {
            // 同一路径下如果已经有了可变路径,维持之前的可变路径不变, 否则增加可变结点
            hash = (
                hash.variablePaths ||
                (hash.variablePaths = new RouteNode(hash, segment, true))
            );
            
            // 如果该可变路径不是* 而是:,则抛出冲突的异常,即不可以同时在同一路径下分别设置*和:两种通配符可变路径
            if (!hash.isSplat) {
                throw RouteConflictError(pathname, hash);
            }
        } else if (segment.indexOf(':') === 0) { // 如果是:通配符,增加可变路径结点
            segment = segment.slice(1); // 获取:后的元素
            // 同一路径下如果已经有了可变路径,维持之前的可变路径不变, 否则增加可变结点
            hash = (
                hash.variablePaths ||
                (hash.variablePaths = new RouteNode(hash, segment))
            );
            // 如果设置的可变路径元素跟之前的不一致 或者之前设置是*可变路径,则抛出冲突异常
            if (hash.segment !== segment || hash.isSplat) {
                throw RouteConflictError(pathname, hash);
            }
        } else if (segment === '__proto__') {
            hash = (
                (
                    hash.hasOwnProperty('proto') &&
                    hash.proto
                ) ||
                (hash.proto = new RouteNode(hash, segment))
            );
        } else { // 设置静态路径 查找当前has下的静态路径是否已经存在该路径元素 如果存在,直接复制结点,否则创建新的结点
            hash = (
                (
                    hash.staticPaths.hasOwnProperty(segment) &&
                    hash.staticPaths[segment]
                ) ||
                (hash.staticPaths[segment] = new RouteNode(hash, segment))
            );
        }
    }
    // 判断是否设置了重复路径,重复则抛出冲突异常
    if (!hash.handler) {
        hash.src = pathname;
        hash.handler = handler;
    } else {
        throwRouteConflictError(pathname, hash);
    }
}

插入结点逻辑:

  • 将整个路径以/分割成一个数组,遍历每层路径元素,通过判断*和:[key]插入可变路径结点,否则插入静态路径结点
  • 同一层路径结点的静态路径结点,即兄弟结点可以有多个
  • 同一层路径结点的可变路径结点只有一个

抛出冲突异常的几个地方

  • 重复set了相同的路径
  • 同一层路径下set了两个不一样的通配符(如:一个是 * ,一个是 :[key] ),避免同时出现 /api/*/api/:name

get(pathname)

function get(pathname) {
    var pathSegments = pathname.split('/');
    // 当前路由hash结点树
    var hash = this._hash;
    var splat = null;
    var params = {};
    var variablePaths;

    for (var i = 0; i < pathSegments.length; i++) {
        var segment = pathSegments[i];

        if (!segment && !hash.isSplat) {
            continue;
        } else if (
            segment === '__proto__' &&
            hash.hasOwnProperty('proto')
        ) {
            hash = hash.proto;
        } else if (hash.staticPaths.hasOwnProperty(segment)) {
            hash = hash.staticPaths[segment];
        } else if ((variablePaths = hash.variablePaths)) {
            if (variablePaths.isSplat) {
                splat = pathSegments.slice(i).join('/');
                hash = variablePaths;
                break;
            } else {
                params[variablePaths.segment] = segment;
                hash = variablePaths;
            }
        } else {
            hash = null;
            break;
        }
    }

    return new RouteResult(hash, params, splat);
}

查找逻辑:

  • 优先从静态路径中查找, __proto__ > staticPaths > variablePaths 因此可以通过设置静态路由覆盖可变路径,如 api/review 覆盖 api/:name
  • splat保存指代 * 的路径内容,params保存 :[key] 的路径内容

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Linux内核设计与实现(原书第3版)

Linux内核设计与实现(原书第3版)

Robert Love / 陈莉君、康华 / 机械工业出版社华章公司 / 2011-4-30 / 69.00元

《Linux内核设计与实现(原书第3版)》详细描述了Linux内核的设计与实现。内核代码的编写者、开发者以及程序开发人员都可以通过阅读本书受益,他们可以更好理解操作系统原理,并将其应用在自己的编码中以提高效率和生产率。 《Linux内核设计与实现(原书第3版)》详细描述了Linux内核的主要子系统和特点,包括Linux内核的设计、实现和接口。从理论到实践涵盖了Linux内核的方方面面,可以满......一起来看看 《Linux内核设计与实现(原书第3版)》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码