chi 路由器:为 Go HTTP 服务打造的轻量、惯用且可组合的 REST 路由库(以 inngest 项目为例)

发布时间:2026/9/18 4:05:49
chi 路由器:为 Go HTTP 服务打造的轻量、惯用且可组合的 REST 路由库(以 inngest 项目为例) chi 路由器为 Go HTTP 服务打造的轻量、惯用且可组合的 REST 路由库以 inngest 项目为例【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest导读chi 是一个建立在 Go 标准库net/http之上的轻量级、惯用且高度可组合的路由器它特别擅长帮助你构建能够随项目增长而持续保持可维护性的大型 REST API 服务。在 inngest 项目中chi 被广泛用于组织事件 API、GraphQL 服务、dev server 调试接口以及 Connect 网关等模块的路由结构。读完本文你将掌握 chi 的核心路由 API、中间件体系、URL 参数机制以及如何像 inngest 一样用Route、Group、Mount等组合方式组织大型服务的路由树。为什么需要 chi设计动机与核心理念chi 项目诞生于 Pressly API 服务的开发过程中其公共 API 服务支撑了所有客户端应用。在开发过程中作者 Peter Kieltyka 希望通过 chi 寻求一种优雅、舒适的方式来编写 REST API 服务器。chi 设计的核心考量包括项目结构与可维护性将大型系统解构为许多小部分而不是堆积在一个巨大的 handler 文件中标准库优先完全兼容net/http可以使用生态系统中任何兼容net/http的 http 或 middleware 包开发者生产力路由声明直观、易读基于 Go 1.7 引入的context包处理跨 handler 链的信号传递、取消和请求作用域值chi 的核心路由器非常小约 1000 行代码以内但它内置了可选的子包middleware中间件、render响应渲染和docgen路由文档生成。chi 不依赖任何第三方包纯用 Go 标准库实现这是它能够在 inngest 这类大型 Go 项目中被放心采用的重要前提。安装与快速开始安装 chi 非常简单只需要一条命令go get -u github.com/go-chi/chi/v5在 inngest 项目中chi 以 vendor 形式存在于 vendor/github.com/go-chi/chi/v5包含chi.go路由核心、mux.goMux 实现、tree.go路由树、context.go路由上下文与 URL 参数以及chain.go中间件链组合等文件其导入路径为github.com/go-chi/chi/v5。一个最小可用的 chi 服务长这样package main import ( net/http github.com/go-chi/chi/v5 github.com/go-chi/chi/v5/middleware ) func main() { r : chi.NewRouter() r.Use(middleware.Logger) r.Get(/, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(welcome)) }) http.ListenAndServe(:3000, r) }chi.NewRouter()返回一个实现Router接口的*Mux对象见 chi.go 中NewRouter的实现它可以作为http.ListenAndServe的 handler 直接使用——这体现了 chi 100% 兼容 net/http 的设计原则。Router 接口路由的核心抽象chi 的路由器基于一种 Patricia Radix trie基数树数据结构实现完全兼容net/http。树之上是Router接口定义了路由的核心方法见 chi.gotype Router interface { http.Handler Routes // Use appends one or more middlewares onto the Router stack. Use(middlewares ...func(http.Handler) http.Handler) // With adds inline middlewares for an endpoint handler. With(middlewares ...func(http.Handler) http.Handler) Router // Group adds a new inline-Router along the current routing // path, with a fresh middleware stack for the inline-Router. Group(fn func(r Router)) Router // Route mounts a sub-Router along a pattern string. Route(pattern string, fn func(r Router)) Router // Mount attaches another http.Handler along ./pattern/* Mount(pattern string, h http.Handler) // Handle and HandleFunc adds routes for pattern that matches // all HTTP methods. Handle(pattern string, h http.Handler) HandleFunc(pattern string, h http.HandlerFunc) // Method and MethodFunc adds routes for pattern that matches // the method HTTP method. Method(method, pattern string, h http.Handler) MethodFunc(method, pattern string, h http.HandlerFunc) // HTTP-method routing along pattern Connect(pattern string, h http.HandlerFunc) Delete(pattern string, h http.HandlerFunc) Get(pattern string, h http.HandlerFunc) Head(pattern string, h http.HandlerFunc) Options(pattern string, h http.HandlerFunc) Patch(pattern string, h http.HandlerFunc) Post(pattern string, h http.HandlerFunc) Put(pattern string, h http.HandlerFunc) Query(pattern string, h http.HandlerFunc) Trace(pattern string, h http.HandlerFunc) // NotFound defines a handler to respond whenever a route could // not be found. NotFound(h http.HandlerFunc) // MethodNotAllowed defines a handler to respond whenever a method is // not allowed. MethodNotAllowed(h http.HandlerFunc) }接口中每个路由方法都接受一个 URLpattern和一个 handler 链。URL pattern 支持命名参数如/users/{userID}和通配符如/admin/*。运行时可以通过chi.URLParam(r, userID)获取命名参数通过chi.URLParam(r, *)获取通配符参数。此外还有Routes接口chi.go它提供Routes() []Route返回易于遍历的路由树结构Middlewares() Middlewares返回路由器当前使用的中间件列表Match(rctx *Context, method, path string) bool在路由树中查找匹配的 handler但不执行 handler相当于只路由不执行Find(...)查找匹配的 pattern这个接口正是docgen子包生成路由文档的基础——它可以遍历路由树把每个路由节点、中间件链渲染成 JSON 或 Markdown 文档。中间件体系一切皆是标准 net/httpchi 的中间件就是标准的net/http中间件 handler没有任何特殊之处。这意味着路由器以及所有工具都设计为与社区中任何中间件兼容和友好这提供了更好的可扩展性和包复用能力也正是 chi 的核心宗旨。一个典型的中间件示例——把用户标识写入请求上下文// HTTP middleware setting a value on the request context func MyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // create new context from r request context, and assign key user // to value of 123 ctx : context.WithValue(r.Context(), user, 123) // call the next handler in the chain, passing the response writer and // the updated request object with the new context value. next.ServeHTTP(w, r.WithContext(ctx)) }) }对应的 handler 从请求上下文中读取数据// HTTP handler accessing data from the request context. func MyRequestHandler(w http.ResponseWriter, r *http.Request) { // here we read from the request context and fetch out user key set in // the MyMiddleware example above. user : r.Context().Value(user).(string) // respond to the client w.Write([]byte(fmt.Sprintf(hi %s, user))) }chi 通过chain.go中的Chain类型把多个中间件组合成一条链Use方法会将这些中间件顺序应用到路由上。在 inngest 中这种标准中间件模式被大量运用例如 pkg/coreapi/coreapi.go 中一次性挂载了cors.Handler、headers.StaticHeadersMiddleware和loader.Middleware三个中间件。核心中间件速查chi 自带的middleware包提供了一套标准的net/http中间件源码见 vendor/github.com/go-chi/chi/v5/middleware中间件作用AllowContentEncoding强制请求 Content-Encoding 头白名单AllowContentType显式白名单接受哪些请求 Content-TypeBasicAuth基础 HTTP 认证Compress为接受压缩响应的客户端提供 Gzip 压缩ContentCharset确保请求 Content-Type 头的字符集CleanPath清理请求路径中的双斜杠GetHead自动将未定义的 HEAD 请求路由到 GET handlerHeartbeat监控端点检查服务器的脉搏Logger记录每个请求的开始与结束及耗时NoCache设置响应头防止客户端缓存Profiler轻松将 net/http/pprof 附加到路由上ClientIPFromHeader从可信的单 IP 头X-Real-IP、CF-Connecting-IP 等捕获客户端 IPClientIPFromXFF从 X-Forwarded-For 捕获客户端 IP跳过列出的可信 CIDR 前缀ClientIPFromXFFTrustedProxies给定固定数量的可信代理从 X-Forwarded-For 捕获客户端 IPClientIPFromRemoteAddr从 TCP RemoteAddr 捕获客户端 IP服务器直接暴露在公网时RealIP已弃用——易受 IP 欺骗攻击改用 ClientIPFromXFF 或其他 ClientIPFrom* 中间件Recoverer优雅吸收 panic 并打印堆栈跟踪RequestID为每个请求注入请求 ID 到上下文RedirectSlashes重定向路由路径上的斜杠RouteHeaders针对请求头的路由处理SetHeader设置响应头键/值的快捷中间件StripSlashes去除路由路径上的斜杠Sunset向响应设置 Deprecation/Sunset 头Throttle对并发请求数量设置上限Timeout超时截止时间到达时向请求上下文发出信号URLFormat从 URL 解析扩展名并放到请求上下文WithValue在请求上下文设置键/值的快捷中间件在 inngest 中middleware.Recoverer被用于事件 API 和 dev server确保某个 handler panic 不会导致整个进程崩溃。inngest 还利用了 chi 生态的第三方包——如 pkg/api/api.go 使用github.com/go-chi/cors配置 CORS 策略允许任意来源、特定 HTTP 方法和暴露Link、Mcp-Session-Id头。选择正确的 ClientIP 中间件旧版RealIP中间件已弃用——它易受 IP 欺骗攻击相关安全公告GHSA-3fxj-6jh8-hvhx、GHSA-rjr7-jggh-pgcp、GHSA-9g5q-2w5x-hmxf并且会修改r.RemoteAddr。应改用四个ClientIPFrom*中间件之一根据你的网络部署选择恰好一个并通过GetClientIP返回 string或GetClientIPAddr返回netip.Addr读取结果你的部署场景使用直接暴露在公网无代理middleware.ClientIPFromRemoteAddr位于 nginxX-Real-IP、CloudflareCF-Connecting-IP、ApacheX-Client-IP之后middleware.ClientIPFromHeader(your-trusted-header)位于一个或多个你能列出 IP 范围的代理之后middleware.ClientIPFromXFF(10.0.0.0/8, ...)位于已知固定数量、动态 IP 的代理之后middleware.ClientIPFromXFFTrustedProxies(2)r : chi.NewRouter() r.Use(middleware.RequestID) // Pick exactly one. Examples for common deployments: // Direct internet exposure (no proxy): // r.Use(middleware.ClientIPFromRemoteAddr) // Behind Cloudflare: // r.Use(middleware.ClientIPFromHeader(CF-Connecting-IP)) // Behind AWS CloudFront (or any proxy fleet with known CIDRs): r.Use(middleware.ClientIPFromXFF( 13.32.0.0/15, // CloudFront IPv4 52.46.0.0/18, // CloudFront IPv4 2600:9000::/28, // CloudFront IPv6 )) // Behind a known number of proxies with dynamic IPs: // r.Use(middleware.ClientIPFromXFFTrustedProxies(2)) r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Get(/, func(w http.ResponseWriter, r *http.Request) { clientIP : middleware.GetClientIP(r.Context()) // for logs, rate-limit keys, etc. _ clientIP })这些中间件从不修改r.RemoteAddr。它们会在请求上下文中存储一个规范化的netip.Addr——IPv4 映射的 IPv6::ffff:a.b.c.d会被折叠为普通 IPv4头部中携带的 IPv6 zone 标识符会被剥离因此一个逻辑客户端对应一个规范化的键可用于日志、限流和 ACL。扩展中间件与包chi 的 GitHub 组织下还有一系列扩展包其中最常用的包括包描述cors跨域资源共享CORSdocgen运行时打印 chi.Router 路由jwtauthJWT 认证hostrouter基于域名/主机名路由httplog小而强大的结构化 HTTP 请求日志httprateHTTP 请求限流器httptracerHTTP 请求性能追踪库httpvcr为外部源编写确定性测试stampedeHTTP 请求合并器URL 参数与请求处理chi 的路由器会把 URL 参数解析后直接存放到请求上下文routing context中。这个机制实现在 context.goContext结构体通过URLParams保存路由参数栈URLParam方法会从后向前查找匹配的键子路由的参数会覆盖父路由的同名参数见 context.go。chi.URLParam(r, key)正是通过RouteContext(r.Context())取出路由上下文后读取参数。// HTTP handler accessing the url routing parameters. func MyRequestHandler(w http.ResponseWriter, r *http.Request) { // fetch the url parameter userID from the request of a matching // routing pattern. An example routing pattern could be: /users/{userID} userID : chi.URLParam(r, userID) // fetch key from the request context ctx : r.Context() key : ctx.Value(key).(string) // respond to the client w.Write([]byte(fmt.Sprintf(hi %v, %v, userID, key))) }URL 参数的匹配规则见 chi.go 的包级文档简单的命名占位符{name}匹配到下一个/或 URL 末尾之前的任意字符序列。路径尾部斜杠需要显式处理带冒号的占位符支持正则表达式匹配例如{number:\d}。正则语法是 Go 标准 regexp 的 RE2 语法但/永远不会被匹配允许匿名正则模式如{:\d}特殊的星号占位符*匹配请求 URL 的剩余部分是唯一会匹配/字符的占位符一些示例/user/{name} matches /user/jsmith but not /user/jsmith/info or /user/jsmith/ /user/{name}/info matches /user/jsmith/info /page/* matches /page/intro/latest /page/{other}/latest also matches /page/intro/latest /date/{yyyy:\d\d\d\d}/{mm:\d\d}/{dd:\d\d} matches /date/2017/04/01组合式路由Group、Route 与 Mountchi 最强大的特性之一是组合式路由设计。通过Group、Route、Mount和With可以把大型 API 解构成多层子路由器每层有独立的中间件栈和路径前缀。Use在路由栈上追加一个或多个中间件影响当前路由器的所有子路由With为某个端点 handler 添加内联中间件不污染其他路由Group沿着当前路由路径添加一个内联路由器拥有全新的中间件栈Route沿着pattern字符串挂载一个子路由器Mount将另一个http.Handler附加到./pattern/*路径上REST 路由预览下面是一个完整的 REST 风格路由示例展示了 chi 的各种特性取自 chi README路由声明模式与 inngest 的 API 组织方式完全一致func main() { r : chi.NewRouter() // A good base middleware stack r.Use(middleware.RequestID) r.Use(middleware.ClientIPFromRemoteAddr) // pick one ClientIPFrom* based on your infra, see below r.Use(middleware.Logger) r.Use(middleware.Recoverer) // Set a timeout value on the request context (ctx), that will signal // through ctx.Done() that the request has timed out and further // processing should be stopped. r.Use(middleware.Timeout(60 * time.Second)) r.Get(/, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(hi)) }) // RESTy routes for articles resource r.Route(/articles, func(r chi.Router) { r.With(paginate).Get(/, listArticles) // GET /articles r.With(paginate).Get(/{month}-{day}-{year}, listArticlesByDate) // GET /articles/01-16-2017 r.Post(/, createArticle) // POST /articles r.Get(/search, searchArticles) // GET /articles/search // Regexp url parameters: r.Get(/{articleSlug:[a-z-]}, getArticleBySlug) // GET /articles/home-is-toronto // Subrouters: r.Route(/{articleID}, func(r chi.Router) { r.Use(ArticleCtx) r.Get(/, getArticle) // GET /articles/123 r.Put(/, updateArticle) // PUT /articles/123 r.Delete(/, deleteArticle) // DELETE /articles/123 }) }) // Mount the admin sub-router r.Mount(/admin, adminRouter()) http.ListenAndServe(:3333, r) } func ArticleCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { articleID : chi.URLParam(r, articleID) article, err : dbGetArticle(articleID) if err ! nil { http.Error(w, http.StatusText(404), 404) return } ctx : context.WithValue(r.Context(), article, article) next.ServeHTTP(w, r.WithContext(ctx)) }) } func getArticle(w http.ResponseWriter, r *http.Request) { ctx : r.Context() article, ok : ctx.Value(article).(*Article) if !ok { http.Error(w, http.StatusText(422), 422) return } w.Write([]byte(fmt.Sprintf(title:%s, article.Title))) } // A completely separate router for administrator routes func adminRouter() http.Handler { r : chi.NewRouter() r.Use(AdminOnly) r.Get(/, adminIndex) r.Get(/accounts, adminListAccounts) return r } func AdminOnly(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx : r.Context() perm, ok : ctx.Value(acl.permission).(YourPermissionType) if !ok || !perm.IsAdmin() { http.Error(w, http.StatusText(403), 403) return } next.ServeHTTP(w, r) }) }实战chi 在 inngest 项目中的应用inngest 是工作流编排平台其 Go 服务端大量使用 chi 组织 HTTP 层。下面是几个真实的应用场景可以帮助你理解 chi 在大型项目中的组织方式。1. 事件 API全局中间件 参数化路由pkg/api/api.go 中NewAPI创建了核心事件 APIapi : API{ Router: chi.NewMux(), config: o.Config, handler: o.EventHandler, ... } cors : cors.New(cors.Options{...}) api.Use(cors.Handler) api.Use(headers.StaticHeadersMiddleware(o.Config.GetServerKind())) api.Get(HealthPath, api.HealthCheck) api.Post(/e/{key}, api.ReceiveEvent) api.Post(/invoke/{slug}, api.Invoke)这里演示了 chi 的两个关键用法全局Use挂载 CORS 与静态响应头中间件对所有路由生效路由参数{key}、{slug}直接在 handler 中通过chi.URLParam(r, key)/chi.URLParam(r, slug)读取见 pkg/api/api.go 与 pkg/api/api.goAPI结构体直接内嵌chi.Router接口然后以Handler: a.Router的方式传给http.Server启动pkg/api/api.go——这正是 chi 路由器即 http.Handler 的直接体现。2. Dev ServerGroup 分层 鉴权隔离 SPA 回退pkg/devserver/api.go 展示了更复杂的组合式用法func NewDevAPI(d *devserver, o DevAPIOptions) chi.Router { api : devapi{ Router: chi.NewMux(), devserver: d, disableUI: o.disableUI, } api.addRoutes(o.AuthMiddleware) return api }addRoutes中先用Use挂载日志注入中间件和静态响应头中间件然后a.Post(/dev/traces, a.OTLPTrace) // Intentionally outside the AuthMiddleware a.Group(func(r chi.Router) { r.Use(AuthMiddleware) r.Get(/dev, a.Info) r.Post(/fn/register, a.Register) r.Delete(/fn/remove, a.RemoveApp) ... })这里的关键设计用Group创建独立的中间件栈把需要鉴权的路由函数注册、移除、步数限制设置等与公开路由OTLP trace 上报隔离完全复用同一路由器。dev server 还演示了通配符与静态文件服务、NotFound回退的配合staticFS, _ : fs.Sub(static, static/client) a.Get(/images/*, http.FileServer(http.FS(staticFS)).ServeHTTP) a.Get(/assets/*, http.FileServer(http.FS(staticFS)).ServeHTTP) ... // Everything else loads the UI (SPA fallback) a.NotFound(a.UI)/images/*使用星号通配符匹配所有静态资源路径而NotFound(a.UI)把未匹配到的所有路径回退到前端 SPA 入口实现单页应用的路由回退——这是 chiNotFound钩子的典型应用场景。3. API v1Route 前缀 Mount 挂载子服务pkg/api/apiv1/apiv1.go 展示了GroupRouteMount的多层嵌套a.Group(func(r chi.Router) { r.Use(middleware.Recoverer) // 实时 API仅在配置了 JWT 密钥时启用 r.Group(func(r chi.Router) { rt : realtime.NewAPI(...) r.Mount(/, rt) }) // 带鉴权与缓存的业务路由组 r.Group(func(r chi.Router) { if a.opts.AuthMiddleware ! nil { r.Use(a.opts.AuthMiddleware) } if a.opts.CachingMiddleware ! nil { r.Use(a.opts.CachingMiddleware.Middleware) } ... r.Post(/signals, a.receiveSignal) r.Get(/events, a.getEvents) r.Get(/events/{eventID}, a.getEvent) r.Get(/runs/{runID}, a.GetFunctionRun) r.Delete(/runs/{runID}, a.cancelFunctionRun) r.Get(/apps/{appName}/functions, a.GetAppFunctions) ... }) })可以看到r.Get(/events/{eventID}/runs, a.getEventRuns)这类多级 REST 资源路径配合chi.URLParam在 handler 中提取eventID是 chi 参数化路由在真实生产代码中的标准写法。4. Core API 与 Connect 网关pkg/coreapi/coreapi.go 展示了Handle匹配所有 HTTP 方法、With内联中间件和Mount挂载独立子服务的组合// GraphQL playground 与 gql 服务 a.Handle(/, playground.Handler(GraphQL playground, /v0/gql)) a.Handle(/gql, srv) // V0 APIs a.With(o.AuthMiddleware).Delete(/runs/{runID}, a.CancelRun) a.With(o.AuthMiddleware).Get(/runs/{runID}/batch, a.GetEventBatch) a.With(o.AuthMiddleware).Get(/runs/{runID}/actions, a.GetActions) a.With(o.AuthMiddleware).Post(/telemetry, a.TrackEvent) // 挂载 Connect REST 子服务 a.With(o.AuthMiddleware).Mount(/connect, connectv0.New(a, o.ConnectOpts))而 pkg/connect/rest/v0/v0.go 中 Connect 服务内部同样使用 chi 的Group和Use组织 worker 管理接口cr.Group(func(r chi.Router) { r.Use(middleware.Recoverer) r.Use(headers.ContentTypeJsonResponse()) r.Get(/envs/{envID}/conns, cr.showConnections) r.Get(/envs/{envID}/groups/{groupID}, cr.showWorkerGroup) }) // Worker API cr.Group(func(r chi.Router) { r.Post(/start, cr.start) r.Post(/flush, cr.flushBuffer) })这正是 chi 组合式设计的威力每个模块事件 API、dev server、GraphQL、Connect都返回一个 chi.Router 或 http.Handler再通过Mount组装成完整的服务树模块之间完全解耦。关于 context 包chi 建立在 Go 1.7 引入的标准库context包之上。context是一个小包提供跨调用栈和 goroutine 传递信号的简单接口最初由 Sameer Ajmani 编写。chi 的Timeout中间件就是利用context.WithTimeout实现的超时截止时间到达时通过ctx.Done()向 handler 链发送取消信号后续处理应停止。chi 的Context结构体context.go本身也实现了context.Context接口通过内嵌parentCtx优化节省 1 次内存分配从而把路由信息URL 参数、匹配的 pattern 栈直接嵌入请求上下文中这是chi.URLParam、RoutePattern()等方法能够工作的底层原理。性能基准chi 在 Go 官方 benchmark 套件go-http-routing-benchmark中有公开结果。以下数据基于 2020 年 11 月 29 日、Go 1.5.5 之前README 标注为 Go 1.15.5、Linux AMD 3950x 平台的测量BenchmarkChi_Param 3075895 384 ns/op 400 B/op 2 allocs/op BenchmarkChi_Param5 2116603 566 ns/op 400 B/op 2 allocs/op BenchmarkChi_Param20 964117 1227 ns/op 400 B/op 2 allocs/op BenchmarkChi_GithubStatic 3045488 395 ns/op 400 B/op 2 allocs/op BenchmarkChi_GithubParam 2204115 540 ns/op 400 B/op 2 allocs/op BenchmarkChi_GithubAll 10000 113811 ns/op 81203 B/op 406 allocs/op BenchmarkChi_GPlusStatic 3337485 359 ns/op 400 B/op 2 allocs/op BenchmarkChi_GPlusAll 194220 5950 ns/op 5200 B/op 26 allocs/op BenchmarkChi_ParseStatic 3365324 356 ns/op 400 B/op 2 allocs/op BenchmarkChi_ParseAll 109567 11295 ns/op 10400 B/op 52 allocs/op BenchmarkChi_StaticAll 16846 71308 ns/op 62802 B/op 314 allocs/op注意benchmark 中的 allocs 来自http.Request.WithContext(context.Context)方法的调用它会克隆http.Request、在新请求上设置Context()并返回新对象——这只是 Go 中在请求上设置 context 的工作方式并非路由器的额外开销。版本与兼容性从 v5 开始chi 支持go.mod模块化Go 版本要求与发布策略见仓库的 CHANGELOGvendor/github.com/go-chi/chi/v5/CHANGELOG.md包文档声明 chi 支持 Go 最近的四个大版本见 chi.go 的包注释许可协议为 MITvendor/github.com/go-chi/chi/v5/LICENSE总结chi 的设计哲学可以概括为路由器只是把请求处理分解成许多更小层的工具。它不绑定任何特定框架或 ORM中间件就是标准net/http路由就是可组合的Router接口。从 inngest 的实际应用可以看到轻量无依赖纯标准库实现核心约 1000 行便于审计与维护完全兼容 net/http可自由混用社区中间件Router本身就是http.Handler组合式设计Use、With、Group、Route、Mount五件套支撑从单体服务到多模块大型系统的路由组织context 优先URL 参数、请求 ID、超时、用户身份等请求作用域数据都通过context.Context传递贯穿整个 handler 链无论你是要构建一个小型 API 服务还是要像 inngest 一样组织包含事件摄入、GraphQL、调试接口和网关的多模块系统chi 的小而美哲学都能让你的路由代码保持清晰、可维护、可测试。【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考