美文网首页
Express学习 - 路由

Express学习 - 路由

作者: _palm | 来源:发表于2016-10-26 16:13 被阅读629次

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”(小写的)。

相关文章

  • node学习4

    Nodejs 路由模块封装、封装仿照 express 的路由 Nodejs 路由模块封装 封装仿照 express...

  • Express学习 - 路由

    在Express学习 - hello world,知道了Express是在Node.js的基础之上对Node.js...

  • express路由

    路由能力:var express = require("express"); var app = express(...

  • express 基础以及mongo基础

    express框架 express使用步骤 express的路由 express之next方法 express之通...

  • koa-router的使用

    Koa中的路由和Express不同,Express是把路由集成在Express中,Koa则需要通过kao-rout...

  • Express路由

    Express路由 一、Express路由简介 路由表示应用程序端点 (URI) 的定义以及响应客户端请求的方式。...

  • Express框架

    1. 课程介绍 ◆ Express介绍(了解) ◆ Express安装及使用(掌握) ◆ Express路由(掌握...

  • 前端面试2021-009

    1、如何创建并启动一个Express服务应用 2、什么是路由?Express中是如何管理路由的? 路由是WEB服务...

  • 在nodeJS中使用MongoDB

    (该代码仅作为参考例子使用,路由为自定义的模拟express框架路由) index.js express-rout...

  • Express 路由

    Express 中的路由 在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。 Expr...

网友评论

      本文标题:Express学习 - 路由

      本文链接:https://www.haomeiwen.com/subject/kdwruttx.html