覆盖 Express API
Express API 由请求和响应对象上的各种方法和属性组成。这些方法和属性通过原型继承。Express API 有两个扩展点:
🌐 The Express API consists of various methods and properties on the request and response objects. These are inherited by prototype. There are two extension points for the Express API:
- 在
express.request和express.response的全球原型。 - 位于
app.request和app.response的应用特定原型。
修改全局原型将影响同一进程中所有已加载的 Express 应用。如果需要,可以通过在创建新应用后仅修改特定应用的原型来实现应用特定的修改。
🌐 Altering the global prototypes will affect all loaded Express apps in the same process. If desired, alterations can be made app-specific by only altering the app-specific prototypes after creating a new app.
方法
🌐 Methods
你可以通过分配自定义函数来覆盖现有方法的签名和行为。
🌐 You can override the signature and behavior of existing methods with your own, by assigning a custom function.
以下是重写 res.sendStatus 行为的示例。
🌐 Following is an example of overriding the behavior of res.sendStatus.
app.response.sendStatus = function (statusCode, type, message) { // code is intentionally kept simple for demonstration purpose return this.contentType(type).status(statusCode).send(message);};import { type Response } from 'express';
// Broaden the type of sendStatus so call sites accept the new arguments.declare module 'express-serve-static-core' { interface Response { sendStatus(statusCode: number, type: string, message: string): this; }}
app.response.sendStatus = function ( this: Response, statusCode: number, type: string, message: string) { // code is intentionally kept simple for demonstration purpose return this.contentType(type).status(statusCode).send(message);} as Response['sendStatus'];上述实现完全改变了 res.sendStatus 的原始签名。它现在接受一个状态码、编码类型以及要发送给客户端的消息。
🌐 The above implementation completely changes the original signature of res.sendStatus. It now accepts a status code, encoding type, and the message to be sent to the client.
在 TypeScript 中,扩展 express-serve-static-core 会将新的重载添加到 Response 类型,因此像 res.sendStatus(404, 'application/json', body) 这样的调用点可以进行类型检查。赋值使用 as Response['sendStatus'] 进行类型转换,因为用不同签名的方法替换无法针对原始声明进行检查。
🌐 In TypeScript, augmenting express-serve-static-core adds the new overload to the Response type so call sites such as res.sendStatus(404, 'application/json', body) type-check. The assignment is cast with as Response['sendStatus'] because replacing a method with a different signature cannot be checked against the original declaration.
现在可以这样使用被重写的方法:
🌐 The overridden method may now be used this way:
res.sendStatus(404, 'application/json', '{"error":"resource not found"}');属性
🌐 Properties
Express API 中的属性有以下几种类型:
🌐 Properties in the Express API are either:
- 分配的属性(例如:
req.baseUrl,req.originalUrl) - 定义为获取器(例如:
req.secure、req.ip)
由于类别 1 下的属性是在当前请求-响应周期的上下文中动态分配给 request 和 response 对象的,因此它们的行为无法被覆盖。
🌐 Since properties under category 1 are dynamically assigned on the request and response objects in the context of the current request-response cycle, their behavior cannot be overridden.
类别 2 下的属性可以使用 Express API 扩展 API 进行覆盖。
🌐 Properties under category 2 can be overwritten using the Express API extensions API.
以下代码重写了 req.ip 的值应如何获取。现在,它只是返回 Client-IP 请求头的值。
🌐 The following code rewrites how the value of req.ip is to be derived. Now, it simply returns the value of the Client-IP request header.
Object.defineProperty(app.request, 'ip', { configurable: true, enumerable: true, get() { return this.get('Client-IP'); },});import { type Request } from 'express';
Object.defineProperty(app.request, 'ip', { configurable: true, enumerable: true, get(this: Request) { return this.get('Client-IP'); },});在 TypeScript 中扩展 API
🌐 Extending the API in TypeScript
上面的部分会覆盖 Express 已经提供的成员。要向请求或响应添加你自己的属性或方法,请使用 TypeScript 的声明合并在 Express 命名空间中描述它们。将增强放在项目的一部分 .d.ts 文件中。除非自定义的 include 没有覆盖其位置,否则不需要进行 tsconfig.json 更改。
🌐 The sections above override members that Express already provides. To add your own properties or
methods to the request or response, describe them to TypeScript with
declaration merging on the
Express namespace. Put the augmentation in a .d.ts file that is part of your project. No
tsconfig.json change is needed unless a custom include does not cover its location.
例如,身份验证中间件可能会将 user 附加到请求上,而你可能会向响应中添加一个 sendError 助手:
🌐 For example, an authentication middleware may attach a user to the request, and you might add a
sendError helper to the response:
interface User { id: string; name: string;}
declare global { namespace Express { interface Request { user?: User; } interface Response { sendError(status: number, message: string): this; } }}
export {};这些新增功能现在在整个应用中都已知晓:
🌐 The additions are now known throughout the application:
import { type Request, type Response, type NextFunction } from 'express';
app.use((req: Request, res: Response, next: NextFunction) => { req.user = { id: '1', name: 'Tobi' }; next();});
app.response.sendError = function (this: Response, status: number, message: string) { return this.status(status).json({ error: message });};
app.get('/', (req: Request, res: Response) => { if (!req.user) { res.sendError(401, 'unauthorized'); return; } res.send(req.user.name);});Caution
将自定义请求属性声明为可选(user?)。该类型适用于每个请求,但 TypeScript 无法知道在给定处理程序之前运行了哪些中间件,因此必需属性将是一个错误的保证。在依赖它之前检查该值(如上面的 if (!req.user))。
🌐 Declare custom request properties as optional (user?). The type applies to every request, but
TypeScript cannot know which middleware ran before a given handler, so a required property would be a
false guarantee. Check for the value (as with if (!req.user) above) before relying on it.
添加一个新方法无需强制类型转换,这与用不同签名重写现有方法不同,因为该方法在类型上之前不存在。
🌐 Adding a new method needs no cast, unlike overriding an existing method with a different signature, because the method did not previously exist on the type.
原型
🌐 Prototype
为了提供 Express API,传递给 Express 的请求/响应对象(例如通过 app(req, res))需要继承相同的原型链。默认情况下,请求的是 http.IncomingRequest.prototype,响应的是 http.ServerResponse.prototype。
🌐 In order to provide the Express API, the request/response objects passed to Express (via app(req, res), for example) need to inherit from the same prototype chain. By default, this is http.IncomingRequest.prototype for the request and http.ServerResponse.prototype for the response.
除非必要,建议仅在应用级别进行此操作,而不是全局进行。此外,请确保所使用的原型的功能尽可能与默认原型匹配。
🌐 Unless necessary, it is recommended that this be done only at the application level, rather than globally. Also, take care that the prototype that is being used matches the functionality as closely as possible to the default prototypes.
// Use FakeRequest and FakeResponse in place of http.IncomingRequest and http.ServerResponse// for the given app referenceObject.setPrototypeOf(Object.getPrototypeOf(app.request), FakeRequest.prototype);Object.setPrototypeOf(Object.getPrototypeOf(app.response), FakeResponse.prototype);