# 自定义中间件

URL: https://caijiao.org/koa/middleware/custom
Source: docs/koa/middleware/custom.md
Description: 了解如何在 Koa 中创建和使用自定义中间件，以增强应用的功能，处理特定的请求和响应。

在 Koa 中，中间件是构建应用的核心机制。它们能够在请求和响应之间进行拦截和处理，使得在 Koa 中构建复杂的应用变得更加灵活和可扩展。Koa 的中间件机制基于 `async/await`，使得在异步操作中传递控制变得非常简单。

本篇将介绍如何在 [Koa](/koa/) 中创建和使用自定义中间件。

## 1. 创建自定义中间件

Koa 中的每个中间件都是一个异步函数，接收两个参数：`ctx`（上下文对象）和 `next`（一个函数，调用它可以将控制权传递给下一个中间件）。你可以根据需求在中间件中执行任何逻辑。

### 示例：简单的自定义中间件

```js
const Koa = require('koa');
const app = new Koa();

// 自定义中间件：记录请求时间
const logTime = async (ctx, next) => {
  const start = Date.now();
  await next();  // 继续处理下一个中间件
  const duration = Date.now() - start;
  console.log(`Request to ${ctx.url} took ${duration}ms`);
};

app.use(logTime);  // 使用自定义中间件

app.use((ctx) => {
  ctx.body = 'Hello Koa!';
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
````

在上面的代码中，我们创建了一个 `logTime` 中间件，它记录了请求的开始时间和结束时间，并输出请求的处理时长。

### 自定义中间件的结构

一个标准的中间件包含两部分：

* **请求处理逻辑**：你可以在中间件中执行任何逻辑，比如读取请求数据、修改 `ctx` 对象、记录日志等。
* **调用 `next()`**：中间件需要调用 `next()` 来传递控制权给下一个中间件，否则请求会被挂起，无法继续处理。

## 2. 使用多个中间件

Koa 允许你使用多个中间件，按照它们的顺序执行。每个中间件都会依次处理请求，直到最后一个中间件处理响应。

### 示例：使用多个中间件

```js
app.use(async (ctx, next) => {
  console.log('Middleware 1');
  await next();  // 调用下一个中间件
  console.log('Middleware 1 - After next');
});

app.use(async (ctx, next) => {
  console.log('Middleware 2');
  await next();
  console.log('Middleware 2 - After next');
});

app.use((ctx) => {
  console.log('Final middleware');
  ctx.body = 'Hello from Koa!';
});
```

在这个示例中，多个中间件会按照顺序执行，`next()` 会将控制权传递给下一个中间件。

### 中间件的执行顺序

* 中间件的执行顺序是按照它们的 `app.use()` 顺序执行的。
* 在每个中间件内部，`await next()` 表示在当前中间件完成之后，继续执行下一个中间件。可以在 `next()` 之后添加代码，用于响应阶段的处理。

## 3. 在中间件中操作 `ctx` 对象

`ctx`（上下文对象）是 Koa 中所有中间件共享的对象，它包含了请求和响应的相关信息。你可以在中间件中修改 `ctx` 来传递数据或改变请求与响应的内容。

### 示例：在中间件中修改 `ctx` 对象

```js
const addCustomHeader = async (ctx, next) => {
  ctx.set('X-Custom-Header', 'Koa Middleware Example');
  await next();
};

app.use(addCustomHeader);

app.use((ctx) => {
  ctx.body = 'Hello Koa!';
});
```

在这个例子中，`addCustomHeader` 中间件会在每个响应中添加一个自定义的 HTTP 头。

## 4. 异常处理与错误捕获

在 Koa 中，你可以使用自定义中间件来捕获错误，防止应用崩溃，并可以根据需求记录或返回自定义错误信息。

### 示例：全局错误捕获

```js
const errorHandler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    console.error('Error occurred:', err.message);
    ctx.status = err.status || 500;
    ctx.body = { error: err.message };
  }
};

app.use(errorHandler);

app.use(async (ctx) => {
  throw new Error('Something went wrong!');
});
```

在这个例子中，`errorHandler` 中间件捕获了应用中发生的所有错误，并返回自定义的错误信息。

## 5. 自定义中间件与路由结合

你可以将自定义中间件与路由结合使用。在 Koa 中，`koa-router` 提供了路由功能，你可以在路由中使用自定义中间件来处理特定路径的请求。

### 示例：自定义中间件与路由结合使用

```js
const Router = require('koa-router');
const router = new Router();

const authMiddleware = async (ctx, next) => {
  if (ctx.headers.authorization === 'Bearer secret') {
    await next();
  } else {
    ctx.status = 401;
    ctx.body = 'Unauthorized';
  }
};

router.get('/protected', authMiddleware, (ctx) => {
  ctx.body = 'This is a protected route';
});

app.use(router.routes());
```

在这个例子中，`authMiddleware` 中间件检查请求头中的授权信息，只有通过认证的请求才能访问 `/protected` 路由。

## 6. 总结

自定义中间件是 Koa 应用灵活性和可扩展性的核心。你可以在中间件中进行请求处理、响应修改、错误捕获等多种操作。通过自定义中间件，可以很容易地实现请求日志记录、认证授权、数据处理等功能，并将它们与路由、其他中间件结合使用，帮助你构建高效且灵活的 Web 应用。

自定义中间件使得 Koa 更加简洁和强大，让开发者可以根据自己的需求，精确控制请求和响应的处理过程。
