
unkey 的 kitchensink一个纯 Go 标准库实现的平台功能探针 HTTP 服务【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkeykitchensink是 unkey 仓库中一个刻意保持“最小化”的 Go 服务它只依赖 Go 标准库把平台各项能力网关策略、部署路由、环境变量注入、请求头透传、日志采集等各自封装成独立子包每个子包就是一个端到端的“探针probe”。本文将以 svc/kitchensink/README.md 为主线结合 main.go 及全部探针子包的源码实现完整讲解它的设计约束、路由清单、配置方式、扩展方法、运行方式以及每个探针背后真正验证的平台能力。读完本文你将掌握如何运行、探测和扩展这个服务也能理解它如何被当作 unkey 平台各类网关与部署特性的“活文档”。kitchensink 是什么探针即示例Stdlib-only HTTP server. Each probe lives in its own subpackage and demonstrates one platform feature end-to-end — gateway policies, deployment routing, env injection, header propagation, and so on.kitchensink 是一个仅使用 Go 标准库的 HTTP 服务器。它的核心理念是每个探针probe独立成包端到端演示一项平台能力——网关策略gateway policies、部署路由deployment routing、环境变量注入env injection、请求头透传header propagation等。这些探针同时承担“可运行的工作示例worked example”角色工程师阅读任意一个子包就能获得一份完整、可直接复制粘贴的集成示例了解如何接入对应能力而无需在仓库里追着 import 到处跑。换句话说kitchensink 的代码本身就是写给平台用户的教程。这个定位在入口文件的注释里同样明确——main.go 开头写道Command kitchensink runs a stdlib-only HTTP server that exposes every probe subpackage as a real HTTP endpoint.kitchensink 命令运行一个纯标准库 HTTP 服务器把每个探针子包暴露为真实 HTTP 端点。三条硬性约束标准库、无状态、小而精README 为 kitchensink 定义了三条不可逾越的约束这也决定了它的代码风格与架构边界仅用 Go 标准库Go standard library only。不允许任何第三方依赖。唯一被允许的本地 import 是internal/httpx用于平凡的响应拼装例如httpx.JSON它被放在internal/目录下从而保持在“工作示例”的展示面之外。同时探针之间不允许互相 importNo cross-probe imports。无状态Stateless。不使用数据库、不使用缓存、不存活过单个请求的 goroutine。每个 handler 都是“请求的纯函数”。小Small。如果一个探针膨胀到约 50 行以上它大概率应该被拆成独立服务而不是继续塞在 kitchensink 的某个角落。这三条约束在代码中体现得相当彻底。以internal/httpx为例httpx.go 的包注释明确解释了它存在的意义让每个探针的 handler 专注于自己演示的能力——一次辅助调用胜过四行响应头拼装同时放在internal/下让读者看到的探针代码是“标准库调用 一个很小的本地 import”而不是一层庞大的工具库。httpx目前只提供唯一一个函数// JSON writes v as indented JSON with the given status code and the // application/json Content-Type. func JSON(w http.ResponseWriter, status int, v any) { w.Header().Set(Content-Type, application/json) w.WriteHeader(status) enc : json.NewEncoder(w) enc.SetIndent(, ) _ enc.Encode(v) }它负责“以指定状态码写出带缩进的 JSON并设置application/json的 Content-Type”——这是绝大多数探针的公共响应方式。配置只有环境变量没有 CLI 标志kitchensink只通过环境变量配置不提供任何 CLI 标志。README 对此的解释是部署Deployments负责注入环境变量而这正是 kitchensink 存在要验证的契约contract——如果环境变量注入链路出了问题这个服务第一时间就能暴露出来。目前唯一的配置项是环境变量作用默认值PORT监听端口8080这一点在 main.go 中体现为很直接的三行逻辑port : os.Getenv(PORT) if port { port 8080 } addr : : port目录布局与完整路由清单README 给出的目录布局与探针清单如下svc/kitchensink/ ├── main.go — wires methodpath → handler ├── hello/ — GET /hello, smoke test ├── env/ — GET /env, process environment ├── buildinfo/ — GET /buildinfo, value injected via -ldflags -X ├── principal/ — GET /principal, decodes X-Unkey-Principal ├── headers/ — GET /headers, echoes request headers ├── echo/ — POST /echo, echoes body verbatim ├── logs/ — POST /log, logs body at INFO ├── status/ — GET /status/{code}, returns arbitrary status └── sleep/ — GET /sleep?dduration, blocks before responding结合源码实际目录中还存在两个 README 布局里未展开的成员index/GET /路由清单页和internal/httpx/公共响应辅助。真实目录结构以 svc/kitchensink 下的文件为准。所有路由在 main.go 的routes切片中集中注册注册表本身就是一份“端点 → 用途”的对照表方法与路径作用对应子包GET /列出全部已注册路由indexGET /hello冒烟测试返回hello, worldhelloGET /env以 JSON 返回进程环境可用?prefix过滤envGET /buildinfo返回经-ldflags -X注入的构建期版本号buildinfoGET /principal解码X-Unkey-Principal头并返回principalGET /headers以 JSON 回显收到的请求头headersPOST /echo原样返回请求体echoPOST /log以 INFO 级别记录请求体并回显logsGET /status/{code}返回任意指定 HTTP 状态码statusGET /sleep按?dduration阻塞后响应支持取消sleep启动装配与优雅退出main.go 展示了完整的启动流程其中值得注意的工程细节包括JSON 结构化日志slog.New(slog.NewJSONHandler(os.Stdout, nil))并slog.SetDefault所有探针如logs通过全局slog记录日志统一输出为 JSON 到 stdout便于平台日志采集器log aggregator拾取。路由注册即索引mux.HandleFunc(rt.Pattern, rt.Fn)的同时调用index.Register(rt.Pattern, rt.Description)把每个路由登记到GET /的自述页上。超时保护http.Server设置了ReadHeaderTimeout: 10 * time.Second防止慢速请求头攻击或僵尸连接拖垮服务。优雅退出监听os.Interrupt与syscall.SIGTERM收到信号后以 5 秒超时的srv.Shutdown(ctx)完成优雅下线配合容器的停止流程。自述页的实现index 探针index是唯一需要知道“有哪些路由”的探针因此它与其他探针不同main.go在启动时为每个路由调用一次Register再把Handler挂载到根路径。其实现见 index/handler.goRegister用strings.Cut(pattern, )把形如GET /hello的 ServeMux 模式拆成方法与路径Handler则按路径排序后以纯文本渲染出带分隔线的清单页。逐个探针深入每个端点验证什么下面结合每个子包的源码说明各探针的具体行为以及它在平台中对应的验证场景。GET /hello —— 最小可用形态与冒烟测试hello/handler.go 是整个仓库最简单的探针也是“新增探针的参考形状”一个常量200 OK响应Content-Type 为text/plain; charsetutf-8正文是hello, world\n。// Handler writes hello, world as text/plain. Registered by main.go. func Handler(w http.ResponseWriter, r *http.Request) { w.Header().Set(Content-Type, text/plain; charsetutf-8) _, _ w.Write([]byte(hello, world\n)) }它的定位是管道本身的冒烟测试smoke test for the pipeline itself如果请求能打到这个端点并拿到预期响应说明从客户端、网关、路由到容器的整条链路是通的。GET /env —— 验证部署环境变量注入env/handler.go 把进程环境变量以 JSON 返回并支持?prefix按键前缀过滤func Handler(w http.ResponseWriter, r *http.Request) { prefix : r.URL.Query().Get(prefix) out : map[string]string{} for _, kv : range os.Environ() { k, v, _ : strings.Cut(kv, ) if prefix ! !strings.HasPrefix(k, prefix) { continue } out[k] v } httpx.JSON(w, http.StatusOK, out) }它的用途是端到端验证部署环境变量注入verifying deployment env injection works end-to-end把服务部署上去后curl localhost:8080/env?prefixMYAPP_就能确认平台注入的变量是否真的进入了进程环境。GET /buildinfo —— 验证-ldflags -X构建期注入buildinfo/handler.go 演示的是链接期变量注入这条管线契约build-time variable → ldflags -X → package var与env探针的“运行时环境 → os.Getenv”形成鲜明对照。其核心是一个包级变量var Version unsetunset是兜底哨兵值本地go run没有注入时探针依然能正常响应。覆盖它的命令是go build -ldflags -X github.com/unkeyed/unkey/svc/kitchensink/buildinfo.Versionvaluehandler 则把该值以 JSON 返回{version: ...}。在 Dockerfile 中可以看到这条管线在真实构建中的落地构建阶段通过 Docker BuildKit 的 secret mount 把平台变量挂载到/run/secrets/.envif [ -f /run/secrets/.env ]; then set -a . /run/secrets/.env set a; fi加载后以-ldflags -X github.com/unkeyed/unkey/svc/kitchensink/buildinfo.Version${VERSION:-unset}注入版本号——-f守卫保证本地无 secret 挂载时构建依然可用${VERSION:-unset}与源码中的哨兵值保持一致。GET /principal —— 解码网关注入的鉴权主体principal/handler.go 验证的是frontline 网关在鉴权策略通过后设置的X-Unkey-Principal头。该头的常量与svc/frontline/internal/policies.PrincipalHeader保持一致注释说明为保持 kitchensink 纯标准库、避免跨服务 import 而在此复制一份。行为分三种情况头不存在即绕过 frontline 直连本端点返回401 Unauthorized提示“需要带鉴权策略经由 frontline 访问本端点”头存在但 JSON 解析失败frontline 传来了垃圾数据返回502 Bad Gateway——这是“网关的错不是调用方的错”头合法解析为 JSON 后原样返回。raw : r.Header.Get(header) if raw { http.Error(w, header header not set; reach this endpoint through frontline with an auth policy, http.StatusUnauthorized) return } var p map[string]any if err : json.Unmarshal([]byte(raw), p); err ! nil { http.Error(w, invalid principal JSON from frontline: err.Error(), http.StatusBadGateway) return } httpx.JSON(w, http.StatusOK, p)GET /headers —— 回显请求头验证头透传headers/handler.go 把收到的请求头整体以 JSON 返回func Handler(w http.ResponseWriter, r *http.Request) { httpx.JSON(w, http.StatusOK, r.Header) }它的价值在于调试请求头经过 frontline、负载均衡器或任何中间代理后的透传情况debugging header propagation端到端请求后检查响应里的头集合即可确认自定义头、认证头是否被正确保留或改写。POST /echo —— 验证请求体不被改写echo/handler.go 原样返回请求体并尽量保留Content-Typefunc Handler(w http.ResponseWriter, r *http.Request) { if ct : r.Header.Get(Content-Type); ct ! { w.Header().Set(Content-Type, ct) } _, _ io.Copy(w, r.Body) }实现直接用io.Copy把r.Body流式拷贝到w不需要在内存里读完整请求体。它用于验证请求体透传与代理未静默改写 payloadproxies arent silently rewriting payloads。POST /log —— 验证平台日志采集logs/handler.go 读取请求体、以 INFO 级别记录到 stdout并回显已记录的内容body, err : io.ReadAll(r.Body) if err ! nil { http.Error(w, failed to read body: err.Error(), http.StatusBadRequest) return } msg : string(body) slog.Info(kitchensink/log, body, msg) httpx.JSON(w, http.StatusOK, map[string]string{logged: msg})其用途是验证平台日志采集器log aggregator能拾取应用日志发送一条请求后到平台日志侧确认kitchensink/log这条 INFO 日志是否如期出现。值得注意的是包名取logs而非log正是为了避免遮蔽调用方可能用到的标准库log包见该文件包注释。GET /status/{code} —— 任意状态码演练上游错误处理status/handler.go 从路径参数解析状态码并原样返回code, err : strconv.Atoi(r.PathValue(code)) if err ! nil || code 100 || code 599 { http.Error(w, code must be a valid HTTP status (100-599), http.StatusBadRequest) return } http.Error(w, http.StatusText(code), code)实现依赖 Go 1.22 的http.ServeMux路径通配符{code}与r.PathValue。它的用途是演练 frontline 对上游错误502、429 等的处理而不需要一个真的会失败的 upstream——比如curl localhost:8080/status/503即可随时制造一个 503 上游。GET /sleep —— 阻塞响应演练超时与慢上游sleep/handler.go 按?dduration阻塞指定时长后返回 200dStr : r.URL.Query().Get(d) if dStr { http.Error(w, d query param required, e.g. /sleep?d500ms, http.StatusBadRequest) return } d, err : time.ParseDuration(dStr) if err ! nil || d 0 { http.Error(w, d must be a valid duration (e.g. 500ms, 2s, 1m): dStr, http.StatusBadRequest) return } select { case -time.After(d): w.WriteHeader(http.StatusOK) _, _ w.Write([]byte(slept d.String() \n)) case -r.Context().Done(): }时长用time.ParseDuration解析500ms、2s、1m30s等。实现通过select同时监听定时器与r.Context().Done()尊重客户端取消避免测试场景下泄漏 goroutine。它用于测试 frontline 的超时与慢上游行为Frontline timeouts and slow-upstream behavior。新增一个探针三步走README 给出了新增探针的标准流程结合源码可以展开为第 1 步创建子包与 handler。在svc/kitchensink/name/handler.go中实现行为。第 2 步导出标准签名。写出func Handler(w http.ResponseWriter, r *http.Request)。hello/handler.go是最小可用形态的模板——只依赖net/http不引入任何框架。第 3 步注册路由。在 main.go 的routes切片中追加一行GET /name: name.Handler,注册键使用Go 1.22 的http.ServeMux模式语法GET /foo、POST /bar/{id}等且方法 路径与 handler 一一对应。注册时main.go会自动完成三件事挂载 handler、写入index自述页、打印一条registeredINFO 日志。值得注意的是README 中的注册示例GET /name: name.Handler是简化写法实际代码中routes是包含Pattern、Description、Fn三个字段的结构体切片因此真实新增时需要同时提供路径模式、人类可读描述与 handler 三者例如{GET /hello, Smoke test — returns hello, world., hello.Handler},运行方式本机、自定义端口与 DockerREADME 给出了三种运行方式均可直接套用。方式一直接以 Go 运行默认端口 8080go run ./svc/kitchensink方式二自定义端口通过PORT环境变量覆盖PORT9090 go run ./svc/kitchensink方式三Docker 构建运行。注意构建上下文是仓库根目录而不是 kitchensink 所在目录——因为二进制属于主 Go module需要根目录的go.mod参与构建docker build -f svc/kitchensink/Dockerfile -t kitchensink . docker run --rm -p 8080:8080 kitchensinkDockerfile 的实现细节也值得注意多阶段构建builder 阶段基于golang:1.25-alpine先单独拷贝go.mod/go.sum并go mod download以利用层缓存运行阶段基于gcr.io/distroless/static-debian13:nonroot以非 root 用户运行镜像内ENV PORT8080并EXPOSE 8080ENTRYPOINT直接执行二进制。构建时还通过 BuildKit secret 挂载.env来注入VERSION见上文 buildinfo 探针。方式四本地构建二进制如需注入版本号go build -ldflags -X github.com/unkeyed/unkey/svc/kitchensink/buildinfo.Versionv1.0.0 -o kitchensink ./svc/kitchensink启动后可以直接用 README 给出的三连探测验证服务健康curl localhost:8080/hello curl localhost:8080/env curl localhost:8080/status/503另外还可以curl localhost:8080/查看全部已注册路由的自述清单curl -X POST localhost:8080/echo -d {foo:bar} -H Content-Type: application/json验证请求体回显curl localhost:8080/sleep?d2s制造一次 2 秒的慢响应curl -X POST localhost:8080/log -d hello logs触发一条 INFO 日志curl -H X-Unkey-Principal: {sub:user_123} localhost:8080/principal模拟网关注入的鉴权主体无该头则返回 401。小结kitchensink 在 unkey 中的角色从仓库结构看unkey 的svc/下并列着 api、ctrl、frontline、heimdall、krane、logdrain、vault 等生产服务而 kitchensink 刻意保持了极简它是验证平台特性的“测试插头”同时也是面向集成者的“活文档”。每一个探针都用标准库写成一个最小可用的端到端示例让工程师不必在大量业务代码中摸索就能确认网关策略、部署路由、环境变量注入、请求头透传、日志采集、超时处理等能力是否按预期工作。如果你想深入某个能力背后的实现可以从这些路径继续阅读网关侧的X-Unkey-Principal注入逻辑见 svc/frontline 下的 policies 相关代码平台日志采集链路见 svc/logdrain而 kitchensink 自身的全部探针源码则集中在 svc/kitchensink 目录下可作为新增探针或集成对接的第一手参考。【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkey创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考