响应

Response

response 类 的实例用于响应 HTTP 请求。AdonisJS 支持发送 HTML 片段JSON 对象等等。可以使用 ctx.response 属性访问响应实例。

发送响应

发送响应最简单的方式是从路由处理器返回一个值。

import router from '@adonisjs/core/services/router'
router.get('/', async () => {
/** 纯字符串 */
return 'This is the homepage.'
/** Html 片段 */
return '<p> This is the homepage </p>'
/** JSON 响应 */
return { page: 'home' }
/** 转换为 ISO 字符串 */
return new Date()
})

除了从路由处理器返回值之外,你还可以使用 response.send 方法显式设置响应体。不过,多次调用 response.send 方法会覆盖旧的响应体,只保留最新的那个。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ response }) => {
/** 纯字符串 */
response.send('This is the homepage')
/** Html 片段 */
response.send('<p> This is the homepage </p>')
/** JSON 响应 */
response.send({ page: 'home' })
/** 转换为 ISO 字符串 */
response.send(new Date())
})

可以使用 response.status 方法设置响应的自定义状态码。

response.status(200).send({ page: 'home' })
// 发送空的 201 响应
response.status(201).send('')

流式传输内容

response.stream 方法允许将一个流管道连接到响应。该方法会在流结束后内部销毁它。

response.stream 方法不会设置 content-typecontent-length 头;你必须在流式传输内容之前显式设置它们。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ response }) => {
const image = fs.createReadStream('./some-file.jpg')
response.stream(image)
})

发生错误时,会向客户端发送一个 500 状态码。不过,你可以通过定义第二个参数的回调来自定义错误码和错误消息。

const image = fs.createReadStream('./some-file.jpg')
response.stream(image, () => {
const message = 'Unable to serve file. Try again'
const status = 400
return [message, status]
})

下载文件

当你想从磁盘流式传输文件时,我们建议使用 response.download 方法而非 response.stream 方法。这是因为 download 方法会自动设置 content-typecontent-length 头。

import app from '@adonisjs/core/services/app'
import router from '@adonisjs/core/services/router'
router.get('/uploads/:file', async ({ response, params }) => {
const filePath = app.makePath(`uploads/${params.file}`)
response.download(filePath)
})

可选地,你可以为文件内容生成一个 Etag。使用 Etag 有助于浏览器复用来自之前请求的缓存响应(如果有的话)。

const filePath = app.makePath(`uploads/${params.file}`)
const generateEtag = true
response.download(filePath, generateEtag)

response.stream 方法类似,你可以通过定义最后一个参数的回调来发送自定义错误消息和状态码。

const filePath = app.makePath(`uploads/${params.file}`)
const generateEtag = true
response.download(filePath, generateEtag, (error) => {
if (error.code === 'ENOENT') {
return ['File does not exists', 404]
}
return ['Cannot download file', 400]
})

强制下载文件

response.attachment 方法与 response.download 方法类似,但它通过设置 Content-Disposition 头,强制浏览器将文件保存到用户的计算机上。

import app from '@adonisjs/core/services/app'
import router from '@adonisjs/core/services/router'
router.get('/uploads/:file', async ({ response, params }) => {
const filePath = app.makePath(`uploads/${params.file}`)
response.attachment(filePath, 'custom-filename.jpg')
})

设置响应状态码和头

设置状态码

你可以使用 response.status 方法设置响应状态码。调用此方法会覆盖已有的响应状态码(如果存在)。不过,你可以使用 response.safeStatus 方法,仅在状态码为 undefined 时设置它。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ response }) => {
/**
* 将状态码设置为 200
*/
response.safeStatus(200)
/**
* 不会设置状态码,因为它
* 已经被设置过了
*/
response.safeStatus(201)
})

设置头

你可以使用 response.header 方法设置响应头。此方法会覆盖已有的头值(如果已存在)。不过,你可以使用 response.safeHeader 方法,仅在头为 undefined 时设置它。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ response }) => {
/**
* 定义 content-type 头
*/
response.safeHeader('Content-type', 'text/html')
/**
* 不会设置 content-type 头,因为它
* 已经被设置过了
*/
response.safeHeader('Content-type', 'text/html')
})

你可以使用 response.append 方法向已有的头值追加内容。

response.append('Set-cookie', 'cookie-value')

response.removeHeader 方法移除已有的头。

response.removeHeader('Set-cookie')

X-Request-Id 头

如果当前请求中存在该头,或者启用了生成请求 ID,该头就会出现在响应中。

重定向

response.redirect 方法返回 Redirect 类的实例。redirect 类使用流畅的 API 来构造重定向 URL。

执行重定向最简单的方式是调用 redirect.toPath 方法并传入重定向路径。

import router from '@adonisjs/core/services/router'
router.get('/posts', async ({ response }) => {
response.redirect().toPath('/articles')
})

redirect 类也允许从预注册的路由构造 URL。redirect.toRoute 方法接受路由标识符 作为第一个参数,路由参数作为第二个参数。

import router from '@adonisjs/core/services/router'
router.get('/articles/:id', async () => {}).as('articles.show')
router.get('/posts/:id', async ({ response, params }) => {
response.redirect().toRoute('articles.show', { id: params.id })
})

重定向回上一页

在表单提交遇到校验错误时,你可能希望将用户重定向回上一页。你可以使用 redirect.back 方法来做到这一点。

response.redirect().back()

重定向状态码

重定向响应的默认状态码是 302;你可以通过调用 redirect.status 方法来更改它。

response.redirect().status(301).toRoute('articles.show', { id: params.id })

携带查询字符串的重定向

你可以使用 withQs 方法为重定向 URL 追加查询字符串。该方法接受一个键值对对象,并将其转换为字符串。

response.redirect().withQs({ page: 1, limit: 20 }).toRoute('articles.index')

要转发当前请求 URL 中的查询字符串,直接不带任何参数调用 withQs 方法即可。

// 转发当前 URL 的查询字符串
response.redirect().withQs().toRoute('articles.index')

当重定向回上一页时,withQs 方法会转发上一页的查询字符串。

// 转发当前 URL 的查询字符串
response.redirect().withQs().back()

通过错误中止请求

你可以使用 response.abort 方法通过抛出异常来结束请求。该方法会抛出 E_HTTP_REQUEST_ABORTED 异常,并触发异常处理 流程。

router.get('posts/:id/edit', async ({ response, auth, params }) => {
const post = await Post.findByOrFail(params.id)
if (!auth.user.can('editPost', post)) {
response.abort({ message: 'Cannot edit post' })
}
// 继续其余的逻辑
})

默认情况下,该异常会创建一个状态码为 400 的 HTTP 响应。不过,你可以将自定义状态码指定为第二个参数。

response.abort({ message: 'Cannot edit post' }, 403)

在响应完成后执行动作

你可以使用 response.onFinish 方法监听 Node.js 将响应写入 TCP socket 的事件。底层我们使用了 on-finished 包,欢迎查阅该包的 README 文件以深入了解技术细节。

router.get('posts', ({ response }) => {
response.onFinish(() => {
// 清理逻辑
})
})

访问 Node.js 的 res 对象

你可以使用 response.response 属性访问 Node.js res 对象

router.get('posts', ({ response }) => {
console.log(response.response)
})

响应体序列化

使用 response.send 方法设置的响应体,在被作为响应写入 到传出的消息流之前,会被序列化为字符串。

以下是受支持的数据类型及其序列化规则。

  • 数组和对象使用 safe stringify 函数 进行字符串化。该方法类似于 JSON.stringify,但会移除循环引用并将 BigInt 序列化。
  • 数字和布尔值会被转换为字符串。
  • Date 类的实例通过调用 toISOString 方法转换为字符串。
  • 正则表达式和错误对象通过调用 toString 方法转换为字符串。
  • 任何其他数据类型都会导致异常。

内容类型推断

序列化响应后,response 类会自动推断并设置 content-typecontent-length 头。

以下是我们设置 content-type 头所遵循的规则列表。

  • 数组和对象的内容类型设置为 application/json
  • HTML 片段的内容类型设置为 text/html
  • JSONP 响应以 text/javascript 内容类型发送。
  • 其他所有内容的内容类型设置为 text/plain

扩展 Response 类

你可以使用宏(macro)或 getter 为 Response 类添加自定义属性。如果你还不熟悉宏的概念,请务必先阅读 扩展 AdonisJS 指南

import { Response } from '@adonisjs/core/http'
Response.macro('property', function (this: Response) {
return value
})
Response.getter('property', function (this: Response) {
return value
})

由于宏和 getter 是在运行时添加的,你必须让 TypeScript 知道它们的类型。

declare module '@adonisjs/core/http' {
export interface Response {
property: valueType
}
}