關於Node JS 的後端框架,無論是Express
,Koa
, 甚至Eggjs
(Eggjs 是基於Koa 底層封裝的框架),都是基於NodeJS 的http
模塊進行處理的,其最重要的是方法是git
const server = http.createServer((rep, res) => {
res.end('hello world')
})
server.listen(prot, () => {
console.log('Server Started. Port: '+ prot)
})
複製代碼
不管那個框架的中間件, 路由等的處理,都是從這裏是一個入口,對服務器的資源的任何訪問都會先進入http.createServer
方法的回調函數,也就是下面的方法github
(rep, res) => {
res.end('hello world')
}
複製代碼
以前咱們已經分析過了Eggjs 框架,可是沒有分析Nodejs實現後端框架實現的底層原理,由於Eggjs 是基於Koa 實現的一個上層框架, 咱們此次來經過Express來分析下Nodejs 實現後端框架的底層原理。express
咱們先從express clone 一份源碼,其對應的lib 文件夾就是express
框架的整個源碼json
其最重要的幾個文件是:後端
createApplication
方法)(重點)createApplication
方法,返回的app 對象去掛載不少方法)(重點)http.createServer
方法中的rep 和 res 進行相應的封裝處理咱們下面根據啓動服務* 和 ** 訪問服務 兩個流程來分析express
, 會針對上面標註爲(重點)的相應的文件,進行詳細的分析.數組
咱們先從怎麼使用開始,做爲入口,下面是一個簡單的express的demo.瀏覽器
const express = require('./lib/express')
const app = module.exports = express()
app.get('/', (req, res) => {
res.end('hello world')
})
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000')
}
複製代碼
上面一段簡單的代碼,咱們就已經搭建好了一個後端服務,當咱們用瀏覽器打開http://localhost:3000/
時,就會顯示hello world.
,下面咱們就來看看是怎麼實現的.緩存
const app = module.exports = express()
能夠看出express()
應該是express.js 文件裏面暴露出來的一個方法, 其對應的腳本是: exports = module.exports = createApplication;
createApplication
方法以下:bash
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);// 合併prototype
mixin(app, proto, false);// 合併proto
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.init();
return app;
}
複製代碼
這個方法,返回一個app 對象, 這個對象至關於繼承與EventEmitter.prototype
和一個proto
對象原型, 而後執行了app.init()
方法,這個方法主要是作一些初始化工做並清空cache
, engines
, settings
, 而且去初始化一些配置,好比說:服務器
this.enable('x-powered-by');
this.set('etag', 'weak');
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
複製代碼
this.set
設置的值是保存在settings中的
, 好比咱們咱們能夠this.settings['x-powered-by']
能夠在應用中任何的地方去調用.因此這裏有一個擴展出一個應用:
const express = require('./lib/express');
const app = module.exports = express();
app.set('config', {
url: 'http://localhost:8080',
userInfo: {
name: 'ivan fan',
age: 18
}
})
app.get('/', (req, res) => {
const config = app.get('config')
console.log(config)
res.end('hello world')
})
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
複製代碼
上面咱們經過app.set
去設置一個config
的值,咱們在其餘的地方能夠經過app.get
去獲取這個值,這樣看起來感受沒有什麼用途,由於咱們能夠直接定義一個變量就能夠,不必經過app.set
, 可是若是咱們的應用很大的時候,咱們將項目拆分紅了不少單獨的文件,咱們只是共享了app
對象,可是在多個js文件中可能須要公用一個全局的配置,咱們能夠建立一個config.json文件,在不一樣的頁面都去import 進來,可是若是若是咱們在app.js
中將這個配置注入到app
中其餘的地方,只要經過app.get
就能夠達到共享的做用。
總結:
createApplication
方法, 而且返回了一個app
對象上面咱們已經分析了express.js
文件,知道其返回了一個app
對象,可是咱們至今位置沒有看到哪裏定義了listen
和get
方法。
咱們在上面分析發現,執行了mixin(app, proto, false);
方法,這個是在app 原型上去添加了另一個原型,而proto
指向的就是application.js
文件, 下面咱們就來具體分析這個文件。
首先咱們找到listen 方法,其代碼以下:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
複製代碼
這個就是咱們在一開始說的,全部的Nodejs 後端框架都是基於http 這個模塊的實現的,因此這個裏咱們就已經實現了一個後端的服務。
在咱們的demo 中,咱們有調用一個app.get
方法,其代碼以下:
app.get('/', (req, res) => {
const config = app.get('config')
console.log(config)
res.end('hello world')
})
複製代碼
可是咱們找遍了整個application.js
文件,都沒有找到這個方法在哪裏實現的, get
只是http
請求衆多方法的其中一個, http方法,還有'post','put','delete'等一些列方法,爲了簡潔,express 引用了第三方庫methods, 這個庫幾乎涵蓋了http 請求的常見方法,因此經過循環去給app
掛載不一樣的方法(Koa 也是這樣處理)
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
this.lazyrouter();
var route = this._router.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
複製代碼
首先this.lazyrouter();
方法是去給app
對象掛載一個_router
的路由(Router)屬性, 而後咱們在看下route
方法:
proto.route = function route(path) {
var route = new Route(path);
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
return route;
};
複製代碼
從上面的代碼可知,var route = this._router.route(path);
, route
也就是this.stack 中的layer
中的reoute
, 因此最後回調函數是掛載在stack 的layer 上面的。
route[method].apply(route, slice.call(arguments, 1));
將app.get
的回調函數掛載在route
屬性上面,其代碼以下(刪除異常處理代碼):
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];
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
複製代碼
咱們先不具體分析代碼邏輯, 咱們能夠根據上面的圖片,分析下app對象下的一個數據結構:
_router
屬性, (router/index.js)_router
下面有一個stack
的屬性,其是一個數組stack
數組中,保存的都是一個Layer
類型的對象Layer
對象中又掛載了一個route
(Route)的對象route
對象保存了path
(path:/abc), methods
, 一樣也有一個stack
的屬性,也是一個數組, 一樣裏面保存的也是一個Layer
對象Layer
裏面掛載了一個重要的屬性handle
, 其實從如今的分析看,這個handle
就是咱們app.get
方法的第二個回調函數參數.上面咱們已經分析了express
啓動的過程,下面咱們來分析訪問服務express
處理的過程,也就是咱們訪問http://localhost:3000/
時,express
到底作了些什麼.
從一開始,咱們就知道,對服務器的方法,首先都會進入http.createServer
的回調函數,並且express
是經過listen
方法,執行這個方法的
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
複製代碼
其中this
就是app
實例,也就是在express.js
文件中定義的,以下:
var app = function(req, res, next) {
app.handle(req, res, next);
};
複製代碼
在這個方法中,會調用handle
方法,下面咱們來分析下這個方法
handle
的代碼以下:
app.handle = function handle(req, res, callback) {
var router = this._router;
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
複製代碼
而後會執行router.handle(req, res, done);
, 在上面咱們已經得知, this._router
指向的是router/index.js
這個文件夾的對象,下面咱們進入到這個handle
方法中, 這個方法很長,可是其實就是根據咱們訪問的路徑來查找對應的Layer
, 其關鍵代碼是:
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
...
}
複製代碼
經過matchLayer(layer, path);
去匹配layer. 找到Layer
後,而後去執行layer.handle_request(req, res, next);
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
複製代碼
var fn = this.handle;
這個fn
其實指向的就是app.get
裏面的第二參數,也就是回調函數,
app.get('/', (req, res) => {
res.end('hello world')
})
複製代碼
而後就至關於請求完成了。
上面咱們已經分析了,Express 在啓動的整個過程,主要是進行數據的一些加載處理和路由的處理,並且也分析了咱們在請求Server時的整個過程。
後續我會繼續分析use 的用法,而且針對express.static 源碼來分析Express 中間件的處理和總結中間件的使用方式,以及express.static
對緩存的處理(Etag
, Last-Modified
)