请求

本指南介绍在 AdonisJS 中处理 HTTP 请求。你将了解:

  • 读取请求体和上传的文件
  • 访问查询字符串和路由参数
  • 处理请求头和元数据
  • 读取 Cookie
  • 理解请求 ID 的生成
  • 配置可信代理和 IP 地址提取

概述

Request 类保存了与 HTTP 请求相关的所有信息,包括请求体、上传的文件、查询字符串、URL、方法、请求头和 Cookie。你可以通过 HttpContext 的 request 属性访问它,该属性在路由处理器、中间件和异常处理器中都可用。

读取请求体和文件

请求体包含客户端发送的数据,通常来自 HTML 表单或 API 请求。AdonisJS 使用 bodyparser 根据 Content-Type 请求头自动解析请求体,将 JSON、表单数据和 multipart 数据转换为便于处理的 JavaScript 对象。

访问整个请求体

使用 all 方法将请求体中的所有数据作为对象检索。当你希望一起处理所有提交的字段时,这很有用。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.post('/signup', ({ request }) => {
  const body = request.all()
  console.log(body)
  // { fullName: 'John Doe', email: 'john@example.com', password: 'secret' }
})
Note

类型安全与验证: 请求体数据并非类型安全的,因为 bodyparser 只收集和解析原始请求数据,并不验证它。请使用 验证系统 来确保请求数据的运行时安全和 TypeScript 类型安全。

访问特定字段

当你需要从请求体中读取特定字段时,使用 input 方法。该方法接受一个字段名,以及字段不存在时的可选默认值。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.post('/signup', ({ request }) => {
  const email = request.input('email')
  const newsletter = request.input('newsletter', false)
  
  console.log(email)
  console.log(newsletter)
})

你也可以使用 only 方法检索多个特定字段,或使用 except 方法检索除某些字段之外的所有字段。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.post('/signup', ({ request }) => {
  /**
   * 只获取 fullName 和 email,忽略其他字段
   */
  const credentials = request.only(['fullName', 'email'])
  console.log(credentials)
  
  /**
   * 获取除 password 之外的所有字段
   */
  const safeData = request.except(['password'])
  console.log(safeData)
})

访问上传的文件

通过 multipart 表单数据上传的文件可以使用 file 方法获取。该方法返回一个文件对象,带有元数据和用于验证与存储的方法。

另请参阅: 文件上传指南 了解详细的文件处理与存储

start/routes.ts
import router from '@adonisjs/core/services/router'

router.post('/avatar', ({ request }) => {
  const avatar = request.file('avatar')
  console.log(avatar)
})

你可以在访问文件时通过提供验证选项来验证文件。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.post('/avatar', ({ request }) => {
  const avatar = request.file('avatar', {
    size: '2mb',
    extnames: ['jpg', 'png', 'jpeg']
  })
  
  console.log(avatar)
})
Tip

文件验证方法: 你可以在使用 request.file() 访问文件时进行验证,也可以使用验证器。推荐使用验证器方法,因为它能与其他请求数据一起提供一致的验证和更好的错误处理。

可用方法

方法描述
all()将所有请求体数据作为对象返回
body()all() 方法的别名
input(key, defaultValue?)返回特定字段值,可带默认值
only(keys)只返回指定的字段
except(keys)返回除指定字段之外的所有字段
file(key, options?)返回上传的文件,可带验证选项

读取查询字符串和路由参数

查询字符串和路由参数是通过 URL 传递数据的两种不同方式。查询字符串是 URL 中 ? 之后的部分(如 ?page=1&limit=10),而路由参数是在路由模式中定义的动态片段(如 /posts/:id)。

访问查询字符串参数

使用 qs 方法将所有查询字符串参数作为对象检索。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts', ({ request }) => {
  const queryString = request.qs()
  console.log(queryString)
  // { page: '1', limit: '10', orderBy: 'created_at' }
})

你可以使用 input 方法访问单个查询参数,该方法对请求体数据和查询字符串参数都适用。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts', ({ request }) => {
  const page = request.input('page', 1)
  const limit = request.input('limit', 20)
  const orderBy = request.input('orderBy', 'id')
  
  console.log({ page, limit, orderBy })
})

访问路由参数

路由参数可通过 param 方法或直接使用 params 对象访问。params 对象也直接可在 HttpContext 上获取。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts/:id', ({ request }) => {
  const id = request.param('id')
  console.log(id)
})

可用方法

方法描述
qs()将所有查询字符串参数作为对象返回
param(key, defaultValue?)返回特定路由参数,可带默认值
params()将所有路由参数作为对象返回

读取请求头、方法、URL 和 IP 地址

请求元数据包含有关请求的发起方式、来源,以及客户端期望得到何种响应的信息。这包括 HTTP 请求头、请求方法(GET、POST 等)、请求的 URL 和客户端的 IP 地址。

访问请求头

使用 header 方法读取特定的请求头值。请求头名称不区分大小写。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/profile', ({ request }) => {
  const authToken = request.header('Authorization')
  const userAgent = request.header('User-Agent')
  
  console.log(authToken)
  console.log(userAgent)
})

你可以使用 headers 方法检索所有请求头。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/debug', ({ request }) => {
  const allHeaders = request.headers()
  console.log(allHeaders)
})

访问请求方法

请求方法(GET、POST、PUT、DELETE 等)可通过 method 方法获取。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.all('/endpoint', ({ request }) => {
  const method = request.method()
  console.log(method) // 'GET'、'POST' 等
})

访问请求 URL

使用 url 方法获取不带域名和协议的请求 URL,或使用 completeUrl 获取包含域名的完整 URL。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts', ({ request }) => {
  const path = request.url()
  console.log(path) // '/posts?page=1'
  
  const fullUrl = request.completeUrl()
  console.log(fullUrl) // 'https://example.com/posts?page=1'
})

访问客户端 IP 地址

ip 方法返回客户端的 IP 地址。当你的应用位于反向代理或负载均衡器之后时,你需要配置 可信代理 以正确 检测真实的客户端 IP 。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/track', ({ request }) => {
  const clientIp = request.ip()
  console.log(clientIp)
})

当请求经过多个代理时,ips 方法返回一个 IP 地址数组。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/track', ({ request }) => {
  const ipChain = request.ips()
  console.log(ipChain) // ['client-ip', 'proxy-1', 'proxy-2']
})

可用方法

方法描述
header(key, defaultValue?)返回特定的请求头值
headers()将所有请求头作为对象返回
method()返回 HTTP 方法(GET、POST 等)
url()返回不带域名的请求 URL
completeUrl()返回包含域名的完整 URL
ip()返回客户端 IP 地址
ips()位于代理之后时返回 IP 数组
getPreviousUrl(allowedHosts, fallback?)从 Referer 请求头返回经过验证的上一个 URL

Cookie 是存储在客户端浏览器中的小块数据,随每个请求一起发送。AdonisJS 提供了通过 Request 类读取普通 Cookie 以及签名/加密 Cookie 的方法。

使用 cookie 方法读取签名 Cookie 值。默认情况下,所有 Cookie 都是签名的。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/preferences', ({ request }) => {
  const theme = request.cookie('theme', 'light')
  const language = request.cookie('language', 'en')
  
  console.log({ theme, language })
})

对加密 Cookie 使用 encryptedCookie。该方法会自动解密并验证 Cookie 值。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/dashboard', ({ request }) => {
  const sessionId = request.encryptedCookie('session_id')
  console.log(sessionId)
})

可用方法

方法描述
cookie(key, defaultValue?)返回签名 Cookie 值
cookiesList()将所有 Cookie 作为对象返回,但不解密或取消签名
encryptedCookie(key, defaultValue?)返回解密后的 Cookie 值
plainCookie(key, defaultValue?)返回普通 Cookie 的值

读取请求 ID 并理解 ID 生成

AdonisJS 中的每个 HTTP 请求都会被分配一个唯一的请求 ID。该 ID 对于分布式追踪、日志记录,以及在你的应用和微服务间关联相关操作非常有用。

访问请求 ID

使用 id 方法检索分配给当前请求的唯一标识符。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/api/posts', ({ request }) => {
  const requestId = request.id()
  console.log(`Processing request: ${requestId}`)
})

请求 ID 如何生成

AdonisJS 按优先级依次使用以下方法之一生成请求 ID。

Note

你必须先在 config/app.ts 文件中启用请求 ID 生成,AdonisJS 才会生成请求 ID(以防缺少 X-Request-Id 请求头)。

  1. 来自 X-Request-Id 请求头: 如果客户端或代理发送了 X-Request-Id 请求头,AdonisJS 会使用该值。这允许你在多个服务之间追踪请求。
  2. 由 AdonisJS 生成: 如果不存在 X-Request-Id 请求头,AdonisJS 默认使用 uuid 包生成一个唯一 ID(如果已启用)。

将请求 ID 用于分布式追踪

请求 ID 在分布式系统中特别有价值,因为单个用户请求可能触发跨多个服务的操作。通过为每个操作记录请求 ID,你可以追踪请求通过系统的完整流程。

start/routes.ts
import router from '@adonisjs/core/services/router'
import logger from '@adonisjs/core/services/logger'

router.post('/checkout', async ({ request }) => {
  const requestId = request.id()
  
  logger.info({ requestId }, 'Starting checkout process')
  // 你的结账逻辑在这里
  logger.info({ requestId }, 'Checkout completed')
})

内容协商

内容协商允许你的应用根据客户端接受的内容提供不同的响应格式或语言。Request 类提供了读取 Accept、Accept-Language、Accept-Charset 和 Accept-Encoding 请求头,并将其与你的应用支持的格式进行匹配的方法。

选择响应格式

使用 accepts 方法根据客户端的 Accept 请求头确定最佳的响应格式。当你的 API 可以以多种格式(如 JSON、HTML 或 XML)返回数据时,这很有用。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts', ({ request, response }) => {
  const bestFormat = request.accepts(['html', 'json'])
  
  if (bestFormat === 'json') {
    return response.json({ posts: [] })
  }
  
  if (bestFormat === 'html') {
    return response.view('posts/index')
  }
  
  // 客户端不接受任何支持的格式
  return response.status(406).send('Not Acceptable')
})

types 方法按客户端偏好顺序返回所有接受的内容类型。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/posts', ({ request }) => {
  const acceptedTypes = request.types()
  console.log(acceptedTypes) // ['application/json', 'text/html', '*/*']
})

国际化

使用 language 方法根据客户端的 Accept-Language 请求头确定最佳语言。这有助于以用户偏好的语言提供内容。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/welcome', ({ request, response }) => {
  const language = request.language(['en', 'fr', 'es']) || 'en'
  
  const messages = {
    en: 'Welcome',
    fr: 'Bienvenue',
    es: 'Bienvenido'
  }
  
  return response.send(messages[language])
})

languages 方法按客户端偏好顺序返回所有接受的语言。

start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/welcome', ({ request }) => {
  const acceptedLanguages = request.languages()
  console.log(acceptedLanguages) // ['en-US', 'fr', 'es']
})

可用方法

方法描述
accepts(types)返回最佳匹配的内容类型或 null
types()按偏好顺序返回所有接受的内容类型
language(languages)返回最佳匹配的语言或 null
languages()按偏好顺序返回所有接受的语言
charset(charsets)返回最佳匹配的字符集或 null
charsets()按偏好顺序返回所有接受的字符集
encoding(encodings)返回最佳匹配的编码或 null
encodings()按偏好顺序返回所有接受的编码

信任代理服务器

当你的应用运行在反向代理(如 Nginx)或负载均衡器之后时,你需要配置要信任哪些代理 IP 地址。这允许 AdonisJS 正确读取代理添加到请求中的 X-Forwarded-* 请求头。

config/app.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/core/http'
import proxyAddr from 'proxy-addr'

export const http = defineConfig({
  /**
   * 信任回环地址和私有 IP 范围。
   * 对于大多数部署场景(代理运行在同一台机器或私有网络上)
   * 这是安全的。
   */
  trustProxy: proxyAddr.compile(['loopback', 'uniquelocal'])
})

trustProxy 选项接受 proxy-addr 包支持的任何值。常见配置包括:

// 信任所有代理(不建议用于生产环境)
trustProxy: () => true

// 信任特定的 IP 地址
trustProxy: proxyAddr.compile(['127.0.0.1', '192.168.1.1'])

// 使用 CIDR 表示法信任 IP 范围
trustProxy: proxyAddr.compile('10.0.0.0/8')

自定义 IP 地址提取

默认情况下,AdonisJS 使用标准方法从请求中提取客户端 IP 地址。但是,当运行在 Cloudflare 等代理或 CDN 之后时,你可能需要从自定义请求头中提取 IP。

config/app.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/core/http'
import type { IncomingMessage } from 'node:http'

export const http = defineConfig({
  /**
   * 从 Cloudflare 的 CF-Connecting-IP 请求头提取 IP
   * 如果请求头不存在,则回退到标准 IP 提取
   */
  getIp(request: IncomingMessage) {
    const cloudflareIp = request.headers['cf-connecting-ip']
    
    if (cloudflareIp && typeof cloudflareIp === 'string') {
      return cloudflareIp
    }
    
    // 返回 undefined 以回退到默认的 IP 提取
    return undefined
  }
})

getIp 方法接收 Node.js 的 IncomingMessage 对象,必须返回一个字符串形式的 IP 地址,或返回 undefined 以回退到默认行为。当你使用在自定义请求头中提供真实客户端 IP 的 CDN 时,这很有用。