Skip to main content

延迟接受请求

¥Delay Accepting Requests

介绍

¥Introduction

Fastify 提供了几个 hooks,可用于各种情况。其中之一是 onReady 钩子,它对于在服务器开始接受新请求之前执行任务很有用。但是,没有直接的机制来处理你希望服务器开始接受特定请求并拒绝所有其他请求的场景,至少在某个点之前是这样。

¥Fastify provides several hooks useful for a variety of situations. One of them is the onReady hook, which is useful for executing tasks right before the server starts accepting new requests. There isn't, though, a direct mechanism to handle scenarios in which you'd like the server to start accepting specific requests and denying all others, at least up to some point.

举例来说,你的服务器需要通过 OAuth 提供商进行身份验证才能开始服务请求。为此,它需要参与 OAuth 授权代码流程,这将要求它监听来自身份验证提供程序的两个请求:

¥Say, for instance, your server needs to authenticate with an OAuth provider to start serving requests. To do that it'd need to engage in the OAuth Authorization Code Flow, which would require it to listen to two requests from the authentication provider:

  1. 授权码 webhook

    ¥the Authorization Code webhook

  2. 令牌网络钩子

    ¥the tokens webhook

在授权流程完成之前,你将无法满足客户请求。那该怎么办呢?

¥Until the authorization flow is done you wouldn't be able to serve customer requests. What to do then?

有多种解决方案可以实现这种行为。在这里,我们将介绍其中一种技术,希望你能够尽快开始工作!

¥There are several solutions for achieving that kind of behavior. Here we'll introduce one of such techniques and, hopefully, you'll be able to get things rolling asap!

解决方案

¥Solution

概述

¥Overview

所提出的解决方案是处理这种情况以及许多类似情况的多种可能方法之一。它仅依赖于 Fastify,因此不需要花哨的基础设施技巧或第三方库。

¥The proposed solution is one of many possible ways of dealing with this scenario and many similar to it. It relies solely on Fastify, so no fancy infrastructure tricks or third-party libraries will be necessary.

为了简化事情,我们不会处理精确的 OAuth 流程,而是模拟一个场景,其中需要某个密钥来服务请求,并且该密钥只能在运行时通过外部提供者进行身份验证来检索。

¥To simplify things we won't be dealing with a precise OAuth flow but, instead, simulate a scenario in which some key is needed to serve a request and that key can only be retrieved in runtime by authenticating with an external provider.

这里的主要目标是尽早拒绝否则会失败的请求并提供一些有意义的上下文。这对于服务器(分配给必然失败的任务的资源更少)和客户端(他们获得一些有意义的信息并且不需要等待很长时间)都很有用。

¥The main goal here is to deny requests that would otherwise fail as early as possible and with some meaningful context. That's both useful for the server (fewer resources allocated to a bound-to-fail task) and for the client (they get some meaningful information and don't need to wait long for it).

这将通过将两个主要功能封装到自定义插件中来实现:

¥That will be achieved by wrapping into a custom plugin two main features:

  1. 使用身份验证密钥与提供程序 decorating fastify 对象进行身份验证的机制(从这里开始为 magicKey

    ¥the mechanism for authenticating with the provider decorating the fastify object with the authentication key (magicKey from here onward)

  2. 拒绝请求的机制,否则会失败

    ¥the mechanism for denying requests that would, otherwise, fail

实际操作

¥Hands-on

对于此示例解决方案,我们将使用以下内容:

¥For this sample solution we'll be using the following:

  • node.js v16.14.2

  • npm 8.5.0

  • fastify 4.0.0-rc.1

  • fastify-plugin 3.0.1

  • undici 5.0.0

假设我们首先设置了以下基础服务器:

¥Say we have the following base server set up at first:

const Fastify = require('fastify')

const provider = require('./provider')

const server = Fastify({ logger: true })
const USUAL_WAIT_TIME_MS = 5000

server.get('/ping', function (request, reply) {
reply.send({ error: false, ready: request.server.magicKey !== null })
})

server.post('/webhook', function (request, reply) {
// It's good practice to validate webhook requests really come from
// whoever you expect. This is skipped in this sample for the sake
// of simplicity

const { magicKey } = request.body
request.server.magicKey = magicKey
request.log.info('Ready for customer requests!')

reply.send({ error: false })
})

server.get('/v1*', async function (request, reply) {
try {
const data = await provider.fetchSensitiveData(request.server.magicKey)
return { customer: true, error: false }
} catch (error) {
request.log.error({
error,
message: 'Failed at fetching sensitive data from provider',
})

reply.statusCode = 500
return { customer: null, error: true }
}
})

server.decorate('magicKey')

server.listen({ port: '1234' }, () => {
provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS)
.catch((error) => {
server.log.error({
error,
message: 'Got an error while trying to get the magic key!'
})

// Since we won't be able to serve requests, might as well wrap
// things up
server.close(() => process.exit(1))
})
})

我们的代码只是使用一些路由设置 Fastify 服务器:

¥Our code is simply setting up a Fastify server with a few routes:

  • 通过检查 magicKey 是否已设置来指定服务是否已准备好处理请求的 /ping 路由

    ¥a /ping route that specifies whether the service is ready or not to serve requests by checking if the magicKey has been set up

  • 当我们的提供商准备好共享 magicKey 时,会使用 /webhook 端点与我们联系。然后,将 magicKey 保存到 fastify 对象上先前设置的装饰器中

    ¥a /webhook endpoint for our provider to reach back to us when they're ready to share the magicKey. The magicKey is, then, saved into the previously set decorator on the fastify object

  • 用于模拟客户发起的请求的通用 /v1* 路由。这些请求依赖于我们拥有有效的 magicKey

    ¥a catchall /v1* route to simulate what would have been customer-initiated requests. These requests rely on us having a valid magicKey

provider.js 文件模拟外部提供商的操作,如下所示:

¥The provider.js file, simulating actions of an external provider, is as follows:

const { fetch } = require('undici')
const { setTimeout } = require('node:timers/promises')

const MAGIC_KEY = '12345'

const delay = setTimeout

exports.thirdPartyMagicKeyGenerator = async (ms) => {
// Simulate processing delay
await delay(ms)

// Simulate webhook request to our server
const { status } = await fetch(
'http://localhost:1234/webhook',
{
body: JSON.stringify({ magicKey: MAGIC_KEY }),
method: 'POST',
headers: {
'content-type': 'application/json',
},
},
)

if (status !== 200) {
throw new Error('Failed to fetch magic key')
}
}

exports.fetchSensitiveData = async (key) => {
// Simulate processing delay
await delay(700)
const data = { sensitive: true }

if (key === MAGIC_KEY) {
return data
}

throw new Error('Invalid key')
}

这里最重要的代码片段是 thirdPartyMagicKeyGenerator 函数,它将等待 5 秒钟,然后向我们的 /webhook 端点发出 POST 请求。

¥The most important snippet here is the thirdPartyMagicKeyGenerator function, which will wait for 5 seconds and, then, make the POST request to our /webhook endpoint.

当我们的服务器启动时,我们开始监听新连接,而无需设置我们的 magicKey。在我们收到来自外部提供商的 webhook 请求之前(在此示例中,我们模拟了 5 秒的延迟),我们在 /v1* 路径下的所有请求(客户请求)都将失败。比这更糟糕的是:当我们使用无效密钥联系提供商并从他们那里收到错误后,它们就会失败。这浪费了我们和我们的客户的时间和资源。根据我们预期的应用类型,这种延迟是不可接受的,或者至少非常烦人。

¥When our server spins up we start listening to new connections without having our magicKey set up. Until we receive the webhook request from our external provider (in this example we're simulating a 5 second delay) all our requests under the /v1* path (customer requests) will fail. Worse than that: they'll fail after we've reached out to our provider with an invalid key and got an error from them. That wasted time and resources for us and our customers. Depending on the kind of application we're running and on the request rate we're expecting this delay is not acceptable or, at least, very annoying.

当然,可以通过在 /v1* 处理程序中访问提供程序之前检查是否已设置 magicKey 来简单地缓解这种情况。当然可以,但这会导致代码膨胀。想象一下,我们有数十条不同的路由,具有不同的控制器,需要该密钥。我们是否应该重复地将检查添加到所有这些检查中?这很容易出错,但还有更优雅的解决方案。

¥Of course, that could be simply mitigated by checking whether or not the magicKey has been set up before hitting the provider in the /v1* handler. Sure, but that would lead to bloat in the code. And imagine we have dozens of different routes, with different controllers, that require that key. Should we repeatedly add that check to all of them? That's error-prone and there are more elegant solutions.

为了全面改善此设置,我们将创建一个 Plugin,它将全权负责确保我们做到以下两点:

¥What we'll do to improve this setup overall is create a Plugin that'll be solely responsible for making sure we both:

  • 在我们准备好之前,不要接受否则会失败的请求

    ¥do not accept requests that would otherwise fail until we're ready for them

  • 确保我们尽快联系我们的提供商

    ¥make sure we reach out to our provider as soon as possible

这样,我们将确保有关此特定业务规则的所有设置都放置在单个实体上,而不是分散在我们的代码库中。

¥This way we'll make sure all our setup regarding this specific business rule is placed on a single entity, instead of scattered all across our code base.

通过更改来改进此行为,代码将如下所示:

¥With the changes to improve this behavior, the code will look like this:

index.js
const Fastify = require('fastify')

const customerRoutes = require('./customer-routes')
const { setup, delay } = require('./delay-incoming-requests')

const server = new Fastify({ logger: true })

server.register(setup)

// Non-blocked URL
server.get('/ping', function (request, reply) {
reply.send({ error: false, ready: request.server.magicKey !== null })
})

// Webhook to handle the provider's response - also non-blocked
server.post('/webhook', function (request, reply) {
// It's good practice to validate webhook requests really come from
// whoever you expect. This is skipped in this sample for the sake
// of simplicity

const { magicKey } = request.body
request.server.magicKey = magicKey
request.log.info('Ready for customer requests!')

reply.send({ error: false })
})

// Blocked URLs
// Mind we're building a new plugin by calling the `delay` factory with our
// customerRoutes plugin
server.register(delay(customerRoutes), { prefix: '/v1' })

server.listen({ port: '1234' })
provider.js
const { fetch } = require('undici')
const { setTimeout } = require('node:timers/promises')

const MAGIC_KEY = '12345'

const delay = setTimeout

exports.thirdPartyMagicKeyGenerator = async (ms) => {
// Simulate processing delay
await delay(ms)

// Simulate webhook request to our server
const { status } = await fetch(
'http://localhost:1234/webhook',
{
body: JSON.stringify({ magicKey: MAGIC_KEY }),
method: 'POST',
headers: {
'content-type': 'application/json',
},
},
)

if (status !== 200) {
throw new Error('Failed to fetch magic key')
}
}

exports.fetchSensitiveData = async (key) => {
// Simulate processing delay
await delay(700)
const data = { sensitive: true }

if (key === MAGIC_KEY) {
return data
}

throw new Error('Invalid key')
}
delay-incoming-requests.js
const fp = require('fastify-plugin')

const provider = require('./provider')

const USUAL_WAIT_TIME_MS = 5000

async function setup(fastify) {
// As soon as we're listening for requests, let's work our magic
fastify.server.on('listening', doMagic)

// Set up the placeholder for the magicKey
fastify.decorate('magicKey')

// Our magic -- important to make sure errors are handled. Beware of async
// functions outside `try/catch` blocks
// If an error is thrown at this point and not captured it'll crash the
// application
function doMagic() {
fastify.log.info('Doing magic!')

provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS)
.catch((error) => {
fastify.log.error({
error,
message: 'Got an error while trying to get the magic key!'
})

// Since we won't be able to serve requests, might as well wrap
// things up
fastify.close(() => process.exit(1))
})
}
}

const delay = (routes) =>
function (fastify, opts, done) {
// Make sure customer requests won't be accepted if the magicKey is not
// available
fastify.addHook('onRequest', function (request, reply, next) {
if (!request.server.magicKey) {
reply.statusCode = 503
reply.header('Retry-After', USUAL_WAIT_TIME_MS)
reply.send({ error: true, retryInMs: USUAL_WAIT_TIME_MS })
}

next()
})

// Register to-be-delayed routes
fastify.register(routes, opts)

done()
}

module.exports = {
setup: fp(setup),
delay,
}
customer-routes.js
const fp = require('fastify-plugin')

const provider = require('./provider')

module.exports = fp(async function (fastify) {
fastify.get('*', async function (request ,reply) {
try {
const data = await provider.fetchSensitiveData(request.server.magicKey)
return { customer: true, error: false }
} catch (error) {
request.log.error({
error,
message: 'Failed at fetching sensitive data from provider',
})

reply.statusCode = 500
return { customer: null, error: true }
}
})
})

之前存在的文件有一个非常具体的更改值得一提:之前,我们使用 server.listen 回调来启动与外部提供商的身份验证过程,并且在初始化服务器之前装饰 server 对象。这使得我们的服务器初始化设置变得臃肿,其中包含了不必要的代码,并且与启动 Fastify 服务器没有太大关系。它是一种业务逻辑,在代码库中没有其特定的位置。

¥There is a very specific change on the previously existing files that is worth mentioning: Beforehand we were using the server.listen callback to start the authentication process with the external provider and we were decorating the server object right before initializing the server. That was bloating our server initialization setup with unnecessary code and didn't have much to do with starting the Fastify server. It was a business logic that didn't have its specific place in the code base.

现在我们已经在 delay-incoming-requests.js 文件中实现了 delayIncomingRequests 插件。事实上,一个模块分为两个不同的插件,这两个插件将构建一个用例。这是我们行动的大脑。让我们来看看插件的作用:

¥Now we've implemented the delayIncomingRequests plugin in the delay-incoming-requests.js file. That's, in truth, a module split into two different plugins that will build up to a single use-case. That's the brains of our operation. Let's walk through what the plugins do:

setup

setup 插件负责确保我们尽快联系我们的提供商,并将 magicKey 存储在所有处理程序都可用的某个地方。

¥The setup plugin is responsible for making sure we reach out to our provider asap and store the magicKey somewhere available to all our handlers.

  fastify.server.on('listening', doMagic)

一旦服务器开始监听(与向 server.listen 的回调函数添加一段代码的行为非常相似),就会发出 listening 事件(有关更多信息,请参阅 https://nodejs.cn/api/net.html#event-listening)。我们利用 doMagic 函数尽快联系我们的提供商。

¥As soon as the server starts listening (very similar behavior to adding a piece of code to the server.listen's callback function) a listening event is emitted (for more info refer to https://nodejs.cn/api/net.html#event-listening). We use that to reach out to our provider as soon as possible, with the doMagic function.

  fastify.decorate('magicKey')

magicKey 装饰现在也是插件的一部分。我们用占位符初始化它,等待检索有效值。

¥The magicKey decoration is also part of the plugin now. We initialize it with a placeholder, waiting for the valid value to be retrieved.

delay

delay 本身不是插件。它实际上是一个插件工厂。它需要一个带有 routes 的 Fastify 插件,并导出实际的插件,该插件将使用 onRequest 钩子处理封装这些路由,以确保在我们准备好之前不会处理任何请求。

¥delay is not a plugin itself. It's actually a plugin factory. It expects a Fastify plugin with routes and exports the actual plugin that'll handle enveloping those routes with an onRequest hook that will make sure no requests are handled until we're ready for them.

const delay = (routes) =>
function (fastify, opts, done) {
// Make sure customer requests won't be accepted if the magicKey is not
// available
fastify.addHook('onRequest', function (request, reply, next) {
if (!request.server.magicKey) {
reply.statusCode = 503
reply.header('Retry-After', USUAL_WAIT_TIME_MS)
reply.send({ error: true, retryInMs: USUAL_WAIT_TIME_MS })
}

next()
})

// Register to-be-delayed routes
fastify.register(routes, opts)

done()
}

我们无需更新可能使用 magicKey 的每个控制器,只需确保在一切准备就绪之前不会提供与客户请求相关的任何路由。还有更多:我们快速失败,并有可能向客户提供有意义的信息,例如他们应该等待多长时间才能重试请求。更进一步,通过发出 503 状态码,我们仍未准备好接收传入请求,并且它们应该将流量重定向到其他实例(如果可用),此外我们估计这个问题需要多长时间才能解决。所有这些都只需几行简单的代码!

¥Instead of updating every single controller that might use the magicKey, we simply make sure that no route that's related to customer requests will be served until we have everything ready. And there's more: we fail FAST and have the possibility of giving the customer meaningful information, like how long they should wait before retrying the request. Going even further, by issuing a 503 status code we're signaling to our infrastructure components (namely load balancers) we're still not ready to take incoming requests and they should redirect traffic to other instances, if available, besides in how long we estimate that will be solved. All of that in a few simple lines!

值得注意的是,我们没有在 delay 工厂中使用 fastify-plugin 封装器。这是因为我们希望 onRequest 钩子仅在特定范围内设置,而不是在调用它的范围(在我们的例子中,是 index.js 中定义的主要 server 对象)内设置。fastify-plugin 设置 skip-override 隐藏属性,其实际效果是使我们对 fastify 对象所做的任何更改都可用于上层范围。这也是我们将其与 customerRoutes 插件一起使用的原因:我们希望这些路由可用于其调用范围,即 delay 插件。有关该主题的更多信息,请参阅 插件

¥It's noteworthy that we didn't use the fastify-plugin wrapper in the delay factory. That's because we wanted the onRequest hook to only be set within that specific scope and not to the scope that called it (in our case, the main server object defined in index.js). fastify-plugin sets the skip-override hidden property, which has a practical effect of making whatever changes we make to our fastify object available to the upper scope. That's also why we used it with the customerRoutes plugin: we wanted those routes to be available to its calling scope, the delay plugin. For more info on that subject refer to Plugins.

让我们看看它的实际表现如何。如果我们使用 node index.js 启动我们的服务器并发出一些请求来测试一下。这些是我们会看到的日志(删除了一些臃肿的内容以简化操作):

¥Let's see how that behaves in action. If we fired our server up with node index.js and made a few requests to test things out. These were the logs we'd see (some bloat was removed to ease things up):

{"time":1650063793316,"msg":"Doing magic!"} {"time":1650063793316,"msg":"Server listening at http://127.0.0.1:1234"} {"time":1650063795030,"reqId":"req-1","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51928},"msg":"incoming request"} {"time":1650063795033,"reqId":"req-1","res":{"statusCode":503},"responseTime":2.5721680000424385,"msg":"request completed"} {"time":1650063796248,"reqId":"req-2","req":{"method":"GET","url":"/ping","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51930},"msg":"incoming request"} {"time":1650063796248,"reqId":"req-2","res":{"statusCode":200},"responseTime":0.4802369996905327,"msg":"request completed"} {"time":1650063798377,"reqId":"req-3","req":{"method":"POST","url":"/webhook","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51932},"msg":"incoming request"} {"time":1650063798379,"reqId":"req-3","msg":"Ready for customer requests!"} {"time":1650063798379,"reqId":"req-3","res":{"statusCode":200},"responseTime":1.3567829988896847,"msg":"request completed"} {"time":1650063799858,"reqId":"req-4","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51934},"msg":"incoming request"} {"time":1650063800561,"reqId":"req-4","res":{"statusCode":200},"responseTime":702.4662979990244,"msg":"request completed"}

我们重点关注几个部分:

¥Let's focus on a few parts:

{"time":1650063793316,"msg":"Doing magic!"} {"time":1650063793316,"msg":"Server listening at http://127.0.0.1:1234"}

这些是服务器启动后我们会看到的初始日志。我们在有效时间窗口内尽早联系外部提供商(在服务器准备好接收连接之前我们无法这样做)。

¥These are the initial logs we'd see as soon as the server started. We reach out to the external provider as early as possible within a valid time window (we couldn't do that before the server was ready to receive connections).

当服务器尚未准备好时,会尝试一些请求:

¥While the server is still not ready, a few requests are attempted:

{"time":1650063795030,"reqId":"req-1","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51928},"msg":"incoming request"} {"time":1650063795033,"reqId":"req-1","res":{"statusCode":503},"responseTime":2.5721680000424385,"msg":"request completed"} {"time":1650063796248,"reqId":"req-2","req":{"method":"GET","url":"/ping","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51930},"msg":"incoming request"} {"time":1650063796248,"reqId":"req-2","res":{"statusCode":200},"responseTime":0.4802369996905327,"msg":"request completed"}

第一个(req-1)是失败的 GET /v1(FAST - responseTimems 中)与我们的 503 状态代码和响应中的有意义的信息。以下是对该请求的响应:

¥The first one (req-1) was a GET /v1, that failed (FAST - responseTime is in ms) with our 503 status code and the meaningful information in the response. Below is the response for that request:

HTTP/1.1 503 Service Unavailable
Connection: keep-alive
Content-Length: 31
Content-Type: application/json; charset=utf-8
Date: Fri, 15 Apr 2022 23:03:15 GMT
Keep-Alive: timeout=5
Retry-After: 5000

{
"error": true,
"retryInMs": 5000
}

然后我们尝试一个新的请求(req-2),它是 GET /ping。正如预期的那样,由于这不是我们要求插件过滤的请求之一,因此它成功了。这也可以用作通知相关方我们是否已准备好处理请求的手段(尽管 /ping 通常与活跃度检查相关联,并且这将是就绪性检查的责任 - 好奇的读者可以通过 ready 字段获得有关这些术语 此处 的更多信息)。以下是对该请求的响应:

¥Then we attempt a new request (req-2), which was a GET /ping. As expected, since that was not one of the requests we asked our plugin to filter, it succeeded. That could also be used as means of informing an interested party whether or not we were ready to serve requests (although /ping is more commonly associated with liveness checks and that would be the responsibility of a readiness check -- the curious reader can get more info on these terms here) with the ready field. Below is the response for that request:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 29
Content-Type: application/json; charset=utf-8
Date: Fri, 15 Apr 2022 23:03:16 GMT
Keep-Alive: timeout=5

{
"error": false,
"ready": false
}

之后还有更多有趣的日志消息:

¥After that there were more interesting log messages:

{"time":1650063798377,"reqId":"req-3","req":{"method":"POST","url":"/webhook","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51932},"msg":"incoming request"} {"time":1650063798379,"reqId":"req-3","msg":"Ready for customer requests!"} {"time":1650063798379,"reqId":"req-3","res":{"statusCode":200},"responseTime":1.3567829988896847,"msg":"request completed"}

这次是我们模拟的外部提供商来告诉我们身份验证进展顺利,并告诉我们我们的 magicKey 是什么。我们将其保存到我们的 magicKey 装饰器中,并通过一条日志消息庆祝,说我们现在已准备好迎接客户的到来!

¥This time it was our simulated external provider hitting us to let us know authentication had gone well and telling us what our magicKey was. We saved that into our magicKey decorator and celebrated with a log message saying we were now ready for customers to hit us!

{"time":1650063799858,"reqId":"req-4","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51934},"msg":"incoming request"} {"time":1650063800561,"reqId":"req-4","res":{"statusCode":200},"responseTime":702.4662979990244,"msg":"request completed"}

最后,发出了最后一个 GET /v1 请求,这次成功了。其回应如下:

¥Finally, a final GET /v1 request was made and, this time, it succeeded. Its response was the following:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 31
Content-Type: application/json; charset=utf-8
Date: Fri, 15 Apr 2022 23:03:20 GMT
Keep-Alive: timeout=5

{
"customer": true,
"error": false
}

结论

¥Conclusion

每个问题的实现细节都会有所不同,但本指南的主要目标是展示可以在 Fastify 生态系统中解决的问题的一个非常具体的用例。

¥Specifics of the implementation will vary from one problem to another, but the main goal of this guide was to show a very specific use case of an issue that could be solved within Fastify's ecosystem.

本指南是关于使用插件、装饰器和钩子来解决应用上特定请求延迟服务问题的教程。它不适用于生产环境,因为它保留本地状态(magicKey)并且不是水平可扩展的(我们不想淹没我们的提供商,对吧?)。改进它的一种方法是将 magicKey 存储在其他地方(也许是缓存数据库?)。

¥This guide is a tutorial on the use of plugins, decorators, and hooks to solve the problem of delaying serving specific requests on our application. It's not production-ready, as it keeps local state (the magicKey) and it's not horizontally scalable (we don't want to flood our provider, right?). One way of improving it would be storing the magicKey somewhere else (perhaps a cache database?).

这里的关键字是 装饰器钩子插件。结合 Fastify 所提供的功能可以为各种问题提供非常巧妙且富有创意的解决方案。让我们发挥创意吧!:)

¥The keywords here were Decorators, Hooks, and Plugins. Combining what Fastify has to offer can lead to very ingenious and creative solutions to a wide variety of problems. Let's be creative! :)