Express学习 - 路由

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的一个实例,METHODHTTP的METHOD含义是一致的。

  • path 为URI. -- 统一资源标识,例如: /blog/about

URIURL

  • URI(Universal Resource Identifier/统一资源标识) 它是指一个资源域或者某一确定的资源, 它着重强调资源。例如: /example/blog/page1/
  • URL(Uniform Resource Locator/统一资源定位器) 它完整的描述了整个资源的绝对路径,它包含以下三方面内容:
  1. scheme: http:// 、https://、ftp://等
  2. address: 例如: www.jianshu.com
  3. 资源定位: /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路由针对 getpost等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-searchhttp方法需要使用括号记法,如:

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


  1. 一个 JavaScript 标识符必须以字母、下划线(_)或者美元符号($)开头;后续的字符也可以是数字(0-9)。因为 JavaScript 语言是区分大小写的,这里所指的字母可以是“A”到“Z”(大写的)和“a”到“z”(小写的)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容