Response对象 #
一、Response概述 #
Response对象(在Fastify中称为Reply)用于构建和发送HTTP响应。
1.1 Reply对象结构 #
javascript
fastify.get('/test', async (request, reply) => {
console.log(reply.statusCode)
console.log(reply.sent)
console.log(reply.elapsedTime)
return { ok: true }
})
1.2 发送响应的方式 #
javascript
fastify.get('/way1', async (request, reply) => {
return { message: 'Auto send' }
})
fastify.get('/way2', async (request, reply) => {
reply.send({ message: 'Manual send' })
})
fastify.get('/way3', async (request, reply) => {
reply.code(200).send({ message: 'Chained send' })
})
二、状态码 #
2.1 设置状态码 #
javascript
fastify.get('/ok', async (request, reply) => {
reply.code(200)
return { message: 'OK' }
})
fastify.get('/created', async (request, reply) => {
reply.code(201)
return { message: 'Created' }
})
fastify.get('/no-content', async (request, reply) => {
reply.code(204).send()
})
fastify.get('/bad-request', async (request, reply) => {
reply.code(400)
return { error: 'Bad Request' }
})
fastify.get('/not-found', async (request, reply) => {
reply.code(404)
return { error: 'Not Found' }
})
fastify.get('/server-error', async (request, reply) => {
reply.code(500)
return { error: 'Internal Server Error' }
})
2.2 状态码别名 #
javascript
fastify.register(require('@fastify/sensible'))
fastify.get('/status-alias', async (request, reply) => {
reply.badRequest('Invalid parameters')
})
fastify.get('/unauthorized', async (request, reply) => {
reply.unauthorized('Token required')
})
fastify.get('/forbidden', async (request, reply) => {
reply.forbidden('Access denied')
})
fastify.get('/not-found-alias', async (request, reply) => {
reply.notFound('Resource not found')
})
2.3 常用状态码 #
| 状态码 | 方法 | 说明 |
|---|---|---|
| 200 | - | 成功 |
| 201 | - | 创建成功 |
| 204 | - | 无内容 |
| 400 | badRequest | 请求错误 |
| 401 | unauthorized | 未授权 |
| 403 | forbidden | 禁止访问 |
| 404 | notFound | 未找到 |
| 409 | conflict | 冲突 |
| 429 | tooManyRequests | 请求过多 |
| 500 | internalServerError | 服务器错误 |
三、响应头 #
3.1 设置响应头 #
javascript
fastify.get('/headers', async (request, reply) => {
reply
.header('X-Custom-Header', 'value')
.header('X-Another-Header', 'another-value')
return { message: 'Headers set' }
})
3.2 常用响应头 #
javascript
fastify.get('/common-headers', async (request, reply) => {
reply
.header('Content-Type', 'application/json')
.header('Cache-Control', 'public, max-age=3600')
.header('X-Request-Id', request.id)
.header('X-Response-Time', '10ms')
return { message: 'Common headers' }
})
3.3 CORS响应头 #
javascript
fastify.get('/cors', async (request, reply) => {
reply
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
return { message: 'CORS headers' }
})
3.4 删除响应头 #
javascript
fastify.get('/remove-header', async (request, reply) => {
reply.removeHeader('X-Powered-By')
return { message: 'Header removed' }
})
3.5 获取响应头 #
javascript
fastify.get('/get-headers', async (request, reply) => {
reply.header('X-Custom', 'value')
return {
customHeader: reply.getHeader('X-Custom'),
allHeaders: reply.getHeaders()
}
})
四、响应体 #
4.1 JSON响应 #
javascript
fastify.get('/json', async (request, reply) => {
return {
message: 'Hello JSON',
timestamp: Date.now()
}
})
fastify.get('/json-explicit', async (request, reply) => {
reply.send({
message: 'Explicit JSON',
timestamp: Date.now()
})
})
4.2 字符串响应 #
javascript
fastify.get('/string', async (request, reply) => {
reply.type('text/plain')
return 'Hello World'
})
fastify.get('/html', async (request, reply) => {
reply.type('text/html')
return '<h1>Hello World</h1>'
})
4.3 Buffer响应 #
javascript
fastify.get('/buffer', async (request, reply) => {
const buffer = Buffer.from('Hello Buffer')
reply.type('application/octet-stream')
return buffer
})
4.4 Stream响应 #
javascript
const fs = require('fs')
const path = require('path')
fastify.get('/stream', async (request, reply) => {
const stream = fs.createReadStream(path.join(__dirname, 'file.txt'))
reply.type('text/plain')
return stream
})
4.5 空响应 #
javascript
fastify.get('/empty', async (request, reply) => {
reply.code(204).send()
})
fastify.get('/no-content', async (request, reply) => {
reply.code(204)
return reply.send()
})
五、Content-Type #
5.1 设置Content-Type #
javascript
fastify.get('/json-type', async (request, reply) => {
reply.type('application/json')
return { message: 'JSON' }
})
fastify.get('/html-type', async (request, reply) => {
reply.type('text/html')
return '<h1>HTML</h1>'
})
fastify.get('/xml-type', async (request, reply) => {
reply.type('application/xml')
return '<root><message>XML</message></root>'
})
fastify.get('/csv-type', async (request, reply) => {
reply.type('text/csv')
return 'name,email\nJohn,john@example.com'
})
5.2 字符集设置 #
javascript
fastify.get('/charset', async (request, reply) => {
reply.type('text/html; charset=utf-8')
return '<h1>UTF-8 Content</h1>'
})
六、重定向 #
6.1 基本重定向 #
javascript
fastify.get('/redirect', async (request, reply) => {
reply.redirect('/target')
})
fastify.get('/target', async (request, reply) => {
return { message: 'Redirected here' }
})
6.2 外部重定向 #
javascript
fastify.get('/external', async (request, reply) => {
reply.redirect('https://example.com')
})
6.3 指定状态码 #
javascript
fastify.get('/permanent', async (request, reply) => {
reply.redirect(301, '/new-location')
})
fastify.get('/temporary', async (request, reply) => {
reply.redirect(307, '/temporary-location')
})
七、Cookie #
7.1 设置Cookie #
javascript
fastify.register(require('@fastify/cookie'))
fastify.get('/set-cookie', async (request, reply) => {
reply.setCookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600,
path: '/'
})
return { message: 'Cookie set' }
})
7.2 Cookie选项 #
javascript
fastify.get('/cookie-options', async (request, reply) => {
reply.setCookie('token', 'xyz789', {
domain: 'example.com',
path: '/',
secure: true,
httpOnly: true,
sameSite: 'strict',
expires: new Date(Date.now() + 3600000),
maxAge: 3600
})
return { message: 'Cookie with options' }
})
7.3 清除Cookie #
javascript
fastify.get('/clear-cookie', async (request, reply) => {
reply.clearCookie('sessionId', {
path: '/'
})
return { message: 'Cookie cleared' }
})
7.4 签名Cookie #
javascript
fastify.register(require('@fastify/cookie'), {
secret: 'my-secret-key'
})
fastify.get('/signed-cookie', async (request, reply) => {
reply.setCookie('data', 'value', { signed: true })
return { message: 'Signed cookie set' }
})
八、文件下载 #
8.1 发送文件 #
javascript
fastify.get('/download', async (request, reply) => {
reply.download('/path/to/file.pdf')
})
fastify.get('/download-custom', async (request, reply) => {
reply.download('/path/to/file.pdf', 'custom-name.pdf')
})
8.2 发送静态文件 #
javascript
fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public'),
prefix: '/static/'
})
fastify.get('/file', async (request, reply) => {
reply.sendFile('document.pdf')
})
九、响应序列化 #
9.1 Schema序列化 #
javascript
fastify.get('/users', {
schema: {
response: {
200: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' }
}
}
}
}
}
}, async (request, reply) => {
return [
{ id: 1, name: 'John', password: 'secret' },
{ id: 2, name: 'Jane', password: 'secret' }
]
})
9.2 自定义序列化器 #
javascript
fastify.get('/custom-serialize', {
serializerCompiler: ({ schema, method, url, httpStatus }) => {
return (data) => JSON.stringify({
success: true,
data,
timestamp: Date.now()
})
}
}, async (request, reply) => {
return { message: 'Custom serialization' }
})
9.3 禁用序列化 #
javascript
fastify.get('/raw', async (request, reply) => {
reply.serializer(null)
return 'Raw response without serialization'
})
十、响应钩子 #
10.1 preSerialization #
javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (payload && typeof payload === 'object') {
return {
...payload,
timestamp: Date.now()
}
}
return payload
})
10.2 onSend #
javascript
fastify.addHook('onSend', async (request, reply, payload) => {
reply.header('X-Response-Time', Date.now() - request.startTime)
return payload
})
十一、响应装饰 #
11.1 装饰Reply #
javascript
fastify.decorateReply('success', function (data) {
return this.send({
success: true,
data,
timestamp: new Date().toISOString()
})
})
fastify.get('/success', async (request, reply) => {
reply.success({ message: 'Hello' })
})
11.2 自定义响应方法 #
javascript
fastify.decorateReply('paginated', function (items, total, page, limit) {
return this.send({
success: true,
data: items,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
})
})
fastify.get('/users', async (request, reply) => {
const { page = 1, limit = 10 } = request.query
const users = await getUsers(page, limit)
const total = await countUsers()
reply.paginated(users, total, page, limit)
})
十二、最佳实践 #
12.1 统一响应格式 #
javascript
fastify.decorateReply('success', function (data) {
return this.send({
success: true,
data,
timestamp: new Date().toISOString()
})
})
fastify.decorateReply('error', function (message, code = 400) {
return this.code(code).send({
success: false,
error: message,
timestamp: new Date().toISOString()
})
})
fastify.get('/success-response', async (request, reply) => {
reply.success({ users: [] })
})
fastify.get('/error-response', async (request, reply) => {
reply.error('User not found', 404)
})
12.2 分页响应 #
javascript
fastify.decorateReply('paginate', function (items, total, page, limit) {
return this.send({
success: true,
data: items,
meta: {
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / limit)
}
}
})
})
12.3 条件响应 #
javascript
fastify.get('/conditional', async (request, reply) => {
const etag = 'abc123'
const lastModified = new Date().toUTCString()
reply.header('ETag', etag)
reply.header('Last-Modified', lastModified)
if (request.headers['if-none-match'] === etag) {
reply.code(304).send()
return
}
return { message: 'Fresh content' }
})
十三、总结 #
本章我们学习了:
- 状态码:设置状态码、状态码别名
- 响应头:设置、删除、获取响应头
- 响应体:JSON、字符串、Buffer、Stream
- Content-Type:设置类型、字符集
- 重定向:基本重定向、外部重定向
- Cookie:设置、清除、签名Cookie
- 文件下载:发送文件、静态文件
- 响应序列化:Schema序列化、自定义序列化
- 响应钩子:preSerialization、onSend
- 响应装饰:自定义响应方法
- 最佳实践:统一格式、分页响应、条件响应
接下来让我们学习数据验证!
最后更新:2026-03-28