摘要:入口文件在文件夾下的,其向外界暴露了一些方法。方法也是從中繼承的。入口文件很清晰,主要是完成方法的暴露以及的一些初始化操作。下一篇寫寫路由的實現。
還沒用express寫過server,先把部分源碼擼了一遍,各位大神求輕拍。
express入口文件在lib文件夾下的express.js,其向外界暴露了一些方法。
最主要的(express.js 第36-47行):
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); //各中間件的處理入口,handle方法通過mixin拓展于proto }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; } exports = module.exports = createApplication;
我們經常在自己的業(yè)務代碼中這樣寫:
var express = require("express"); var app = express();
其實就是調用createApplication方法.這樣就實例化了一個app。這個app比較特殊,通過mixin集成了一些其他的屬性
mixin(app, EventEmitter.prototype, false); //拓展了事件發(fā)射器原型對象 mixin(app, proto, false); //拓展了application.js中的屬性和方法
在我們業(yè)務代碼實例化app的時候,調用了app.init()方法完成了一些初始化的配置。init()方法也是從application.js中繼承的。
入口文件很清晰,主要是完成方法的暴露以及app的一些初始化操作。
接下來看下application.js中的部分代碼邏輯:
第136-146行,延遲實例化一個_router
app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing") }); this._router.use(query(this.get("query parser fn"))); this._router.use(middleware.init(this)); } };
第157-174行,這是各個middleware的入口,
app.handle = function handle(req, res, callback) { var router = this._router; //獲取已經實例化得router // final handler var done = callback || finalhandler(req, res, { env: this.get("env"), onerror: logerror.bind(this) }); // no routes if (!router) { debug("no routes defined on app"); done(); return; } router.handle(req, res, done); //當http過來時,對于request和response的處理從這個地方開始 };
第187-242行,app.use方法提供了應用級的middleware,但是事實上在214行,this.lazyrouter()新建一個route,第219-221行,然后根據app.use(fn)傳入的參數掛載到了route.use()路由級中間件上了。app.use()是route.use的一個代理。
app.use = function use(fn) { var offset = 0; var path = "/"; // default path to "/" // disambiguate app.use([fn]) if (typeof fn !== "function") { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== "function") { offset = 1; path = fn; } } var fns = flatten(slice.call(arguments, offset)); //鋪平arguments if (fns.length === 0) { throw new TypeError("app.use() requires middleware functions"); } // setup router this.lazyrouter(); //如果沒有route實例則新建一個 var router = this._router; fns.forEach(function (fn) { // non-express app //如果傳入的不是express實例app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); //將中間件注入到router中 } debug(".use app under %s", path); fn.mountpath = path; fn.parent = this; // restore .app property on req and res router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { req.__proto__ = orig.request; res.__proto__ = orig.response; next(err); }); }); // mounted an app fn.emit("mount", this); }, this); return this; };
第255-258行,代理到router實例的route()的方法中:
app.route = function route(path) { this.lazyrouter(); return this._router.route(path); };
router實例是通過router/index.js提供構造函數來創(chuàng)建的,在這個文件夾中第42-60行:
var proto = module.exports = function(options) { var opts = options || {}; function router(req, res, next) { router.handle(req, res, next); //handle方法繼承于proto } // mixin Router class functions router.__proto__ = proto; router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive; router.mergeParams = opts.mergeParams; router.strict = opts.strict; router.stack = []; //初始化一個stack.這個stack中保存了注冊的所有中間件 return router; };
提供了一個router的構造函數,它的原型對象上,第136行提供了proto.handle方法,這個方法的作用就是接收來自http的req和res。
第413行,提供了proto.use方法,正如上面所說的app.use是route.use的代理,這個方法的特殊性就在任何的http請求都會經過在app.use上掛載的中間件,例如現在express4.x已經將很多中間件從自身移除,需要你重新通過npm去安裝,然后在業(yè)務代碼中進行引用,例如使用body-parser中間件:
var express = require("express"); var app = express(); var bodyParser = require("body-parser"); app.use(bodyParser.json());
這樣每次http請求過來的時候首先會經過bodyParser.json()這個中間件,它提供了一個向req添加req.body = {}方法,并傳向下一個中間件的作用。
同時在route.use內部,第439-458行,
for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== "function") { throw new TypeError("Router.use() requires middleware function but got a " + gettype(fn)); } // add the middleware debug("use %s %s", path, fn.name || ""); var layer = new Layer(path, { //新建一個layer,layer上掛載了error_handler和request_handler sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); //route自身會維護一個stack,將每個新建的layer都推入stack當中,這個layer實例最終會對匹配的path,作出error_handle或者request_handle。 }
第477行,proto.route方法提供了一個新建route的方法。
proto.route = function route(path) { var route = new Route(path); //新建一個route,這個Route構建函數內部實現見./route.js,它里面提供了一個空的stack,用以 var layer = new Layer(path, { //新建一個layer,layer的作用見下面的講解 sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); //新建一個route,這個route會維護自身的stack return route; };
var route = require("express").Router(),但是這個方法不同的地方
在于,它會自身維護一個stack,這個stack中有你在這個方法上面定義的所有中間件。同樣,你可以通過這個route掛載對于不同路徑的req, res的處理。
使用的方法:
var express = require("express"); var app = express(); var router = express.Router(); //沒有掛載任何路徑的中間件,通過該路由的每個請求都會執(zhí)行該中間件 router.use(function(req, res, next) { console.log("route.use"); }) router.get("/test", function(req, res, next) { console.log("route.get"); }); //最后需要將這個router掛載到應用 app.use("/", router);
以上部分主要是整個express的中間件的掛載。總結一下:
通過app.use()掛載的中間件最終都代理到了router.use()方法下
router.use()方法,新建一個layer,layer上保存了路徑,默認為"/",及相應的處理方法,并存入這個app維護的stack中。
通過var router = require("express").Router()新建的router路徑級實例,同樣可以掛載不同的中間件,不過最后需要將這個router路由注入到app應用當中:app.use("/", router);
接下來講下當http請求到來的時候,數據的流向: 在你定義中間件的過程中,因為是維護了一個app或者route實例,它們分別都有一個stack。這個stack是FIFO的,因此每當一個請求過來的時候,數據從最開始的定義的中間件開始,一直向下按順序進行傳遞,因此你可以自己定義,當然,你需要調用next()方法。就比如Route.protoype.dispath方法
//將req, res分發(fā)給這個route Route.prototype.dispatch = function dispatch(req, res, done) { var idx = 0; var stack = this.stack; if (stack.length === 0) { return done(); } var method = req.method.toLowerCase(); if (method === "head" && !this.methods["head"]) { method = "get"; } req.route = this; next(); function next(err) { if (err && err === "route") { return done(); } var layer = stack[idx++]; if (!layer) { return done(err); } if (layer.method && layer.method !== method) { //匹配傳入的req請求方式,和layer的method進行對比 return next(err); } //調用layer.handle,用以錯誤處理或者request處理 if (err) { layer.handle_error(err, req, res, next); } else { layer.handle_request(req, res, next); } } };
最后,http請求的處理:
在app或者route實例中,自身有一個stack,這個stack就存放了在掛載中間時新建的layer,每個layer實例都保存了對應的路徑,以及相應的error_handle和request_handle。
謝謝大家看到這里,歡迎大家斧正。
下一篇寫寫express路由的實現。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/80143.html
摘要:為應用增加新的特性和處理新的情況可能都會改變文件的結構。寫一個模板的最佳實踐是,不要在模板中處理數據。在上面這四個文件夾中,主要的測試代碼將是單元測試,這意味著你需要將被測試的代碼與應用分離開來。 前言 Node和Express并不嚴格要求它的應用的文件結構。你可以以任意的結構來組織你的web應用。這對于小應用來說,通常是不錯的,十分易于學習和實驗。 但是,當你的應用在體積和復雜性上都...
摘要:只能在不同的時候選用不同的假設和不同的理論來解釋問題,許來西的文章講到科學一定程度上通過放棄一貫性換取了實用性,放棄自洽性換取了它洽性。然而遺憾的是本身只提供了模塊和洋蔥模型的最小封裝。 在寫干貨之前,我想先探(qiang)討(diao)兩個問題,模式的局限性?模式有什么用? 最近看到一篇文章對我啟發(fā)很大,許來西在知乎的回答《哲學和科學有什么關聯(lián)?》,全篇較長,這里摘錄我要引出的一點:...
摘要:是的源碼,算是一個基本的博客系統(tǒng),包含文章發(fā)布,關注,評論等功能。這些功能可以說是任何一個網站的基礎。比如運營數據配置和其他數據配置分開,因為很有可能需要做一個小的工具來讓非技術人員配置相關參數。模式在中有一個專門的章節(jié)來講解。 1. About 1.1 what: nodeclub是cnodejs.com的源碼,cnode算是一個基本的博客系統(tǒng),包含文章發(fā)布, 關注,評論等功能...
摘要:三的洋蔥模型這里簡單講講在中是如何分層的,也就是說請求到達服務端后如何層層處理,直到響應請求并將結果返回客戶端。從而使得端支持跨域等。 ??最近已經使用過一段時間的nestjs,讓人寫著有一種java spring的感覺,nestjs可以使用express的所有中間件,此外完美的支持typescript,與數據庫關系映射typeorm配合使用可以快速的編寫一個接口網關。本文會介紹一下作...
摘要:三的洋蔥模型這里簡單講講在中是如何分層的,也就是說請求到達服務端后如何層層處理,直到響應請求并將結果返回客戶端。從而使得端支持跨域等。 ??最近已經使用過一段時間的nestjs,讓人寫著有一種java spring的感覺,nestjs可以使用express的所有中間件,此外完美的支持typescript,與數據庫關系映射typeorm配合使用可以快速的編寫一個接口網關。本文會介紹一下作...
閱讀 1476·2023-04-26 00:08
閱讀 809·2021-11-23 18:51
閱讀 1683·2021-11-12 10:34
閱讀 1018·2021-10-14 09:43
閱讀 509·2021-08-18 10:23
閱讀 2589·2019-08-30 15:55
閱讀 3399·2019-08-30 11:05
閱讀 2799·2019-08-29 12:50