Express是一基於Node的一個框架,用來快速建立Web服務的一個工具,爲何要使用Express呢,由於建立Web服務若是從Node開始有不少繁瑣的工做要作,而Express爲你解放了不少工做,從而讓你更加關注於邏輯業務開發。舉個例子:html
建立一個很簡單的網站:node
1. 使用Node來開發:express
var http = require('http'); var url = require("url"); http.createServer(function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); var url_str = url.parse(req.url,true); res.end('Hello World\n' + url_str.query); }).listen(8080, "127.0.0.1"); console.log('Server running at http://127.0.0.1:8080/');
這是一個簡單的 hello world,運行之後訪問http://127.0.0.1會打印相關字符串,這是最普通的頁面,但實際上真正的網站要比這個複雜不少,主要有:數組
(1) 多個頁面的路由功能app
(2) 對請求的邏輯處理框架
那麼使用node原生寫法就要進行如下處理函數
// 加載所需模塊 var http = require("http"); // 建立Server var app = http.createServer(function(request, response) { if(request.url == '/'){ response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Home Page!\n"); } else if(request.url == '/about'){ response.writeHead(200, { "Content-Type": "text/plain" }); response.end("About Page!\n"); } else{ response.writeHead(404, { "Content-Type": "text/plain" }); response.end("404 Not Found!\n"); } }); // 啓動Server app.listen(1984, "localhost");
代碼裏在createServer函數裏傳遞一個回調函數用來處理http請求並返回結果,在這個函數裏有兩個工做要作:工具
(1)路由分析,對於不一樣的路徑須要進行分別處理post
(2)邏輯處理和返回,對某個路徑進行特別的邏輯處理學習
在這裏會有什麼問題?若是一個大型網站擁有海量的網站(也就是路徑),每一個網頁的處理邏輯也是交錯複雜,那這裏的寫法會很是混亂,無法維護,爲了解決這個問題,TJ提出了Connect的概念,把Java裏面的中間件概念第一次進入到JS的世界,Web請求將一個一個通過中間件,並經過其中一箇中間件返回,大大提升了代碼的可維護性和開發效率。
// 引入connect模塊 var connect = require("connect"); var http = require("http"); // 創建app var app = connect(); // 添加中間件 app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello world!\n"); });
// 啓動應用 http.createServer(app).listen(1337);
可是TJ認爲還應該更好一點,因而Express誕生了,經過Express開發以上的例子:
2. 使用Express來開發:
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); });
app.get('/about', function (req, res) { res.send('About'); });
var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
從Express例子能夠看出,使用Express大大減小了代碼函數,並且邏輯更爲簡潔,因此使用Express能夠提升開發效率並下降工程維護成本。
首先Express有幾個比較重要的概念:路由,中間件和模版引擎
開發人員能夠爲Web頁面註冊路由,將不一樣的路徑請求區分到不一樣的模塊中去,從而避免了上面例子1所說的海量路徑問題,例如
var express = require("express"); var http = require("http"); var app = express(); app.all("*", function(request, response, next) { response.writeHead(404, { "Content-Type": "text/plain" }); next(); }); app.get("/", function(request, response) { response.end("Welcome to the homepage!"); }); app.get("/about", function(request, response) { response.end("Welcome to the about page!"); }); app.get("*", function(request, response) { response.end("404!"); }); http.createServer(app).listen(1337);
開發人員能夠爲特定的路由開發中間件模塊,中間件模塊能夠複用,從而解決了複雜邏輯的交錯引用問題,例如
var express = require('express'); var app = express(); // 沒有掛載路徑的中間件,應用的每一個請求都會執行該中間件 app.use(function (req, res, next) { console.log('Time:', Date.now()); next(); }); // 掛載至 /user/:id 的中間件,任何指向 /user/:id 的請求都會執行它 app.use('/user/:id', function (req, res, next) { console.log('Request Type:', req.method); next(); }); // 路由和句柄函數(中間件系統),處理指向 /user/:id 的 GET 請求 app.get('/user/:id', function (req, res, next) { res.send('USER'); }); var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
同時Express對Request和Response對象進行了加強,添加了不少工具函數。
其中路由和中間件還有不少細節問題,能夠參考http://www.expressjs.com.cn/來學習
下面咱們來看看Express的工做原理
咱們首先來看看Express的源碼結構:
簡單介紹下:
Middleware:中間件
init.js 初始化request,response
query.js 格式化url,將url中的rquest參數剝離, 儲存到req.query中
Router:路由相關
index.js: Router類,用於存儲中間件數組
layer.js 中間件實體類
route.js route類,用於處理不一樣Method
Application.js 對外API
Express.js 入口
Request.js 請求加強
Response.js 返回加強
Utils.js 工具函數
View.js 模版相關
如今看不明白不要緊,能夠先看看後面的解釋而後再回頭看就明白了:
咱們前面有說道路由和中間件,那麼咱們就須要有地方來保存這些信息,好比路由信息,好比中間件回調函數等等,express中有一個對象Router對象專門用來存儲中間件對象,他有一個數組叫stack,保存了全部的中間件對象,而中間件對象是Layer對象。
Router對象就是router/index.js文件,他的代碼是:
Router對象的主要做用就是存儲中間件數組,對請求進行處理等等。
Layer對象在router/layer.js文件中,是保存中間件函數信息的對象,主要屬性有:
源碼見:
這裏面的細節先很少考慮,只須要了解關鍵的信息path,handler和route
handler是保存中間件回調函數的地方,path是路由的url,route是一個指針,指向undefined或者一個route對象,爲什麼會有兩種狀況呢,是由於中間件有兩種類型:
(1)普通中間件:普通中間件就是不論是什麼請求,只要路徑匹配就執行回調函數
(2)路由中間件:路由中間件就是區分了HTTP請求的類型,好比get/post/put/head 等等(有幾十種)類型的中間件,就是說還有區分請求類型才執行。
因此有兩種Layer,一種是普通中間件,保存了name,回調函數已經undefined的route變量。
另一種是路由中間件,除了保存name,回調函數,route還會建立一個route對象:
route對象在router/route.js文件中,
咱們看到route對象有path變量,一個methods對象,也有一個stack數組,stack數組其實保存的也是Layer對象,這個Layer對象保存的是對於不一樣HTTP方法的不一樣中間件函數(handler變量)。
也許你會問,這個route的數組裏面的Layer和上面router的數組裏面的Layer有何不一樣,他們有一些相同之處也有一些不一樣之處,主要是由於他們的做用不一樣:
相同之處:他們都是保存中間件的實例對象,當請求匹配到指定的中間件時,該對象實例將會觸發。
不一樣之處:
Router對象的Layer對象有route變量,若是爲undefined表示爲普通中間件,若是指向一個route對象表示爲路由中間件,沒有method對象。而route對象的Layer實例是沒有route變量的,有method對象,保存了HTTP請求類型。
因此Router對象中的Layer對象是保存普通中間件的實例或者路由中間件的路由,而route對象中的Layer是保存路由中間件的真正實例。
咱們來看個例子,加入有段設置路由器的代碼:
app.use("/index.html",function(){ //此處省略一萬行代碼});
app.use("/contract.html",function(){ //此處省略一萬行代碼});
app.get("/index.html",function(){ //此處省略一萬行代碼});
app.post("/index.html",function(){ //此處省略一萬行代碼});
app.get("/home.html",function(){ //此處省略一萬行代碼});
代碼中註冊了2個普通中間件about.html和contract.html,兩個路由中間件,index.html和home.html,對index.html有get和post兩種中間件函數,對home.html只有get中間件函數,在內存中存儲的形式就是:
咱們上面看到了幾種註冊中間件的方式,下面就來介紹下路由器的幾個動做邏輯:
route對象:
router.METHOD(path,callback);//METHOD是HTTP請求方法(get/post等),他的實現過程在這裏:
methods變量是一個數組包含了幾十個http請求類型,這段代碼給route對象添加了幾十個方法,主要邏輯就是建立一個Layer對象,保存中間件函數對象和Method方法,添加到route的stack數組中去。
咱們再來看看Router對象的方法:
proto.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate router.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 callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) { throw new TypeError('Router.use() requires middleware functions'); } 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 || '<anonymous>'); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this; };
這個就是app.use的實現方法,實際上app.use就是調用了router.use,後面詳細介紹,先看看這個方法作了什麼,當咱們調用app.use(function(){XXX});的時候,這裏的函數首先判斷了參數類型,看看有沒有path傳遞進來,沒有path就是"/"有的話保存到path變量,而後對後面的全部中間件函數進行了如下處理:
建立了一個layer對象,保存了路徑,中間件函數而且設置了route變量爲undefined,最後把這個變量保存到router的stack數組中去,到此一個普通中間件函數建立完成,爲什麼要設置route變量爲undefined,由於app.use建立的中間件確定是普通中間件,app.METHOD建立的纔是路由中間件。
當我面調用app.get("",function(){XXX})的時候調用的實際上是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; };
route方法也建立了一個layer對象,可是由於自己是路由中間件,因此還會建立一個route對象,而且保存到layer的route變量中去。
如今咱們總結一下:
1. route對象的主要做用是建立一個路由中間件,而且建立多個方法的layer保存到本身的stack數組中去。
2. router對象的主要做用是建立一個普通中間件或者路由中間件的引導着(這個引導着Layer對象連接到一個route對象),而後將其保存到本身的stack數組中去。
因此route對象的stack數組保存的是中間件的方法的信息(get,post等等)而router對象的stack數組保存的是路徑的信息(path)
好了,說完了這些基礎組件,下面說一下真正暴露給開發者的對外接口,很顯然剛纔說的都是內部實現細節,咱們開發者一般不須要了解這些細節,只須要使用application提供的對外接口。
application在application.js文件下,主要保存了一些配置信息和配置方法,而後是一些對外操做接口,也就是咱們說的app.use,app.get/post等等,有幾個重要的方法:
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)); if (fns.length === 0) { throw new TypeError('app.use() requires middleware functions'); } // setup router this.lazyrouter(); var router = this._router; fns.forEach(function (fn) { // non-express app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); } 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; };
咱們看到app.use在進行了一系列的參數處理後,最終調用的是router的use方法建立一個普通中間件。
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; }; });
同route同樣,將全部的http請求的方法建立成函數添加到application對象中去,從而可使用app.get/post/等等,最終的效果是調用router的route方法建立一個路由中間件。
全部的方法再經過express入口文件暴露在對外接口中去。而middleware中的兩個文件是對application作的一些初始化操做,request.js和response.js是對請求的兩個對象的一些加強。
到此咱們基本瞭解了express的建立路由和中間件函數的基本原理,下一篇咱們來了解如何調用這些路由和中間件函數。