yuheng/doc/fastify-docs/docs/Encapsulation.md
2025-03-19 15:54:28 +08:00

5.3 KiB
Raw Blame History

Fastify

封装

“封装上下文”是 Fastify 的一个基础特性,负责控制路由能访问的装饰器钩子以及插件。下图是封装上下文的抽象表现:

Figure 1

上图可归纳为以下几块内容:

  1. 顶层上下文 (root context)
  2. 三个 顶层插件 (root plugin)
  3. 两个 子上下文 (child context),每个 子上下文 拥有
    • 两个 子插件 (child plugin)
    • 一个 孙子上下文 (grandchild context),其又拥有
      • 三个 子插件 (child plugin)

任意 子上下文孙子上下文 都有权访问 顶层插件孙子上下文 有权访问它上级的 子上下文 中注册的 子插件,但 子上下文 无权 访问它下级的 孙子上下文中 注册的 子插件

在 Fastify 中,除了 顶层上下文,一切皆为插件。下文的例子也不例外,所有的“上下文”和“插件”都是包含装饰器、钩子、插件及路由的插件。该例子为有三个路由的 REST API 服务器,第一个路由 (/one) 需要鉴权 (使用 fastify-bearer-auth),第二个路由 (/two) 无需鉴权,第三个路由 (/three) 有权访问第二个路由的上下文:

"use strict";

const fastify = require("fastify")();

fastify.decorateRequest("answer", 42);

fastify.register(async function authenticatedContext(childServer) {
  childServer.register(require("fastify-bearer-auth"), { keys: ["abc123"] });

  childServer.route({
    path: "/one",
    method: "GET",
    handler(request, response) {
      response.send({
        answer: request.answer,
        // request.foo 会是 undefined因为该值是在 publicContext 中定义的
        foo: request.foo,
        // request.bar 会是 undefined因为该值是在 grandchildContext 中定义的
        bar: request.bar,
      });
    },
  });
});

fastify.register(async function publicContext(childServer) {
  childServer.decorateRequest("foo", "foo");

  childServer.route({
    path: "/two",
    method: "GET",
    handler(request, response) {
      response.send({
        answer: request.answer,
        foo: request.foo,
        // request.bar 会是 undefined因为该值是在 grandchildContext 中定义的
        bar: request.bar,
      });
    },
  });

  childServer.register(async function grandchildContext(grandchildServer) {
    grandchildServer.decorateRequest("bar", "bar");

    grandchildServer.route({
      path: "/three",
      method: "GET",
      handler(request, response) {
        response.send({
          answer: request.answer,
          foo: request.foo,
          bar: request.bar,
        });
      },
    });
  });
});

fastify.listen(8000);

上面的例子展示了所有封装相关的概念:

  1. 每个 子上下文 (authenticatedContextpublicContextgrandchildContext) 都有权访问在 顶层上下文 中定义的 answer 请求装饰器。
  2. 只有 authenticatedContext 能访问 fastify-bearer-auth 插件。
  3. publicContextgrandchildContext 都能访问 foo 请求装饰器。
  4. 只有 grandchildContext 能访问 bar 请求装饰器。

启动服务来验证这些概念吧:

# curl -H 'authorization: Bearer abc123' http://127.0.0.1:8000/one
{"answer":42}
# curl http://127.0.0.1:8000/two
{"answer":42,"foo":"foo"}
# curl http://127.0.0.1:8000/three
{"answer":42,"foo":"foo","bar":"bar"}

在上下文间共享

请注意,在上文例子中,每个上下文都 从父级上下文进行继承,而父级上下文无权访问后代上下文中定义的实体。在某些情况下,我们并不想要这一默认行为。使用 fastify-plugin ,便能允许父级上下文访问到后代上下文中定义的实体。

假设上例的 publicContext 需要获取 grandchildContext 中定义的 bar 装饰器,我们可以重写代码如下:

"use strict";

const fastify = require("fastify")();
const fastifyPlugin = require("fastify-plugin");

fastify.decorateRequest("answer", 42);

// 为了代码清晰,这里省略了 `authenticatedContext`

fastify.register(async function publicContext(childServer) {
  childServer.decorateRequest("foo", "foo");

  childServer.route({
    path: "/two",
    method: "GET",
    handler(request, response) {
      response.send({
        answer: request.answer,
        foo: request.foo,
        bar: request.bar,
      });
    },
  });

  childServer.register(fastifyPlugin(grandchildContext));

  async function grandchildContext(grandchildServer) {
    grandchildServer.decorateRequest("bar", "bar");

    grandchildServer.route({
      path: "/three",
      method: "GET",
      handler(request, response) {
        response.send({
          answer: request.answer,
          foo: request.foo,
          bar: request.bar,
        });
      },
    });
  }
});

fastify.listen(8000);

重启服务,访问 /two/three 路由:

# curl http://127.0.0.1:8000/two
{"answer":42,"foo":"foo","bar":"bar"}
# curl http://127.0.0.1:8000/three
{"answer":42,"foo":"foo","bar":"bar"}