Express中间件设计

date
Sep 23, 2021
slug
express
status
Published
tags
技术原理
type
Post
summary
Express中间件设计

express 中间件

1 普通路由

  • 先写个普通路由,能够根据不同的路径访问不同的文件
    • 这样写实现了我们的功能,但是有个缺陷, 如果找不到文件,怎么办? 此时应该执行其他的逻辑,比如匹配api路由,假设我们要分别匹配文件路由和api路由
      此时我们的逻辑是
      1. fs读目录,判断是否有匹配上文件,如果没有的话放行
      1. 执行api判断,没有的话放行
      1. 如果文件路由和api路由都没匹配上,返回404

1.1 文件路由

  • 先做第一步,判断是否文件能够匹配,我搜了下读文件的api发现了readdir,就按照最简单的逻辑写了一个判断有没有文件的
    • *缺陷:**目录是个树形的结构,这样显然不能遍历到全部的文件
  • 解决以上问题,并且简单加入重定向

    1.2 api路由

    目标是写一个用户登录的接口
    • 先写个简单的ui
      • 现在要获取请求的参数了,这里是post请求,数据在请求体里,并且如果文件过大,那么数据会分组传输,如果数据过大,可能请求行和请求头服务端先受到了,就会执行回调函数,但是数据还没有接收完,这种情况能发挥node异步的优势。
        • 成功拿到请求体
          但是如果这样写能拿到data
          答案是不能, 因为异步,但是由于我还不会promise、async、await之类的方法,所以只能用笨方法写了,那就是回调地狱!!
          这里拿到数据以后还要解析成k-v的形式。实现起来大概如下
          不过本着不重复造轮子的原则,直接用node的内置api
          const querystring = require('querystring')
          小复习:get请求的数据在url里,大概是这样的http://localhost:3000/index.html?a=1&b=2按照道理说,这样应该也能打开页面,但是我们打不开了,这是为什么呢?因为我们的url是严格判断的,所以url要做一下分割, ?开始是参数不重复造轮子,我们看node自带的url格式化工具const url = require('url')其中query是参数,pathname是我们来做路由的。这就是获取get的参数
          notion image
      写好了大概是这样

      1.3 jsp

      为了弄清jsp的本质,我们下一个类似的模板
      npm install --save-dev ejs
      创建个home.ejs的文件在model文件
      里面这样写
      js里面这样写

      1.4 此时的结构

      2 中间件

      首先我们看上面的代码,明显写的比较丑陋,因为有很多if/else的判断
      这个有没有思路解决呢?
      我们可以观察程序执行的过程,自上而下,先判断路由是不是根目录,又判断是不是文件路由,然后判断是否命中api,这其实是一个洋葱模型,加入洋葱每一层都可以看做是个逻辑,比如第一层是根目录的逻辑,第二层是文件路由的逻辑,第三层是api的逻辑,一次类推,那么每一层都可以看做是个中间件
      notion image
      • 注册中间件然后遍历
        • 大概是这样调用
          这样就可以把每个路由分开,然后规划,甚至放到不同的目录文件
          这样还不算是中间件,还有点问题,不过可以实现模块化了
          中间件可以看做一个抽象的层,修改其中一层不会影响其他层,互相解耦,纵向拆分是分成,每一层可以看成是中间件。(还有横向拆分)express这个库是中间件的思想,可以让路由接口,纵向分层
      问题: 无法处理没有url的函数,比如写个文件路由,所以我们需要支持不传url的中间件注册
      • 能处理没有url的中间件注册
        • 从例子中可以看出 中间件的先后顺序会决定最终的结果
          不过这里还有问题,你看出来什么了嘛?
        • *问题:**如果匹配了没有设置url,在本例子中匹配的是文件路由和404路由,如果有文件,文件返回了但是还是会遍历到404,这显然是不对的
        • 所以每个中间层都要有一个控制力,就是能否进入下一层的控制能力
          我们可以设计这样的用法,加第三个参数,就是next参数,这个参数干嘛的呢,他就是决定是否进入下一层的钥匙,只有调用了才会进入下一层
      实现大概是这样
      • 可以看到有个倒叙,原因是每个中间件的next是下一个中间件的执行逻辑,也就是说当前中间件要在下一个中间件执行逻辑完了以后再执行逻辑。
      • bind其实是一个不完全的函数柯里化的功能,函数柯里化,是把一个固定参数数量的函数,转化成可以多次调用(直到凑齐所有函数的) bind和函数柯里化的区别:bind不是完全的函数柯里化,他和函数柯里化的区别是呀是固定收集两次而不是无限次,如果第二次还没有收集到就当做undefined处理,他的原理是这样的
        • next这个函数是基于下一层的中间件build出来的,比如我们两层命中了路由,第一层是文件路由,第二层是404,文件路由的中间件是404中间件bind出来的函数,也就是说,要先bind404中间件再bind文件路由中间件
        改良:支持正则表达式匹配

        © dana 2023 - 2025