connect 库的介绍、使用及源码分析

connect 库的介绍、使用及源码分析

connect 是 node 中的一种用于拓展 http 服务的框架,支持 node 接入插件(或者说是中间件)。

使用 node http 模块搭建 http 服务:

const http = require('http');

http.createServer((req, res) => {
  // 最初的中间件写法就是把这个回调函数拿出来,每个中间件在处理完之后都把req,res放入到下一个中间件里,
  // 这样就形成了一个调用链条
}).listen(3000)

安装

npm i connect -D

使用

使用 connect 搭建一个中间件服务。

const http = require('http');
const connect = require('connect');
// app 是一个方法,里边有个 route、stack 等属性用来存储中间件
const app = connect();

// 添加新的中间件
app.use((req, res, next) => {
  // 一定要添加 next(),不然下面的中间件无法使用
  next();
})

// 只有请求url跟配置的route相同才会触发这个中间件
app.use(route ,(req, res, next) => {
  next();
})

http.createServer(app);

api 介绍

通过 import 导入的是 connect 库是一个用于创建 app 的构造函数,因此我们需要调用 connect 方法来创建一个 app:

const connect = require('connect');
const app = connect();

以下介绍的 api 都是 app 对象的:

use

语法:

app.use([route,]fn)

用来添加中间件。

app 也可以当成是一个中间件

添加中间件的顺序很重要,存储中间件的数组就是按照调用 .use 方法进行组装。

  • route:可选,以 route 作为 key 来保存对应路由要执行的中间件。
    • 如果没有配置 route 参数,默认就是 /,那么每个请求都会触发这个中间件。
    • 如果配置了 route 参数,就会判断 req.url 的开头是否为 route,是的话就会执行这个中间件(比如配置的 route 为 /foo,当 req.url 为 /foo/bar 时就会执行)。注意:如果 route 以 / 结尾,那么会移除 /(eg. /foo/ 会变成 /foo),在这种情况下,/foo、/foo/bar、/foo.bar 都能匹配成功。同时 req.url 移除掉 route 参数中的字符,原请求路径会添加到 req.originalUrl 中(eg. route: /foo,req.url: /foo/bar,那么 req.url 会被修改成 /bar,req.originalUrl 为 /foo/bar)。
  • fn:中间件处理函数,接收三个参数:
    • request:http 模块中的 request 对象。
    • response:http 模块中的 response 对象。
    • next:用于控制是否需要执行下一个中间件。如果已经满足需要,则不需要处理;如果还需要继续执行下面的中间件,需要调用 next()。

handle

用于处理其他 http 服务。接收三个参数:

  • request:http 模块中的 request 对象。
  • response:http 模块中的 response 对象。
  • out:一个函数,当中间件没有处理 request 对象或者报错的时候触发。

listen

启动 http 服务。

原理讲解及源码解读

创建 app

function createServer() {
  function app(req, res, next) { app.handle(req,res,next) };
  merge(app, proto);
  // 合并 EventEmitter 的原型
  merge(app, EventEmitter.prototype);
  app.route = '/';
  app.stack = [];
  return app;
}

可以看到 app 其实就是一个方法,其中合并 EventEmitter 的原型(使得 app 具有 emit 等功能 ),往 app 添加了 use、handle、listen 方法,同时添加了 route(用来处理嵌套 app 作为中间件的情况,此时这个 route 属性作为 key,要执行的中间件为这个嵌套 app)、stack(用来保存中间件) 属性。

添加中间件

前面提到了使用 use 方法添加中间件,并放在一个队列中,我们来看看 use 方法具体做了什么

proto.use = function use(route, fn) {
  var handle = fn;
  var path = route;

  // 这个就是处理只传入一个中间件函数逻辑,将 route 设置成 /
  if (typeof route !== "string") {
    handle = route;
    path = "/";
  }

  // 嵌套 app,就是把 app 也当作一个中间件使用
  if (typeof handle.handle === "function") {
    // 获取到嵌套 app 的 handle 属性
    var server = handle;
    // 保存请求路径
    // TODO 好像没什么作用?
    server.route = path;

    // 中间件处理函数(调用 嵌套app 的 handle 方法)
    handle = function (req, res, next) {
      server.handle(req, res, next);
    };
  }

  // 移除配置的 route 结尾的 / 字符
  if (path[path.length - 1] === "/") {
    path = path.slice(0, -1);
  }
  
  // 把中间件放入队列中
  this.stack.push({ route: path, handle: handle });
  return this;
}

中间件的执行方式

const app = connect();
http.createServer(app);

function app(req, res, next){ app.handle(req, res, next); }

app 作为一个回调函数传入到 createServer 方法中,当接受到请求时就会执行上面的 app 方法,核心就是 app.handle 方法:

执行 next 方法:

proto.handle = function handle(req, res, out) {

  ...

  function next(err) {
    // 下一个中间件
    var layer = stack[index++];

    // 全部中间件执行完毕
    if (!layer) {
      defer(done, err);
      return;
    }

    // 解析请求路径
    var path = parseUrl(req).pathname || "/";
    // 中间件对应的路径
    var route = layer.route;

    // 路径不匹配直接换下一个中间件
    if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
      return next(err);
    }

    // 请求的路径长度 大于 中间件的路径,判断中间件路径是否包含在请求路径上
    var c = path.length > route.length && path[route.length];
    // route: /foo  req.url: /foo.bar 和 /foo/bar 说明都符合设计,应该用当前的中间件处理
    if (c && c !== "/" && c !== ".") {
      return next(err);
    }

    // 删除 route,重新覆写 req.url 值(移除前面的 route)
    if (route.length !== 0 && route !== "/") {
      removed = route;
      req.url = protohost + req.url.substr(protohost.length + removed.length);
    }

    // 执行中间件
    call(layer.handle, route, err, req, res, next);
  }

  next();
}

根据当前的路由(key)来判断需要执行当前中间件。

使用 try…catch 来捕获中间件错误。

当存在错误的时候,中间件接收的第一个参数其实是 error,把错误暴露给中间件函数,由中间件函数自行处理逻辑。

function call(handle, route, err, req, res, next) {
  try {
    if (hasError && arity === 4) {
      // 把错误暴露给中间件函数,由中间件函数自行处理逻辑
      handle(err, req, res, next);
      return;
    } else if (!hasError && arity < 4) {
      handle(req, res, next);
      return;
    }
  } catch (e) {
    // replace the error
    error = e;
  }
  next(error);
}

通过 Function.length 来获取传入的参数个数。

相关推荐

  1. connect 介绍使用分析

    2024-03-24 09:52:04       44 阅读
  2. 【spring分析】@Conditional使用以及分析

    2024-03-24 09:52:04       56 阅读
  3. Flutter 中 Crypto 介绍使用

    2024-03-24 09:52:04       29 阅读
  4. 分享-golangBMP文件读写

    2024-03-24 09:52:04       58 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-24 09:52:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-24 09:52:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-24 09:52:04       82 阅读
  4. Python语言-面向对象

    2024-03-24 09:52:04       91 阅读

热门阅读

  1. 模板 前缀和 NC

    2024-03-24 09:52:04       42 阅读
  2. 基于单片机的小区安防控制系统设计

    2024-03-24 09:52:04       38 阅读
  3. 如何在OpenCV中实现实时人脸识别?

    2024-03-24 09:52:04       38 阅读
  4. 24计算机考研调剂 | 江西理工大学

    2024-03-24 09:52:04       41 阅读
  5. 日志收集监控告警平台的选型思考

    2024-03-24 09:52:04       38 阅读
  6. Github 2024-03-24 开源项目日报Top10

    2024-03-24 09:52:04       38 阅读