Skip to Content

回复

引言

处理函数的第二个参数是 ReplyReply 是 Fastify 的核心对象,提供了以下功能和属性:

  • .code(statusCode) - 设置状态码。
  • .status(statusCode) - .code(statusCode) 的别名。
  • .statusCode - 读取和设置 HTTP 状态码。
  • .elapsedTime - 返回自请求被 Fastify 接收以来经过的时间。
  • .server - 对 fastify 实例对象的引用。
  • .header(name, value) - 设置响应头。
  • .headers(object) - 将对象的所有键作为响应头设置。
  • .getHeader(name) - 获取已设置的头部值。
  • .getHeaders() - 获取当前所有响应头的浅拷贝。
  • .removeHeader(key) - 移除之前设置的头部值。
  • .hasHeader(name) - 判断是否设置了某个头部。
  • .writeEarlyHints(hints, callback) - 在准备响应时向用户发送早期提示信息。
  • .trailer(key, function) - 设置响应尾部。
  • .hasTrailer(key) - 判断是否设置了某个尾部。
  • .removeTrailer(key) - 移除之前设置的尾部值。
  • .type(value) - 设置 Content-Type 头部。
  • .redirect(dest, [code]) - 重定向到指定 URL,状态码是可选的(默认为 302)。
  • .callNotFound() - 调用自定义未找到处理程序。
  • .serialize(payload) - 使用默认 JSON 序列化器或自定义序列化器(如果已设置)对指定负载进行序列化,并返回序列化的负载。
  • .getSerializationFunction(schema | httpStatus, [contentType]) - 返回为指定模式或 HTTP 状态码的序列化函数,如果有任何一项被设置的话。
  • .compileSerializationSchema(schema, [httpStatus], [contentType]) - 编译指定模式并使用默认(或自定义)的 SerializerCompiler 返回一个序列化函数。可选的 httpStatus 会传递给 SerializerCompiler,默认为 undefined
  • .serializeInput(data, schema, [, httpStatus], [contentType]) - 序列化将 SerializerCompiler 提供的值传递给 SerializerCompiler,默认为 undefined
  • .serializeInput(data, schema, [,httpStatus], [contentType]) - 使用指定的模式序列化指定的数据,并返回序列化的负载。如果提供了可选参数 httpStatuscontentType,函数将使用特定内容类型和 HTTP 状态码提供的序列化器函数。默认为 undefined
  • .serializer(function) - 设置自定义的负载序列化器。
  • .send(payload) - 将负载发送给用户,可以是纯文本、缓冲区、JSON、流或错误对象。
  • .sent - 一个布尔值,用于判断是否已经调用了 send 方法。
  • .hijack() - 中断正常的请求生命周期。
  • .raw - 来自 Node 核心的 http.ServerResponse 对象。
  • .log - 入站请求的日志实例。
  • .request - 入站请求。

以下是翻译后的Markdown内容:

fastify.get('/', options, function (request, reply) { // 您的代码 reply .code(200) .header('Content-Type', 'application/json; charset=utf-8') .send({ hello: 'world' }) })

.code(statusCode)

如果未通过 reply.code 设置,最终的 statusCode 将为 200

.elapsedTime

调用自定义响应时间获取器以计算自从请求被 Fastify 接收到的时间长度。

const milliseconds = reply.elapsedTime

.statusCode

此属性用于读取和设置 HTTP 状态码。当作为赋值操作符使用时,它是 reply.code() 的别名。

if (reply.statusCode >= 299) { reply.statusCode = 500 }

.server

当前 封装上下文 中的 Fastify 服务器实例。

fastify.decorate('util', function util () { return 'foo' }) fastify.get('/', async function (req, rep) { return rep.server.util() // foo })

.header(key, value)

设置响应头。如果值被省略或为 undefined,则会被强制转换为 ''

ℹ️ 注意:必须使用诸如 encodeURI 或类似的模块(如 encodeurl)对响应头的值进行适当的编码。无效字符会导致 500 TypeError 响应。

更多信息请参阅 http.ServerResponse#setHeader

    • 当使用 set-cookie 作为键发送不同的值时,每个值都会被当作 cookie 发送,而不是替换之前的值。
reply.header('set-cookie', 'foo'); reply.header('set-cookie', 'bar');
  • 浏览器只会考虑 set-cookie 头中最新的引用。这是为了避免在添加到响应时解析 set-cookie 头,并加快响应的序列化速度。

  • 要重置 set-cookie 头,需要显式调用 reply.removeHeader('set-cookie'),更多关于 .removeHeader(key) 的信息请参阅 这里

.headers(object)

将对象的所有键设置为响应头。 .header 会在内部被调用。

reply.headers({ 'x-foo': 'foo', 'x-bar': 'bar' })

.getHeader(key)

获取之前已设置的头部值。

reply.header('x-foo', 'foo') // setHeader: key, value reply.getHeader('x-foo') // 'foo'

.getHeader(key)

获取之前设置的头部值。

reply.header('x-foo', 'foo') // 设置头部:键,值 reply.getHeader('x-foo') // 'foo'

.getHeaders()

获取当前所有响应头的浅拷贝(包括通过原始 http.ServerResponse 设置的那些)。请注意,Fastify 设置的头部优先于通过 http.ServerResponse 设置的头部。

reply.header('x-foo', 'foo') reply.header('x-bar', 'bar') reply.raw.setHeader('x-foo', 'foo2') reply.getHeaders() // { 'x-foo': 'foo', 'x-bar': 'bar' }

.removeHeader(key)

移除之前设置的头部值。

reply.header('x-foo', 'foo') reply.removeHeader('x-foo') reply.getHeader('x-foo') // undefined

.hasHeader(key)

返回一个布尔值,指示指定的头部是否已设置。

.writeEarlyHints(hints, callback)

向客户端发送早期提示。早期提示允许客户端在最终响应发送之前开始处理资源。这可以通过让客户端预加载或预先连接到资源来提高性能,同时服务器仍在生成响应。

hints 参数是一个包含早期提示键值对的对象。

示例:

reply.writeEarlyHints({ Link: '</styles.css>; rel=preload; as=style' });

可选的 callback 参数是在发送提示或发生错误时调用的一个函数。

.trailer(key, function)

设置响应尾部字段。尾部字段通常用于需要在 data 之后发送的头部信息,例如 Server-TimingEtag。这可以确保客户端尽快接收到响应数据。

ℹ️ 注意:一旦使用了尾部字段,将会添加 Transfer-Encoding: chunked 头部。这是 Node.js 中使用尾部字段的一个硬性要求。

ℹ️ 注意:传递给 done 回调的任何错误将被忽略。如果您想查看错误信息,请开启 debug 级别的日志记录。

reply.trailer('server-timing', function() { return 'db;dur=53, app;dur=47.2' }) const { createHash } = require('node:crypto') // 尾部字段函数接收两个参数 // @param {object} reply fastify 回复对象 // @param {string|Buffer|null} payload 已发送的数据,注意在流传输时为 null // @param {function} done 设置尾部字段值的回调函数 reply.trailer('content-md5', function(reply, payload, done) { const hash = createHash('md5') hash.update(payload) done(null, hash.digest('hex')) }) // 当您更喜欢使用 async-await 时 reply.trailer('content-md5', async function(reply, payload) { const hash = createHash('md5') hash.update(payload) return hash.digest('hex') })

.hasTrailer(key)

返回一个布尔值,指示指定的尾部字段是否已设置。

.removeTrailer(key)

移除之前设置的尾部字段的值。

reply.trailer('server-timing', function() { return 'db;dur=53, app;dur=47.2' }) reply.removeTrailer('server-timing') reply.getTrailer('server-timing') // undefined

.redirect(dest, [code ,])

将请求重定向到指定的URL,状态码是可选参数,默认为 302(如果状态码尚未通过调用 code 设置)。

ℹ️ 注意:输入的 URL 必须使用 encodeURI 或类似的模块如 encodeurl 进行正确编码。无效的 URL 将导致 500 TypeError 响应。

示例(未调用 reply.code())将状态码设置为 302 并重定向到 /home

reply.redirect('/home')

示例(未调用 reply.code())将状态码设置为 303 并重定向到 /home

reply.redirect('/home', 303)

示例(调用了 reply.code())将状态码设置为 303 并重定向到 /home

reply.code(303).redirect('/home')

示例(调用了 reply.code())将状态码设置为 302 并重定向到 /home

reply.code(303).redirect('/home', 302)

.callNotFound()

调用自定义的未找到处理程序。请注意,它只会调用在 setNotFoundHandler 中指定的 preHandler 钩子。

reply.callNotFound()

.type(contentType)

为响应设置内容类型。这是对 reply.header('Content-Type', 'the/type') 的快捷方式。

reply.type('text/html')

如果 Content-Type 包含 JSON 子类型,并且未设置字符集参数,则默认使用 utf-8 作为字符集。对于其他内容类型,必须显式设置字符集。

.getSerializationFunction(schema | httpStatus, [contentType])

通过使用提供的 schemahttpStatus 调用此函数,并可选地提供 contentType,它将返回一个可以用于序列化各种输入的 serialzation 函数。如果未找到任何序列化函数,则返回 undefined

这主要依赖于附加到路由上的 schema#responses 或使用 compileSerializationSchema 编译的序列化函数。

const serialize = reply.getSerializationFunction({ type: 'object', properties: { foo: { type: 'string' } } }) serialize({ foo: 'bar' }) // '{"foo":"bar"}' // 或 const serialize = reply.getSerializationFunction(200) serialize({ foo: 'bar' }) // '{"foo":"bar"}' // 或 const serialize = reply.getSerializationFunction(200, 'application/json') serialize({ foo: 'bar' }) // '{"foo":"bar"}'

有关如何编译序列化模式的更多信息,请参见 .compileSerializationSchema(schema, [httpStatus], [contentType])

编译序列化模式(schema, [httpStatus], [contentType])

此函数将编译一个序列化模式,并返回一个可以用于序列化数据的函数。返回的函数(也称为“序列化函数”)是通过提供的 SerializerCompiler 编译生成的,同时使用弱映射缓存来减少编译调用次数。

可选参数 httpStatuscontentType 如果提供的话,将直接传递给 SerializerCompiler,以便在使用自定义 SerializerCompiler 时可以用来编译序列化函数。

这高度依赖于附加到路由的 schema#responses 或通过 compileSerializationSchema 编译的序列化函数。

const serialize = reply .compileSerializationSchema({ type: 'object', properties: { foo: { type: 'string' } } }) serialize({ foo: 'bar' }) // '{"foo":"bar"}' // 或 const serialize = reply .compileSerializationSchema({ type: 'object', properties: { foo: { type: 'string' } } }, 200) serialize({ foo: 'bar' }) // '{"foo":"bar"}' // 或 const 序列化 = reply .compileSerializationSchema({ '3xx': { content: { 'application/json': { schema: { name: { type: 'string' }, phone: { type: 'number' } } } } } }, '3xx', 'application/json') 序列化({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'

请注意,在使用此函数时要小心,因为它会根据提供的模式缓存编译后的序列化函数。如果提供的模式被修改或更改,则序列化函数将无法检测到模式已被更改,并且可能会重用之前基于所提供模式引用的编译后的序列化函数。

如果您需要更改模式的属性,请始终选择创建一个全新的对象,否则实现将不会从缓存机制中受益。

:使用以下模式作为示例:

const schema1 = { type: 'object', properties: { foo: { type: 'string' } } }

const serialize = reply.compileSerializationSchema(schema1) // 后续... schema1.properties.foo.type. = 'integer' const newSerialize = reply.compileSerializationSchema(schema1) console.log(newSerialize === serialize) // true

而是

const serialize = reply.compileSerializationSchema(schema1) // 后续... const newSchema = Object.assign({}, schema1) newSchema.properties.foo.type = 'integer' const newSerialize = reply.compileSerializationSchema(newSchema) console.log(newSerialize === serialize) // false

.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])

此函数将根据提供的模式或HTTP状态码序列化输入数据。如果同时提供了模式和HTTP状态码,则优先使用httpStatus

如果没有为给定的schema提供序列化函数,将会编译一个新的序列化函数,并在提供的情况下传递httpStatuscontentType

reply .serializeInput({ foo: 'bar'}, { type: 'object', properties: { foo: { type: 'string' } } }) // '{"foo":"bar"}' // 或 reply .serializeInput({ foo: 'bar'}, { type: 'object', properties: { foo: { type: 'string' } } }, 200) // '{"foo":"bar"}' // 或 reply .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}' // 或 reply .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'

有关如何编译序列化模式的更多信息,请参阅 .compileSerializationSchema(schema, [httpStatus], [contentType])

.serializer(func)

默认情况下,.send() 会将除 BufferstreamstringundefinedError 之外的任何值进行 JSON 序列化。如果您需要为特定请求替换默认序列化器并使用自定义序列化器,则可以使用 .serializer() 工具来实现。请注意,如果使用了自定义序列化器,则必须设置一个自定义的 'Content-Type' 标头。

reply .header('Content-Type', 'application/x-protobuf') .serializer(protoBuf.serialize)

注意,在处理程序 (handler) 内部不需要使用此工具,因为 Bufferstream 和字符串(除非设置了序列化器)被视为已经序列化的数据。

reply .header('Content-Type', 'application/x-protobuf') .send(protoBuf.serialize(data))

有关发送不同类型值的更多信息,请参阅 .send()

.raw

这是 Node 核心中的 http.ServerResponse。虽然您正在使用 Fastify 的 Reply 对象,但使用 Reply.raw 函数时需自行承担风险,因为这会跳过所有处理 HTTP 响应的 Fastify 逻辑。例如:

app.get('/cookie-2', (req, reply) => { reply.setCookie('session', 'value', { secure: false }) // 这将不会被使用 // 在这种情况下,我们仅使用 Node.js http 服务器响应对象 reply.raw.writeHead(200, { 'Content-Type': 'text/plain' }) reply.raw.write('ok') reply.raw.end() })

Reply.raw 的误用的另一个示例在 Reply 中进行了说明。

.sent

如名称所示,.sent 是一个属性,用于指示响应是否已通过 reply.send() 发送。如果使用了 reply.hijack(),该属性也将为 true

当路由处理程序被定义为异步函数或返回 Promise 时,可以调用 reply.hijack() 来表示在处理程序的 Promise 解析后应跳过自动调用 reply.send()。通过调用 reply.hijack(),应用程序声明完全负责底层请求和响应。此外,钩子将不会被调用。

直接修改 .sent 属性是不推荐的,请使用前述的 .hijack() 方法来实现相同的效果。

.hijack()

有时您可能需要停止执行正常的请求生命周期,并手动发送响应。

为此,Fastify 提供了 reply.hijack() 方法,该方法可以在请求生命周期中调用(在调用 reply.send() 之前任意时刻),允许您阻止 Fastify 发送响应并跳过剩余的钩子(如果在回复被劫持前,则不执行用户处理程序)。

app.get('/', (req, reply) => { reply.hijack() reply.raw.end('hello world') return Promise.resolve('this will be skipped') })

如果使用 reply.raw 向用户发送响应,onResponse 钩子仍然会被执行。

.send(data)

如名称所示,.send() 是将负载发送给最终用户的函数。

对象

如上所述,如果您要发送 JSON 对象,则 send 将使用 fast-json-stringify  序列化该对象,前提是您设置了输出模式。否则将使用 JSON.stringify()

fastify.get('/json', options, function (request, reply) { reply.send({ hello: 'world' }) })

字符串

如果您在不设置 Content-Type 的情况下向 send 传递一个字符串,则它将以 text/plain; charset=utf-8 发送。如果设置了 Content-Type 头部并传递了一个字符串给 send,则会使用自定义序列化器进行序列化(如果有)。否则,将不修改地发送该字符串(除非 Content-Type 头部设置为 application/json; charset=utf-8,在这种情况下,它将以与对象相同的方式 JSON 序列化 —— 请参阅上面的部分)。

fastify.get('/json', options, function (request, reply) { reply.send('plain string') })

如果您发送一个流并且没有设置 'Content-Type' 头部,send 将将其设置为 'application/octet-stream'

如上所述,流被视为预序列化的,因此它们将不修改地发送且不会进行响应验证。

const fs = require('node:fs') fastify.get('/streams', function (request, reply) { const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') reply.send(stream) })

当使用 async-await 时,您需要返回或等待回复对象:

const fs = require('node:fs') fastify.get('/streams', async function (request, reply) { const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') return reply.send(stream) })

缓冲区

如果您发送的是一个缓冲区,并且没有设置 'Content-Type' 标头,send 将其设置为 'application/octet-stream'

如上所述,缓冲区被视为预序列化数据,因此它们将不经过修改直接发送,不会进行响应验证。

const fs = require('node:fs') fastify.get('/streams', function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) }) })

当使用 async-await 时,您需要返回或等待回复对象:

const fs = require('node:fs') fastify.get('/streams', async function (request, reply) { fs.readFile('some-file', (err, fileBuffer) => { reply.send(err || fileBuffer) }) return reply })

类型化数组

send 管理类型化数组的方式与处理缓冲区相同,并且如果未设置 'Content-Type' 标头,则将其设置为 'application/octet-stream'

如上所述,类型化数组/缓冲区被视为预序列化的数据,因此它们将不经过修改直接发送,不会进行响应验证。

const fs = require('node:fs') fastify.get('/streams', function (request, reply) { const typedArray = new Uint16Array(10) reply.send(typedArray) })

可读流

ReadableStream 将被视为上述提到的 node 流,内容被认为是预序列化的数据,因此它们将不经过修改直接发送,不会进行响应验证。

const fs = require('node:fs') const { ReadableStream } = require('node:stream/web') fastify.get('/streams', function (request, reply) { const stream = fs.createReadStream('some-file') reply.header('Content-Type', 'application/octet-stream') reply.send(ReadableStream.from(stream)) })

响应

Response 允许在一个地方管理回复负载、状态码和头部信息。在 Response 中提供的负载被视为预序列化的,因此它们将未经修改地直接发送,并且不会进行响应验证。

请注意,在使用 Response 时,状态码和头部信息不会直接反映到 reply.statusCodereply.getHeaders() 上。这种行为基于 Response 只允许读取状态码和头部信息(即只读),不允许双向编辑数据。这可能会在检查 onSend 回调中的 payload 时造成混淆。

const fs = require('node:fs') const { ReadableStream } = require('node:stream/web') fastify.get('/streams', function (request, reply) { const stream = fs.createReadStream('some-file') const readableStream = ReadableStream.from(stream) const response = new Response(readableStream, { status: 200, headers: { 'content-type': 'application/octet-stream' } }) reply.send(response) })

错误

如果您传递给 send 的是一个 Error 实例对象,Fastify 将会自动创建如下结构的错误:

{ error: String // HTTP 错误消息 code: String // Fastify 错误代码 message: String // 用户错误消息 statusCode: Number // HTTP 状态码 }

您可以向 Error 对象添加自定义属性,例如 headers,这些属性将用于增强 HTTP 响应。

ℹ️ 注意:如果您传递给 send 的是一个错误对象,并且状态码小于 400,则 Fastify 将自动将其设置为 500。

提示:您可以使用 http-errors 模块或 @fastify/sensible 插件来生成错误,从而简化错误处理:

fastify.get('/', function (request, reply) { reply.send(httpErrors.Gone()) })

要自定义 JSON 错误输出,请执行以下操作:

  • 设置您需要的状态码的响应 JSON 模式
  • Error 实例添加额外属性

请注意,如果返回的状态码不在响应模式列表中,则将应用默认行为。

fastify.get('/', { schema: { response: { 501: { type: 'object', properties: { statusCode: { type: 'number' }, code: { type: 'string' }, error: { type: 'string' }, message: { type: 'string' }, time: { type: 'string' } } } } } }, function (request, reply) { const error = new Error('此端点尚未实现') error.time = '将在两周内实现' reply.code(501).send(error) })

如果您想自定义错误处理,请查阅 setErrorHandler API。

ℹ️ 注意:在自定义错误处理程序时,您需要负责记录日志。

API:

fastify.setErrorHandler(function (error, request, reply) { request.log.warn(error) const statusCode = error.statusCode >= 400 ? error.statusCode : 500 reply .code(statusCode) .type('text/plain') .send(statusCode >= 500 ? 'Internal server error' : error.message) })

请注意,在自定义错误处理程序中调用 reply.send(error) 将会把错误发送到默认的错误处理器。 更多详情请查阅 Reply 生命周期

路由生成的未找到错误将使用 setNotFoundHandler

API:

fastify.setNotFoundHandler(function (request, reply) { reply .code(404) .type('text/plain') .send('a custom not found') })

最终负载的类型

发送的有效载荷(经过序列化和任何 onSend 钩子处理后)必须是以下类型之一,否则会抛出错误:

  • string
  • Buffer
  • stream
  • undefined
  • null

异步/等待与 Promise

Fastify 原生支持 Promise 并且支持 async/await。

注意,在以下示例中我们没有使用 reply.send。

const { promisify } = require('node:util') const delay = promisify(setTimeout) fastify.get('/promises', options, function (request, reply) { return delay(200).then(() => { return { hello: 'world' }}) }) fastify.get('/async-await', options, async function (request, reply) { await delay(200) return { hello: 'world' } })

被拒绝的 Promise 默认返回 500 HTTP 状态码。在拒绝 Promise 或者 在异步函数中使用 throw 时,传入一个包含 statusCode(或 status)和 message 属性的对象来修改回复。

fastify.get('/teapot', async function (request, reply) { const err = new Error() err.statusCode = 418 err.message = 'short and stout' throw err }) fastify.get('/botnet', async function (request, reply) { throw { statusCode: 418, message: 'short and stout' } // 将返回相同的 JSON 给客户端 })

如需了解更多,请参阅 路由#异步/等待

.then(履行回调, 拒绝回调)

如名称所示,一个 Reply 对象可以被等待,即 await reply 将会一直等待直到回复发送完毕。使用 await 语法时会调用 reply.then()

reply.then(fulfilled, rejected) 接受两个参数:

  • 当响应完全发送后将调用 fulfilled
  • 如果底层流出现错误(例如,套接字已被销毁),则将调用 rejected

更多详情请参阅: