Skip to content

Express基础与原理详解 ​

express.js是一款基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

Express基础功能 ​

作为相对底层的Web服务框架,Express比其他底层框架来说,在功能模型上相对复杂了一些提供的语法功能也比较多,其中间件架构模型的实现也比其他的服务框架复杂了一些。 让我们跟踪一个从前端发起的请求,直到后端响应的完整过程:

  1. 前端发起请求
  2. 请求到达 Express 服务器。
  3. Express 开始遍历中间件链:
    • 应用级中间件(如日志记录)
    • 路由级中间件
  4. 找到匹配的路由
  5. 执行路由处理函数
  6. 响应被发送回客户端。
  7. 前端接收响应并处理数据。
js
const Express = require('express');

const app = new Express();

app.get('/', (req, res) => {
  res.send('Hello Express!')
});

app.listen(3002, () => {
  console.log('express server running at port 3002.');
});

Express原理讲解 ​

我们来通过原生 Node.js 和 Express.js 的对比,讲解 Express 的主要 API 在源码中的封装原理。

创建服务器 ​

js
//原生node.js
const http = require('http');
const server = http.createServer((req, res) => {
  // 处理请求
});

server.listen(3000, () => {
  console.log('Server running on port 3000');
});

// Express.js
const express = require('express');
const app = express();

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Express 在内部仍然使用 Node.js 的 http 模块创建服务器。Express 的 app.listen() 方法是对 http.createServer() 的封装。

js
app.listen = function() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

这里,this 指向 Express 应用实例,它实现了 requestListener 接口,因此可以直接传给 http.createServer()。

路由处理 ​

js
//原生 Node.js
const server = http.createServer((req, res) => {
  if (req.url === '/api' && req.method === 'GET') {
    // 处理 GET /api 请求
  } else if (req.url === '/users' && req.method === 'POST') {
    // 处理 POST /users 请求
  }
});

// Express.js
app.get('/api', (req, res) => {
  // 处理 GET /api 请求
});

app.post('/users', (req, res) => {
  // 处理 POST /users 请求
});

Express 使用了一个复杂的路由系统来管理不同的路由。主要涉及以下几个类:

  1. Router: 管理一组路由规则
  2. Layer: 表示一个单独的路由或中间件
  3. Route: 表示一个具体的路由,包含多个 HTTP 方法的处理程序
js
class Router {
  constructor() {
    this.stack = [];
  }

  route(path) {
    const route = new Route(path);
    const layer = new Layer(path, route.dispatch.bind(route));
    this.stack.push(layer);
    return route;
  }

  get(path, fn) {
    const route = this.route(path);
    route.get(fn);
    return this;
  }

  // 其他 HTTP 方法类似...
}

class Route {
  constructor(path) {
    this.path = path;
    this.stack = [];
    this.methods = {};
  }

  get(fn) {
    const layer = new Layer('/', fn);
    this.methods['get'] = true;
    this.stack.push(layer);
    return this;
  }

  // 其他 HTTP 方法类似...
}

class Layer {
  constructor(path, fn) {
    this.handle = fn;
    this.name = fn.name || '<anonymous>';
    this.path = path;
  }

  // 处理请求的方法...
}

当你调用 app.get() 或其他 HTTP 方法时,Express 会创建相应的 Route 和 Layer 对象,并将它们添加到路由栈中。

中间件 ​

原生 Node.js 没有内置的中间件概念。你需要手动在请求处理函数中实现类似的功能。

js
// Express.js
app.use(express.json());

app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

Express 的中间件系统是建立在其路由系统之上的。每个中间件实际上都是一个 Layer 对象。 当你调用 app.use() 时,Express 会创建一个新的 Layer 对象,并将其添加到主路由栈中:

js
app.use = function use(fn) {
  var path = '/', route = null;

  // ... 处理参数

  var layer = new Layer(path, {
    sensitive: this.enabled('case sensitive routing'),
    strict: false,
    end: false
  }, fn);

  layer.route = route;

  this._router.use(path, layer);

  return this;
};

中间件的执行是通过一个递归的过程实现的。每个中间件函数接收 next 参数,调用 next() 会执行下一个中间件。

请求和响应对象 ​

js
// 原生 Node.js
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'Hello World' }));
});

// Express.js
app.get('/', (req, res) => {
  res.json({ message: 'Hello World' });
});

Express 通过扩展原生的 req 和 res 对象,添加了许多便利方法。这是通过在请求处理过程开始时应用一系列内部中间件实现的。 例如,res.json() 方法的简化实现如下

js
res.json = function json(obj) {
  var val = obj;

  // 设置 Content-Type
  var app = this.app;
  var replacer = app.get('json replacer');
  var spaces = app.get('json spaces');
  var body = JSON.stringify(val, replacer, spaces);

  // 发送响应
  return this.send(body);
};

这些方法被添加到 req 和 res 对象的原型上,使得每个请求都能使用这些增强的方法。

Koa解析 ​

Koa 的核心概念 ​

上下文(Context) ​

Koa 的上下文(Context)对象将 Node.js 的 request 和 response 对象封装在一个对象中,提供了更简洁的 API。上下文对象通常命名为 ctx。

js
app.use(async (ctx, next) => {
  // ctx.request 和 ctx.response 是 Koa 封装的请求和响应对象
  console.log(ctx.request.method, ctx.request.url);
  await next();
});

中间件(Middleware) ​

Koa 的核心是中间件。中间件是一个函数,它可以访问上下文对象(ctx)和下一个中间件函数(next)。Koa 的中间件是基于洋葱模型(Onion Model)的,请求会依次经过所有中间件,然后再依次返回。

js
app.use(async (ctx, next) => {
  console.log('Middleware 1: Start'); //1
  await next();
  console.log('Middleware 1: End');//4
});

app.use(async (ctx, next) => {
  console.log('Middleware 2: Start'); //2
  await next();
  console.log('Middleware 2: End'); //3
});

路由(Routing) ​

Koa 本身不提供路由功能,但可以通过第三方库(如 koa-router)来实现路由。

js
const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/', async (ctx, next) => {
  ctx.body = 'Hello, World!';
});

router.post('/data', async (ctx, next) => {
  const data = ctx.request.body;
  ctx.body = { received: data };
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});