express

358 阅读5分钟

express功能极简,主要是路由和中间件构成,在本质上,其实就是调用各种中间件。

我先把API简单的列出来:

  • express() // 返回一个Application
  • express.static(root, [options) // 内置的唯一个中间件,负责托管应用内的静态资源
  • Application
  • app.locals
  • app.mountpath // 子应用挂在的路径
  • app.on('mount', callback(parent))
  • app.all(path, callback[, callback...]); //匹配所有的http动词
  • app.(delete get post put METHOD...)(path, callback[, callbacks]);
  • app.disable(name)
  • app.disabled()
  • app.enable(name)
  • app.enabled()
  • app.engine(ext, callback);
  • app.listen(port[, hostname][, backlog][, callback])
  • app.param([name, ]callback)
  • app.path()
  • app.render(view[, locals], callback)
  • app.route(path)
  • app.set(name, value)
  • app.use([path,]function[, function...])
  • req
  • req.app
  • req.baseUrl
  • req.body
  • req.cookies
  • req.fresh
  • req.hostname
  • req.ip
  • req.ips
  • req.originalUrl
  • req.params
  • req.path
  • req.protocol
  • req.qurey
  • req.route
  • req.secure
  • req.signedCookies
  • req.stale
  • req.subdomains
  • req.xhr
  • req.accepts(types)
  • req.acceptsCharsets(charset [, ...])
  • req.acceptsEncodings(encoding [, ...])
  • req.acceptsLanguages(lang [, ...])
  • req.get(field)
  • req.is(type)
  • req.param(name [, defaultValue])
  • res.app
  • res.headersSend
  • res.locals
  • res.append(field [, value])
  • res.attachment([filename])
  • res.clearCookie(name [, options])
  • res.download(path [, filename] [, fn])
  • res.end([data] [, encoding])
  • res.format(object)
  • res.get(field)
  • res.json([body])
  • res.jsonp([body])
  • res.links(links)
  • res.location(path)
  • res.redirect([status,] path)
  • res.render(view [, locals] [, callback])
  • res.send([body])
  • res.sendFile(path [, options] [, fn])
  • res.sendStatus(statusCode)
  • res.set(field [, value])
  • res.status(code)
  • res.type(type)
  • res.vary(field)
  • Router
  • express.Router([options])
  • router.all(path, [callback, ...] callback)
  • router.METHOD(path, [callback, ...] callback)
  • router.param([name,] callback)
  • router.route(path)
  • router.use([path], [function, ...] function)

以上就是express的全部api

路由功能

  • 路由就是根据不同的路径和方法,返回不同的内容。

    在express中是这样的:

let app = express();
app.get('/hello', function(req, res){
    console.log(req.query); // {name:'zfpx',age:8}
    console.log(req.path); // /user
    console.log(req.hostname);//主机机
    res.end('hello');
})
// *表示所有路径
app.get('*', function(req, res){
    res.end('hello');
})
app.listen(8080, function(){
    console.log('服务器已启动');
})
app.get('/uesrage/:userid/:age', function (req, res) {
    console.log(req.user);
    req.user.age = req.params.age;
    setUser(req.user);
    res.end('update age successfully');
});

中间件

原生 Node 的单一请求处理函数,随着功能的扩张势必会变的越来越难以维护。而 Express 框架则可以通过中间件的方式按照模块和功能对处理函数进行切割处理。这样拆分后的模块不仅逻辑清晰,更重要的是对后期维护和开发非常有利。

实现

express.js。相当于提供了一个工厂函数,创建app

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

application.js

//实现Router 和应用的分离
const Router = require('./router');
const http = require('http');
const methods = require('methods');//['get','post']
console.log(methods);
const slice = Array.prototype.slice;
// methods = http.METHODS
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;

挂在了app下各种方法。其中有两大块单独提出来:一个是路由,一个是路由下的路由对象,最后一个是存放的层。

看router.js

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;
//let r = Router()
//let r = new Router();
function Router() {
    function router(req, res, next) {
        router.handle(req, res, next);
    }
    Object.setPrototypeOf(router, proto);
    router.stack = [];
    //声明一个对象,用来缓存路径参数名它对应的回调函数数组
    router.paramCallbacks = {};
    //在router一加载就会加载内置 中间件
    // query
   // router.use(init);
    return router;
}
let proto = Object.create(null);
//创建一个Route实例,向当前路由系统中添加一个层
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;//我们正是通过layer有没有route来判断是一个中间件函数还是一个路由
    this.stack.push(layer);
}
methods.forEach(function (method) {
    proto[method] = function (path) {
        let route = this.route(path);//是在往Router里添一层
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    }
});
proto.param = function (name, handler) {
    if (!this.paramCallbacks[name]) {
        this.paramCallbacks[name] = [];
    }
    // {uid:[handle1,hander2]}
    this.paramCallbacks[name].push(handler);
}
/**
 * 1.处理中间件
 * 2. 处理子路由容器 
 */
proto.handle = function (req, res, out) {
    //slashAdded是否添加过/ removed指的是被移除的字符串
    let idx = 0, self = this, slashAdded = false, removed = '';
    // /user/2
    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++];
        //在此匹配路径 params   正则+url= req.params
        if (layer.match(pathname)) {// layer.params
            if (!layer.route) { //这一层是中间件层//  /user/2
                removed = layer.path;//  /user
                req.url = req.url.slice(removed.length);// /2
                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) && !err) {
                    //把layer的parmas属性拷贝给req.params
                    req.params = layer.params;
                    self.process_params(layer, req, res, () => {
                        layer.handle_request(req, res, next);
                    });
                } else {
                    next(err);
                }
            }
        } else {
            next(err);
        }
    }
    next();
}
//用来处理param参数,处理完成后会走out函数
proto.process_params = function (layer, req, res, out) {
    let keys = layer.keys;
    let self = this;
    //用来处理路径参数
    let paramIndex = 0 /**key索引**/, key/**key对象**/, name/**key的值**/, val, callbacks, callback;
    //调用一次param意味着处理一个路径参数
    function param() {
        if (paramIndex >= keys.length) {
            return out();
        }
        key = keys[paramIndex++];//先取出当前的key
        name = key.name;// uid
        val = layer.params[name];
        callbacks = self.paramCallbacks[name];// 取出等待执行的回调函数数组
        if (!val || !callbacks) {//如果当前的key没有值,或者没有对应的回调就直接处理下一个key
            return param();
        }
        execCallback();
    }
    let callbackIndex = 0;
    function execCallback() {
        callback = callbacks[callbackIndex++];
        if (!callback) {
            return param();//如果此key已经没有回调等待执行,则意味本key处理完毕,该执行一下key
        }
        callback(req, res, execCallback, val, name);
    }
    param();
}
module.exports = Router;
/**
 * Router
 *   stack
 *      layer
 *         path route
 *                 method handler
 * Layer
 * Router Layer 路径 处理函数(route.dispatch) 有一个特殊的route属性
 * Route  layer  路径 处理函数(真正的业务代码)  有一特殊的属性method
 */

 /**
  * 1.params param
    2.模板引擎 我们会自己写一个模板支持,逻辑判断 。if while do
  */

route.js

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();//route.dispath里的out刚好是Router的next
        }
        let layer = self.stack[idx++];
        if (layer.method == req.method.toLowerCase()) {
            layer.handle_request(req, res, next);
        } else {
            next();
        }
    }
    next();
}
module.exports = Route;

layer.js

const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
    this.path = path;
    this.handler = handler;
    this.keys = [];
    // this.path =/user/:uid   this.keys = [{name:'uid'}];
    this.regexp = pathToRegexp(this.path, this.keys);
    // /user/:uid    /^\/user\/([^\/]+?)(?:\/)?$/i
    // /user2/:uid/:name   /^\/user2\/([^\/]+?)\/([^\/]+?)(?:\/)?$/i
    // /   /^\/(?:\/)?$/i
}
//判断这一层和传入的路径是否匹配
Layer.prototype.match = function (path) {
    if (this.path == path) {
        return true;
    }
    if (!this.route) {//这一层是一个中间件层  /user/2
        // this.path = /user  
        return path.startsWith(this.path + '/');
    }
    //如果这个Layer是一个路由的 Layer
    if (this.route) {
        let matches = this.regexp.exec(path); //   /user/1
        // ['',1,'zfpx']
        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, dd) {
    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;