请求
请求实例 (http request) 请求对象 包含当前请求的数据。 当前请求包含这些数据: request body, uploaded files, cookies 等等。
你可以在路由、中间件、异常处理程序中访问到 HTTP context 对象的 request 对象。
Route.get('/', (ctx) => {
console.log(ctx.request.url())
})
使用解构方式:
Route.get('/', async ({ request }) => {
console.log(request.url())
})
URL 中的查询字符串和路由参数#
可以使用 request.qs()
函数,得到查询字符串
Route.get('/', async ({ request }) => {
/*
* URL: /?username=virk
* qs: { username: 'virk' }
*/
request.qs()
})
调用 request.params()
函数,得到请求参数。
Route.get('/posts/:id/:slug', async ({ request }) => {
/*
* URL: /posts/1/hello-world
* Params: { id: '1', slug: 'hello-world' }
*/
request.params()
})
你还可以使用 request.param
函数访问单个参数。
request.param('id')
// 可以设置一个默认值(可选)
request.param('id', 1)
请求体(Request body)#
你可以使用 request.body
函数访问请求体。
Route.post('/', async ({ request }) => {
request.body()
})
request.all#
当然,你也可以使用 request.all
函数,它返回请求正文(request body)和请求查询字符串的合并副本。
request.all()
request.input#
你可以使用 request.input
函数读取读取单个输入字段的值。该方法还支持使用点(.)嵌套取值。
request.input('title')
// 读嵌套的值
request.input('user.profile.username')
当实际值为 null
或 undefined
时,你还可以定义要返回的默认值。
// 缺失标题时返回 "Hello world"
request.input('title', 'Hello world')
request.only/request.except#
你可以使用 request.only
和 request.except
方法从请求主体中挑选 / 过滤特定键。
// 特定筛选
const body = request.only(['title', 'description'])
// 过滤
const body = request.except(['submit', 'csrf_token'])
主体解析 & 支持的内容类型#
请求正文使用预先配置的 bodyparser 中间件进行解析。它在 start/kernel.ts
文件中注册为全局中间件。
// 文件名: start/kernel.ts
Server.middleware.register([
() => import('@ioc:Adonis/Core/BodyParser')
])
bodyparser 的配置存储在 config/bodyparser.ts
文件中。配置文件是自文档型的,因此请随时熟悉所有可用的选项。
将空字符串转换为 null#
HTML 表单为没有值的输入字段提交一个空字符串。你可以通过启用 convertEmptyStringsToNull
标记将所有空字符串值标准化为 null。
注意:该选项仅适用于
multipart
和urlencoded
表单提交。
// 文件名: config/bodyparser.ts
{
form: {
// ... 其余的配置
convertEmptyStringsToNull: true
},
multipart: {
// ... 其余的配置
convertEmptyStringsToNull: true
}
}
支持的内容类型#
bodyparser 能够解析以下内容类型。
JSON#
JSON 解析器能处理会发送具有以下内容类型之一的 JSON 字符串的请求。
- application/json
- application/json-patch+json
- application/vnd.api+json
- application/csp-report
你可以在 config/bodyparser.ts
文件内的 json.types
数组中添加更多内容类型,JSON 解析器也会处理它们。
URL 编码#
使用 URL 编码解析器解析带有 content-type='application/x-www-form-urlencoded'
的 URL 编码字符串的请求。
混合内容#
content-type='multipart/form-data'
的混合内容请求使用混合内容解析器进行解析。请务必阅读有关 文件上传 的指南以查看所有可用的配置选项。
原始内容#
content-type='text/*'
的所有请求都使用默认解析器读取。你可以在中间件或路由处理程序中进一步处理原始字符串。
你可以使用默认解析器来处理自定义 / 不受支持的内容类型。例如
注册自定义内容类型#
// 文件名: config/bodyparser.ts
{
raw: {
// ...
types: ['text/*', 'my-custom-content-type']
}
}
创建一个中间件来进一步解析内容类型#
Route
.get('/', ({ request }) => {
console.log(request.all())
})
.middleware(async ({ request }, next) => {
const contentType = request.header('content-type')
if (contentType === 'my-custom-content-type') {
const body = request.raw()
const parsed = someCustomParser(body)
request.updateBody(parsed)
}
await next()
})
请求路由#
request
类保存 HTTP 请求的当前匹配路由,你可以通过以下方式访问它:
Route.get('/', ({ request }) => {
/**
* 路由匹配
*/
console.log(request.route.pattern)
/**
* 处理路由请求的处理器
*/
console.log(request.route.handler)
/**
* 附加到路由的中间件
*/
console.log(request.route.middleware)
/**
* 路由名称(如果路由被命名,则存在)
*/
console.log(request.route.name)
})
你还可以检查当前请求 URL 是否与给定路由匹配。
if (request.matchesRoute('/posts/:id')) {
}
或者传递一个数组来检查多个路由。如果任何路由与当前请求 URL 匹配,则该方法返回 true。
if (request.matchesRoute(['/posts/:id', '/posts/:id/comments'])) {
}
请求 URL#
你可以使用 request.url()
方法访问请求 URL。它返回不带域名或端口的路径名。
request.url()
// 包含查询字符串
request.url(true)
request.completeUrl()
方法返回完整的 URL,包括域和端口 (如果有)。
request.completeUrl()
// 包含查询字符串
request.completeUrl(true)
请求方法#
方法#
返回给定请求的 HTTP 方法。启用 表格方法掩饰 时会返回被掩饰的方法。
request.method()
原始方法#
intended
方法返回实际的 HTTP 方法,而不是掩饰的方法。
request.intended()
请求 id#
通过将唯一 ID 关联到每个日志条目,给定 HTTP 请求的请求 ID 可以帮助你调试和跟踪日志。
AdonisJS 遵循行业标准,对使用 X-Request-Id
请求头具有最佳支持。
生成请求 ID#
打开 config/app.ts
并将 http.generateRequestId
的值设置为 true。
仅当 X-Request-Id
标头未设置时才会生成请求 ID。这允许你在代理服务器级别生成请求 ID,然后在你的 AdonisJS 应用程序中引用它们。
// 文件名: config/app.ts
{
http: {
generateRequestId: true
}
}
获取请求 ID#
request.id()
方法通过读取 X-Request-Id
请求头返回请求 ID。流程如下所示:
- 读取
X-Request-Id
请求头的值。如果存在,则返回该值。 - 如果在配置中启用了
generateRequestId
标志,则手动生成和设置标头。 - 当请求头丢失时返回
null
,并且generateRequestId
被禁用。
request.id()
日志中的请求 ID#
附加到 HTTP 上下文的日志记录器实例会自动在每个日志语句上设置 request_id
属性。
Route.get('/', ({ logger }) => {
// { msg: 'hello world', request_id: 'ckk9oliws0000qt3x9vr5dkx7' }
logger.info('hello world')
})
请求头#
request.headers()
和 request.header()
方法允许访问请求标头。
// 获取所有请求头
console.log(request.headers())
header
方法返回单个请求头字段的值。请求头名称不区分大小写。
request.header('X-CUSTOM-KEY') === request.header('x-custom-key')
// 具有默认值的请求头
request.header('x-header-name', 'default value')
请求的 IP 地址#
request.ip()
方法为 HTTP 请求返回最受信任的 IP 地址。请务必阅读 受信任的代理 部分以了解当你进行代理之后时如何获得正确的 IP 地址。
request.ip()
request.ips()
方法返回一个 IP 地址数组,从最受信任的 IP 地址到最不受信任的 IP 地址。
request.ips()
自定义 IP 检索方式#
如果可信代理设置不足以确定正确的 IP 地址,你可以实现自己的 getIp
方法。
打开 config/app.ts
文件,定义 getIp
方法如下:
http: {
getIp(request) {
const nginxRealIp = request.header('X-Real-Ip')
if (nginxRealIp) {
return nginxRealIp
}
return request.ips()[0]
}
}
表单方法掩饰#
标准 HTML 表单不能使用除了 GET
和 POST
之外的所有 HTTP 动词。因此,例如,这意味着你不能使用 PUT
方法创建表单。
但是,AdonisJS 允许你使用 _method
查询字符串来欺骗 HTTP 方法。在以下示例中,请求将被路由转发到侦听 PUT
请求的路由。
<form method="POST" action="/posts/1?_method=PUT"></form>
表单方法伪造仅适用于:
- 当
http.allowMethodSpoofing
的值在config/app.ts
文件中设置为 true 时。 - 原来的请求方法是
POST
。
内容协商#
内容协商 是一种用于为来自同一 URL 的资源的不同表示提供服务的机制。
发出请求的客户端可以使用不同的 Accept
标头协商 resource representation、charset、language 和 encoding,你可以按如下方式处理它们。
接收内容#
request.accepts
方法采用内容类型数组 (包括简写形式) 并通过检查 Accept
请求头返回最合适的内容类型。你可以在 这里 找到支持的内容类型列表。
Route.get('posts', async ({ request, view }) => {
const posts = [
{
title: 'Adonis 101',
},
]
switch (request.accepts(['html', 'json'])) {
case 'html':
return view.render('posts/index', { posts })
case 'json':
return posts
default:
return view.render('posts/index', { posts })
}
})
请求类型#
request.types
方法通过检查 Accept
请求头返回内容类型数组。该数组按客户端的偏好排序 (最优先排序)。
const types = request.types()
语言#
根据 Accept-language
请求头协商请求的语言。
const language = request.language(['fr', 'de'])
if (language) {
return view.render(`posts/${language}/index`)
}
return view.render('posts/en/index')
多种语言#
languages
方法通过检查 Accept-language
请求头返回一组接受的语言。该数组按客户端的偏好排序 (最优先排序)。
const languages = request.languages()
编码#
使用 Accept-encoding
标头查找最佳编码。
switch (request.encoding(['gzip', 'br'])) {
case 'gzip':
return compressAsGzip(someValue)
case 'br':
return compressAsBr(someValue)
default:
return value
}
多种编码方式#
encodings
方法通过检查 Accept-encoding
请求头返回一个接受的编码数组。该数组按客户端的偏好排序 (最优先排序)。
const encodings = request.encodings()
字符集#
使用 Accept-charset
请求头查找最佳字符集。
const charset = request.charset(['utf-8', 'hex', 'ascii'])
return Buffer.from('hello-world').toString(charset || 'utf-8')
多字符集#
charsets
方法通过检查 Accept-charset
请求头返回一个接受的字符集数组。该数组按客户端的偏好排序 (最优先排序)。
const charsets = request.charsets()
信任代理#
大多数 Node.js 应用程序都经过了 Nginx 或 Caddy 等代理。因此,remoteAddress 的值是代理服务器的 IP 地址,而不是客户端的 IP 地址。
但是,所有代理服务器都设置了 X-Forwaded
请求头以反映请求的原始值,并且你必须通知 AdonisJS 信任代理服务器请求头。
你可以通过修改 config/app.ts
中的 http.trustProxy
值来控制要信任的代理。
// 文件名: config/app.ts
{
http: {
trustProxy: proxyAddr.compile(valueComesHere)
}
}
Ip 地址#
你还可以定义一个或一组要信任的代理服务器 IP 地址。
{
trustProxy: proxyAddr.compile('127.0.0.0/8')
}
// 或者
{
trustProxy: proxyAddr.compile(['127.0.0.0/8', 'fc00:ac:1ab5:fff::1/64'])
}
你还可以使用以下速记关键字代替 IP 地址。
loopback
: IPv4 和 IPv6 的回环地址 (比如::1
,127.0.0.1
).linklocal
: IPv4 和 IPv6 本地链路地址 (比如fe80:
,1
1
169.254.0.1
).uniquelocal
: IPv4 私有地址和 IPv6 唯一本地地址 (比如fc00
,1ab5
:1
192.168.0.1
).
自定义函数#
你还可以定义一个自定义函数,该函数根据每个请求返回一个布尔值。
{
trustProxy: proxyAddr.compile((address, index) => {
return address === '127.0.0.1' || address === '123.123.123.123'
})
}
使用中的代理请求头#
以下来自于请求类中的方法依赖于受信任的代理来返回正确的值。
- hostname:
request.hostname()
的值源自X-Forwarded-Host
请求头。 - protocol:
request.protocol()
的值源自X-Forwarded-Proto
请求头。 - ip/ips:
request.ips()
和request.ip()
的值来源于X-Forwaded-For
请求头。但是,http.getIp
配置方法在定义时具有更高优先级。 了解更多
CORS#
AdonisJS 内置支持响应 CORSOPTIONS
请求,前提是在 config/cors.ts
文件中启用它。
// 文件名: config/cors.ts
{
enabled: true,
// ... 余下的配置
}
配置文件用途广泛。确保浏览了所有选项并阅读相关评论以了解它们的用法。
其他方法和属性#
以下是 Request 类上其他可用方法和属性的列表。
主机名#
返回请求主机名。如果 代理请求头 是可信的,则 X-Forwarded-Host
的优先级高于 Host
标头。
request.hostname()
ajax#
查看请求头 X-Requested-With
是否设置为 'xmlhttprequest'
。
if (request.ajax()) {
// 返回 ajax 请求的响应
}
匹配路由#
matchesRoute
查找当前请求是否匹配给定路由。该方法接受路由标识符作为唯一参数。标识符可以是路由模式、controller.method 名称或路由名称。
if (request.matchesRoute('posts.show')) {
}
你还可以匹配多个路由。如果返回的 URL 与任何定义的标识符匹配,则该方法返回 true
。
if (request.matchesRoute(['posts.show', 'posts.edit'])) {
}
is#
匹配给定类型并返回请求的最佳匹配内容类型。
内容类型是从 Content-Type
请求头中选择的,并且该请求必须具有请求主体。
const contentType = request.is(['json', 'xml'])
if (contentType === 'json') {
// 以 JSON 方式处理请求主体
}
if (contentType === 'xml') {
// 以 XML 方式处理请求主体
}
更新请求主体#
updateBody
允许更新请求正文。请求主体始终是一个字符串。
request.updateBody(myCustomPayload)
更新原始请求主体#
updateRawBody
允许更新原始请求正文。原始请求主体始终是一个字符串。
request.updateRawBody(JSON.stringify(myCustomPayload))
更新查询字符串#
updateQs
允许更新解析的查询字符串的值。
request.updateQs(someCustomParser(request.parsedUrl.query))
原始请求#
返回由 bodyparser 解析的请求的原始请求主体。调用 updateBody
方法不会更改原始内容。
request.original()
包含请求主体#
hasBody
查找请求是否有正文。 bodyparser 使用此方法在解析请求之前知道请求是否有正文。
if (request.hasBody()) {
// 解析请求主体
}
扩展请求类#
你可以使用 macros 或 getters 扩展 Request 类。扩展请求的最佳位置是在自定义服务内部。
打开预设的 providers/AppProvider.ts
文件并在 boot
方法中写入以下代码。
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor(protected app: ApplicationContract) {}
public async boot() {
const Request = this.app.container.use('Adonis/Core/Request')
Request.macro('wantsJSON', function () {
const types = this.types()
return (
types[0] && (types[0].includes('/json') || types[0].includes('+json'))
)
})
}
}
在上面的例子中,我们在请求类中添加了 wantsJSON
方法。它读取 Accept
标头的值,如果第一个值是 JSON ,则返回 true。
你可以按如下方式使用新添加的方法。
Route.get('/', ({ request }) => {
if (request.wantsJSON()) {
return {}
}
})
通知 TypeScript 该方法#
wantsJSON
属性是在运行时添加的,因此 TypeScript 不知道它。为了通知 TypeScript,我们将使用 声明合并 并将属性添加到 RequestContract
接口。
在路径 contracts/request.ts
处创建一个新文件 (文件名不重要) 并在其中粘贴以下内容。
// 文件名: contracts/request.ts
declare module '@ioc:Adonis/Core/Request' {
interface RequestContract {
wantsJSON(): boolean
}
}
扩展阅读#
以下是一些补充资料,可以了解有关本文档未涵盖的主题的更多信息。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。