简短的ExpressJS实施(第1部分)
#javascript #网络开发人员 #node #express

介绍

expressjs是我们大多数人都知道的网络框架。我真的很喜欢使用Express设置新服务器的容易和快速。

在引擎盖下,Express是节点HTTP模块周围的轻质包装器。它抽象了HTTP模块的一些复杂性,并为我们提供了一种构建后端服务器的简单方法。

在本文中,我想了解如何实施Express,看看我是否可以从中学到一些东西。

开始阅读代码

ExpressJS的源代码可用:https://github.com/expressjs/express

首先,让我们检查目录结构:

├── index.js
├── lib
│   ├── application.js
│   ├── express.js
│   ├── middleware
│   │   ├── init.js
│   │   └── query.js
│   ├── request.js
│   ├── response.js
│   ├── router
│   │   ├── index.js
│   │   ├── layer.js
│   │   └── route.js
│   ├── utils.js
│   └── view.js

主要代码现用在lib文件夹中,其中有一些模块,例如:应用程序,中间件,请求,响应,路由器,确实很简单,与其他网络框架相比,只有几个文件。

现在,我们可以直接跳入express.js并读取代码。但是我想以不同的方式处理阅读过程。

让我们说我们有一个使用Express的微小应用程序。

const express = require('express')
const myapp = express()
const port = 3000

myapp.use(whateverLogger())

myapp.use('/hello', (req, res) => {
  res.send('Hello World!')
})

myapp.post('/world', (req, res) => {
  res.send('ok')
})

myapp.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

我们可以将此应用程序分为两个阶段:

  • 阶段1:注册处理程序
    • 在此阶段,处理程序(中间件)已设置为如下:whateverLogger/hello/world的处理程序。
  • 第2阶段:处理请求
    • 此阶段是服务器接收请求并处理它们的地方

让我们开始在每个阶段开始读取源代码。

阶段1:注册处理程序

我们需要注意的主要方法是两个:

  • myapp = express()
  • myapp.use()

对于express(),我的第一个猜测是阅读express.js

express.js

...
var proto = require('./application');
var Route = require('./router/route');
var Router = require('./router');
...

exports = module.exports = createApplication;

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);
  ...

  return app
}

(为了清晰而省略其他细节)

函数调用返回app,这也是一个函数。 mixin(app, proto, false)表明该app的大多数逻辑将位于./application

我们可以跳过混合素逻辑,并假定其目的是将方法和属性添加到app

我们的下一个跳动是application.js

application.js

在第194行,我们可以看到app.use的定义:

...
var Router = require('./router');
...

app.use = function use(fn) {
  ...
  var router = this._router;
  var fns = flatten(slice.call(arguments, offset));
  ...

  fns.forEach(function (fn) {
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }

    ...
  }, this);

  return this;
};

这种方法中有很多活动,但是首先要吸引我们的眼睛的是fns.forEach,称为router.use(path, fn)

app.use收到功能列表(这是我们的路径的处理程序)。然后使用router注册每个功能。

现在让我们跳到router.js

router.js

这个文件中发生了很多有趣的事情。

首先,构造函数代码显示了stack

每当我看到堆栈(或队列)时,我立即想到一个数组,当我们插入或访问数组时具有某种顺序。


var proto = module.exports = function(options) {
  ...
  router.stack = [];

  return router;
};

继续,我们找到router.use

的定义

最佳点是为循环创建新的Layer并将其推到堆栈
的循环

proto.use = function use(fn) {
  ...
  var callbacks = flatten(slice.call(arguments, offset));
  ...

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[I];
    ...
    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};
  • 堆栈上的每个项目都是Layer
  • Layer只能接收pathfn回调

现在,在这一点上,我可以想象该设置如何存储在堆栈中:

myapp.use('/hello', handler1, handler2)
myapp.use('/world', handler3, handler4)

这将成为:

router.stack = [
  Layer('/world', handler4),
  Layer('/world', handler3),
  Layer('/hello', handler2),
  Layer('/hello', handler1) // <-- index: 0
]

我相信我们现在对注册过程的工作方式有基本的了解,而Scence

背后的数据结构是什么

在转到第2阶段之前,这里是总结所有内容的图。

Summary of phase 1: registering handlers
https://www.codediagram.io/app/shares?token=5dce1a36

on 第2阶段:处理请求,我们通过研究next()函数的实现

来关注如何通过中间件处理请求

请保持调整ð