源码级实现Express常用功能

Express是基于Node.js平台,快速、开发、极简的web开发框架

路由

路由:根据不同的方法和不同的路径返回不同的内容

我们可以定义任意定义路由规则,比如有些时候希望只匹配路径 ,不管什么方法都能处理
简单的路由方法

app.all('*', function (req, res) {
    res.end('404');
});
app.get('/hello', function (req, res) {
    res.end('hello');
});

中间件

使用use来定义一个中间件,next也是一个函数,调用它则意味着当前的中间件执行完毕,可以继续向下执行别的中间件了

简单的中间件方法

app.use(function (req, res, next) {
    res.setHeader('Content-Type', 'text/html;charset=utf8');
    console.log('没有路径的中间件');
    next('我错了');
});
app.use('/water', function (req, res, next) {
    console.log('过滤杂质');
    next();
});
app.get('/water', function (req, res) {
    res.end('water');
});
app.use('/hello', function (err, req, res, next) {
    res.end('hello ' + err);
});
  • 错误处理中间件有四个参数err, req, res, next
  • 如果调用next的时候如果传一个任意参数就表示此函数发生了错误,然后express就会跳过后面所有的中间件和路由,交给错误处理中间件来处理

param
内置中间件,用来处理路径参数的,在此保存路径参数名和处理函数

const express = require('./express');
const app = express();
function getUser(userid) {
    return { userid: 1, age: 8, name: 'zfpx' };
}
function setUser(user) {
    //向数据库里保存用户
}
app.get('/username/:userid/:name', function (req, res) {
    req.user.name = req.params.name;
    setUser(req.user);
    res.end('update  name successfully');
});
app.get('/uesrage/:userid/:age', function (req, res) {
    req.user.age = req.params.age;
    setUser(req.user);
    res.end('update age successfully');
});
app.param('userid', function (req, res, next, userid) {
    req.user = getUser(userid);
    next();
});
app.listen(8080);

模拟实现Express
项目结构:

这里写图片描述
application.js中挂载了所有核心方法,实现Router和应用的分离

const Router = require('./router');
const http = require('http');
const methods = require('methods');//['get','post']
const slice = Array.prototype.slice;
function Application() {
    this.settings = {};//用来保存参数
    this.engines = {};//用来保存文件扩展名和渲染函数的函数
}
Application.prototype.lazyrouter = function () {
    if (!this._router) {
        this._router = new Router();
    }
}
Application.prototype.param = function (name, handler) {
    this.lazyrouter();
    this._router.param.apply(this._router, arguments);
}
// 传二个参数表示设置,传一个参数表示获取
Application.prototype.set = function (key, val) {
    if (arguments.length == 1) {
        return this.settings[key];
    }
    this.settings[key] = val;
}
//规定何种文件用什么方法来渲染
Application.prototype.engine = function (ext, render) {
    let extension = ext[0] == '.' ? ext : '.' + ext;
    this.engines[extension] = render;
}


methods.forEach(function (method) {
    Application.prototype[method] = function () {
        if (method == 'get' && arguments.length == 1) {
            return this.set(arguments[0]);
        }
        this.lazyrouter();
        //这样写可以支持多个处理函数
        this._router[method].apply(this._router, slice.call(arguments));
        return this;
    }
});
Application.prototype.route = function (path) {
    this.lazyrouter();
    //创建一个路由,然后创建一个layer ,layer.route = route.this.stack.push(layer)
    this._router.route(path);
}
//添加中间件,而中间件和普通的路由都是放在一个数组中的,放在this._router.stack
Application.prototype.use = function () {
    this.lazyrouter();
    this._router.use.apply(this._router, arguments);
}
Application.prototype.listen = function () {
    let self = this;
    let server = http.createServer(function (req, res) {
        function done() {//如果没有任何路由规则匹配的话会走此函数
            res.end(`Cannot ${req.method} ${req.url}`);
        }
        //如果路由系统无法处理,也就是没有一条路由规则跟请求匹配,是会把请求交给done
        self._router.handle(req, res, done);
    });
    server.listen(...arguments);
}
module.exports = Application;
  • param 内置中间件方法,批量设置参数
  • set 传二个参数表示设置,传一个参数表示获取
  • engine 规定何种文件用什么方法来渲染
  • route 创建一个路由,然后创建一个layer ,layer.route = route.this.stack.push(layer)
  • use 添加中间件,而中间件和普通的路由都是放在一个数组中的,放在this._router.stack
  • listen 监听客户端发来请求的函数

express.js 导出创建的application实例

const http = require('http');
const url = require('url');
const Router = require('./router');
const Application = require('./application');
function createApplicaton() {
    return new Application();
}
createApplicaton.Router = Router;
module.exports = createApplicaton;

html.js实现模板引擎

const fs = require('fs');
function render(filepath, options, callback) {
    fs.readFile(filepath, 'utf8', function (err, str) {
        let head = "let tpl = ``;\nwith (obj) {\n tpl+=`";
        str = str.replace(/<%=([\s\S]+?)%>/g, function () {
            return "${" + arguments[1] + "}";
        });
        str = str.replace(/<%([\s\S]+?)%>/g, function () {
            return "`;\n" + arguments[1] + "\n;tpl+=`";
        });
        let tail = "`}\n return tpl; ";
        let html = head + str + tail;
        let fn = new Function('obj', html);
        let result = fn(options);
        callback(null, result);
    });
}
module.exports = render;

router中的文件关系如下:

这里写图片描述
* Router Layer 路径 处理函数(route.dispatch) 有一个特殊的route属性
* Route layer 路径 处理函数(真正的业务代码) 有一特殊的属性method

router/index
Router的stack数组存放子路由和中间件,handle方法,循环stack数组,根据规则执行回调函数

const Route = require('./route');
const Layer = require('./layer');
const url = require('url');
const methods = require('methods');
const init = require('../middle/init');
const slice = Array.prototype.slice;
function Router() {
    function router(req, res, next) {
        router.handle(req, res, next);
    }
    Object.setPrototypeOf(router, proto);
    router.stack = [];
    router.paramCallbacks = {};
    router.use(init);
    return router;
}
let proto = Object.create(null);
proto.route = function (path) {
    let route = new Route(path);
    let layer = new Layer(path, route.dispatch.bind(route));
    layer.route = route;
    this.stack.push(layer);

    return route;
}
proto.use = function (path, handler) {
    if (typeof handler != 'function') {
        handler = path;
        path = '/';
    }
    let layer = new Layer(path, handler);
    layer.route = undefined;
    this.stack.push(layer);
}
methods.forEach(function (method) {
    proto[method] = function (path) {
        let route = this.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    }
});
proto.param = function (name, handler) {
    if (!this.paramCallbacks[name]) {
        this.paramCallbacks[name] = [];
    }
    this.paramCallbacks[name].push(handler);
}
proto.handle = function (req, res, out) {
    let idx = 0, self = this, slashAdded = false, removed = '';
    let { pathname } = url.parse(req.url, true);
    function next(err) {
        if (removed.length > 0) {
            req.url = removed + req.url;
            removed = '';
        }
        if (idx >= self.stack.length) {
            return out(err);
        }
        let layer = self.stack[idx++];
        if (layer.match(pathname)) {
            if (!layer.route) { 
                removed = layer.path;
                req.url = req.url.slice(removed.length);
                if (err) {
                    layer.handle_error(err, req, res, next);
                } else {
                    layer.handle_request(req, res, next);
                }
            } else {
                if (layer.route && layer.route.handle_method(req.method)) {
                    req.params = layer.params;
                    self.process_params(layer, req, res, () => {
                        layer.handle_request(req, res, next);
                    });
                } else {
                    next(err);
                }
            }
        } else {
            next(err);
        }
    }
    next();
}
proto.process_params = function (layer, req, res, out) {
    let keys = layer.keys;
    let self = this;
    let paramIndex = 0 ;
    function param() {
        if (paramIndex >= keys.length) {
            return out();
        }
        key = keys[paramIndex++];
        name = key.name;
        val = layer.params[name];
        callbacks = self.paramCallbacks[name];
        if (!val || !callbacks) {
            return param();
        }
        execCallback();
    }
    let callbackIndex = 0;
    function execCallback() {
        callback = callbacks[callbackIndex++];
        if (!callback) {
            return param();
        }
        callback(req, res, execCallback, val, name);
    }
    param();
}
module.exports = Router;

router/layer 把同一个path形成一层,并提供match方法进行匹配判断,handle方法来执行当前层的深入匹配methods

const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
    this.path = path;
    this.handler = handler;
    this.keys = [];
    this.keys = [{name:'uid'}];
    this.regexp = pathToRegexp(this.path, this.keys);
}
Layer.prototype.match = function (path) {
    if (this.path == path) {
        return true;
    }
    if (!this.route) {
        return path.startsWith(this.path + '/');
    }
    if (this.route) {
        let matches = this.regexp.exec(path);
        if (matches) {
            this.params = {};
            for (let i = 1; i < matches.length; i++) {
                let name = this.keys[i - 1].name;
                let val = matches[i];
                this.params[name] = val;
            }
            return true;
        }
    }
    return false;
}
Layer.prototype.handle_request = function (req, res, next) {
    this.handler(req, res, next);
}
Layer.prototype.handle_error = function (err, req, res, next) {
    if (this.handler.length != 4) {
        return next(err);
    }
    this.handler(err, req, res, next);
}
module.exports = Layer;

router/route.js layer形成了层,每一层具体的事情有route来做

const Layer = require('./layer');
const methods = require('methods');
const slice = Array.prototype.slice;
function Route(path) {
    this.path = path;
    this.stack = [];
    this.methods = {};
}
Route.prototype.handle_method = function (method) {
    method = method.toLowerCase();
    return this.methods[method];
}
methods.forEach(function (method) {
    Route.prototype[method] = function () {
        let handlers = slice.call(arguments);
        this.methods[method] = true;
        for (let i = 0; i < handlers.length; i++) {
            let layer = new Layer('/', handlers[i]);
            layer.method = method;
            this.stack.push(layer);
        }
        return this;
    }
});

Route.prototype.dispatch = function (req, res, out) {
    let idx = 0, self = this;
    function next(err) {
        if (err) {
            return out(err);
        }
        if (idx >= self.stack.length) {
            return out();
        }
        let layer = self.stack[idx++];
        if (layer.method == req.method.toLowerCase()) {
            layer.handle_request(req, res, next);
        } else {
            next();
        }
    }
    next();
}
module.exports = Route;

middle/init.js是处理内置中间件的,主要是处理query path

const url = require('url');
module.exports = function (req, res, next) {
    let { pathname, query } = url.parse(req.url, true);
    req.path = pathname;
    req.query = query;

    res.json = function (obj) {
        res.setHeader('Content-Type', 'application/json');
        const str = JSON.stringify(obj);
        res.end(str);
    }

    next();
}

以上就模拟实现了express的基本功能,结合用例进行测试,可使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值