0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

express源码阅读(一)

修罗 2022-07-10 2113 0条评论 nodejs

首页 / 正文

express源码阅读(一)

下载源码:https://github.com/expressjs/express

git clone https://github.com/expressjs/express.git

个人总结的源码阅读方法:不要多牛逼的技术,耐心最为重要。

一、官方例子

var express = require("../../index");
var app = express();

app.get("/", function (req, res) {
  res.send("Hello World");
});

app.listen(3000, function () {
  console.log("Express started on port 3000");
});

分析:

  • express函数调用做了啥?
  • get方法怎么执行,如何处理路径和回调的?
  • listen监听端口怎么实现的?
  • 浏览器请求路径又是如何匹配的?

二、express函数调用

var express = require("../../index");
var app = express();

2.1 找到require("../../index")中的导入的index.js

module.exports = require('./lib/express');

1638368793971.png

2.2 找到lib/express文件

1638369414039.png

2.2.1 express中的代码解析

1. 导出模块

// 导出createApplication函数
exports = module.exports = createApplication;

2. express()调用后返回的函数

var app = function (req, res, next) {
  // 暂时不用关注,用户请求才走到这里
  app.handle(req, res, next);
};

3. mixin

合并到原对象(原对象,合并对象,false原对象有则重写)

// 使app有发布订阅的功能
mixin(app, EventEmitter.prototype, false);

// 混入application的属性
mixin(app, proto, false);

EventEmitterprote来自模块导入:

var EventEmitter = require("events").EventEmitter;
var proto = require("./application");

4. 初始化配置

app.init();

init方法来自proto的mixin混入,proto来自./application

2.2.2 application.js

app.init = function init() {
  this.cache = {};
  this.engines = {};
  this.settings = {};

  this.defaultConfiguration();
};

上面把app当对象用了,我写个例子说明类似情况:

function app() {}
app.usename = "修罗";
app.getUsername = function () {
  console.log(this); // function app(){}
  console.log(this.usename); // "修罗"
};

app.getUsername();
  • this.defaultConfiguration();执行后给app添加了配置对象。
app.locals.settings =  
app.setting = {
    "x-powered-by": true,
    etag: "weak",
    "etag fn": function generateETag(body, encoding) {...},
    env: "development",
    "query parser": "extended",
    "query parser fn": function parseExtendedQueryString(str) { ...},
    "subdomain offset": 2,
    "trust proxy": false,
    "trust proxy fn": function trustNone() { return false; },
    view: function View(name, options) {...},
    views: "d:\\source\\express\\views",
    "jsonp callback name": "callback",
};
                                        
app.mountpath = "/";

三、app.get

app.get("/", function (req, res) {
  res.send("Hello World");
});

3.1 方法定义在application.js

application模块加载执行:

  • 定义app.get、app.post等等方法

1638376135312.png

// method: [ 'get', 'post', 'put', 'head', 'delete', 'options'...]
methods.forEach(function (method) {
  app[method] = function(path){...}
});

3.2 例子:app.get

// 参数:"/", function (req, res) {}
app.get = function (path) {
    // app.get传一个参数
    if (method === "get" && arguments.length === 1) {
        return this.set(path);
    }

    // 初始化router,只执行一次
    this.lazyrouter();
    
    // 返回route对象
    var route = this._router.route(path);
    
    // 最后调route.get(fn):只传fn,不传path过去
    route.get.apply(route, slice.call(arguments, 1));
    
    // 把app返回,链式调用
    return this;
};

3.3 lazyrouter

1638377178705.png

3.3.1 创建Router对象

this._router = new Router({
    // 找setting中有没有,没有返回false,下面都false
    caseSensitive: this.enabled("case sensitive routing"),
    strict: this.enabled("strict routing"),
});
  • Router定义在router/index.js

new Routernew proto()

var proto = (module.exports = function (options) {
  var opts = options || {};

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

  // 内部函数router可以拿外部函数proto的属性
  setPrototypeOf(router, proto);

  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = [];
  // router: function(){}: { params..., __proto__: proto}
  return router;
});

3.3.2 router.use

this._router.use(query(this.get("query parser fn")));
this._router.use(middleware.init(this));

1. this._router.use(query(this.get("query parser fn")))

  • this.get("query parser fn")

this.get("query parser fn") 从setting中拿到函数

(str) => qs.parse(str, { allowPrototypes: true })
  • query()
query((str) => qs.parse(str, { allowPrototypes: true }))

query函数:

返回中间件(req, res, next)=>{}函数,函数内部调了next();

// 此中间件即在req上挂了一个属性:query,url参数解析为query对象。
// 如参数:username=xiuluo&pass=hui => { usename:xiuluo, pass:hui }
module.exports = function query(options) {
  ...
  return function query(req, res, next) {
    if (!req.query) {
      var val = parseUrl(req).query;
      req.query = queryparse(val, opts);
    }
    next();
  };
};
  • this._router.use传入中间件函数
proto.use = function use(fn) {
  var offset = 0;
  var path = "/";

  ...列出本例重要代码,省略部分代码

  // flatten([ fn ])
  // callbacks:[(req, res, next) { next() }]
  // 多维数组拍成一维数组
  var callbacks = flatten(slice.call(arguments, offset));

  // 遍历,每个中间件都创建了一个layer对象,并放到router的stack属性中
  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    var layer = new Layer(
      path,
      {
        // false,大小写不敏感
        sensitive: this.caseSensitive,
        strict: false,
        end: false,
      },
      fn
    );

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};

Layer

function Layer(path, options, fn) {
  // 不是通过new调用
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  var opts = options || {};

  // 保存中间件函数
  this.handle = fn;
  this.name = fn.name || "<anonymous>";
  this.params = undefined;
  this.path = undefined;
  // path的正则
  // /get/: id
  // pathRegexp("/get/:id").exec(/get/1) : [...] else null
  this.regexp = pathRegexp(path, (this.keys = []), opts);

  // set fast path flags
  this.regexp.fast_star = path === "*";
  this.regexp.fast_slash = path === "/" && opts.end === false;
}

2.this._router.use(query(this.get("query parser fn")))

middleware.init(this)

exports.init = function (app) {
  // 中间件:给请求响应对象挂一些属性
  return function expressInit(req, res, next) {
    // 响应头添加'x-powered-by'
    if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express");

    req.res = res;
    res.req = req;
    req.next = next;

    setPrototypeOf(req, app.request);
    setPrototypeOf(res, app.response);

    res.locals = res.locals || Object.create(null);

    next();
  };
};

到此,lazyrouter执行完毕。

3.4 回到app.get()

// route对象挂载layer.route中
var route = this._router.route(path);

// route.get(fn):不传path过去
route[method].apply(route, slice.call(arguments, 1));

router.route

这里router存layer,layer存route,route存中间件函数

function route(path) {
  var route = new Route(path);

  var layer = new Layer(
    path,
    {
      sensitive: this.caseSensitive,
      strict: this.strict,
      end: true,
    },
    route.dispatch.bind(route)
  );

  layer.route = route;

  this.stack.push(layer);
  return route;
};

Route

function Route(path) {
  this.path = path;
  this.stack = [];

  // route handlers for various http methods
  this.methods = {};
}

route.get(fn)

route存layer

methods.forEach(function (method) {
  Route.prototype[method] = function () {
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      var layer = Layer("/", {}, handle);
      layer.method = method;
      // route['method'] = true
      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});

评论(0)


最新评论

  • https://www.studyglobus.com/author/carroll34a5552/

    Ценники на вывоз мусора сейчас космос, любые лайфхаки по экономии в тему.

  • Richardgrold

    1xbet onlayn kazino platforması ilə vaxtınızı daha səmərəli və maraqlı keçirin. Saytın canlı kazino bölməsi istifadəçilərə fərqli bir dünya təklif edir. Burada canlı dilerli kazino oyunlarının hər bir növünü sınaqdan keçirə bilərsiniz. Krupyelərin peşəkarlığı oyunun dinamikasını və marağını artırır. Müntəzəm olaraq yenilənən oyun siyahısı cansıxıcılığa yol vermir. Hesabınızı artırmaq üçün onlarla fərqli və etibarlı üsul mövcuddur. Qazanılan pullar cəmi bir neçə dəqiqə ərzində şəxsi hesabınıza köçürülür. Aktiv fəaliyyətinizə görə fərdi bonuslar və hədiyyələr qazana bilərsiniz. Dəstək xidməti həm çat, həm də e-poçt vasitəsilə kömək göstərir. 1xbet ailəsinə qoşulun və böyük uduşlar üçün ilk addımı atın. onlayn kazino

  • 砂岩白丘

    我一直梦想, 看到你们相册那样的地方。真棒。

  • 城市地標

    我喜欢, 这里分享真实经验。你的项目 就是 属于这里的。请继续。

  • 宗教地標

    我热爱这样的想法, 去那么多国家。真的很鼓舞。

  • 觀景高地

    能感受到热爱。继续保持 感受。

  • 最佳季節

    我关注这样的资源, 写得很实在。你的网站 就是 属于这里的。加油。

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

日历

2026年07月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis