express 中间件的简单应用与实现

看了慕课网双越老师的课之后结合自己的理解做了一些简单的总结,如有不恰当之处,欢迎指正。

提到 express 就不得不提到中间件,接下来就简单的介绍一下 expres 中间件的简单应用与部分常用函数的实现。

1. express 中间件的简单应用

在日常项目的开发中,登录验证是一个非常常见的场景,这个时候 express 中间件就可以派上用场了。接下来分别使用原生 nodeexpress 中间件的方法实现简单的登录验证。

应用场景:在获取博客列表之前要进行登录验证,只有在登录状态下才可以获取博客列表。

原生 node.js 实现登录验证:

if(method === 'GET' && req.path === '/api/blog/list') {
  // 若req.session.username有值说明已经登录
  if (req.session.username){
    // 登录状态下可以获取博客列表
    获取博客列表
  }
}

express 中间件实现登录验证:

function loginCheck(req, res, next) {
    if (某个登录成功的条件) {
       next(); // 登录成功执行next
    } else {
       res.json({
         errno: -1,
         msg: 'login fail'
    })
    }
}
// 若在登录状态下,loginCheck会执行next函数,从而执行第二个中间件,获取博客列表。
app.get('/api/blog/list', loginCheck, (req, res, next) => {
    获得博客列表
})

虽然上面这个简单的例子中看上去似乎原生 node.js 要更简单一些,但是使用中间件的方式封装性更好,在一些比价复杂的项目中就能体现出优势了。

2. express 中 use, get, post, next, listen 等方法的实现

结合上面的流程图和最后给出的完整代码,简单说明一下实现的过程:

这里以 use (在实现思路上和 get, post 是一样的)为例说明:

  • 获取路径和中间件

    use 将用户调用时传入的参数传给 register 函数,register 函数将获取到参数后分离出路径和中间两部分,路径存放在 info.path 中,中间件存放在 info.stack 中。然后 register 函数将分离开的路径和中间件再返回给 useuse 拿到分离后的路径和中间件之后,会将其赋值给 constructor 函数中 this.routes.all

注意:register 在分离路径时需要注意的一点是对于没有路径只有中间件的参数会将其路径赋值为根路径 /

  • listen 方法的实现

    使用 express 时调用 app.listen(3000) 实际上不只是监听 3000 端口还创建了一个 http 服务,参数是一个回调函数,代码如下:

    listen(...args) {
      const server = http.createServer(this.callback());
      server.listen(...args);
    }
  • res.json 方法的实现

    在回调函数 callback 中首先实现一个 res.json 方法,实现方式也比较简单。

    1. 定义 resHeaderContent-typeapplication/json
    2. 执行res.end({JSON.stringify(data)}) 将用户传入的 data 对象转换为 JSON 字符串输出到屏幕上。
  • 获取需要执行的中间件

    这部分通过 match 函数实现。通过 req.urlreq.method.toLowerCase 来获取需要执行的中间件。中间实现的方法就有很多了,思路就是通过method 找出相应的路径和中间件,这个已经在 this.routes 中分类存储了。然后找出路径和中间件之后利用 indexOf 方法,

    if (url.indexOf(routeInfo.path) === 0) 如果 if 判断成立说明满足条件,那么需要执行的中间件也就找出来了。

  • app.next 方法的实现

    这部分通过 handle 函数实现。思路:定义一个 next 函数,在那些需要执行的中间件中,先取出第一个,如果不为空那么开始执行,三个参数分别为 req, res, next。后面需要执行的中间件到底执不执行取决于前一个中间件的程序中是否调用了 next 方法。代码如下:

     const next = () => {
          // shift 取得数组的第一项
          const middleware = stack.shift();
          if (middleware) {
            // 执行中间件函数
            middleware(req, res, next);
          }
        };
        // 定义完后立即执行
        next();

    注意:在定义完next函数之后,必须立即执行一次next函数,否则第一个需要执行的中间件也不会执行了。

    中间件中传入的参数中的next,就是在这里定义的next函数,如果前一个中间件程序中执行了next函数,那么会再执行 stack.shift 取出需要执行的中间件中的下一个中间件。

附上完整代码:

const http = require('http');
const slice = Array.prototype.slice;

class LikeExpress {
  constructor() {
    // 存放路径和中间件,all中存放的是调用use传入的路径和中间件
    this.routes = {
      all: [],
      get: [],
      post: []
    }
  }

  // 分离出路径和中间件,info.path中存放路径,info.stack 中存放中间件
  register(path) {
    const info = {};
    if (typeof path === 'string') {
      info.path = path;
      // info.stack 存放所有的中间件
      // 如果第一个参数是路由在取中间件时就要从数组的第2个位置开始取
      // slice.call(arguments, 1) 的作用就是取arguments从第二个位置开始之后的所有元素都取出来并变成数组的形式。
      info.stack = slice.call(arguments, 1);
    } else {
      // 如果第一个参数不是一个路由,那么我们就假定第一个参数是一个根路由
      info.path = '/';
      info.stack = slice.call(arguments, 0);
    }
    return info;
  }

  use() {
    // 实际使用时,参数是通过use传递进来的
    // 将所有的参数传入到register函数中
    const info = this.register.apply(this, arguments);
    // info 是一个对象,info.path 中存放的是路径,info.stack 中存放的是中间件
    this.routes.all.push(info);

  }

  get() {
    const info = this.register.apply(this, arguments);
    this.routes.get.push(info);

  }

  post() {
    const info = this.register.apply(this, arguments);
    this.routes.post.push(info);
  }

  // 匹配use,get或post方法会执行的中间件
  match(method, url) {
    let stack = [];
    if (url === '/favicon.ico') {
      return stack;
    }
    // 获取routes
    let curRoutes = [];
    // concat 是数组中的一个方法,如果没有参数,那么会生成一个当前数组的副本并将其赋值给前面的变量,如果有参数会将参数加入到生成的副本的后面然后将其赋值给变量
    // 如果是use,那么就把use中的路径和中间列表复制到curRoutes中
    // 如果方法是get或post那么下面这句话,由于this.routes.all是undefined,所以会将当前curRoutes生成一个副本赋值给curRoutes,其实一点变化都没有
    curRoutes = curRoutes.concat(this.routes.all);
    // 如果是get或post,那么就把相应的路径和中间件复制到curRoutes中
    curRoutes = curRoutes.concat(this.routes[method]);

    curRoutes.forEach(routeInfo => {
      // url='/api/get-cookie' routeInfo.path='/'
      // url='/api/get-cookie' routeInfo.path='/api'
      // url='api/get-cookie' routeInfo.path='/api/get-cookie'
      if (url.indexOf(routeInfo.path) === 0) {
        // 匹配成功
        stack = stack.concat(routeInfo.stack);
      }
    });
    return stack;
  }

  // 核心的 next 机制
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一个匹配的中间件
      // shift 取得数组的第一项
      const middleware = stack.shift();
      if (middleware) {
        // 执行中间件函数
        middleware(req, res, next);
      }
    };
    // 定义完后立即执行
    next();

  }

  // callback是一个(req, res) => {} 的函数,结合http-test中的代码去理解
  callback() {
    return (req, res) => {
      // res.json 是一个函数,在express中使用时传入一个对象即可在屏幕中显示出来,这里实现了这个功能
      res.json = (data) => {
        res.setHeader('Content-type', 'application/json');
        res.end(
          JSON.stringify(data)
        );
      };

      const url = req.url;
      const method = req.method.toLowerCase();
      // 找到需要执行的中间件(通过路径和method,有可能一个需要执行的中间件也没有)
      const resultList = this.match(method, url);
      this.handle(req, res, resultList);
    }
  }

  // express 中listen的作用不仅仅是监听端口,还要创建服务器
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
}

module.exports = () => {
  return new LikeExpress()
};

express 中间件的简单应用与实现的更多相关文章

  1. Express中间件简单的实现原理

    上一篇理解Express的使用之后, 再总结一篇Express中间件的简单实现原理. 我们知道Express中间件就是一个个的函数, 那么怎么让这些函数有序的执行呢? 那就需要我们调用 next 函数 ...

  2. express中间件原理 && 实现

    一.什么是express中间件? 什么是express中间件呢? 我们肯定都听说过这个词,并且,如果你用过express,那么你就一定用过express中间件,如下: var express = re ...

  3. 1.express中间件的简介

    express中间件的意思 1, 中间件是一个模块.在js中,模块意味着函数,所以中间件是一个函数.那么这个函数长什么样子? 这还要从中间件的功能说起,它拦截http 服务器提供的请求和响应对象,执行 ...

  4. Express中间件的原理及实现

    在Node开发中免不了要使用框架,比如express.koa.koa2拿使用的最多的express来举例子开发中肯定会用到很多类似于下面的这种代码 var express = require('exp ...

  5. node的express中间件之session

    虽然session与cookie是分开保存的.但是session中的数据经过加密处理后默认保存在一个cookie中.因此在使用session中间件之前必须使用cookieParser中间件. app. ...

  6. Express中间件原理详解

    前言 Express和Koa是目前最主流的基于node的web开发框架,他们的开发者是同一班人马.貌似现在Koa更加流行,但是仍然有大量的项目在使用Express,所以我想通过这篇文章说说Expres ...

  7. Node.js连接Mysql,并把连接集成进Express中间件中

    引言 在node.js连接mysql的过程,我们通常有两种连接方法,普通连接和连接池. 这两种方法较为常见,当我们使用express框架时还会选择使用中间express-myconnection,可以 ...

  8. vue+webpack+express中间件接口使用

    环境:vue 2.9.3; webpack 目的:接口的调用 跨域方式: 1.express中间的使用 2.nginx代理 3.谷歌浏览器跨域设置 -------------------------- ...

  9. express 中间件

    [express 中间件] 中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 ...

随机推荐

  1. 以太网驱动的流程浅析(一)-Ifconfig主要流程【原创】

    以太网驱动的流程浅析(一)-Ifconfig主要流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<Lin ...

  2. Spring基于注解的配置1——@Required、@Autowired、@Qualifier示例及与传统注入方法的对比

    @Required注释 作用:用于属性的set方法,那么这个属性必须在xml文件的bean标签里面进行配置,否则就会抛出一个BeanInitializationException异常. 首先准备一个类 ...

  3. pwnable.kr 第一天

    1.FD 直接通过ssh连接上去,然后,看下源代码. #include <stdio.h> #include <stdlib.h> #include <string.h& ...

  4. C#音频截取与原文匹配

    1.需求 上传一个音频文件(例如英语听力的音频)与音频对应的原文word文档.大概估算音频中一段对话到另一端对话的时间间隔,将音频截取为不同对话的小音频文件,通过百度语音识别转换成英文,然后与原文对比 ...

  5. day 26 约束、自定义异常、加密hashlib、logging

    一.约束 建议使用: class BaseMessage(object): def send(self): """ 必须继承BaseMessage,然后其中必须编写sen ...

  6. JDK动态代理和CGLIB字节码增强

    一.JDK动态代理 Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及 Invocatio ...

  7. 感觉async await 异步编程 并不能提升性能?

    我有2个方法 代码相同 都是执行上传文件IO操作 一个同步 一个异步 接着我用POSTMAN 分别用200个线程 去同时测试2个接口 结果很意外 2个接口 同时执行完成的速度 异步更慢 之前经常看别人 ...

  8. MySQL5.7.18自动化安装脚本

    背景 很好的朋友邱启明同学,擅长MySQL,目前任职某大型互联网业MySQL DBA,要来一套MySQL自动安装的Shell脚本,贴出来保存一些. 此版本为 MySQL 5.7.18 ###### 自 ...

  9. 怎么把宿主机上的镜像推送到hub上

    怎么把宿主机上的镜像推送到hub上: 1.查看系统中存在的镜像: [root@izuf63bjp8ts8nkl13pxh1z devicemapper]# docker imagesREPOSITOR ...

  10. RestTemplate 中文乱码

    @Configuration public class RestTemplateWithoutLoadBalance { @Bean("normalRestTemplate") p ...