Express基础与原理详解
express.js是一款基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
Express基础功能
作为相对底层的Web服务框架,Express比其他底层框架来说,在功能模型上相对复杂了一些提供的语法功能也比较多,其中间件架构模型的实现也比其他的服务框架复杂了一些。 让我们跟踪一个从前端发起的请求,直到后端响应的完整过程:
- 前端发起请求
- 请求到达 Express 服务器。
- Express 开始遍历中间件链:
- 应用级中间件(如日志记录)
- 路由级中间件
- 找到匹配的路由
- 执行路由处理函数
- 响应被发送回客户端。
- 前端接收响应并处理数据。
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 在源码中的封装原理。
创建服务器
//原生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() 的封装。
app.listen = function() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};这里,this 指向 Express 应用实例,它实现了 requestListener 接口,因此可以直接传给 http.createServer()。
路由处理
//原生 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 使用了一个复杂的路由系统来管理不同的路由。主要涉及以下几个类:
Router:管理一组路由规则Layer:表示一个单独的路由或中间件Route:表示一个具体的路由,包含多个 HTTP 方法的处理程序
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 没有内置的中间件概念。你需要手动在请求处理函数中实现类似的功能。
// Express.js
app.use(express.json());
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});Express 的中间件系统是建立在其路由系统之上的。每个中间件实际上都是一个 Layer 对象。 当你调用 app.use() 时,Express 会创建一个新的 Layer 对象,并将其添加到主路由栈中:
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() 会执行下一个中间件。
请求和响应对象
// 原生 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() 方法的简化实现如下
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。
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)的,请求会依次经过所有中间件,然后再依次返回。
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)来实现路由。
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');
});