在Express学习 - hello world,知道了Express是在Node.js的基础之上对Node.js的http模块更方便的封装,利用express创建一个server,在响应各种http/https请求上,代码实现相对精简了很多,看起来确是也直观了许多。
下面是刚开始学习express时,实现的一个hello world
:
var express = require('express') ;
var app = express() ; //得到express实例,类似 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) ;
}) ;
上面的代码,只能拦截/
请求,其他请求都不能得到处理,比如/start
等,所以这个servlet
功能还不完善,可以使用express的中间件注册函数use
来对缺失的handle
进行处理。如:
var express = require('express') ;
var app = express() ;
app.get('/',function(req,res) {
res.send('hello world!') ;
}) ;
//默认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) ;
}) ;
这样,如果没有一个handle
能处理这个请求,则交给默认handle
来处理。这是一个兜底的handle
。这样就不会出现Cannot GET /start
错误了。
中间件注册函数use()
express的中间件的作用更像是spring的aop,类似一个切面,在我理解,express的中间件(Middleware) 也有前置和后置的区分,比如上面的例子就可以算作是一个后置通知。
在express中,中间件是一个处理函数,例如:
app.use(function(req,res,next) {
res.send('404,unknown request') ;
// next() ;
}) ;
中间件函数接收三个参数:
- request对象 ;
- response对象 ;
- next回调函数; 它具有传递性,一个中间件执行完毕,可以将参数(req,res)传递给下一个中间件执行,它们是串行的。直到调用结束,否则会一直调用下去。这是
express/lib/router/route.js
中关于next
实现的一段代码:
//express router next 源码实现
/**
* 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
注册的中间件都会被保存到当前路由对象的stack
然后顺序取出执行,如:
/**
* 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
函数中,参数用于来handle异常,如果传递给next函数一个有效参数('route'除外),则标识这是一个错误handle
,从而进入错误处理流程,而且该错误会一直传递下去,一直抛给最后一个中间件,如果没有手动捕获该错误的情况下。
如果没有显式的将参数传递给next()
函数,则表示调用下一个中间件。
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.') ;
}) ;
每次请求都会首先经过
app.use(function(req,res,next) { console.log('Time : ',Date.now()) ; next() ;// continue }) ;
中间件打印当前调用时间,然后进入
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
这些中间件的执行顺序和代码的顺序是一致的,例如:
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
下一个中间件的调用完全依赖next
,如果没有next
调用,则request和response对象将不会继续传递下去。这个next
有点像java linklist的next[Entry]
对象,指向下一个Entry
对象。
之前使用get
挂载中间件的方式用use
也可以,使用use
挂载中间件函数然后在use
函数内部对资源请求path进行判断,这适合针对某一类资源的综合处理,如:
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() ;
}) ;
这样,也可以对/
和/about
不同的请求进行有效的处理。use
函数也可以像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() ;
}
}) ;
下面的中间件永远也执行不到,因为请求路径不能匹配到/about
,只能匹配/about/about
。可以使用use
有针对性的对某一个路由进行切面处理。例如:
app.use('/blog',function(req,res,next){
//do something
next() ;
});
另外,express的all
函数也可以做到use
的功能,它是use
的别名实现。在/route.js中实现是这样的:
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
都一样的操作,都是将中间件函数push到 Router对象的 stack数组中,方便next函数
分发。
express 的use
函数在没有指定path的情况会默认将中间件函数挂载到/
,利用这个特性解决了默认请求拦截的问题。
express 路由支持
路由, 由一个资源path和一个或若干个资源处理函数组成。在express中它的结构定义为:
app.METHOD(path, [callback...], callback)
这里的app是express的一个实例,METHOD
和HTTP的METHOD含义是一致的。
-
path
为URI. -- 统一资源标识,例如:/blog/about
。
URI
和URL
:
- URI(Universal Resource Identifier/统一资源标识) 它是指一个资源域或者某一确定的资源, 它着重强调资源。例如: /example/blog/page1/
- URL(Uniform Resource Locator/统一资源定位器) 它完整的描述了整个资源的绝对路径,它包含以下三方面内容:
- scheme: http:// 、https://、ftp://等
- address: 例如: www.greatytc.com
- 资源定位: /notebooks/6732245
从这个意义上来说,URL是URI的一个子类,URI更像是URL的一个规范,URL更具体更准确的指定某一资源位置。在java中,通过Request对象获取URL信息和URI信息是这样的,例如这样一个请求: 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() ;// 返回请求参数键值对。type = 42
-
callback
为处理请求资源的回调函数,可以是一个中间件组,也可以组合使用。
express路由针对 get
、post
等http行为有不同的函数支持如:
router.get('/',function(req,res) {
res.send('hello welcom') ;
}) ;
router.post('/',function(req,res) {
res.send(' post : hello welcom') ;
}) ;
Express 定义了如下和 HTTP 请求对应的路由方法:
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方法需要使用括号记法,如:
app['m-search']('/',function(req,res) {
res.send('xxxxxxxxxx') ;
}) ;
因为在javascript语法中,读写不符合javascript变量命名规则[1]的属性时,需要使用类似数组访问方式,如:
var obj = {} ;
obj['mid-school'] = 'xxx中学' ;
//访问也需要如此
console.log(obj['mid-school']) ;
express中一个路由可以被多个回调函数所处理,如:
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() ;
}
不过需要注意next
关联,业务没有处理完毕之前不能缺失next
回调函数,否则,业务流将中断。例如, f3函数不将req,res传递下去,则请求一直停在该函数中,既不能结束请求,也不能响应该请求,这是很致命的。
一般地,请求不同的资源和具体的业务息息相关,所以,需要把这种业务逻辑尽可能的抽象出来,就像最开始学习编写路由支持的 dispatcher.js
, 在express中,可以使用use
注册一个路由对象(Router)到某一资源域上 ,利用这个可以把业务放在这个对象中,然后使用use
将不同的分类的业务操作挂载到不同的资源虚拟目录,如:
//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
中所有的请求都映射到/blog
域之下了。
express.Router实例是一个完整的中间件和路由系统, 相对index.js
来说,blog.js
已经变成了一个路由模块。在index.js
中,将这个路由模块包含的所有路由挂载到/blog
上,中间件的调用模式照旧。
使用app.route()
还可以完成一个链接路由调用,像这样:
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.') ;
}) ;
这可以使同一资源响应不同的http方法。而不用重复地将一类资源挂载到不同http方法上。这个route函数和use函数功能差不多,都是向express中间件数组容器push
挂载中间件函数,然后等待next
调用。
在express路由支持中,支持更复杂的模式匹配,可以使用javascript正则表达式对象来匹配资源请求路由。
express使用use对错误路由的支持
通过对函数的签名来约定错误路由中间件,以此捕获错误并处理,将blog.js
修改如下:
var express = require('express') ;
var route = express.Router() ;
route.use(function(req,res,next) {
console.log('path ',req.url) ;
next() ;
}) ;
//访问/blog 主动抛一个error实例,然后express会自动将路由转到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) { //如果有错误,输出错误。
res.send('error...' + error) ;
}else { //没有错误执行下一个中间件
next() ;
}
}) ;
module.exports = route ;
express的set函数
使用express#set函数可以进行一些项目资源设置,完成诸如setting name to value的工作,如指定前端页面模版渲染引擎、指定html模板文件目录:
app.set('view engine','jade') ; //渲染引擎
app.set('views', __dirname + '/views') ; //模板目录
也可以设置某些环境变量的值, 如:
app.set('foo', true) ;
上面的操作等同于app.enable('foo')
。更多set
操作,在这里 ;
express 也是 class: Events 的实现类,默认挂载中间件的时候会被mount
事件函数监听到 ,如: app.on('mount', callback(parent))
app.on('mount',function(parent) {
console.log('mount call',parent) ;
}) ;
END
-
一个 JavaScript 标识符必须以字母、下划线(_)或者美元符号($)开头;后续的字符也可以是数字(0-9)。因为 JavaScript 语言是区分大小写的,这里所指的字母可以是“A”到“Z”(大写的)和“a”到“z”(小写的)。 ↩