请求

Request

request 类 的实例持有正在进行的 HTTP 请求的数据,包括请求体对上传文件的引用cookie请求头等等。可以使用 ctx.request 属性访问请求实例。

查询字符串与路由参数

request.qs 方法将解析后的查询字符串作为对象返回。

import router from '@adonisjs/core/services/router'
router.get('posts', async ({ request }) => {
/*
* URL: /?sort_by=id&direction=desc
* qs: { sort_by: 'id', direction: 'desc' }
*/
request.qs()
})

request.params 方法返回一个 路由参数 的对象。

import router from '@adonisjs/core/services/router'
router.get('posts/:slug/comments/:id', async ({ request }) => {
/*
* URL: /posts/hello-world/comments/2
* params: { slug: 'hello-world', id: '2' }
*/
request.params()
})

你可以使用 request.param 方法访问单个参数。

import router from '@adonisjs/core/services/router'
router.get('posts/:slug/comments/:id', async ({ request }) => {
const slug = request.param('slug')
const commentId = request.param('id')
})

请求体

AdonisJS 使用注册在 start/kernel.ts 文件中的 body-parser 中间件 来解析请求体。

你可以使用 request.body() 方法访问请求体。该方法将解析后的请求体作为对象返回。

import router from '@adonisjs/core/services/router'
router.post('/', async ({ request }) => {
console.log(request.body())
})

request.all 方法返回请求体和查询字符串的合并副本。

import router from '@adonisjs/core/services/router'
router.post('/', async ({ request }) => {
console.log(request.all())
})

挑选特定值

request.inputrequest.onlyrequest.except 方法可以从请求数据中挑选特定属性。所有挑选方法都会在请求体和查询字符串中查找值。

request.only 方法返回的对象的只包含所提到的属性。

import router from '@adonisjs/core/services/router'
router.post('login', async ({ request }) => {
const credentials = request.only(['email', 'password'])
console.log(credentials)
})

request.except 方法返回排除了所提到属性的对象。

import router from '@adonisjs/core/services/router'
router.post('register', async ({ request }) => {
const userDetails = request.except(['password_confirmation'])
console.log(userDetails)
})

request.input 方法返回特定属性的值。可选地,你可以将默认值作为第二个参数传入。当实际值缺失时,会返回默认值。

import router from '@adonisjs/core/services/router'
router.post('comments', async ({ request }) => {
const email = request.input('email')
const commentBody = request.input('body')
})

类型安全的请求体

默认情况下,AdonisJS 不会对 request.allrequest.body 或挑选方法强制数据类型,因为它无法提前知道请求体预期的内容。

为了确保类型安全,你可以使用 校验器 来校验和解析请求体,确保所有值都正确且符合预期的类型。

请求 URL

request.url 方法返回相对于主机名的请求 URL。默认情况下,返回值不包含查询字符串。不过,你可以通过调用 request.url(true) 获取带查询字符串的 URL。

import router from '@adonisjs/core/services/router'
router.get('/users', async ({ request }) => {
/*
* URL: /users?page=1&limit=20
* url: /users
*/
request.url()
/*
* URL: /users?page=1&limit=20
* url: /users?page=1&limit=20
*/
request.url(true) // 返回查询字符串
})

request.completeUrl 方法返回完整的 URL,包含主机名。同样,除非显式指定,返回值不包含查询字符串。

import router from '@adonisjs/core/services/router'
router.get('/users', async ({ request }) => {
request.completeUrl()
request.completeUrl(true) // 返回查询字符串
})

请求头

request.headers 方法将请求头作为对象返回。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ request }) => {
console.log(request.headers())
})

你可以使用 request.header 方法访问单个头的值。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ request }) => {
request.header('x-request-id')
// 头名称不区分大小写
request.header('X-REQUEST-ID')
})

请求方法

request.method 方法返回当前请求的 HTTP 方法。当启用了表单方法欺骗 时,此方法返回被欺骗的方法;你可以使用 request.intended 方法获取原始的请求方法。

import router from '@adonisjs/core/services/router'
router.patch('posts', async ({ request }) => {
/**
* 用于路由匹配的方法
*/
console.log(request.method())
/**
* 实际的请求方法
*/
console.log(request.intended())
})

用户 IP 地址

request.ip 方法返回当前 HTTP 请求的用户 IP 地址。此方法依赖于由 Nginx 或 Caddy 等代理服务器设置的 X-Forwarded-For 头。

请阅读 配置受信任代理 一节,以配置你的应用应当信任的代理。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ request }) => {
console.log(request.ip())
})

request.ips 方法返回由中间代理设置的所有 IP 地址的数组。该数组按从最可信到最不可信的 IP 地址排序。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ request }) => {
console.log(request.ips())
})

定义自定义 getIp 方法

如果受信任的代理设置不足以确定正确的 IP 地址,你可以实现自定义的 getIp 方法。

该方法定义在 config/app.ts 文件的 http 设置对象内。

export const http = defineConfig({
getIp(request) {
const ip = request.header('X-Real-Ip')
if (ip) {
return ip
}
return request.ips()[0]
}
})

内容协商

AdonisJS 提供了多个用于内容协商 的方法,通过解析所有常用支持的 Accept 头来实现。例如,你可以使用 request.types 方法获取给定请求接受的所有内容类型列表。

request.types 方法的返回值按客户端的偏好排序(最优先的在前)。

import router from '@adonisjs/core/services/router'
router.get('/', async ({ request }) => {
console.log(request.types())
})

以下是内容协商方法的完整列表。

Method使用的 HTTP 头
typesAccept
languagesAccept-language
encodingsAccept-encoding
charsetsAccept-charset

有时你希望根据服务器能够支持的内容来查找偏好的内容类型。

为此,你可以使用 request.accepts 方法。该方法接受一个受支持的内容类型数组,并在检查 Accept 头后返回最偏好的一个。当无法找到匹配项时,返回 null 值。

import router from '@adonisjs/core/services/router'
router.get('posts', async ({ request, view }) => {
const posts = [
{
title: 'Adonis 101',
},
]
const bestMatch = request.accepts(['html', 'json'])
switch (bestMatch) {
case 'html':
return view.render('posts/index', { posts })
case 'json':
return posts
default:
return view.render('posts/index', { posts })
}
})

request.accept 类似,以下方法可用于查找其他 Accept 头的偏好值。

// 偏好语言
const language = request.language(['fr', 'de'])
// 偏好编码
const encoding = request.encoding(['gzip', 'br'])
// 偏好字符集
const charset = request.charset(['utf-8', 'hex', 'ascii'])

生成请求 ID

请求 ID 通过为每个 HTTP 请求分配一个唯一 ID,帮助你从日志中调试和追踪应用问题。默认情况下,请求 ID 的创建是关闭的。不过,你可以在 config/app.ts 文件中启用它。

请求 ID 是使用 cuid2 包生成的。在生成 ID 之前,我们会检查 X-Request-Id 请求头,并使用其值(如果存在)。

config/app.ts
export const http = defineConfig({
generateRequestId: true
})

启用后,你可以使用 request.id 方法访问该 ID。

router.get('/', ({ request }) => {
// ckk9oliws0000qt3x9vr5dkx7
console.log(request.id())
})

同一个请求 ID 也会被添加到使用 ctx.logger 实例生成的所有日志中。

router.get('/', ({ logger }) => {
// { msg: 'hello world', request_id: 'ckk9oliws0000qt3x9vr5dkx7' }
logger.info('hello world')
})

配置受信任代理

大多数 Node.js 应用部署在 Nginx 或 Caddy 等代理服务器后面。因此我们必须依赖 X-Forwarded-HostX-Forwarded-ForX-Forwarded-Proto 等 HTTP 头来了解发出 HTTP 请求的真实终端客户端。

这些头只有在你的 AdonisJS 应用能够信任来源 IP 地址时才会被使用。

你可以在 config/app.ts 文件中使用 http.trustProxy 配置选项来配置要信任哪些 IP 地址。

import proxyAddr from 'proxy-addr'
export const http = defineConfig({
trustProxy: proxyAddr.compile(['127.0.0.1/8', '::1/128'])
})

trustProxy 的值也可以是一个函数。如果 IP 地址受信任,该方法应返回 true;否则返回 false

export const http = defineConfig({
trustProxy: (address) => {
return address === '127.0.0.1' || address === '123.123.123.123'
}
})

如果你在同一台服务器上与应用代码一起运行 Nginx,你需要信任回环 IP 地址,即 127.0.0.1

import proxyAddr from 'proxy-addr'
export const http = defineConfig({
trustProxy: proxyAddr.compile('loopback')
})

假设你的应用只能通过负载均衡器访问,而你又没有该负载均衡器的 IP 地址列表。那么,你可以通过定义一个总是返回 true 的回调来信任该代理服务器。

export const http = defineConfig({
trustProxy: () => true
})

配置查询字符串解析器

来自请求 URL 的查询字符串使用 qs 模块进行解析。你可以在 config/app.ts 文件中配置解析器设置。

查看列表 了解所有可用的选项。

export const http = defineConfig({
qs: {
parse: {
},
}
})

表单方法欺骗

HTML 表单的表单方法只能设置为 GETPOST,这使得利用 restful HTTP 方法 变得不可能。

不过,AdonisJS 允许你通过表单方法欺骗来绕过这个限制。表单方法欺骗是一个花哨的说法,即通过 _method 查询字符串来指定表单方法。

要使方法欺骗生效,你必须将表单 action 设置为 POST,并在 config/app.ts 文件中启用该功能。

config/app.ts
export const http = defineConfig({
allowMethodSpoofing: true,
})

启用后,你可以像下面这样欺骗表单方法。

<form method="POST" action="/articles/1?_method=PUT">
<!-- 更新表单 -->
</form>
<form method="POST" action="/articles/1?_method=DELETE">
<!-- 删除表单 -->
</form>

扩展 Request 类

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

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

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

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