回复
- 回复
- 引言
- .code(statusCode)
- .elapsedTime
- .statusCode
- .server
- .header(key, value)
- .headers(object)
- .getHeader(key)
- .getHeaders()
- .removeHeader(key)
- .hasHeader(key)
- .writeEarlyHints(hints, callback)
- .trailer(key, function)
- .hasTrailer(key)
- .removeTrailer(key)
- .redirect(dest, [code ,])
- .callNotFound()
- .type(contentType)
- .getSerializationFunction(schema | httpStatus, [contentType])
- .compileSerializationSchema(schema, [httpStatus], [contentType])
- .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
- .serializer(func)
- .raw
- .sent
- .hijack()
- .send(data)
- .then(fulfilled, rejected)
引言
处理函数的第二个参数是 Reply
。Reply
是 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])
- 使用指定的模式序列化指定的数据,并返回序列化的负载。如果提供了可选参数httpStatus
和contentType
,函数将使用特定内容类型和 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
)对响应头的值进行适当的编码。无效字符会导致 500TypeError
响应。
更多信息请参阅
http.ServerResponse#setHeader
。
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-Timing
和 Etag
。这可以确保客户端尽快接收到响应数据。
ℹ️ 注意:一旦使用了尾部字段,将会添加
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 将导致 500TypeError
响应。
示例(未调用 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])
通过使用提供的 schema
或 httpStatus
调用此函数,并可选地提供 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
编译生成的,同时使用弱映射缓存来减少编译调用次数。
可选参数 httpStatus
和 contentType
如果提供的话,将直接传递给 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
提供序列化函数,将会编译一个新的序列化函数,并在提供的情况下传递httpStatus
和contentType
。
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()
会将除 Buffer
、stream
、string
、undefined
或 Error
之外的任何值进行 JSON 序列化。如果您需要为特定请求替换默认序列化器并使用自定义序列化器,则可以使用 .serializer()
工具来实现。请注意,如果使用了自定义序列化器,则必须设置一个自定义的 'Content-Type'
标头。
reply
.header('Content-Type', 'application/x-protobuf')
.serializer(protoBuf.serialize)
注意,在处理程序 (handler
) 内部不需要使用此工具,因为 Buffer
、stream
和字符串(除非设置了序列化器)被视为已经序列化的数据。
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.statusCode
和 reply.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
。
更多详情请参阅: