Express 簡介
Express 是一個(gè)簡潔而靈活的 node.js Web應(yīng)用框架, 提供了一系列強(qiáng)大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具腿倚。
使用 Express 可以快速地搭建一個(gè)完整功能的網(wǎng)站。
Express官網(wǎng) 也是一個(gè)非常友好的文檔奋刽。
Express 框架核心特性:
- 路由: 定義了路由表用于執(zhí)行不同的 HTTP 請求動(dòng)作备典。
- 中間件: 可以設(shè)置中間件來響應(yīng) HTTP 請求异旧。
- 模板引擎: 可以通過向模板傳遞參數(shù)來動(dòng)態(tài)渲染 HTML 頁面。
這里先從Express路由開始學(xué)習(xí)解析提佣,再繼續(xù)它的其他核心特性進(jìn)行學(xué)習(xí)與探索吮蛹。
安裝 Express (V4.16.2)
npm i express -S
Express簡單使用
1.創(chuàng)建一個(gè)接收get請求的服務(wù)器
// 1.get.js
const express = require('express');
const app = express();
const port = 8080;
// path 是服務(wù)器上的路徑,callback是當(dāng)路由匹配時(shí)要執(zhí)行的函數(shù)拌屏。
app.get('/', function(req,res){ // 注冊一個(gè)get請求路由
res.end('hello express!'); // 結(jié)束響應(yīng)
});
app.listen(port,function(){ // 開啟監(jiān)聽端口
console.log(`server started on port ${port}`);
});
2.執(zhí)行上面代碼
nodemon是一種實(shí)用工具潮针,將為您的源的任何變化并自動(dòng)重啟服務(wù)器監(jiān)控。
$ nodemon 1.get.js
瀏覽器訪問 localhost:8080 結(jié)果如下:
hello express!
若訪問一個(gè)未處理的路由路徑 如localhost:8080/user 結(jié)果如下:
Cannot GET /user 也就是我們常見的404 Not Found
根據(jù)上面測試案例自己實(shí)現(xiàn)一個(gè)簡單express
express簡單實(shí)現(xiàn)
// express-1.0.js
const http = require('http');
const url = require('url');
function createApplication() {
function app(req, res) {
app.handle(req, res);
}
app.routes = []; // 路由容器
app.get = function(path, handle) { // 注冊get方法的路由
app.routes.push({ // 路由表
path,
handle,
method: 'get'
});
}
app.listen = function() { // 創(chuàng)建http服務(wù)器
const server = http.createServer(app);
server.listen.apply(server, arguments);
}
app.handle = function(req, res) { // 路由處理
let { pathname } = url.parse(req.url); // 獲取請求路徑
let ids = 0;
let routes = this.routes; // 獲取路由表
function next() { // next控制路由進(jìn)入下一匹配
if (ids >= routes.length) {
return res.end(`Cannot ${req.method} ${pathname}`);
}
let {path, method, handle} = routes[ids++];
// 進(jìn)行路由匹配
if ((pathname === path || pathname === '*') && method === req.method.toLowerCase()) {
return handle(req, res)
} else { // 如果不匹配 則去下一個(gè)路由匹配
next();
}
}
next();
}
return app;
}
module.exports = createApplication;
導(dǎo)入我們自己的express.js
const express = require('./express-1.0.js');
const app = express();
app.get('/get', function(req, res) {
res.send('hello express');
});
下面我們開始進(jìn)行源碼分析
源碼分析
express源碼目錄結(jié)構(gòu)
路由系統(tǒng)
對于路由中間件倚喂,在整個(gè)個(gè)Router路由系統(tǒng)中stack 存放著一個(gè)個(gè)layer, 通過layer.route 指向route路由對象, route的stack的里存放的也是一個(gè)個(gè)layer每篷,每個(gè)layer中包含(method/handler)瓣戚。
在源碼里面主要涉及到幾個(gè)類和方法
createApplicaton
Application(proto)
Router
Route
Layer
- express()返回一個(gè)app
實(shí)際上express指向內(nèi)部createApplication函數(shù) 函數(shù)執(zhí)行返回app
var mixin = require('merge-descriptors'); // merge-descriptors是第三方模塊,合并對象的描述符
var proto = require('./application'); // application.js中 導(dǎo)出 proto
function createApplicaton() { // express()
var app = function(req, res, next) { // app是一個(gè)函數(shù)
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導(dǎo)出的proto對象焦读, 在router/index.js中 也有一個(gè)命名為proto的函數(shù)對象 掛載了一些靜態(tài)屬性方法 proto.handle proto.param proto.use ...
// router/index.js
var proto = module.exports = function(options) {}
在application.js 導(dǎo)出的proto中 掛載這合并到app上的請求方法子库,源碼如下:
// application.js
// methods是一個(gè)數(shù)組集合[get, post, put ...],里面存放了一系列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(); // 創(chuàng)建一個(gè)Router實(shí)例 this._router = new Router();
var route = this._router.route(path); // 對應(yīng)router/index 中的 proto.route
route[method].apply(route, slice.call(arguments, 1)); // 添加路由中間件 下面詳細(xì)講解
return this;
};
});
this.lazyrouter 用來創(chuàng)建一個(gè)Router實(shí)例 并掛載到 app._router
// application.js
methods.forEach(function(method){
app[method] = function(path){
this.lazyrouter(); // 創(chuàng)建一個(gè)Router實(shí)例 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]添加路由中間件张症,這兩種添加方式都在內(nèi)部調(diào)用了Router的相關(guān)方法來實(shí)現(xiàn):
注冊非路由中間件
// application.js
app.use = function(fn) { // fn 中間件函數(shù) 有可能是[fn]
var offset = 0;
var path = '/';
var fns = flatten(slice.call(arguments, offset)); // 展平數(shù)組
// setup router
this.lazyrouter(); // 創(chuàng)建路由對象
var router = this._router;
fns.forEach(function(fn) {
router.use(path, function mounted_app(req, res, next) { // app.use 底層調(diào)用了router.use
var orig = req.app;
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err);
});
});
}
通過上面知道了 app.use底層調(diào)用了router.use 接下來我們再來看看router.use
// router/index.js
var Layer = require('./layer');
proto.use = function(fn) {
var offset = 0; // 參數(shù)偏移值
var path = '/'; // 默認(rèn)路徑 / 因?yàn)?use
// 第一個(gè)參數(shù)有可能是path 所以要從第二個(gè)參數(shù)開始截取
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
// 展平中間件函數(shù)集合 如中間件函數(shù)是以數(shù)組形式注冊的如[fn, fn] 當(dāng)slice截取時(shí)會(huì)變成[[fn, fn]] 所以需要展平為一維數(shù)組
var callbacks = flatten(slice.call(arguments, offset)); // 截取中間件函數(shù)
for(var i = 0; i < callbacks.length; i++) { // 遍歷每一個(gè)中間件函數(shù)
var fn = callbacks[i];
if (typeof fn !== 'function') { // 錯(cuò)誤提示
throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
}
// 添加中間件
// 實(shí)例化一個(gè)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關(guān)于請求 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(); // 創(chuàng)建Router實(shí)例對象 并賦給this._router
// 調(diào)用this._router.route => router/index.js中的proto.route
var route = this._router.route(path);
route[method].apply(route, slice.call(arguments, 1)); // 調(diào)用router中對應(yīng)的method方法 注冊路由
return this;
};
});
在上面 app[method]中底層實(shí)際調(diào)用了router[method] 也就是 this._router.route[method]
我們看看this._router.route(path); 這段代碼發(fā)生了什么
var route = this._router.route(path); // 里面創(chuàng)建了一個(gè)route實(shí)例 并返回
route[method].apply(route, slice.call(arguments, 1)); // 調(diào)用route中的對象的route[method]
router 中的 this._router.route 創(chuàng)建一個(gè)route對象 生成一個(gè)layer 將path 和 route.dispatch 傳入layer, layer的route指向 route對象 將Router和Route關(guān)聯(lián)來起來, 最后把route對象作為返回值
// router/index.js
var Route = require('./route');
var Layer = require('./layer');
proto.route = function (path) {
var route = new Route(path); // app[method]注冊一個(gè)路由 就會(huì)創(chuàng)建一個(gè)route對象
var layer = new Layer(path, { // 生成一個(gè)route layer
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route)); // 里將生成的route對象的dispatch作為參數(shù)傳給layer里面
// 指向剛實(shí)例化的路由對象(非常重要)俗他,通過該字段將Router和Route關(guān)聯(lián)來起來
layer.route = route;
this.stack.push(layer); // 將layer添加到Router的stack中
return route; // 將生成的route對象返回
}
對于路由中間件脖捻,路由容器中的stack(Router.stack)里面的layer通過route字段指向了路由對象,那么這樣一來兆衅,Router.stack就和Route.stack發(fā)生了關(guān)聯(lián)地沮,關(guān)聯(lián)后的示意模型如下圖所示:
app[method]中 調(diào)用 route[method].apply(route, slice.call(arguments, 1));
// router/index.js
// 實(shí)際上 application.js 中 app[method] 調(diào)用的是 router對象中對應(yīng)的http請求方法 額外添加了一個(gè)all方法
methods.concat('all').forEach(function(method){
proto[method] = function(path){
var route = this.route(path) // 返回創(chuàng)建的route對象
route[method].apply(route, slice.call(arguments, 1)); // router[method] 又調(diào)用了 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)); // 傳遞進(jìn)來的處理函數(shù)
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中也有l(wèi)ayer 里面保存著 method和handle
layer.method = method;
this.methods[method] = true; // 標(biāo)識 存在這個(gè)method的路由處理函數(shù)
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方法標(biāo)識
this.stack.push(layer); // 添加到route的stack中
}
return this;
};
最終路由注冊關(guān)系鏈 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; // 存儲(chǔ)處理函數(shù)
this.regexp = pathRegexp(path, this.keys = [], opts); // 根據(jù)路由路徑 生成路由規(guī)則正則 用來路由匹配
}
Layer.prototype.match = function(path) { // 請求路徑 是否匹配 該層路由路徑規(guī)則this.regexp
}
啟動(dòng)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);
};
路由調(diào)用
app.handle 調(diào)用this._router.handle 進(jìn)行路由處理
express.js
function createApplicaton() {
let app = function(req, res, next) { // 持續(xù)監(jiān)聽請求
app.handle(req, res, next); // 路由處理函數(shù)
}
}
express.js中的app.handle 實(shí)際來自application.js的app.handle 底層調(diào)用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); // 底層調(diào)用router.handle
};
router.handle 調(diào)用 內(nèi)部next函數(shù) 在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) { // 一層層進(jìn)行路由匹配
layer = stack[idx++]; // 從router的stack取出沒一個(gè)layer
match = matchLayer(layer, path); // matchLayer調(diào)用 該layer的match方法進(jìn)行路由路徑進(jìn)行匹配
route = layer.route; // 得到關(guān)聯(lián)的route對象
var method = req.method; // 獲取請求方法
var has_method = route._handles_method(method); // 調(diào)用route的_handles_method返回Boolean值
if (match !== true) { // 如果沒匹配上處理
return done(layerError);
}
if (route) { // 調(diào)用layer的handle_request 處理請求 執(zhí)行handle
return layer.handle_request(req, res, next);
}
}
}
}
路由處理 app.handle => router.handle => layer.handle_request
layer的handle_request 調(diào)用next一次獲取route stack中的處理方法
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle; // 獲取new Layer時(shí)保存的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);
}
};
小結(jié)
app.use用來添加非路由中間件,app[method]添加路由中間件涯保,中間件的添加需要借助Router和Route來完成诉濒,app相當(dāng)于是facade周伦,對添加細(xì)節(jié)進(jìn)行了包裝夕春。
Router可以看做是一個(gè)存放了中間件的容器。對于里面存放的路由中間件专挪,Router.stack中的layer有個(gè)route屬性指向了對應(yīng)的路由對象及志,從而將Router.stack與Route.stack關(guān)聯(lián)起來,可以通過Router遍歷到路由對象的各個(gè)處理程序寨腔。