Express 路由

路由用于确定应用程序如何响应对特定端点的客户机请求,包含一个 URI(或路径)和一个特定的 HTTP 请求方法(GET、POST 等)。

每个路由可以具有一个或多个处理程序函数,这些函数在路由匹配时执行。它的使用形式如下:

app.METHOD(PATH, HANDLER)

其中:

app 是 express 的实例。
METHOD 是 HTTP 请求方法。
PATH 是服务器上的路径。
HANDLER 是在路由匹配时执行的函数。

我们先来演示一下路由的简单使用:

router.get('/', function(req, res, next) {
  res.send('receive get method:'+ req.url);
});
router.post('/', function(req, res, next) {
  res.send('receive post method:'+ req.url);
});

在这里插入图片描述

上面演示的get,post都属于路由方法,它们是派生自 HTTP 方法之一,附加到 express 类的实例。

Express 支持对应于 HTTP 方法的以下路由方法:get、post、put、head、delete、options、trace、copy、lock、mkcol、move、purge、propfind、proppatch、unlock、report、mkactivity、checkout、merge、m-search、notify、subscribe、unsubscribe、patch、search 和 connect。

也就是说上面的使用其实是对node.js中http模块的封装。但是有一个方法例外:app.all(),该方法并非派生自 HTTP 方法,它用于在所有请求方法的路径中装入中间件函数。

说直白一点就是使用这个路由方法我们可以不用管发来的是get还是post请求,我都可以处理。
使用示例:

app.all('/secret', function (req, res, next) {
  // do some things
  next(); // 将控制全交给后续路由
});

我们常见的路由是准确的路径,比如:’/’,’/login’等,但是还有一些特殊的路由,它们可以是字符串模式或者正则表达式。

基于字符串模式的路由路径
此路由路径将匹配 acd 和 abcd。
app.get('/ab?cd', function(req, res) {
  res.send('ab?cd');
});
基于正则表达式的路由路径:
此路由路径将匹配名称中具有“a”的所有路由。
app.get(/a/, function(req, res) {
  res.send('/a/');
});

我们可以提供多个回调函数,以类似于中间件的行为方式来处理请求。唯一例外是这些回调函数可能调用 next(‘route’) 来绕过剩余的路由回调。可以使用此机制对路由施加先决条件,在没有理由继续执行当前路由的情况下,可将控制权传递给后续路由。

路由处理程序的形式可以是一个函数、一组函数或者两者的结合,如以下示例中所示。

多个回调函数可以处理一个路由:

app.get('/example/b', function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});

大家注意一点如果有多个路由处理程序,那么不是最后一个路由的情况下,其参数要有next:


    function (req, res, next)

接下来我们要提的是中间件函数的概念,中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。

中间件函数可以执行以下任务:

执行任何代码。
对请求和响应对象进行更改。
结束请求/响应循环。
调用堆栈中的下一个中间件。
如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。
下面是一张来自官方文档的示例:
在这里插入图片描述

next() 函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三自变量。next() 函数可以命名为任何名称,但是按约定,始终命名为“next”。为了避免混淆,请始终使用此约定。
next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么请求将被挂起,后边定义的中间件将得不到被执行的机会。

这里还有一点需要注意:next('route') 它表示 将控制权传递给下一个路由,那么next()函数表示的应该就是讲控制权传递给下一个路由处理程序了吧。我们看个例子:

app.use('/', function(req,res,next){
    console.log('func 1');
},function(req,res,next){
    console.log('func 2');
},function(req,res){
    console.log('func 3')
});

假设我们有这么一个路由,那么按照我们预想的应该是依次输出:

func 1 func 2 func 3

我们先来运行一下:
在这里插入图片描述

并且此时你访问浏览器,你会看到:
在这里插入图片描述
是不是很奇怪,在官方文档中有这样的描述:

您可以提供多个回调函数,以类似于中间件的行为方式来处理请求。
唯一例外是这些回调函数可能调用 next('route') 来绕过剩余的路由回调。
’您可以使用此机制对路由施加先决条件,在没有理由继续执行当前路由的情况下,可将控制权传递给后续路由。
下表中响应对象 (res) 的方法可以向客户机发送响应,并终止请求/响应循环。如果没有从路由处理程序调用其中任何方法,客户机请求将保持挂起状态。

在这里插入图片描述

那么,我们分析一下我们的代码,在这段代码中,我们只是打印了一句log,并没有对客户机进行回应,也就是说此时客户机请求 是挂起状态,通俗点就是你女朋友说快下楼了,然后你就一直在楼下等。

那么next()的作用显而易见,就是执行后边的回调函数,我们在试试:

app.use('/', function(req,res,next){
    console.log('func 1');
    next()
},function(req,res,next){
    console.log('func 2');
    next()
},function(req,res){
    console.log('func 3')
    res.send("hello world")
});

在这里插入图片描述

在这里插入图片描述

好了,next()方法是个什么意思想必大家都明白了。接下来再看看next('route')

app.get('/',function(req,res,next){
    console.log('func 1');
    next('route');
},function(req,res,next){
    console.log('func 2');
    next();
});

app.use('/', function(req,res){
    console.log('func 4');
    res.send("hello world2");
});

运行结果:
在这里插入图片描述

ok,当执行了next('route')后,func2 回调函数被跳过,然后执行下一个路由。这样大家想必有了一个比较直观的认识了。

特别注意:next('route') 仅在使用 app.METHOD() 或 router.METHOD() 函数装入的中间件函数中有效。

那么next参数是什么东西?我打印了一下next参数:

var myLogger = function (req, res, next) {
    console.log('LOGGED'+next);
    next();
  };

app.use(myLogger);

此时我们执行程序,那么在控制台你会得到这么一段代码:

function next(err) {
    var layerError = err === 'route'
      ? null
      : err;

    // remove added slash
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    // restore altered req.url
    if (removed.length !== 0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = '';
    }

    // signal to exit router
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }

    // no more matching layers
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }

    // get pathname of request
    var path = getPathname(req);

    if (path == null) {
      return done(layerError);
    }

    // find next matching layer
    var layer;
    var match;
    var route;

    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;

      if (typeof match !== 'boolean') {
        // hold on to layerError
        layerError = layerError || match;
      }

      if (match !== true) {
        continue;
      }

      if (!route) {
        // process non-route handlers normally
        continue;
      }

      if (layerError) {
        // routes do not match with a pending error
        match = false;
        continue;
      }

      var method = req.method;
      var has_method = route._handles_method(method);

      // build up automatic options response
      if (!has_method && method === 'OPTIONS') {
        appendMethods(options, route._options());
      }

      // don't even bother matching route
      if (!has_method && method !== 'HEAD') {
        match = false;
        continue;
      }
    }

    // no match
    if (match !== true) {
      return done(layerError);
    }

    // store route for dispatch on change
    if (route) {
      req.route = route;
    }

    // Capture one-time layer values
    req.params = self.mergeParams
      ? mergeParams(layer.params, parentParams)
      : layer.params;
    var layerPath = layer.path;

    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err);
      }

      if (route) {
        return layer.handle_request(req, res, next);
      }

      trim_prefix(layer, layerError, layerPath, path);
    });
  }

分析源代码:next函数内部有个while循环,每次循环都会从stack中拿出一个layer,这个layer中包含了路由和中间件信息,然后就会用layer和请求的path就行匹配,如果匹配成功就会执行layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个layer(即中间件)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值