在Express學(xué)習(xí) - hello world,知道了Express是在Node.js的基礎(chǔ)之上對(duì)Node.js的http模塊更方便的封裝,利用express創(chuàng)建一個(gè)server,在響應(yīng)各種http/https請(qǐng)求上,代碼實(shí)現(xiàn)相對(duì)精簡(jiǎn)了很多,看起來(lái)確是也直觀了許多。
下面是剛開始學(xué)習(xí)express時(shí),實(shí)現(xiàn)的一個(gè)hello world
:
var express = require('express') ;
var app = express() ; //得到express實(shí)例,類似 new express()
app.get('/',function(req,res) {
res.send('hello world!') ;
}) ;
var server = app.listen(8081,function() {
var host = server.address().address ;
var port = server.address().port ;
console.log('server has started at http:// %s:%s', host,port) ;
}) ;
上面的代碼,只能攔截/
請(qǐng)求,其他請(qǐng)求都不能得到處理,比如/start
等,所以這個(gè)servlet
功能還不完善,可以使用express的中間件注冊(cè)函數(shù)use
來(lái)對(duì)缺失的handle
進(jìn)行處理。如:
var express = require('express') ;
var app = express() ;
app.get('/',function(req,res) {
res.send('hello world!') ;
}) ;
//默認(rèn)handle
app.use(function(req,res) {
console.log(req,res) ;
res.send('default hander.') ;
}) ;
var server = app.listen(8081,function() {
var host = server.address().address ;
var port = server.address().port ;
console.log('server has started at http://%s:%s', host,port) ;
}) ;
這樣,如果沒(méi)有一個(gè)handle
能處理這個(gè)請(qǐng)求,則交給默認(rèn)handle
來(lái)處理。這是一個(gè)兜底的handle
。這樣就不會(huì)出現(xiàn)Cannot GET /start
錯(cuò)誤了。
中間件注冊(cè)函數(shù)use()
express的中間件的作用更像是spring的aop,類似一個(gè)切面,在我理解,express的中間件(Middleware) 也有前置和后置的區(qū)分,比如上面的例子就可以算作是一個(gè)后置通知。
在express中,中間件是一個(gè)處理函數(shù),例如:
app.use(function(req,res,next) {
res.send('404,unknown request') ;
// next() ;
}) ;
中間件函數(shù)接收三個(gè)參數(shù):
- request對(duì)象 ;
- response對(duì)象 ;
- next回調(diào)函數(shù); 它具有傳遞性,一個(gè)中間件執(zhí)行完畢,可以將參數(shù)(req,res)傳遞給下一個(gè)中間件執(zhí)行,它們是串行的。直到調(diào)用結(jié)束,否則會(huì)一直調(diào)用下去。這是
express/lib/router/route.js
中關(guān)于next
實(shí)現(xiàn)的一段代碼:
//express router next 源碼實(shí)現(xiàn)
/**
* dispatch req, res into this route
* @private
*/
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) {
return next(err);
}
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
使用use
注冊(cè)的中間件都會(huì)被保存到當(dāng)前路由對(duì)象的stack
然后順序取出執(zhí)行,如:
/**
* Initialize `Route` with the given `path`,
*
* @param {String} path
* @public
*/
function Route(path) {
this.path = path;
this.stack = [];
debug('new %s', path);
// route handlers for various http methods
this.methods = {};
}
在next
函數(shù)中,參數(shù)用于來(lái)handle異常,如果傳遞給next函數(shù)一個(gè)有效參數(shù)('route'除外),則標(biāo)識(shí)這是一個(gè)錯(cuò)誤handle
,從而進(jìn)入錯(cuò)誤處理流程,而且該錯(cuò)誤會(huì)一直傳遞下去,一直拋給最后一個(gè)中間件,如果沒(méi)有手動(dòng)捕獲該錯(cuò)誤的情況下。
如果沒(méi)有顯式的將參數(shù)傳遞給next()
函數(shù),則表示調(diào)用下一個(gè)中間件。
var express = require('express') ;
var app = express() ;
app.get('/',function(req,res) {
res.send('Helo, World') ;
}) ;
app.use(function(req,res,next) {
console.log('Time : ',Date.now()) ;
next() ;// continue
}) ;
app.use(function(req,res) {
res.send('404,unknown request') ;
// next() ;
}) ;
app.listen(8081,function() {
console.log('server has started.') ;
}) ;
每次請(qǐng)求都會(huì)首先經(jīng)過(guò)
app.use(function(req,res,next) { console.log('Time : ',Date.now()) ; next() ;// continue }) ;
中間件打印當(dāng)前調(diào)用時(shí)間,然后進(jìn)入
app.get('/',function(req,res) { res.send('Helo, World') ; }) ;
或
app.use(function(req,res) { res.send('404,unknown request') ; // next() ; }) ;
這跟Filter
很像。命令行窗口輸出:
server has started.
Time : 1477445808204
Time : 1477445877547
Time : 1477445877764
這些中間件的執(zhí)行順序和代碼的順序是一致的,例如:
var express = require('express') ;
var app = express() ;
app.get('/',function(req,res) {
res.send('Helo, World') ;
}) ;
app.use(function(req,res,next) {
console.log('pre fun1') ;
next() ;// continue
}) ;
app.use(function(req,res,next) {
console.log('Time : ',Date.now()) ;
next() ;// continue
}) ;
app.use(function(req,res) {
res.send('404,unknown request') ;
// next() ;
}) ;
app.listen(8081,function() {
console.log('server has started.') ;
}) ;
輸出:
server has started.
pre fun1
Time : 1477446103655
pre fun1
Time : 1477446149579
pre fun1
Time : 1477446149797
下一個(gè)中間件的調(diào)用完全依賴next
,如果沒(méi)有next
調(diào)用,則request和response對(duì)象將不會(huì)繼續(xù)傳遞下去。這個(gè)next
有點(diǎn)像java linklist的next[Entry]
對(duì)象,指向下一個(gè)Entry
對(duì)象。
之前使用get
掛載中間件的方式用use
也可以,使用use
掛載中間件函數(shù)然后在use
函數(shù)內(nèi)部對(duì)資源請(qǐng)求path進(jìn)行判斷,這適合針對(duì)某一類資源的綜合處理,如:
app.use(function(req,res,next) {
if (req.url === '/') {
console.log('/') ;
res.send('welcom visit xxx ') ;
}else {
next() ;
}
}) ;
app.use(function(req,res,next) {
//
if (req.url === '/about') {
console.log('/about') ;
res.send('i\\'m a cc') ;
}else {
next() ;
}
}) ;
...........
app.use(function(req,res) {
res.send('404,unknown request') ;
// next() ;
}) ;
這樣,也可以對(duì)/
和/about
不同的請(qǐng)求進(jìn)行有效的處理。use
函數(shù)也可以像get/post..
等http方法一樣使用,如:
app.use('/',function(req,res,next) {
if (req.url === '/') {
console.log('/') ;
res.send('welcom visit xxx ') ;
}else {
next() ;
}
}) ;
app.use('/about',function(req,res,next) {
//
if (req.url === '/about') {
console.log('/about') ;
res.send('i\\'m a cc') ;
}else {
next() ;
}
}) ;
下面的中間件永遠(yuǎn)也執(zhí)行不到,因?yàn)檎?qǐng)求路徑不能匹配到/about
,只能匹配/about/about
。可以使用use
有針對(duì)性的對(duì)某一個(gè)路由進(jìn)行切面處理。例如:
app.use('/blog',function(req,res,next){
//do something
next() ;
});
另外,express的all
函數(shù)也可以做到use
的功能,它是use
的別名實(shí)現(xiàn)。在/route.js中實(shí)現(xiàn)是這樣的:
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 callback functions but got a ' + type;
throw new TypeError(msg);
}
var layer = Layer('/', {}, handle);
layer.method = undefined;
this.methods._all = true;
this.stack.push(layer);
}
return this;
};
和use
都一樣的操作,都是將中間件函數(shù)push到 Router對(duì)象的 stack數(shù)組中,方便next函數(shù)
分發(fā)。
express 的use
函數(shù)在沒(méi)有指定path的情況會(huì)默認(rèn)將中間件函數(shù)掛載到/
,利用這個(gè)特性解決了默認(rèn)請(qǐng)求攔截的問(wèn)題。
express 路由支持
路由, 由一個(gè)資源path和一個(gè)或若干個(gè)資源處理函數(shù)組成。在express中它的結(jié)構(gòu)定義為:
app.METHOD(path, [callback...], callback)
這里的app是express的一個(gè)實(shí)例,METHOD
和HTTP的METHOD含義是一致的。
-
path
為URI. -- 統(tǒng)一資源標(biāo)識(shí),例如:/blog/about
。
URI
和URL
:
- URI(Universal Resource Identifier/統(tǒng)一資源標(biāo)識(shí)) 它是指一個(gè)資源域或者某一確定的資源, 它著重強(qiáng)調(diào)資源。例如: /example/blog/page1/
- URL(Uniform Resource Locator/統(tǒng)一資源定位器) 它完整的描述了整個(gè)資源的絕對(duì)路徑,它包含以下三方面內(nèi)容:
- scheme: http:// 、https://、ftp://等
- address: 例如: www.lxweimin.com
- 資源定位: /notebooks/6732245
從這個(gè)意義上來(lái)說(shuō),URL是URI的一個(gè)子類,URI更像是URL的一個(gè)規(guī)范,URL更具體更準(zhǔn)確的指定某一資源位置。在java中,通過(guò)Request對(duì)象獲取URL信息和URI信息是這樣的,例如這樣一個(gè)請(qǐng)求: http:localhost:8080/webapp/xx/abc.do?type=42 :
request.getRequestURI() ; ///webapp/xx/abc.do
request.getRequestURL() ;//http:localhost:8080/webapp/xx/abc.do
request.getQueryString() ;// 返回請(qǐng)求參數(shù)鍵值對(duì)。type = 42
-
callback
為處理請(qǐng)求資源的回調(diào)函數(shù),可以是一個(gè)中間件組,也可以組合使用。
express路由針對(duì) get
、post
等http行為有不同的函數(shù)支持如:
router.get('/',function(req,res) {
res.send('hello welcom') ;
}) ;
router.post('/',function(req,res) {
res.send(' post : hello welcom') ;
}) ;
Express 定義了如下和 HTTP 請(qǐng)求對(duì)應(yīng)的路由方法:
get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify
, unsubscribe, patch, search, 和 connect 。
上面類是m-search
http方法需要使用括號(hào)記法,如:
app['m-search']('/',function(req,res) {
res.send('xxxxxxxxxx') ;
}) ;
因?yàn)樵趈avascript語(yǔ)法中,讀寫不符合javascript變量命名規(guī)則[1]的屬性時(shí),需要使用類似數(shù)組訪問(wèn)方式,如:
var obj = {} ;
obj['mid-school'] = 'xxx中學(xué)' ;
//訪問(wèn)也需要如此
console.log(obj['mid-school']) ;
express中一個(gè)路由可以被多個(gè)回調(diào)函數(shù)所處理,如:
app.use('/',[f1,f2,f3],function(req,res,next) {
//
if (req.url === '/about') {
console.log('/about') ;
res.send('well ...') ;
}else {
// console.log('req.url',req.url) ;
next() ;
}
}) ;
function f1(eq,res,next) {
//
console.log('----f1') ;
next() ;
}
function f2(eq,res,next) {
//
console.log('----f2') ;
next() ;
}
function f3(eq,res,next) {
//
console.log('----f3') ;
next() ;
}
不過(guò)需要注意next
關(guān)聯(lián),業(yè)務(wù)沒(méi)有處理完畢之前不能缺失next
回調(diào)函數(shù),否則,業(yè)務(wù)流將中斷。例如, f3函數(shù)不將req,res傳遞下去,則請(qǐng)求一直停在該函數(shù)中,既不能結(jié)束請(qǐng)求,也不能響應(yīng)該請(qǐng)求,這是很致命的。
一般地,請(qǐng)求不同的資源和具體的業(yè)務(wù)息息相關(guān),所以,需要把這種業(yè)務(wù)邏輯盡可能的抽象出來(lái),就像最開始學(xué)習(xí)編寫路由支持的 dispatcher.js
, 在express中,可以使用use
注冊(cè)一個(gè)路由對(duì)象(Router)到某一資源域上 ,利用這個(gè)可以把業(yè)務(wù)放在這個(gè)對(duì)象中,然后使用use
將不同的分類的業(yè)務(wù)操作掛載到不同的資源虛擬目錄,如:
//blog.js
var express = require('express') ;
var route = express.Router() ;
route.use(function(req,res,next) {
console.log('path ',req.url) ;
next() ;
}) ;
route.get('/',function(req,res) {
console.log(' blog/') ;
res.send(req.url) ;
}) ;
route.get('/about',function(req,res){
console.log(' blog/about') ;
res.send('/about') ;
}) ;
module.exports = route ;
//index.js
var blog = require('./blog') ;
var express = require('express') ;
var app = express() ;
app.use('/blog',blog) ;
app.listen(8081,function(){
console.log('server has started.') ;
}) ;
如此將blog.js
中所有的請(qǐng)求都映射到/blog
域之下了。
express.Router實(shí)例是一個(gè)完整的中間件和路由系統(tǒng), 相對(duì)index.js
來(lái)說(shuō),blog.js
已經(jīng)變成了一個(gè)路由模塊。在index.js
中,將這個(gè)路由模塊包含的所有路由掛載到/blog
上,中間件的調(diào)用模式照舊。
使用app.route()
還可以完成一個(gè)鏈接路由調(diào)用,像這樣:
app.route('/blog').get(
function(req,res,next){
res.send('/blog get method.') ;
}).post(function(req,res,next){
res.send('/blog post method.') ;
}).put(function(req,res,next){
res.send('/blog put method.') ;
}) ;
這可以使同一資源響應(yīng)不同的http方法。而不用重復(fù)地將一類資源掛載到不同http方法上。這個(gè)route函數(shù)和use函數(shù)功能差不多,都是向express中間件數(shù)組容器push
掛載中間件函數(shù),然后等待next
調(diào)用。
在express路由支持中,支持更復(fù)雜的模式匹配,可以使用javascript正則表達(dá)式對(duì)象來(lái)匹配資源請(qǐng)求路由。
express使用use對(duì)錯(cuò)誤路由的支持
通過(guò)對(duì)函數(shù)的簽名來(lái)約定錯(cuò)誤路由中間件,以此捕獲錯(cuò)誤并處理,將blog.js
修改如下:
var express = require('express') ;
var route = express.Router() ;
route.use(function(req,res,next) {
console.log('path ',req.url) ;
next() ;
}) ;
//訪問(wèn)/blog 主動(dòng)拋一個(gè)error實(shí)例,然后express會(huì)自動(dòng)將路由轉(zhuǎn)到error處理中間件
route.get('/',function(req,res) {
console.log(' blog/') ;
throw Error('error') ;
// res.send(req.url) ;
}) ;
route.get('/about',function(req,res){
console.log(' blog/about') ;
res.send('/about') ;
}) ;
//express error 中間件
route.use(function(error,req,res,next) {
if (error) { //如果有錯(cuò)誤,輸出錯(cuò)誤。
res.send('error...' + error) ;
}else { //沒(méi)有錯(cuò)誤執(zhí)行下一個(gè)中間件
next() ;
}
}) ;
module.exports = route ;
express的set函數(shù)
使用express#set函數(shù)可以進(jìn)行一些項(xiàng)目資源設(shè)置,完成諸如setting name to value的工作,如指定前端頁(yè)面模版渲染引擎、指定html模板文件目錄:
app.set('view engine','jade') ; //渲染引擎
app.set('views', __dirname + '/views') ; //模板目錄
也可以設(shè)置某些環(huán)境變量的值, 如:
app.set('foo', true) ;
上面的操作等同于app.enable('foo')
。更多set
操作,在這里 ;
express 也是 class: Events 的實(shí)現(xiàn)類,默認(rèn)掛載中間件的時(shí)候會(huì)被mount
事件函數(shù)監(jiān)聽到 ,如: app.on('mount', callback(parent))
app.on('mount',function(parent) {
console.log('mount call',parent) ;
}) ;
END
-
一個(gè) JavaScript 標(biāo)識(shí)符必須以字母、下劃線(_)或者美元符號(hào)($)開頭;后續(xù)的字符也可以是數(shù)字(0-9)。因?yàn)?JavaScript 語(yǔ)言是區(qū)分大小寫的,這里所指的字母可以是“A”到“Z”(大寫的)和“a”到“z”(小寫的)。 ?