farrow-http 完整 API 参考
目录
核心概念
设计哲学
farrow-http 是一个类型驱动的 Web 框架,核心设计理念:
- 类型安全优先 - 编译期类型检查,运行时自动验证
- 声明式路由 - URL 模式自动推导 TypeScript 类型
- 函数式中间件 - 基于 farrow-pipeline 的洋葱模型
- 自动验证 - 集成 farrow-schema 的运行时验证
- 上下文隔离 - 基于 AsyncLocalStorage 的请求级隔离
三大支柱
farrow-pipeline (中间件管道) → 处理流程控制
farrow-schema (类型验证) → 自动验证和类型推导
farrow-http (HTTP 框架) → 路由和响应构建执行模型
// 洋葱模型执行顺序
app.use((req, next) => {
console.log('1: 进入') // 1
const res = next(req)
console.log('4: 退出') // 4
return res
})
app.use((req, next) => {
console.log('2: 进入') // 2
const res = next(req)
console.log('3: 退出') // 3
return res
})自动异步追踪
farrow-http 会在启动时自动启用异步追踪,无需手动配置:
import { Http } from 'farrow-http'
// 框架内部自动调用 asyncTracerImpl.enable()
const app = Http()
// ✅ Context 在异步操作中自动传递完整导出清单
主入口 (farrow-http)
// HTTP 服务器
export { Http, Https }
export type { HttpPipeline, HttpsPipeline, HttpPipelineOptions, HttpsPipelineOptions }
// 路由系统
export { Router }
export type {
RouterPipeline,
RoutingMethods,
RoutingMethod,
RouterUrlSchema,
RouterRequestSchema,
RouterSharedSchema,
MatchOptions,
HttpMiddleware,
HttpMiddlewareInput,
Pathname
}
// 请求信息
export type {
RequestInfo,
RequestQuery,
RequestHeaders,
RequestCookies
}
// 响应构建
export { Response, matchBodyType }
export type { MaybeAsyncResponse }
// 响应信息
export type {
ResponseInfo,
Body,
BodyMap,
Status,
Headers,
Cookies,
EmptyBody,
StringBody,
JsonBody,
StreamBody,
BufferBody,
FileBody,
RedirectBody,
CustomBody,
FileBodyOptions,
CustomBodyHandler,
RedirectOptions
}
// 上下文钩子
export {
useReq, // 获取 Node.js 请求对象(断言非空)
useRequest, // 获取 Node.js 请求对象(可能为空)
useRequestInfo, // 获取 farrow 请求信息
useRes, // 获取 Node.js 响应对象(断言非空)
useResponse // 获取 Node.js 响应对象(可能为空)
}
// Basename 路由
export { useBasenames, usePrefix }
// 错误处理
export { HttpError }
// 日志
export type { LoggerOptions, LoggerEvent }
// 类型工具
export type { ParseUrl, MarkReadOnlyDeep }配套依赖模块
farrow-http 依赖以下模块,需要单独安装和导入:
farrow-pipeline - 中间件管道系统
// 从 'farrow-pipeline' 导入(不是从 'farrow-http')
import {
createContext, // 创建上下文
createContainer, // 创建容器
useContainer, // 获取当前容器
runWithContainer, // 在指定容器中运行
type Context, // 上下文类型
type Container, // 容器类型
type ContextStorage, // 上下文存储类型
type Middleware, // 中间件类型
type Pipeline, // 同步管道类型
type AsyncPipeline, // 异步管道类型
type Next, // Next 函数类型
type MaybeAsync // 可能异步类型
} from 'farrow-pipeline'farrow-schema - 类型定义和验证
// 从 'farrow-schema' 导入(不是从 'farrow-http')
import {
Schema, // Schema 基类
ObjectType, // 对象类型(支持递归)
StructType, // 结构体类型(不支持递归)
// 基础类型
String, Number, Boolean, Int, Float, ID, Date,
// 复合类型构造函数
List, Optional, Nullable, Record, Tuple,
// 联合与交集
Union, Intersect, Literal,
// 结构体构造函数
Struct,
// 特殊类型
Any, Unknown, Never, Json,
// 工具函数
TypeOf, // 提取 TypeScript 类型
field, // 定义字段元数据
// 类型定义
type FieldDescriptor,
type FieldDescriptors,
type SchemaCtor
} from 'farrow-schema'farrow-schema/validator - 运行时验证
// 从 'farrow-schema/validator' 导入(不是从 'farrow-http')
import {
Validator, // 验证器对象
createSchemaValidator,// 创建专用验证器
ValidatorType, // 自定义验证器基类
RegExp, // 正则表达式验证器
// 类型定义
type ValidationResult,
type ValidationError,
type ValidatorOptions
} from 'farrow-schema/validator'完整使用示例:
// 1. 分别导入各个模块
import { Http, Response } from 'farrow-http'
import { createContext } from 'farrow-pipeline'
import { ObjectType, String, Int, Optional } from 'farrow-schema'
// 2. 定义 Schema
class User extends ObjectType {
name = String
age = Int
email = Optional(String)
}
// 3. 创建上下文
const UserContext = createContext<User | null>(null)
// 4. 创建 HTTP 服务器
const app = Http()
// 5. 使用
app.post('/users', { body: User }).use((req) => {
UserContext.set(req.body)
return Response.status(201).json(req.body)
})
app.listen(3000)安装依赖:
npm install farrow-http farrow-pipeline farrow-schemaHTTP 服务器
Http - 创建 HTTP 服务器
函数签名:
function Http(options?: HttpPipelineOptions): HttpPipeline类型定义:
type HttpPipelineOptions = {
basenames?: string[] // 基础路径前缀
body?: BodyOptions // 请求体解析选项(来自 co-body 库)
cookie?: CookieParseOptions // Cookie 解析选项(来自 cookie 库)
query?: IParseOptions // 查询参数解析选项(来自 qs 库)
contexts?: (params: { // 上下文注入函数
req: IncomingMessage
requestInfo: RequestInfo
basename: string
}) => ContextStorage
logger?: boolean | HttpLoggerOptions // 日志配置
errorStack?: boolean // 是否显示错误堆栈
}
type HttpLoggerOptions = {
transporter?: (str: string) => void // 自定义日志输出
ignoreIntrospectionRequestOfFarrowApi?: boolean // 忽略 farrow-api 内省请求(默认 true)
}
// BodyOptions - 来自 co-body 库
// 用于解析 HTTP 请求体(JSON、表单、文本)
// co-body 是 koa-bodyparser 的底层依赖
type BodyOptions = {
limit?: string | number // 请求体大小限制,如 '1mb', '10kb', 1024000
encoding?: BufferEncoding // 字符编码,默认 'utf8'
strict?: boolean // JSON 严格模式,默认 true
jsonTypes?: string[] // 自定义 JSON content-type 列表,默认 ['application/json']
formTypes?: string[] // 自定义表单 content-type 列表,默认 ['application/x-www-form-urlencoded']
textTypes?: string[] // 自定义文本 content-type 列表,默认 ['text/plain']
}
// CookieParseOptions - 来自 cookie 库
// 用于解析 HTTP Cookie 头
type CookieParseOptions = {
decode?: (val: string) => string // Cookie 值解码函数,默认 decodeURIComponent
}
// IParseOptions - 来自 qs 库
// 用于解析 URL 查询字符串(如 ?a=1&b=2)
type IParseOptions = {
depth?: number // 对象嵌套深度限制,默认 5
arrayLimit?: number // 数组元素数量限制,默认 20
delimiter?: string | RegExp // 查询参数分隔符,默认 '&'
allowDots?: boolean // 是否允许点符号,默认 false(如 ?user.name=Alice)
parseArrays?: boolean // 是否解析数组语法,默认 true(如 ?tags[0]=a&tags[1]=b)
allowPrototypes?: boolean // 是否允许原型污染,默认 false(安全考虑)
plainObjects?: boolean // 是否使用普通对象(无原型),默认 false
comma?: boolean // 是否将逗号视为数组分隔符,默认 false
}
type HttpPipeline = RouterPipeline & {
handle: (req: IncomingMessage, res: ServerResponse, options?: HttpHandlerOptions) => MaybeAsync<void>
listen: (...args: Parameters<Server['listen']>) => Server
server: () => Server
}基础示例:
import { Http, Response } from 'farrow-http'
// 创建服务器
const app = Http()
// 添加路由
app.get('/').use(() => Response.json({ message: 'Hello World' }))
// 启动服务器
app.listen(3000, () => console.log('服务器运行在 http://localhost:3000'))配置示例:
const app = Http({
basenames: ['/api/v1'],
logger: true,
body: {
limit: '10mb',
encoding: 'utf8'
},
cookie: {
decode: decodeURIComponent
},
query: {
depth: 5,
arrayLimit: 100,
allowDots: true,
parseArrays: true
},
errorStack: process.env.NODE_ENV === 'development'
})服务器方法:
listen(port, callback?)
启动服务器并监听端口。
app.listen(3000)
app.listen(3000, () => console.log('服务器启动'))
app.listen(3000, '0.0.0.0', () => console.log('监听所有网卡'))server()
获取底层 Node.js HTTP 服务器实例(用于测试)。
const server = app.server() // http.Server 实例
// 用于测试
import request from 'supertest'
const response = await request(server).get('/').expect(200)handle(req, res, options?)
直接处理 Node.js 原生 HTTP 请求(用于集成到其他框架或自定义服务器)。
import { createServer } from 'http'
import express from 'express'
const app = Http()
app.get('/api/hello').use(() => Response.json({ message: 'Hello' }))
// 方式 1:独立运行(推荐)
app.listen(3000)
// 方式 2:集成到 Express
const expressApp = express()
expressApp.use('/farrow', (req, res) => {
app.handle(req, res)
})
expressApp.listen(3000)
// 方式 3:自定义服务器
const customServer = createServer(app.handle)
customServer.listen(3000)说明: app.server() 内部调用 createServer(app.handle)。
Https - 创建 HTTPS 服务器
函数签名:
function Https(options?: HttpsPipelineOptions): HttpsPipeline类型定义:
type HttpsPipelineOptions = HttpPipelineOptions & {
tls?: SecureContextOptions & TlsOptions
}
type HttpsPipeline = HttpPipeline // 相同的接口示例:
import { Https } from 'farrow-http'
import fs from 'fs'
const app = Https({
tls: {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem')
},
basenames: ['/api'],
logger: true
})
app.get('/').use(() => Response.json({ secure: true }))
app.listen(443, () => console.log('HTTPS 服务器运行在端口 443'))路由系统
Router - 创建独立路由器
函数签名:
function Router(): RouterPipeline类型定义:
type RouterPipeline = AsyncPipeline<RequestInfo, Response> & {
// HTTP 方法路由
get: RoutingMethod
post: RoutingMethod
put: RoutingMethod
patch: RoutingMethod
delete: RoutingMethod
head: RoutingMethod
options: RoutingMethod
// 高级匹配
match<T extends RouterRequestSchema>(
schema: T,
options?: MatchOptions
): AsyncPipeline<TypeOfRequestSchema<T>, Response>
match<U extends string, T extends RouterUrlSchema<U>>(
schema: T,
options?: MatchOptions
): AsyncPipeline<TypeOfUrlSchema<T>, Response>
// 嵌套路由
route(name: string): Pipeline<RequestInfo, MaybeAsyncResponse>
// 静态文件
serve(name: string, dirname: string): void
// 响应拦截
capture<T extends keyof BodyMap>(
type: T,
f: (body: BodyMap[T]) => MaybeAsyncResponse
): void
}
type RoutingMethod = <U extends string, T extends RouterSharedSchema>(
path: U,
schema?: T,
options?: MatchOptions
) => Pipeline<TypeOfUrlSchema<{ url: U, method: string } & T>, MaybeAsyncResponse>基础示例:
import { Router, Response } from 'farrow-http'
const userRouter = Router()
userRouter.get('/').use(() => Response.json({ users: [] }))
userRouter.get('/<id:int>').use((req) => {
return Response.json({ userId: req.params.id })
})
// 挂载到应用
app.route('/users').use(userRouter)HTTP 方法路由
所有 HTTP 方法都支持相同的签名:
type RoutingMethod = <U extends string, T extends RouterSharedSchema>(
path: U, // URL 模式
schema?: T, // 可选的验证 Schema
options?: MatchOptions // 可选的匹配选项
) => Pipeline<...>get(path, schema?, options?)
// 基础路由
app.get('/').use(() => Response.text('首页'))
// 路径参数
app.get('/user/<id:int>').use((req) => {
const userId = req.params.id // number
return Response.json({ id: userId })
})
// 查询参数
app.get('/search?<q:string>&<page?:int>').use((req) => {
const { q, page = 1 } = req.query
return Response.json({ query: q, page })
})
// 带验证的请求头
app.get('/protected', {
headers: { authorization: String }
}).use((req) => {
const token = req.headers.authorization
return Response.json({ authenticated: true })
})post(path, schema?, options?)
import { ObjectType, String, Int, Optional } from 'farrow-schema'
class CreateUserInput extends ObjectType {
name = String
email = String
age = Optional(Int)
}
app.post('/users', {
body: CreateUserInput
}).use((req) => {
// req.body 已验证,类型安全
const { name, email, age } = req.body
return Response.status(201).json({ id: Date.now(), name, email, age })
})put(path, schema?, options?)
class UpdateUserInput extends ObjectType {
name = Optional(String)
email = Optional(String)
}
app.put('/users/<id:int>', {
body: UpdateUserInput
}).use((req) => {
const { id } = req.params
const updates = req.body
return Response.json({ id, ...updates })
})patch(path, schema?, options?)
app.patch('/users/<id:int>/email', {
body: { email: String }
}).use((req) => {
return Response.json({
id: req.params.id,
email: req.body.email
})
})delete(path, schema?, options?)
app.delete('/users/<id:int>').use((req) => {
deleteUser(req.params.id)
return Response.status(204).empty()
})head(path, schema?, options?)
app.head('/users').use(() => {
return Response.status(200).empty()
})options(path, schema?, options?)
app.options('/users').use(() => {
return Response
.header('Allow', 'GET, POST, PUT, DELETE')
.status(200)
.empty()
})URL 模式与自动验证
farrow-http 提供强大的 URL 模式系统,支持自动类型推导和验证。
路径参数类型
// 基础类型
app.get('/user/<id:int>').use((req) => {
req.params.id // number
})
app.get('/user/<name:string>').use((req) => {
req.params.name // string
})
app.get('/price/<value:float>').use((req) => {
req.params.value // number
})
app.get('/active/<flag:boolean>').use((req) => {
req.params.flag // boolean
})
app.get('/user/<userId:id>').use((req) => {
req.params.userId // string(非空)
})联合类型
// 枚举值
app.get('/posts/<status:draft|published|archived>').use((req) => {
req.params.status // 'draft' | 'published' | 'archived'
})
// 字面量类型
app.get('/api/<version:{v1}|{v2}>').use((req) => {
req.params.version // 'v1' | 'v2'
})修饰符
// 可选参数 (?)
app.get('/user/<name?:string>').use((req) => {
req.params.name // string | undefined
})
// 一个或多个 (+)
app.get('/tags/<tags+:string>').use((req) => {
req.params.tags // string[]
})
// 零个或多个 (*)
app.get('/categories/<cats*:string>').use((req) => {
req.params.cats // string[] | undefined
})查询参数
// 必需查询参数
app.get('/products?<sort:asc|desc>').use((req) => {
req.query.sort // 'asc' | 'desc'
})
// 可选查询参数
app.get('/search?<q:string>&<page?:int>').use((req) => {
const { q, page = 1 } = req.query
// q: string, page: number | undefined
})
// 数组查询参数
app.get('/filter?<tags*:string>').use((req) => {
req.query.tags // string[] | undefined
})
// 字面量查询参数
app.get('/products?status=active').use((req) => {
req.query.status // 'active'
})组合示例
app.get('/<category:string>?<search:string>&<page?:int>&<sort?:asc|desc>').use((req) => {
const { category } = req.params // string
const { search, page = 1, sort = 'asc' } = req.query
// search: string
// page: number | undefined
// sort: 'asc' | 'desc' | undefined
return Response.json({
category,
search,
page,
sort,
results: []
})
})match - 高级路由匹配
函数签名:
match<T extends RouterRequestSchema>(
schema: T,
options?: MatchOptions
): AsyncPipeline<TypeOfRequestSchema<T>, Response>
match<U extends string, T extends RouterUrlSchema<U>>(
schema: T,
options?: MatchOptions
): AsyncPipeline<TypeOfUrlSchema<T>, Response>类型定义:
type RouterUrlSchema<T extends string = string> = {
url: T
method?: string | string[]
body?: FieldDescriptor | FieldDescriptors
headers?: RouterSchemaDescriptor
cookies?: RouterSchemaDescriptor
}
type RouterRequestSchema = {
pathname: Pathname
method?: string | string[]
params?: RouterSchemaDescriptor
query?: RouterSchemaDescriptor
body?: FieldDescriptor | FieldDescriptors
headers?: RouterSchemaDescriptor
cookies?: RouterSchemaDescriptor
}
type MatchOptions = {
block?: boolean // 阻塞模式
onSchemaError?( // 验证错误处理
error: ValidationError,
input: RequestInfo,
next: Next<RequestInfo, MaybeAsyncResponse>
): MaybeAsyncResponse | void
}使用示例:
// URL Schema 方式
app.match({
url: '/users/<id:int>?<expand?:string>',
method: ['GET', 'PUT'],
body: { name: String, email: String },
headers: { authorization: String }
}, {
block: true, // 阻塞模式:验证失败返回 400
onSchemaError: (error, input, next) => {
return Response.status(400).json({
error: '验证失败',
field: error.path?.join('.'),
message: error.message,
received: error.value
})
}
}).use((req) => {
// req.params.id: number
// req.query.expand: string | undefined
// req.body: { name: string, email: string }
// req.headers.authorization: string
return Response.json({
id: req.params.id,
...req.body
})
})
// Request Schema 方式
app.match({
pathname: '/users/:id',
method: 'POST',
params: { id: Int },
body: CreateUserInput,
headers: { 'content-type': Literal('application/json') }
}).use((req) => {
return Response.json({ success: true })
})route - 路径前缀(Basename)
函数签名:
route(name: string): Pipeline<RequestInfo, MaybeAsyncResponse>说明:
- 为路径添加前缀(basename),返回的是
Pipeline,不是RouterPipeline - 返回的 Pipeline 没有
get、post等 HTTP 方法 - 用于组织路由层级和挂载独立的
Router
基础用法:
const app = Http({ basenames: ['/api'] })
// ❌ 错误:route() 返回的 Pipeline 没有 HTTP 方法
const v1 = app.route('/v1')
v1.get('/users') // 错误!Pipeline 没有 get 方法
// ✅ 正确:创建独立 Router 再挂载
const v1Router = Router()
v1Router.get('/users').use(() => Response.json({ users: [] }))
v1Router.get('/posts').use(() => Response.json({ posts: [] }))
app.route('/v1').use(v1Router)嵌套路由:
const app = Http({ basenames: ['/api'] })
// 继续嵌套 route
app.route('/v1')
.route('/users')
.use((req) => {
// 请求 /api/v1/users/* 会到这里
// req.pathname 已经去掉了 /api/v1/users 前缀
return Response.json({ path: req.pathname })
})模块化路由(推荐):
// users.ts
export const userRouter = Router()
userRouter.get('/').use(() => Response.json({ users: [] }))
userRouter.get('/<id:int>').use((req) => Response.json({ userId: req.params.id }))
userRouter.post('/', { body: CreateUserInput }).use((req) => {
return Response.status(201).json(createUser(req.body))
})
// posts.ts
export const postRouter = Router()
postRouter.get('/').use(() => Response.json({ posts: [] }))
postRouter.post('/', { body: CreatePostInput }).use((req) => {
return Response.status(201).json(createPost(req.body))
})
// app.ts
import { userRouter } from './users'
import { postRouter } from './posts'
const app = Http({ basenames: ['/api'] })
// 挂载独立 Router
app.route('/users').use(userRouter) // GET /api/users
app.route('/posts').use(postRouter) // GET /api/posts
// 嵌套挂载
const v1Router = Router()
v1Router.route('/users').use(userRouter) // GET /api/v1/users
v1Router.route('/posts').use(postRouter) // GET /api/v1/posts
app.route('/v1').use(v1Router)关键区别:
| 方法 | 返回类型 | 有 HTTP 方法? | 用途 |
|---|---|---|---|
Router() | RouterPipeline | ✅ 有 | 创建独立路由器 |
app.route() | Pipeline | ❌ 没有 | 添加路径前缀 |
serve - 静态文件服务
函数签名:
serve(name: string, dirname: string): void说明: 提供带有内置安全保护的静态文件服务。
内置安全特性:
- ✅ 自动防止目录遍历攻击(路径规范化)
- ✅ 自动文件权限检查
- ✅ 为目录请求提供 index.html
- ✅ 如果文件未找到,优雅地传递给下一个中间件
- ✅ 跨平台安全路径处理
示例:
// 基本静态文件服务
app.serve('/static', './public')
app.serve('/uploads', './storage/uploads')
// URL 映射:
// /static/style.css → ./public/style.css
// /static/ → ./public/index.html(如果存在)
// /static/docs/ → ./public/docs/index.html(如果存在)
// /uploads/../secret → 被阻止(目录遍历防护)
// 组合使用
app.serve('/assets', './public')
app.get('/api/users').use(() => Response.json({ users: [] }))capture - 响应拦截
函数签名:
capture<T extends keyof BodyMap>(
type: T,
f: (body: BodyMap[T]) => MaybeAsyncResponse
): void说明: 拦截特定类型的响应体并进行转换。
示例:
// 为所有 JSON 响应添加时间戳
app.capture('json', (jsonBody) => {
return Response.json({
data: jsonBody.value,
timestamp: new Date().toISOString(),
version: 'v1'
})
})
// 为所有文件响应添加缓存头
app.capture('file', (fileBody) => {
return Response.file(fileBody.value, fileBody.options)
.header('Cache-Control', 'public, max-age=3600')
.header('X-File-Server', 'farrow-http')
})
// 处理字符串响应
app.capture('string', (stringBody) => {
return Response.string(`[${new Date().toISOString()}] ${stringBody.value}`)
})可捕获的响应类型:
type BodyMap = {
empty: EmptyBody
string: StringBody
json: JsonBody
stream: StreamBody
buffer: BufferBody
file: FileBody
redirect: RedirectBody
custom: CustomBody
}请求与验证
RequestInfo - 请求信息类型
类型定义:
type RequestInfo = {
readonly pathname: string // 请求路径,如 "/users/123"
readonly method?: string // HTTP 方法,如 "GET"
readonly query?: RequestQuery // 查询参数
readonly body?: any // 请求体(已解析和验证)
readonly headers?: RequestHeaders // 请求头
readonly cookies?: RequestCookies // Cookies
readonly params?: RequestParams // 路径参数(从 URL 模式解析)
}
type RequestQuery = { readonly [key: string]: any }
type RequestHeaders = { readonly [key: string]: any }
type RequestCookies = { readonly [key: string]: any }
type RequestParams = { readonly [key: string]: any }特点:
- ✅ 所有字段都是只读的(
readonly) - ✅ 路径参数、查询参数、请求体已自动验证和类型转换
- ✅ 在整个中间件链中保持一致性
请求体验证
使用 ObjectType
import { ObjectType, String, Int, Optional, List } from 'farrow-schema'
class CreateArticleInput extends ObjectType {
title = String
content = String
authorId = Int
tags = Optional(List(String))
published = Optional(Boolean)
}
app.post('/articles', {
body: CreateArticleInput
}).use((req) => {
// req.body 已验证,类型安全
const { title, content, authorId, tags = [], published = false } = req.body
const article = createArticle({
title,
content,
authorId,
tags,
published
})
return Response.status(201).json(article)
})使用 FieldDescriptors
app.post('/login', {
body: {
username: String,
password: String,
remember: Optional(Boolean)
}
}).use((req) => {
const { username, password, remember = false } = req.body
const token = authenticate(username, password)
return Response.json({ token, remember })
})嵌套对象验证
class AddressInput extends ObjectType {
street = String
city = String
zipCode = String
}
class CreateUserInput extends ObjectType {
name = String
email = String
address = AddressInput
contacts = Optional(List(String))
}
app.post('/users', {
body: CreateUserInput
}).use((req) => {
// req.body.address 已验证为 AddressInput
const { name, email, address, contacts = [] } = req.body
return Response.status(201).json({
id: Date.now(),
name,
email,
address,
contacts
})
})自定义验证错误处理
app.post('/users', {
body: CreateUserInput
}, {
onSchemaError: (error, input, next) => {
// error.message: 错误消息
// error.path: 字段路径,如 ['body', 'email']
// error.value: 无效的值
return Response.status(400).json({
error: '数据验证失败',
field: error.path?.join('.'), // 'body.email'
message: error.message,
received: error.value,
hint: '请检查输入格式'
})
}
}).use((req) => {
return Response.status(201).json(createUser(req.body))
})请求头和 Cookies 验证
// 请求头验证
app.get('/protected', {
headers: {
authorization: String,
'x-api-key': Optional(String)
}
}).use((req) => {
const token = req.headers.authorization
const apiKey = req.headers['x-api-key']
return Response.json({ authenticated: true })
})
// Cookies 验证
app.get('/dashboard', {
cookies: {
sessionId: String,
theme: Optional(Union(Literal('light'), Literal('dark')))
}
}).use((req) => {
const { sessionId, theme = 'light' } = req.cookies
return Response.json({ sessionId, theme })
})
// 组合验证
app.post('/upload', {
headers: { 'content-type': Literal('multipart/form-data') },
cookies: { sessionId: String },
body: UploadFileInput
}).use((req) => {
return Response.json({ uploaded: true })
})响应构建
Response - 响应对象
类型定义:
type Response = {
info: ResponseInfo // 响应信息
merge: (...responses: Response[]) => Response // 合并响应
is: (...types: string[]) => string | false // 类型检查
// 响应体方法
string: (value: string) => Response
json: (value: JsonType) => Response
html: (value: string) => Response
text: (value: string) => Response
redirect: (url: string, options?: RedirectOptions) => Response
stream: (stream: Stream) => Response
file: (filename: string, options?: FileBodyOptions) => Response
buffer: (buffer: Buffer) => Response
empty: () => Response
custom: (handler: CustomBodyHandler) => Response
// 响应元数据方法
status: (code: number, message?: string) => Response
header: (name: string, value: Value) => Response
headers: (headers: Headers) => Response
cookie: (name: string, value: Value | null, options?: CookieOptions) => Response
cookies: (cookies: Record<string, Value | null>, options?: CookieOptions) => Response
type: (contentType: string) => Response
vary: (...fields: string[]) => Response
attachment: (filename?: string, options?: AttachmentOptions) => Response
}基础响应类型
JSON 响应
// 简单 JSON
Response.json({ message: 'Hello' })
// 带状态码
Response.status(201).json({ id: 1, name: 'Alice' })
// 带标头
Response.header('X-Total-Count', '100').json({ items: [] })
// 链式调用
Response
.status(200)
.header('Cache-Control', 'max-age=3600')
.header('X-API-Version', '1.0')
.json({ data: [] })文本响应
Response.text('纯文本响应')
Response.status(404).text('未找到')HTML 响应
Response.html('<h1>Hello World</h1>')
Response.status(200).html(`
<!DOCTYPE html>
<html>
<head><title>欢迎</title></head>
<body><h1>欢迎来到 farrow-http</h1></body>
</html>
`)文件响应
// 基本文件响应
Response.file('./uploads/document.pdf')
// 带内容类型
Response.file('./images/logo.png', 'image/png')
// 带流控制选项
Response.file('./large-file.zip', {
start: 0,
end: 1024 * 1024 - 1, // 只读取前 1MB
highWaterMark: 1024 * 1024 // 1MB 缓冲区
})
// 作为下载附件
Response.file('./report.pdf')
.attachment('monthly-report.pdf')
.header('Cache-Control', 'private, max-age=3600')
// 中文文件名带后备
Response.file('./数据报告.xlsx')
.attachment('数据报告.xlsx', {
fallback: 'data-report.xlsx' // 兼容旧浏览器
})
// 内联显示(浏览器中打开而非下载)
Response.file('./document.pdf')
.attachment('document.pdf', {
type: 'inline'
})流响应
import { Readable } from 'stream'
const stream = Readable.from(['Hello', ' ', 'World'])
Response.stream(stream)
// 从文件创建流
import fs from 'fs'
const fileStream = fs.createReadStream('./large-file.log')
Response.stream(fileStream)
.header('Content-Type', 'text/plain')重定向响应
// 基本重定向
Response.redirect('/login')
Response.redirect('https://example.com')
// 不使用路由前缀
Response.redirect('/path', { usePrefix: false })
// 在嵌套路由中的行为
const apiRouter = app.route('/api')
apiRouter.use(() => {
return Response.redirect('/users') // 重定向到 /api/users
})
apiRouter.use(() => {
return Response.redirect('/users', { usePrefix: false }) // 重定向到 /users
})
// 重定向到 referer
Response.redirect('back') // 重定向到 HTTP Referer 或 '/'空响应
Response.status(204).empty()
Response.status(304).empty() // Not Modified缓冲区响应
const buffer = Buffer.from('binary data')
Response.buffer(buffer)
.header('Content-Type', 'application/octet-stream')自定义响应
Response.custom(({ req, res, requestInfo, responseInfo }) => {
// req: IncomingMessage - Node.js 请求对象
// res: ServerResponse - Node.js 响应对象
// requestInfo: RequestInfo - farrow 请求信息
// responseInfo: Omit<ResponseInfo, 'body'> - farrow 响应信息(无 body)
res.statusCode = 200
res.setHeader('Content-Type', 'application/octet-stream')
res.end(Buffer.from('binary data'))
})
// 服务器推送事件(SSE)
Response.custom(({ req, res }) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
const sendEvent = (data: any) => {
res.write(`data: ${JSON.stringify(data)}\n\n`)
}
sendEvent({ message: '已连接' })
const interval = setInterval(() => {
sendEvent({ timestamp: Date.now() })
}, 1000)
req.on('close', () => clearInterval(interval))
})响应方法(可链式调用)
状态和标头
// 设置状态码
Response.status(code: number, message?: string): Response
Response.status(200)
Response.status(404, 'Not Found')
Response.status(500, 'Internal Server Error')
// 设置单个标头
Response.header(name: string, value: string): Response
Response.header('Content-Type', 'application/json')
Response.header('X-Custom-Header', 'value')
// 设置多个标头
Response.headers(headers: Record<string, string>): Response
Response.headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-API-Version': '1.0'
})
// 设置内容类型
Response.type(contentType: string): Response
Response.type('json') // application/json
Response.type('html') // text/html
Response.type('text/plain') // text/plain
Response.type('png') // image/png
// 设置 Vary 头
Response.vary(...fields: string[]): Response
Response.vary('Accept-Encoding', 'Accept-Language')Cookies
// 设置单个 Cookie
Response.cookie(
name: string,
value: Value | null,
options?: CookieOptions
): Response
Response.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
maxAge: 86400000, // 24 小时
sameSite: 'lax'
})
// 删除 Cookie
Response.cookie('sessionId', null, {
maxAge: 0
})
// 设置多个 Cookies
Response.cookies(
cookies: Record<string, Value | null>,
options?: CookieOptions
): Response
Response.cookies({
sessionId: 'abc123',
theme: 'dark',
language: 'zh-CN'
}, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
})
type CookieOptions = {
domain?: string // Cookie 域
encode?: (val: string) => string // 编码函数
expires?: Date // 过期时间
httpOnly?: boolean // 仅 HTTP 访问
maxAge?: number // 最大生存时间(毫秒)
path?: string // Cookie 路径
priority?: 'low' | 'medium' | 'high' // 优先级
sameSite?: boolean | 'lax' | 'strict' | 'none' // SameSite 策略
secure?: boolean // 需要 HTTPS
signed?: boolean // 签名 cookie
}附件
// 设置附件(下载)
Response.attachment(filename?: string, options?: AttachmentOptions): Response
Response.file('./document.pdf')
.attachment('monthly-report.pdf')
// 中文文件名带后备
Response.file('./数据报告.xlsx')
.attachment('数据报告.xlsx', {
fallback: 'data-report.xlsx'
})
// 内联显示
Response.file('./document.pdf')
.attachment('document.pdf', {
type: 'inline'
})
type AttachmentOptions = {
type?: 'attachment' | 'inline' // 附件类型
fallback?: string // 后备文件名
}响应操作
类型检查
// 检查响应内容类型
Response.is(...types: string[]): string | false
const response = Response.json({ data: 'test' })
response.is('json') // 返回 'json'
response.is('html') // 返回 false
response.is('json', 'xml') // 返回 'json'(第一个匹配)
const htmlResponse = Response.html('<h1>Hello</h1>')
htmlResponse.is('html') // 返回 'html'
htmlResponse.is('text', 'html') // 返回 'html'响应合并
// 合并多个响应
Response.merge(...responses: Response[]): Response
// ✅ 正确:空主体 + JSON 主体 = JSON 主体
const baseResponse = Response.headers({
'X-API-Version': 'v1',
'X-Request-ID': requestId
}) // 保持空主体
const dataResponse = Response.json({ users: [] })
return baseResponse.merge(dataResponse)
// 结果:有 JSON 主体和所有标头
// ⚠️ 错误顺序:JSON 主体被空主体覆盖
const result = Response.json({ users: [] })
.merge(Response.header('X-Version', 'v1'))
// 结果:只有空主体和标头,JSON 数据丢失!
// ✅ 正确顺序:空主体被 JSON 主体覆盖
const result = Response.header('X-Version', 'v1')
.merge(Response.json({ users: [] }))
// 结果:有 JSON 主体和标头
// ✅ 最佳实践:使用链式调用
const result = Response.json({ users: [] })
.header('X-Version', 'v1')
// 结果:有 JSON 主体和标头,无需担心顺序重要规则:
- 响应合并遵循后者覆盖前者原则
body字段会被完全覆盖(不是合并)status、headers、cookies会被合并(后者优先)vary数组会被追加
matchBodyType - 响应拦截中间件
函数签名:
function matchBodyType<T extends keyof BodyMap>(
type: T,
f: (body: BodyMap[T]) => MaybeAsyncResponse
): Middleware<any, MaybeAsyncResponse>说明: 创建中间件来拦截和处理特定响应主体类型。
示例:
import { matchBodyType } from 'farrow-http'
// 为所有 JSON 响应添加时间戳
app.use(matchBodyType('json', (body) => {
return Response.json({
...body.value,
timestamp: Date.now(),
version: 'v1'
})
}))
// 为所有文件响应添加缓存标头
app.use(matchBodyType('file', (body) => {
return Response.file(body.value, body.options)
.header('Cache-Control', 'public, max-age=3600')
.header('X-File-Server', 'farrow-http')
}))
// 处理字符串响应
app.use(matchBodyType('string', (body) => {
return Response.string(`[${new Date().toISOString()}] ${body.value}`)
}))
// 所有可拦截类型
type BodyMap = {
empty: EmptyBody
string: StringBody
json: JsonBody
stream: StreamBody
buffer: BufferBody
file: FileBody
redirect: RedirectBody
custom: CustomBody
}中间件系统
中间件执行模型
farrow-http 使用洋葱模型,每个中间件可以在请求处理前后执行代码:
app.use((req, next) => {
console.log('1: 进入') // 1
const result = next(req)
console.log('4: 退出') // 4
return result
})
app.use((req, next) => {
console.log('2: 进入') // 2
const result = next(req)
console.log('3: 退出') // 3
return result
})
// 执行顺序:1 → 2 → 3 → 4核心原则:
- ✅ 中间件必须返回 Response 对象
- ✅ 调用
next(req)继续到后续中间件 - ✅ 不调用
next()会中断执行链 - ✅ 支持同步和异步中间件混合使用
中间件类型
类型定义:
type HttpMiddleware = Middleware<RequestInfo, MaybeAsyncResponse>
type Middleware<I, O> = (input: I, next: Next<I, O>) => O
type Next<I, O> = (input?: I) => O
type MaybeAsyncResponse = Response | Promise<Response>中间件编写模式
1. 转换中间件
修改输入传递给下一个中间件。
app.use((req, next) => {
const transformed = {
...req,
pathname: req.pathname.toLowerCase()
}
return next(transformed)
})2. 增强中间件
处理响应结果。
app.use((req, next) => {
const response = next(req)
return response.header('X-Powered-By', 'farrow-http')
})3. 拦截中间件
条件执行。
app.use((req, next) => {
if (!validateRequest(req)) {
return Response.status(400).text('Bad Request')
}
return next(req)
})4. 包装中间件
前后处理。
app.use((req, next) => {
console.log(`开始处理: ${req.pathname}`)
const start = Date.now()
const response = next(req)
console.log(`完成处理: ${Date.now() - start}ms`)
return response
})5. 终止中间件
不调用 next,直接返回响应。
app.use((req) => {
return Response.json({ message: 'Final result' })
})全局中间件
// 日志中间件
app.use(async (req, next) => {
console.log(`${req.method} ${req.pathname}`)
const start = Date.now()
const response = await next(req)
console.log(`耗时 ${Date.now() - start}ms`)
return response
})
// CORS 中间件
app.use((req, next) => {
const response = next(req)
return response.headers({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
})
})
// 认证中间件
app.use(async (req, next) => {
if (req.pathname.startsWith('/protected')) {
const token = req.headers?.authorization
if (!token) {
throw new HttpError('需要授权', 401)
}
const user = await verifyToken(token)
UserContext.set(user)
}
return next(req)
})路由特定中间件
app.get('/protected')
.use(async (req, next) => {
const token = req.headers.authorization
if (!token) {
return Response.status(401).json({ error: '未授权' })
}
return next(req)
})
.use((req) => {
return Response.json({ message: '受保护的内容' })
})异步中间件
app.use(async (req, next) => {
const user = await authenticateUser(req)
UserContext.set(user)
return next(req)
})
// 处理异步响应
app.use(async (req, next) => {
const response = await next(req)
return response.header('X-Processed-At', Date.now().toString())
})嵌套路由器中的中间件执行顺序
// 父路由器的中间件会被子路由器继承
const apiRouter = app.route('/api')
apiRouter.use(corsMiddleware) // 所有 API 路由都有 CORS
apiRouter.use(rateLimitMiddleware) // 所有 API 路由都有限流
const v1Router = apiRouter.route('/v1')
v1Router.use(authMiddleware) // v1 路由需要认证
const userRouter = v1Router.route('/users')
userRouter.use(userValidationMiddleware) // 用户路由有额外验证
// 请求 /api/v1/users 会依次经过:
// 1. corsMiddleware
// 2. rateLimitMiddleware
// 3. authMiddleware
// 4. userValidationMiddleware
// 5. 最终处理函数上下文管理
Context 上下文系统
核心特性:
- ✅ 请求级隔离 - 每个 HTTP 请求有独立的 Context 容器
- ✅ 异步安全 - 基于 AsyncLocalStorage,在 async/await 中自动传递
- ✅ 类型安全 - 完整的 TypeScript 类型推导
- ✅ 自动启用 - farrow-http 自动启用异步追踪
创建上下文:
import { createContext } from 'farrow-pipeline'
// 创建上下文
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const DBContext = createContext<Database>(defaultDB)
type Context<T> = {
id: symbol // 唯一标识
get(): T // 获取当前值
set(value: T): void // 设置当前值
assert(): Exclude<T, undefined | null> // 断言非空
create(value: T): Context<T> // 创建新实例
}使用上下文:
// 设置上下文
app.use((req, next) => {
const user = authenticate(req)
UserContext.set(user)
const requestId = generateRequestId()
RequestIdContext.set(requestId)
return next(req)
})
// 读取上下文
app.use((req) => {
const user = UserContext.get() // User | null
const requestId = RequestIdContext.get() // string
if (!user) {
return Response.status(401).json({ error: '未授权' })
}
return Response.json({ user, requestId })
})
// 断言非空
app.use((req) => {
try {
const user = UserContext.assert() // User(不包含 null)
return Response.json({ userId: user.id })
} catch {
return Response.status(401).json({ error: '未认证' })
}
})上下文隔离示例:
const CounterContext = createContext(0)
app.use((req, next) => {
const count = CounterContext.get()
CounterContext.set(count + 1)
return next(req)
})
app.use((req) => {
const count = CounterContext.get()
return Response.json({ count })
})
// 并发请求,每个都有独立的计数器
await Promise.all([
fetch('/'), // count: 1
fetch('/'), // count: 1
fetch('/') // count: 1
])
// 每次运行都从默认值 0 开始多上下文示例:
const UserContext = createContext<User | null>(null)
const LoggerContext = createContext<Logger>(consoleLogger)
const DBContext = createContext<Database>(defaultDB)
app.use((req, next) => {
// 设置多个上下文
UserContext.set(authenticate(req))
LoggerContext.set(createRequestLogger(req))
DBContext.set(getDatabaseConnection())
return next(req)
})
app.use((req) => {
// 使用多个上下文
const user = UserContext.get()
const logger = LoggerContext.get()
const db = DBContext.get()
logger.log(`User ${user?.id} accessing resource`)
const data = db.query('SELECT * FROM resources')
return Response.json(data)
})上下文钩子
useRequestInfo()
获取当前请求信息。
import { useRequestInfo } from 'farrow-http'
app.use(() => {
const req = useRequestInfo()
console.log({
pathname: req.pathname,
method: req.method,
query: req.query,
params: req.params,
headers: req.headers,
cookies: req.cookies
})
return Response.json({ ok: true })
})useBasenames()
获取当前路由的基础路径列表。
import { useBasenames } from 'farrow-http'
const app = Http({ basenames: ['/api'] })
const v1Router = app.route('/v1')
v1Router.use(() => {
const basenames = useBasenames() // ['/api', '/v1']
return Response.json({ basenames })
})usePrefix()
获取完整的路径前缀(拼接所有 basenames)。
import { usePrefix } from 'farrow-http'
app.route('/api').route('/v1').get('/status').use(() => {
const prefix = usePrefix() // '/api/v1'
return Response.json({
prefix,
endpoint: `${prefix}/status`
})
})useRequest() 和 useReq()
获取 Node.js 原始请求对象。
import { useRequest, useReq } from 'farrow-http'
// useRequest() - 可能为 null
app.use(() => {
const req = useRequest() // IncomingMessage | null
if (req) {
console.log(req.method, req.url)
}
return Response.json({ ok: true })
})
// useReq() - 断言非空,如果为空则抛出错误
app.use(() => {
const req = useReq() // IncomingMessage(保证存在)
console.log(req.headers)
return Response.json({ ok: true })
})useResponse() 和 useRes()
获取 Node.js 原始响应对象。
import { useResponse, useRes } from 'farrow-http'
// useResponse() - 可能为 null
app.use(() => {
const res = useResponse() // ServerResponse | null
if (res) {
// 直接操作(不推荐)
}
return Response.json({ ok: true })
})
// useRes() - 断言非空
app.use(() => {
const res = useRes() // ServerResponse(保证存在)
// 保证存在
return Response.json({ ok: true })
})错误处理
HttpError 类
类型定义:
class HttpError extends Error {
constructor(message: string, public statusCode: number = 500)
}使用示例:
import { HttpError } from 'farrow-http'
// 抛出 HTTP 错误
app.use((req, next) => {
if (!isValidRequest(req)) {
throw new HttpError('错误请求', 400)
}
return next(req)
})
// 自定义错误类
class AuthenticationError extends HttpError {
constructor(message = '需要认证') {
super(message, 401)
}
}
class ValidationError extends HttpError {
constructor(message: string, public field?: string) {
super(message, 400)
this.name = 'ValidationError'
}
}
// 使用自定义错误
app.use((req, next) => {
if (!req.headers.authorization) {
throw new AuthenticationError()
}
if (!validateInput(req.body)) {
throw new ValidationError('Invalid email', 'email')
}
return next(req)
})全局错误处理
// 全局错误处理器
app.use(async (req, next) => {
try {
return await next(req)
} catch (error) {
console.error('请求失败:', {
method: req.method,
url: req.pathname,
error: error.message
})
if (error instanceof ValidationError) {
return Response.status(error.statusCode).json({
error: error.message,
field: error.field,
type: 'validation_error'
})
}
if (error instanceof HttpError) {
return Response.status(error.statusCode).json({
error: error.message,
type: 'http_error'
})
}
return Response.status(500).json({
error: '内部服务器错误',
type: 'server_error'
})
}
})自动错误处理
框架自动捕获中间件中的错误:
// 同步错误
app.use(() => {
throw new Error('出了问题')
// 自动转换为 500 响应
})
// 异步错误
app.use(async () => {
const data = await fetchExternalAPI()
return Response.json(data)
// Promise 异常会被自动捕获
})验证错误处理
app.post('/users', {
body: CreateUserInput
}, {
onSchemaError: (error, input, next) => {
// error.message: 验证错误消息
// error.path: 字段路径,如 ['body', 'email']
// error.value: 无效的值
return Response.status(400).json({
error: '验证失败',
field: error.path?.join('.'),
message: error.message,
received: error.value
})
}
}).use((req) => {
return Response.status(201).json(createUser(req.body))
})错误堆栈控制
const app = Http({
// 开发环境显示完整堆栈
errorStack: process.env.NODE_ENV === 'development'
})工具函数
ParseUrl - URL 模式类型解析
类型定义:
type ParseUrl<T extends string> = {
pathname: string
params: ParseData<ParsePathname<T>>
query: ParseData<ParseQueryString<T>>
}说明: 从 URL 模式字符串中提取 TypeScript 类型。
示例:
import { ParseUrl } from 'farrow-http'
type T1 = ParseUrl<'/user/<id:int>'>
// {
// pathname: string
// params: { id: number }
// query: {}
// }
type T2 = ParseUrl<'/posts?<sort:asc|desc>&<page?:int>'>
// {
// pathname: string
// params: {}
// query: { sort: 'asc' | 'desc', page?: number }
// }
type T3 = ParseUrl<'/<category:string>/<id:int>?<expand?:string>'>
// {
// pathname: string
// params: { category: string, id: number }
// query: { expand?: string }
// }MarkReadOnlyDeep - 深度只读类型
类型定义:
type MarkReadOnlyDeep<T> = T extends {} | any[]
? { readonly [key in keyof T]: MarkReadOnlyDeep<T[key]> }
: T说明: 递归地将对象的所有属性标记为只读。
最佳实践
依赖注入模式
使用 Context 实现依赖注入,便于测试和解耦。
// services/database.ts
import { createContext } from 'farrow-pipeline'
export interface Database {
query: (sql: string) => Promise<any>
}
export const DatabaseContext = createContext<Database>(null as any)
// app.ts
import { createDatabaseConnection } from './db'
const app = Http({
contexts: ({ req }) => {
// 为每个请求注入依赖
return {
db: DatabaseContext.create(createDatabaseConnection())
}
}
})
// routes/users.ts
export const userRouter = Router()
userRouter.get('/<id:int>').use(async (req) => {
const db = DatabaseContext.assert()
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`)
return Response.json(user)
})测试时注入 Mock:
import { createContainer } from 'farrow-pipeline'
test('GET /users/:id', async () => {
const mockDB = {
query: jest.fn().mockResolvedValue({ id: 1, name: 'Test' })
}
const testContainer = createContainer({
db: DatabaseContext.create(mockDB)
})
const app = Http()
app.route('/users').use(userRouter)
const result = await app.run(
{ pathname: '/users/1', method: 'GET' },
{ container: testContainer }
)
expect(mockDB.query).toHaveBeenCalled()
})测试策略
单元测试(使用 supertest)
import { Http } from 'farrow-http'
import request from 'supertest'
const app = Http()
app.get('/health').use(() => Response.json({ status: 'ok' }))
const server = app.server()
test('health check', async () => {
await request(server)
.get('/health')
.expect(200)
.expect({ status: 'ok' })
})集成测试(注入依赖)
test('create user with mock database', async () => {
const mockDB = { save: jest.fn() }
const container = createContainer({
db: DatabaseContext.create(mockDB)
})
// Router() 返回 RouterPipeline,继承自 AsyncPipeline,有 run 方法
const response = await userRouter.run(
{
pathname: '/users',
method: 'POST',
body: { name: 'Alice' }
},
{ container }
)
expect(mockDB.save).toHaveBeenCalledWith({ name: 'Alice' })
})总结
farrow-http 是一个类型驱动的 Web 框架,核心优势:
- 类型安全 - 编译期和运行时双重类型保障
- 自动验证 - URL 模式自动推导类型并验证
- 函数式 - 基于 farrow-pipeline 的纯函数中间件
- 上下文隔离 - 基于 AsyncLocalStorage 的请求级隔离
- 易于测试 - 清晰的接口和依赖注入
- 高性能 - 轻量级实现,零运行时开销
通过这些特性,farrow-http 能够帮助你构建类型安全、可维护、高性能的 Web 应用程序。
相关文档:
