Skip to content

嵌入宿主应用 ​

本页说明 wicket 在宿主进程里是怎样一层层包起来的、哪些请求它处理、哪些交还给你,以及启动和关闭时宿主要做什么。

装配骨架 ​

go
root, err := di.NewRoot()
if err != nil {
	log.Fatal(err)
}
defer root.Close()

opts := godi.GetRequired[*options.WicketOptions](root)
opts.IssuerUri = "https://id.example.com"

cfg := root.ServerConfig()
// ... cfg.SetClients / SetApiScopes / SetSigningCredentialStores / 各类 store ...
cfg.Freeze() // 校验存储注入;失败直接 panic

srv, err := server.DefaultServer(root, server.WithLogger(logger))
if err != nil {
	log.Fatal(err)
}

app := http.NewServeMux() // 你的业务路由
handler := hosting.ScopeMiddleware(root)(srv.UseWicket()(app))
log.Fatal(http.ListenAndServe(":5000", handler))

可直接运行的完整版本见 快速开始。

中间件链顺序 ​

一个请求从外到内依次经过:

text
hosting.ScopeMiddleware(root)        为本次请求创建 DI 作用域,放进 context
 └─ srv.UseWicket() 返回的中间件
     ├─ 动态认证方案中间件           处理运行时注册的外部登录方案(未启用时直接放行)
     ├─ 核心认证中间件               按认证方案识别当前用户
     └─ 端点路由                     路径命中协议端点 → wicket 处理并结束
                                     未命中 → 交给 next(你的 app)

两条硬性要求:

  1. ScopeMiddleware 必须在最外层。 端点路由从 context 取作用域,取不到时不会报错,而是把所有请求原样透传给宿主——表现为“协议端点全部 404/落到你的业务路由”。漏包这一层是最常见的装配错误。
  2. UseWicket() 只调用一次,在装配完成之后。它会在调用当下执行启动校验(见下文),并解析路由器等单例。

哪些路径由 wicket 处理 ​

端点路由对 r.URL.Path 做不区分大小写的精确匹配,命中以下路径之一才处理:

路径端点
/connect/authorize、/connect/authorize/callback授权
/connect/token令牌
/.well-known/openid-configuration发现文档
/.well-known/openid-configuration/jwksJWKS
/connect/userinfoUserInfo
/connect/introspect内省
/connect/endsession、/connect/endsession/callback结束会话
/connect/checksession会话检查 iframe
/connect/cibaCIBA 后端通道认证

其余所有路径都交给宿主 handler,包括 /connect/revocation、/connect/deviceauthorization 和 mTLS 路径——它们在 v0.1.4 只有常量、没有处理器。你的登录页、同意页、业务 API 也就是这样和 wicket 共存的。

只能挂在根路径

wicket 生成的 URL(发现文档里的端点、登录重定向里的回调地址)都以站点根为基准,BasePath 恒为空。不要用 http.StripPrefix 把它挂到子路径下。

Freeze 与启动期失败 ​

wicket 的启动校验都以 panic 的形式出现,不返回 error。建议在进程启动阶段完成全部装配,让问题在部署时就暴露。

时机校验内容失败表现
cfg.Freeze()11 个存储 seam 是否都已注入;或宿主已调用 cfg.AllowInMemoryFallback()panic:store seam "..." not injected by host and in-memory fallback not declared
srv.UseWicket()ErrorUrl、ConsentUrl、ConsentReturnUrlParameter、LogoutIdParameter、ErrorIdParameter、CustomRedirectReturnUrlParameter、CheckSessionCookieName、CorsPolicyName 不能为空panic:如 ConsentUrl is not configured
首次使用某个 store若未调用 Freeze 且未注入:回退内存实现并输出一条 warn 日志静默运行在内存里

关于 Freeze 需要知道的事实:

  • 它只做一次注入校验并置位标记,不会让 ServerConfig 变成只读——之后的 SetXxx 调用仍会生效。请把它当作“装配完成”的检查点,而不是并发保护。
  • 不调用 Freeze 也能运行,但未注入的 store 会悄悄回退到进程内存,多副本部署时必然出错。生产装配务必调用。
  • AllowInMemoryFallback() 必须在 Freeze() 之前调用才有意义,且只用于开发环境,见 持久化存储。

生命周期与 Root.Close ​

go
root, err := di.NewRoot()
// ...
defer root.Close()
  • Root.Close() 按构造的逆序释放容器跟踪的 store 包装器:如果宿主注入的适配器实现了 io.Closer,会转发调用其 Close;没实现的什么也不做。
  • Close 是幂等的,第二次调用返回第一次的结果;关闭之后再从容器解析服务会 panic。
  • 宿主要先停止接收请求(例如 http.Server.Shutdown),再调用 root.Close()。关闭过程中仍有请求在用适配器属于宿主错误,wicket 不做同步。
  • 宿主自己创建的资源(数据库连接池、授权参数消息 store 等)由宿主自己关闭。例如 wicket-pg 的 store 不实现 Close,root.Close() 不会关闭 pgxpool.Pool。

推荐的关闭顺序(示意):

go
_ = httpServer.Shutdown(ctx) // 1. 停止接收新请求,等待在途请求结束
cancelCleanup()              // 2. 停止宿主的过期清理循环
_ = root.Close()             // 3. 释放 wicket 容器
pool.Close()                 // 4. 关闭宿主自己的连接池

与路由器集成 ​

UseWicket() 返回的是标准的 func(http.Handler) http.Handler,ScopeMiddleware 也是。因此任何能接受 http.Handler 的路由器都可以集成,原则只有一条:wicket 包在路由器外面,让协议路径在进入路由器之前就被截走。

go
// 示意:buildMyRouter 返回宿主的路由器
var router http.Handler = buildMyRouter() // 标准库 mux、chi、gin 引擎……任何 http.Handler

handler := hosting.ScopeMiddleware(root)(srv.UseWicket()(router))
http.ListenAndServe(":5000", handler)
  • 标准库 http.ServeMux、chi 的 chi.Mux 都直接实现了 http.Handler。
  • gin 的 *gin.Engine 同样实现了 http.Handler,可以作为上面的 router 传入。

不建议把 wicket 注册成路由器里的某条子路由:wicket 需要同时接管 /connect/* 与 /.well-known/* 两组路径,并依赖根路径计算 URL。

登录、同意与错误页面由宿主实现 ​

wicket 不渲染任何交互页面。授权请求需要用户交互时,它会把浏览器 302 到 WicketOptions.UserInteraction 中配置的地址,并附上一个返回地址参数;宿主页面处理完后再把浏览器送回这个返回地址。

NewRoot() 之后各字段的实际值(已用 v0.1.4 实测):

字段默认值说明
LoginUrl空必须由宿主设置,见下方警告
LoginReturnUrlParameter空建议设为 "returnUrl"
LogoutUrl空结束会话时重定向的宿主登出页
LogoutIdParameter"logoutId"
ConsentUrl"/consent"
ConsentReturnUrlParameter"returnUrl"
ErrorUrl"/home/error"
ErrorIdParameter"errorId"
CustomRedirectReturnUrlParameter"returnUrl"
DeviceVerificationUrl"/device"v0.1.4 无设备授权端点,暂不起作用
CookieMessageThreshold2

LoginUrl 没有默认值

LoginUrl 与 LoginReturnUrlParameter 在 v0.1.4 默认都是空字符串,UseWicket() 也不会校验它们。不设置的话,未登录用户访问授权端点会被重定向到一个形如 /connect/?=... 的无效地址。凡是会用到授权码流的宿主都应显式设置:

go
ui := opts.UserInteraction
ui.LoginUrl = "/account/login"
ui.LoginReturnUrlParameter = "returnUrl"
ui.LogoutUrl = "/account/logout"

设置后,未登录的授权请求会被重定向到 /account/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3F...。

宿主登录页的职责:校验用户凭据 → 通过 wicket 认证方案写入登录会话 → 重定向回 returnUrl(只接受本站相对地址,防止开放重定向)。如 核心概念 所述,wicket 方案的 cookie handler 需要宿主自己提供。完整的交互流程见 授权码 + PKCE。

日志 ​

server.WithLogger(logger) 只作用于 Server 自身(启动信息等)。容器内各服务使用的日志器在 di.NewRoot() 时就已确定:

v0.1.4 的已知限制

cfg.SetLogger(logger) 在 NewRoot() 之后调用,不会改变已构造服务的日志器——实测中容器内部日志始终以 JSON 格式、Debug 级别、附带源码位置写到标准输出。在 v0.1.4 中宿主无法通过 API 调整其级别或去向,请在日志采集侧过滤 Debug 级别。详见 日志与可观测性。

相关阅读 ​

以 Apache License 2.0 发布 · 文档内容 © 2026 Wicket 作者