摘要:框架核心特性路由定義了路由表用于執行不同的請求動作。中間件可以設置中間件來響應請求。注冊一個請求路由結束響應開啟監聽端口執行上面代碼是一種實用工具,將為您的源的任何變化并自動重啟服務器監控。
Express 簡介
Express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你創建各種 Web 應用,和豐富的 HTTP 工具。
使用 Express 可以快速地搭建一個完整功能的網站。
Express官網 也是一個非常友好的文檔。
Express 框架核心特性:
路由: 定義了路由表用于執行不同的 HTTP 請求動作。
中間件: 可以設置中間件來響應 HTTP 請求。
模板引擎: 可以通過向模板傳遞參數來動態渲染 HTML 頁面。
這里先從Express路由開始學習解析,再繼續它的其他核心特性進行學習與探索。安裝 Express (V4.16.2)
npm i express -SExpress簡單使用
1.創建一個接收get請求的服務器
// 1.get.js const express = require("express"); const app = express(); const port = 8080; // path 是服務器上的路徑,callback是當路由匹配時要執行的函數。 app.get("/", function(req,res){ // 注冊一個get請求路由 res.end("hello express!"); // 結束響應 }); app.listen(port,function(){ // 開啟監聽端口 console.log(`server started on port ${port}`); });
2.執行上面代碼
nodemon是一種實用工具,將為您的源的任何變化并自動重啟服務器監控。
$ nodemon 1.get.js 瀏覽器訪問 localhost:8080 結果如下: hello express! 若訪問一個未處理的路由路徑 如localhost:8080/user 結果如下: Cannot GET /user 也就是我們常見的404 Not Found根據上面測試案例自己實現一個express 源碼分析
注冊一個簡單路由
app.get("/get", function(req, res) { res.send("hello express"); });
express源碼目錄結構
路由系統對于路由中間件,在整個個Router路由系統中stack 存放著一個個layer, 通過layer.route 指向route路由對象, route的stack的里存放的也是一個個layer,每個layer中包含(method/handler)。
在源碼里面主要涉及到幾個類和方法
createApplicaton Application(proto) Router Route Layer
express()返回一個app
實際上express指向內部createApplication函數 函數執行返回app
var mixin = require("merge-descriptors"); // merge-descriptors是第三方模塊,合并對象的描述符 var proto = require("./application"); // application.js中 導出 proto function createApplicaton() { // express() var app = function(req, res, next) { // app是一個函數 app.handle(req, res, next); // 處理路由 }; mixin(app, EventEmitter.prototype, false); // 將EventEmitter.prototype的對象屬性合并到app上一份 mixin(app, proto, false); // 將proto中的掛載的一些屬性方法 如 app.get app.post 合并到app上 app.init(); // 初始化 聲明一些私有屬性 return app; }
app上的很多屬性方法都來自application.js導出的proto對象, 在router/index.js中 也有一個命名為proto的函數對象 掛載了一些靜態屬性方法 proto.handle proto.param proto.use ...
// router/index.js var proto = module.exports = function(options) {}
// 在application.js 導出的proto中 掛載這合并到app上的請求方法,源碼如下:
// application.js // methods是一個數組集合,里面存放了一系列http請求方法 通過遍歷給app上過載一系列請求方法,即app.get()、app.post() 等 methods.forEach(function(method){ app[method] = function(path){ if (method === "get" && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); // 創建一個Router實例 this._router = new Router(); var route = this._router.route(path); // 對應router/index 中的 proto.route route[method].apply(route, slice.call(arguments, 1)); // 添加路由中間件 下面詳細講解 return this; }; });
this.lazyrouter 用來創建一個Router實例 并掛載到 app._router
// application.js methods.forEach(function(method){ app[method] = function(path){ this.lazyrouter(); // 創建一個Router實例 this._router = new 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)); } };注冊中間件
可以通過兩種方式添加中間件:app.use用來添加非路由中間件,app[method]添加路由中間件,這兩種添加方式都在內部調用了Router的相關方法來實現:
注冊非路由中間件// application.js app.use = function(fn) { // fn 中間件函數 有可能是[fn] var offset = 0; var path = "/"; var fns = flatten(slice.call(arguments, offset)); // 展平數組 // setup router this.lazyrouter(); // 創建路由對象 var router = this._router; fns.forEach(function(fn) { router.use(path, function mounted_app(req, res, next) { // app.use 底層調用了router.use var orig = req.app; fn.handle(req, res, function (err) { setPrototypeOf(req, orig.request) setPrototypeOf(res, orig.response) next(err); }); }); }
通過上面知道了 app.use底層調用了router.use 接下來我們再來看看router.use
// router/index.js var Layer = require("./layer"); proto.use = function(fn) { var offset = 0; // 參數偏移值 var path = "/"; // 默認路徑 / 因為 use // 第一個參數有可能是path 所以要從第二個參數開始截取 if (typeof arg !== "function") { offset = 1; path = fn; } // 展平中間件函數集合 如中間件函數是以數組形式注冊的如[fn, fn] 當slice截取時會變成[[fn, fn]] 所以需要展平為一維數組 var callbacks = flatten(slice.call(arguments, offset)); // 截取中間件函數 for(var i = 0; i < callbacks.length; i++) { // 遍歷每一個中間件函數 var fn = callbacks[i]; if (typeof fn !== "function") { // 錯誤提示 throw new TypeError("Router.use() requires a middleware function but got a " + gettype(fn)) } // 添加中間件 // 實例化一個layer 對象 var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); // 非路由中間件,該字段賦值為undefined layer.route = undefined; this.stack.push(layer); // 將layer添加到 router.stack } }注冊路由中間件
在上面application.js 中的app對象 添加了很多http關于請求 app[method]就是用來注冊路由中間件
// application.js var app = exports = module.exports = {}; var Router = require("./router"); var methods = require("methods"); methods.forEach(function(method){ app[method] = function(path){ // app上添加 app.get app.post app.put 等請求方法 if (method === "get" && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); // 創建Router實例對象 并賦給this._router // 調用this._router.route => router/index.js中的proto.route var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); // 調用router中對應的method方法 注冊路由 return this; }; });
在上面 app[method]中底層實際調用了router[method] 也就是 this._router.route[method]
我們看看this._router.route(path); 這段代碼發生了什么
var route = this._router.route(path); // 里面創建了一個route實例 并返回 route[method].apply(route, slice.call(arguments, 1)); // 調用route中的對象的route[method]
router 中的 this._router.route 創建一個route對象 生成一個layer 將path 和 route.dispatch 傳入layer, layer的route指向 route對象 將Router和Route關聯來起來, 最后把route對象作為返回值
// router/index.js var Route = require("./route"); var Layer = require("./layer"); proto.route = function (path) { var route = new Route(path); // app[method]注冊一個路由 就會創建一個route對象 var layer = new Layer(path, { // 生成一個route layer sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); // 里將生成的route對象的dispatch作為參數傳給layer里面 // 指向剛實例化的路由對象(非常重要),通過該字段將Router和Route關聯來起來 layer.route = route; this.stack.push(layer); // 將layer添加到Router的stack中 return route; // 將生成的route對象返回 }
對于路由中間件,路由容器中的stack(Router.stack)里面的layer通過route字段指向了路由對象,那么這樣一來,Router.stack就和Route.stack發生了關聯,關聯后的示意模型如下圖所示:
app[method]中 調用 route[method].apply(route, slice.call(arguments, 1));
// router/index.js // 實際上 application.js 中 app[method] 調用的是 router對象中對應的http請求方法 額外添加了一個all方法 methods.concat("all").forEach(function(method){ proto[method] = function(path){ var route = this.route(path) // 返回創建的route對象 route[method].apply(route, slice.call(arguments, 1)); // router[method] 又調用了 route[method] return this; }; });
最后我們來看下 router/route.js 中的Route
// router/route.js function Route(path) { // Route類 this.path = path; this.stack = []; // route的stack debug("new %o", path) this.methods = {}; // 用于各種HTTP方法的路由處理程序 } var Layer = require("./layer"); var methods = require("methods"); // 又是相同的一段代碼 在給Route的原型上添加http方法 methods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments)); // 傳遞進來的處理函數 for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== "function") { var type = toString.call(handle); var msg = "Route." + method + "() requires a callback function but got a " + type throw new Error(msg); } debug("%s %o", method, this.path) var layer = Layer("/", {}, handle); // 在route中也有layer 里面保存著 method和handle layer.method = method; this.methods[method] = true; // 標識 存在這個method的路由處理函數 this.stack.push(layer); // 將layer 添加到route的stack中 } return this; // 將route對象返回 }; });
Route 中的all方法
Route.prototype.all = function all() { var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== "function") { var type = toString.call(handle); var msg = "Route.all() requires a callback function but got a " + type throw new TypeError(msg); } var layer = Layer("/", {}, handle); layer.method = undefined; // all 匹配所以方法 this.methods._all = true; // all方法標識 this.stack.push(layer); // 添加到route的stack中 } return this; };
最終路由注冊關系鏈 app[method] => router[method] => route[method] 最終在route[method]里完成路由注冊
接下來我們看看Layer// route/layer.js var pathRegexp = require("path-to-regexp"); module.exports = Layer; function Layer(path, options, fn) { if (!(this instanceof Layer)) { return new Layer(path, options, fn); } this.handle = fn; // 存儲處理函數 this.regexp = pathRegexp(path, this.keys = [], opts); // 根據路由路徑 生成路由規則正則 用來路由匹配 } Layer.prototype.match = function(path) { // 請求路徑 是否匹配 該層路由路徑規則this.regexp }啟動server
// application.js var http = require("http"); app.listen = function listen() { var server = http.createServer(this); // this => express.js 中的 app return server.listen.apply(server, arguments); }; // express.js 中的 app var app = function(req, res, next) { app.handle(req, res, next); };路由調用
app.handle 調用this._router.handle 進行路由處理
express.js function createApplicaton() { let app = function(req, res, next) { // 持續監聽請求 app.handle(req, res, next); // 路由處理函數 } }
express.js中的app.handle 實際來自application.js的app.handle 底層調用router.handle
// application.js app.handle = function handle(req, res, callback) { var router = this._router; // final handler var done = callback || finalhandler(req, res, { env: this.get("env"), onerror: logerror.bind(this) }); router.handle(req, res, done); // 底層調用router.handle };
router.handle 調用 內部next函數 在router的stack中尋找匹配的layer
// router/index.js function matchLayer(layer, path) { try { return layer.match(path); } catch (err) { return err; } } proto.handle = function(req, res, done) { // middleware and routes var stack = self.stack; // router的stack 里面存放著中間件和路由 var idx = 0; next(); function next(err) { if (idx >= stack.length) { // 邊界判斷 setImmediate(done, layerError); return; } // 獲取請求路徑 var path = getPathname(req); var layer; var match; var route; while (match !== true && idx < stack.length) { // 一層層進行路由匹配 layer = stack[idx++]; // 從router的stack取出沒一個layer match = matchLayer(layer, path); // matchLayer調用 該layer的match方法進行路由路徑進行匹配 route = layer.route; // 得到關聯的route對象 var method = req.method; // 獲取請求方法 var has_method = route._handles_method(method); // 調用route的_handles_method返回Boolean值 if (match !== true) { // 如果沒匹配上處理 return done(layerError); } if (route) { // 調用layer的handle_request 處理請求 執行handle return layer.handle_request(req, res, next); } } } }
路由處理 app.handle => router.handle => layer.handle_request
layer的handle_request 調用next一次獲取route stack中的處理方法
Layer.prototype.handle_request = function handle(req, res, next) { var fn = this.handle; // 獲取new Layer時保存的handle // function Layer() { // this.handle = fn; // } if (fn.length > 3) { // not a standard request handler return next(); } try { fn(req, res, next); } catch (err) { next(err); } };小結
app.use用來添加非路由中間件,app[method]添加路由中間件,中間件的添加需要借助Router和Route來完成,app相當于是facade,對添加細節進行了包裝。
Router可以看做是一個存放了中間件的容器。對于里面存放的路由中間件,Router.stack中的layer有個route屬性指向了對應的路由對象,從而將Router.stack與Route.stack關聯起來,可以通過Router遍歷到路由對象的各個處理程序。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93665.html
摘要:學習的源代碼的好處自然不少。閱讀源代碼可以幫你實現你的好奇心。本文會推薦一些的源代碼分析文章,可以幫助更快的,更加全方位的理解研讀之。 盡管有Hapi,Koa等有力的競爭者,express.js依然是非常流行的nodejs web服務器框架,畢竟它早于2007年就已經在開發了。 學習expressjs的源代碼的好處自然不少。 它可以幫你深刻理解HTTP協議,這個協議是做前端后端都必然需...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:應用的功能這個應用是一個個人簡歷生成器。比較好的教程有這一個。這樣的命名污染問題自然顯而易見。而且發出多次請求也會影響性能。明顯不利于維護。然而我希望能夠不發生變化,因為是在文件的前提下的標簽頁,不能換一個標簽就重建一個。 為什么學習backbone?這是個好問題。在這個前端框架爆炸的年代,比起backbone,對開發來說有更多更好的選擇,react,vue,angular等等。但這些...
閱讀 1457·2023-04-25 19:00
閱讀 4158·2021-11-17 17:00
閱讀 1771·2021-11-11 16:55
閱讀 1530·2021-10-14 09:43
閱讀 3132·2021-09-30 09:58
閱讀 860·2021-09-02 15:11
閱讀 2130·2019-08-30 12:56
閱讀 1408·2019-08-30 11:12