express

express功能極簡(jiǎn)箫攀,主要是路由和中間件構(gòu)成,在本質(zhì)上幼衰,其實(shí)就是調(diào)用各種中間件靴跛。

我先把API簡(jiǎn)單的列出來(lái):

  • express() // 返回一個(gè)Application
  • express.static(root, [options) // 內(nèi)置的唯一個(gè)中間件,負(fù)責(zé)托管應(yīng)用內(nèi)的靜態(tài)資源
  • Application
  • app.locals
  • app.mountpath // 子應(yīng)用掛在的路徑
  • app.on('mount', callback(parent))
  • app.all(path, callback[, callback...]); //匹配所有的http動(dòng)詞
  • 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

路由功能

  • 路由就是根據(jù)不同的路徑和方法渡嚣,返回不同的內(nèi)容梢睛。

    在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);//主機(jī)機(jī)
    res.end('hello');
})
// *表示所有路徑
app.get('*', function(req, res){
    res.end('hello');
})
app.listen(8080, function(){
    console.log('服務(wù)器已啟動(dòng)');
})
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 的單一請(qǐng)求處理函數(shù),隨著功能的擴(kuò)張勢(shì)必會(huì)變的越來(lái)越難以維護(hù)识椰。而 Express 框架則可以通過(guò)中間件的方式按照模塊和功能對(duì)處理函數(shù)進(jìn)行切割處理绝葡。這樣拆分后的模塊不僅邏輯清晰,更重要的是對(duì)后期維護(hù)和開(kāi)發(fā)非常有利腹鹉。

實(shí)現(xiàn)

express.js藏畅。相當(dāng)于提供了一個(gè)工廠函數(shù),創(chuàng)建app

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

application.js

//實(shí)現(xiàn)Router 和應(yīng)用的分離
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 = {};//用來(lái)保存參數(shù)
    this.engines = {};//用來(lái)保存文件擴(kuò)展名和渲染函數(shù)的函數(shù)
}
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);
}
// 傳二個(gè)參數(shù)表示設(shè)置功咒,傳一個(gè)參數(shù)表示獲取
Application.prototype.set = function (key, val) {
    if (arguments.length == 1) {
        return this.settings[key];
    }
    this.settings[key] = val;
}
//規(guī)定何種文件用什么方法來(lái)渲染
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();
        //這樣寫(xiě)可以支持多個(gè)處理函數(shù)
        this._router[method].apply(this._router, slice.call(arguments));
        return this;
    }
});
Application.prototype.route = function (path) {
    this.lazyrouter();
    //創(chuàng)建一個(gè)路由愉阎,然后創(chuàng)建一個(gè)layer ,layer.route = route.this.stack.push(layer)
    this._router.route(path);
}
//添加中間件,而中間件和普通的路由都是放在一個(gè)數(shù)組中的力奋,放在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() {//如果沒(méi)有任何路由規(guī)則匹配的話會(huì)走此函數(shù)
            res.end(`Cannot ${req.method} ${req.url}`);
        }
        //如果路由系統(tǒng)無(wú)法處理榜旦,也就是沒(méi)有一條路由規(guī)則跟請(qǐng)求匹配,是會(huì)把請(qǐng)求交給done
        self._router.handle(req, res, done);
    });
    server.listen(...arguments);
}
module.exports = Application;

掛在了app下各種方法景殷。其中有兩大塊單獨(dú)提出來(lái):一個(gè)是路由溅呢,一個(gè)是路由下的路由對(duì)象,最后一個(gè)是存放的層猿挚。

看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 = [];
    //聲明一個(gè)對(duì)象咐旧,用來(lái)緩存路徑參數(shù)名它對(duì)應(yīng)的回調(diào)函數(shù)數(shù)組
    router.paramCallbacks = {};
    //在router一加載就會(huì)加載內(nèi)置 中間件
    // query
   // router.use(init);
    return router;
}
let proto = Object.create(null);
//創(chuàng)建一個(gè)Route實(shí)例,向當(dāng)前路由系統(tǒng)中添加一個(gè)層
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;//我們正是通過(guò)layer有沒(méi)有route來(lái)判斷是一個(gè)中間件函數(shù)還是一個(gè)路由
    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是否添加過(guò)/ 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();
}
//用來(lái)處理param參數(shù),處理完成后會(huì)走out函數(shù)
proto.process_params = function (layer, req, res, out) {
    let keys = layer.keys;
    let self = this;
    //用來(lái)處理路徑參數(shù)
    let paramIndex = 0 /**key索引**/, key/**key對(duì)象**/, name/**key的值**/, val, callbacks, callback;
    //調(diào)用一次param意味著處理一個(gè)路徑參數(shù)
    function param() {
        if (paramIndex >= keys.length) {
            return out();
        }
        key = keys[paramIndex++];//先取出當(dāng)前的key
        name = key.name;// uid
        val = layer.params[name];
        callbacks = self.paramCallbacks[name];// 取出等待執(zhí)行的回調(diào)函數(shù)數(shù)組
        if (!val || !callbacks) {//如果當(dāng)前的key沒(méi)有值亭饵,或者沒(méi)有對(duì)應(yīng)的回調(diào)就直接處理下一個(gè)key
            return param();
        }
        execCallback();
    }
    let callbackIndex = 0;
    function execCallback() {
        callback = callbacks[callbackIndex++];
        if (!callback) {
            return param();//如果此key已經(jīng)沒(méi)有回調(diào)等待執(zhí)行休偶,則意味本key處理完畢,該執(zhí)行一下key
        }
        callback(req, res, execCallback, val, name);
    }
    param();
}
module.exports = Router;
/**
 * Router
 *   stack
 *      layer
 *         path route
 *                 method handler
 * Layer
 * Router Layer 路徑 處理函數(shù)(route.dispatch) 有一個(gè)特殊的route屬性
 * Route  layer  路徑 處理函數(shù)(真正的業(yè)務(wù)代碼)  有一特殊的屬性method
 */

 /**
  * 1.params param
    2.模板引擎 我們會(huì)自己寫(xiě)一個(gè)模板支持辜羊,邏輯判斷 踏兜。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 = [];
    //表示此路由有有此方法的處理函數(shù)
    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) {//如果一旦在路由函數(shù)中出錯(cuò)了,則會(huì)跳過(guò)當(dāng)前路由
            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) {//這一層是一個(gè)中間件層  /user/2
        // this.path = /user  
        return path.startsWith(this.path + '/');
    }
    //如果這個(gè)Layer是一個(gè)路由的 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;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末八秃,一起剝皮案震驚了整個(gè)濱河市碱妆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昔驱,老刑警劉巖疹尾,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纳本,警方通過(guò)查閱死者的電腦和手機(jī)窍蓝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)繁成,“玉大人吓笙,你說(shuō)我怎么就攤上這事〗硗螅” “怎么了面睛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尊搬。 經(jīng)常有香客問(wèn)我叁鉴,道長(zhǎng),這世上最難降的妖魔是什么佛寿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任幌墓,我火速辦了婚禮,結(jié)果婚禮上狗准,老公的妹妹穿的比我還像新娘克锣。我一直安慰自己,他們只是感情好腔长,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布袭祟。 她就那樣靜靜地躺著,像睡著了一般捞附。 火紅的嫁衣襯著肌膚如雪巾乳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天鸟召,我揣著相機(jī)與錄音胆绊,去河邊找鬼。 笑死欧募,一個(gè)胖子當(dāng)著我的面吹牛压状,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跟继,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼种冬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了舔糖?” 一聲冷哼從身側(cè)響起娱两,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎金吗,沒(méi)想到半個(gè)月后十兢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體趣竣,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年旱物,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遥缕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宵呛,死狀恐怖通砍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烤蜕,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布迹冤,位于F島的核電站讽营,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏泡徙。R本人自食惡果不足惜橱鹏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望堪藐。 院中可真熱鬧莉兰,春花似錦、人聲如沸礁竞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)模捂。三九已至捶朵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狂男,已是汗流浹背综看。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岖食,地道東北人红碑。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像泡垃,于是被迫代替她去往敵國(guó)和親析珊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評(píng)論 25 707
  • 這篇文章主要將會(huì)通過(guò)node手把手的構(gòu)建一個(gè)靜態(tài)文件服務(wù)器兔毙,那么廢話不多說(shuō)唾琼,開(kāi)發(fā)流程走起來(lái),我們先看一下將要做的這...
    嘿_那個(gè)誰(shuí)閱讀 1,177評(píng)論 0 0
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理澎剥,服務(wù)發(fā)現(xiàn)锡溯,斷路器赶舆,智...
    卡卡羅2017閱讀 134,699評(píng)論 18 139
  • 每次看到別人用文字把自己表達(dá)的恰如其分時(shí),我就在想祭饭,他們是怎么做到的芜茵?最近聽(tīng)了很多講座,都是關(guān)于成長(zhǎng)的倡蝙,因?yàn)槟?..
    周周粒粒粥閱讀 916評(píng)論 0 1
  • 如果不是我 你會(huì)不會(huì)有遺憾 你的氣息早已和我分不開(kāi) 你和我牽著的手分不開(kāi) 什么是永遠(yuǎn) 我依然沒(méi)有答案 年少輕狂 曾...
    a4d7730320f9閱讀 393評(píng)論 0 1