Documentation
¶
Index ¶
- Variables
- func GetLoginUID(token string, tokenPrefix string, cache cache.Cache) string
- func IsOriginAllowed(origin string, allowed []string) bool
- func ParseAllowedOrigins(raw string) []string
- func ParseBurstFromEnv(key string, def int) int
- func ParseRPSFromEnv(key string, def float64) float64
- func SecureCORSOverrideMiddleware(allowed []string) gin.HandlerFunc
- func WithUser(ctx context.Context, info UserInfo) context.Context
- type Context
- func (c *Context) CheckLoginRole() error
- func (c *Context) CheckLoginRoleIsSuperAdmin() error
- func (c *Context) GetAppID() string
- func (c *Context) GetLoginName() string
- func (c *Context) GetLoginRole() string
- func (c *Context) GetLoginUID() string
- func (c *Context) GetPage() (pageIndex int64, pageSize int64)
- func (c *Context) GetSpanContext() opentracing.SpanContext
- func (c *Context) RenderError(spec ErrorSpec)
- func (c *Context) Response(data interface{})
- func (c *Context) ResponseError(err error)
- func (c *Context) ResponseErrorWithStatus(err error, status int)
- func (c *Context) ResponseErrorf(msg string, err error)
- func (c *Context) ResponseOK()
- func (c *Context) ResponseWithStatus(status int, data interface{})
- type ErrorRenderer
- type ErrorSpec
- type HandlerFunc
- type RouterGroup
- type TokenParser
- type UserInfo
- type UserRole
- type WKHttp
- func (l *WKHttp) Any(relativePath string, handlers ...HandlerFunc)
- func (l *WKHttp) AuthMiddleware(cache cache.Cache, tokenPrefix string) HandlerFunc
- func (l *WKHttp) GET(relativePath string, handlers ...HandlerFunc)
- func (l *WKHttp) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup
- func (l *WKHttp) HandleContext(c *Context)
- func (l *WKHttp) LoadHTMLGlob(pattern string)
- func (l *WKHttp) POST(relativePath string, handlers ...HandlerFunc)
- func (l *WKHttp) RateLimitMiddleware(ctx context.Context, client *rd.Client, rps float64, burst int, ...) HandlerFunc
- func (l *WKHttp) Run(addr ...string) error
- func (l *WKHttp) RunTLS(addr, certFile, keyFile string) error
- func (l *WKHttp) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (l *WKHttp) SetErrorRenderer(r ErrorRenderer)
- func (l *WKHttp) SetTokenParser(p TokenParser)
- func (l *WKHttp) Static(relativePath string, root string)
- func (l *WKHttp) StrictIPRateLimitMiddleware(ctx context.Context, client *rd.Client, tag string, rps float64, burst int) HandlerFunc
- func (l *WKHttp) UIDRateLimitMiddleware(ctx context.Context, client *rd.Client, rps float64, burst int) HandlerFunc
- func (l *WKHttp) Use(handlers ...HandlerFunc)
- func (l *WKHttp) UseGin(handlers ...gin.HandlerFunc)
- func (l *WKHttp) WKHttpHandler(handlerFunc HandlerFunc) gin.HandlerFunc
Constants ¶
This section is empty.
Variables ¶
var ( // ErrTokenMissing —— token header 字面量为空。 // 映射到 err.shared.auth.token_missing / DefaultMessage "token不能为空,请先登录!"。 ErrTokenMissing = errors.New("wkhttp: token missing") // ErrTokenNotFound —— token 在后端找不到对应会话(cache 未命中、JWT 过期等)。 // 映射到 err.shared.auth.required / DefaultMessage "请先登录!"。 ErrTokenNotFound = errors.New("wkhttp: token not found") // ErrTokenInvalid —— token 在后端能取到但格式/签名不合法。 // 映射到 err.shared.auth.token_invalid / DefaultMessage "token有误!"。 ErrTokenInvalid = errors.New("wkhttp: token invalid") )
Sentinel errors returned by TokenParser implementations and recognised by AuthMiddleware via errors.Is, used to map parse failures to i18n error codes. 公开 sentinel 让下游自定义 parser 可以复用同一套语义/翻译键,避免每个 parser 自己拟一套字符串。errors.Is(err, ErrTokenInvalid) 是预期的判别方式。
Functions ¶
func GetLoginUID ¶
GetLoginUID GetLoginUID
func IsOriginAllowed ¶
IsOriginAllowed 判断 origin 是否命中白名单。 支持精确匹配以及 "*.host" / "scheme://*.host" 的严格子域通配(不匹配裸主机)。 scheme 与 host 比较均大小写不敏感(遵循 RFC 6454 §4)。 注意:不带 scheme 的通配("*.host")会同时匹配 http 与 https 来源; 要限制为 https,请使用 "https://*.host"。
func ParseAllowedOrigins ¶
ParseAllowedOrigins 解析逗号分隔的来源白名单。空项被忽略,前后空白被裁剪。 裸 "*" 会被显式丢弃并打 warning——CORS 规范不允许 "*" 与 Access-Control-Allow-Credentials: true 同时出现,此处也不应接受这种语义。 运维若想允许全部来源,应在反代层显式授权或使用精确域名。
func ParseBurstFromEnv ¶
ParseBurstFromEnv 解析 int 环境变量;语义同 ParseRPSFromEnv。
func ParseRPSFromEnv ¶
ParseRPSFromEnv 解析 float 环境变量;缺省或解析失败回退到 def, 无效值(负数 / 非法格式)打 Warn 日志,避免操作配置错误静默失败。
func SecureCORSOverrideMiddleware ¶
func SecureCORSOverrideMiddleware(allowed []string) gin.HandlerFunc
SecureCORSOverrideMiddleware 返回一个 gin 中间件,用于在上游 CORS 中间件 已写入响应头之后,按白名单重写/剥离 Access-Control-Allow-Origin 与 Access-Control-Allow-Credentials,并追加 Vary: Origin。
**顺序要求**:本中间件必须注册在上游 CORS 中间件**之后**,否则其 Header.Set 会被上游覆盖,导致本中间件失效。
行为:
- 请求无 Origin:删除两个 CORS 头,保持同源语义。
- Origin 命中白名单:反射该 Origin,Allow-Credentials: true,Vary: Origin。
- Origin 未命中:删除两个 CORS 头,仅追加 Vary: Origin。
注意:预检(OPTIONS)通常被上游中间件提前 Abort,不会进入本中间件。 当上游发出的是规范不合法的组合(如 "*" + credentials=true),浏览器本身 会拒绝跨域 credentialed 预检,因此攻击路径依然被封堵。预检顺序的彻底 修复需要把 CORSMiddleware 与本中间件合并为白名单感知的单一中间件, 见 follow-up issue。
返回类型为 gin.HandlerFunc(而非 wkhttp.HandlerFunc):本中间件只操作 响应头,不需要 c.RenderError 等 *Context 能力,且必须能与上游纯 gin 中间件 (来自 dmwork-lib 的 server.New 等)混排注册,因此直接使用 gin 原生签名。
Types ¶
type Context ¶
Context Context
func (*Context) CheckLoginRoleIsSuperAdmin ¶
CheckLoginRoleIsSuperAdmin 检查登录用户为超级管理员
func (*Context) GetSpanContext ¶
func (c *Context) GetSpanContext() opentracing.SpanContext
GetSpanContext 获取当前请求的span context
func (*Context) RenderError ¶
RenderError 通过所属 WKHttp 的 renderer 渲染错误。 调用方仍需自行 c.Abort()(与 gin 中间件惯例一致),以确保后续 handler 不再执行。
func (*Context) ResponseError ¶
ResponseError ResponseError
func (*Context) ResponseErrorWithStatus ¶
ResponseErrorWithStatus ResponseErrorWithStatus
func (*Context) ResponseErrorf ¶
ResponseErrorf ResponseErrorf
func (*Context) ResponseWithStatus ¶
ResponseWithStatus ResponseWithStatus
type ErrorRenderer ¶
ErrorRenderer 由下游服务实现并通过 WKHttp.SetErrorRenderer 注入。 同一个 *WKHttp 实例内只有一个 renderer 生效;不同 WKHttp 实例彼此隔离, 因此并发测试中可以为每个实例独立注入而互不污染。
type ErrorSpec ¶
type ErrorSpec struct {
Code string
DefaultMessage string
TransportStatus int
SemanticStatus int
Params map[string]any
Details map[string]any
Internal bool
}
ErrorSpec 描述一次错误响应的 primitive,供 ErrorRenderer 翻译/渲染。
设计要点:
- Code 是稳定的 i18n key(如 "err.shared.auth.required"),由调用方保证全局唯一。
- DefaultMessage 是未注入 renderer 时的兜底文案(与历史硬编码文案一致), 保证 octo-lib 单独使用时行为不变。
- TransportStatus 是 HTTP 状态码(实际写到响应头)。
- SemanticStatus 是业务侧期望的状态码:上游可能因 envelope 协议把 TransportStatus 固定为 200,但仍需保留语义 401/403/429——这两个字段允许分离。未来扩展用。
- Params 用于翻译模板插值(如 {"retry_seconds": 30}),由 renderer 消费。
- Details 携带不需要翻译的结构化字段,会被 renderer 透传到响应体或响应头 (例如 rate-limit 的 retry_after)。
- Internal=true 时 renderer 应避免把敏感内部细节暴露给客户端。
type HandlerFunc ¶
type HandlerFunc func(c *Context)
HandlerFunc HandlerFunc
func CORSMiddleware ¶
func CORSMiddleware() HandlerFunc
CORSMiddleware 跨域
AllowHeaders 在历史 header 列表基础上增补 i18n 协议三个头:
- X-Octo-Error-Envelope: 客户端声明可解析的错误 envelope 版本
- X-Octo-Lang: 客户端显式覆盖语言(优先级高于 Accept-Language)
- Accept-Language: 标准协商语言头
ExposeHeaders 是新增字段(历史 CORSMiddleware 完全没有这一行):
- Content-Language: 实际返回的响应语言,浏览器可读
- Vary: 让代理 / 浏览器按语言变体缓存
type RouterGroup ¶
type RouterGroup struct {
*gin.RouterGroup
L *WKHttp
}
RouterGroup RouterGroup
func (*RouterGroup) DELETE ¶
func (r *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc)
DELETE DELETE
func (*RouterGroup) GET ¶
func (r *RouterGroup) GET(relativePath string, handlers ...HandlerFunc)
GET GET
func (*RouterGroup) POST ¶
func (r *RouterGroup) POST(relativePath string, handlers ...HandlerFunc)
POST POST
func (*RouterGroup) PUT ¶
func (r *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc)
PUT PUT
type TokenParser ¶
TokenParser 由下游服务注入,负责把 token 字符串解析为 UserInfo。 octo-lib 不感知 token 的具体格式(JWT、自定义 envelope 等),通过此接口解耦。 未注入时 AuthMiddleware 回退到 legacyTokenParser(基于 cache + uid@name@role 的旧实现)。
type UserInfo ¶
UserInfo 是 AuthMiddleware 解析后的登录用户信息 primitive。 Language 为可选字段,由 TokenParser 在解析 token 时填充(或为空), 下游 ErrorRenderer 用它来决定输出语言。
type WKHttp ¶
type WKHttp struct {
// contains filtered or unexported fields
}
WKHttp WKHttp
func (*WKHttp) AuthMiddleware ¶
func (l *WKHttp) AuthMiddleware(cache cache.Cache, tokenPrefix string) HandlerFunc
AuthMiddleware 认证中间件
三处历史硬编码中文错误改为 c.RenderError:
- token 缺失 → err.shared.auth.token_missing
- cache 查不到/为空 → err.shared.auth.required
- 解析格式错误 → err.shared.auth.token_invalid
未注入 ErrorRenderer 时由 default fallback 输出 DefaultMessage(与历史完全一致)。
解析成功后同时执行:
- c.Set("uid"/"name"/"role", ...)(向后兼容旧业务代码)
- WithUser(c.Request.Context(), info) 写入 context(D20,便于 service 层透传)
若服务注入了 TokenParser,则用注入的 parser 解析;否则走 legacyTokenParser (cache 查 "{prefix}{token}" → "uid@name[@role]" split)。
func (*WKHttp) Group ¶
func (l *WKHttp) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup
Group Group
func (*WKHttp) HandleContext ¶
HandleContext HandleContext
func (*WKHttp) LoadHTMLGlob ¶
LoadHTMLGlob LoadHTMLGlob
func (*WKHttp) RateLimitMiddleware ¶
func (l *WKHttp) RateLimitMiddleware(ctx context.Context, client *rd.Client, rps float64, burst int, excludePaths ...string) HandlerFunc
RateLimitMiddleware 全局 per-IP 限流,作为 DDoS 底线(挂载点:l.Use)。
状态存储于 Redis,多副本间共享配额。Redis 不可达时 fail-open(放行 + 告警)。 ctx 目前仅用于未来的取消语义,当前实现不起作用。
拒绝路径调用 c.RenderError,由 WKHttp 注入的 ErrorRenderer 翻译 err.shared.rate.limited;未注入 renderer 时回退到 default fallback, 输出 {msg:"请求过于频繁,请稍后再试", status:429},与历史行为一致。
func (*WKHttp) ServeHTTP ¶
func (l *WKHttp) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP ServeHTTP
func (*WKHttp) SetErrorRenderer ¶
func (l *WKHttp) SetErrorRenderer(r ErrorRenderer)
SetErrorRenderer 注入自定义 ErrorRenderer。线程安全,可在服务启动后任意时刻调用。 传 nil 表示恢复 default fallback。
func (*WKHttp) SetTokenParser ¶
func (l *WKHttp) SetTokenParser(p TokenParser)
SetTokenParser 注入自定义 token parser;线程安全,可在服务启动后任意时刻调用。 同一 *WKHttp 实例最后一次注入生效;nil 表示恢复 legacy 解析。
func (*WKHttp) StrictIPRateLimitMiddleware ¶
func (l *WKHttp) StrictIPRateLimitMiddleware(ctx context.Context, client *rd.Client, tag string, rps float64, burst int) HandlerFunc
StrictIPRateLimitMiddleware 端点级 per-IP 严格限流,挂在敏感端点(登录/注册/SMS/搜索)作为额外防护。
与全局 RateLimitMiddleware 区别:
- 全局:DDoS 底线,宽松阈值(数百 req/s),挂全局 l.Use
- 严格:暴力破解/枚举防御,紧阈值(数 req/min),挂在端点级 RouterGroup
同类端点(如所有登录端点)应共享同一个中间件实例,使同一 IP 的总配额受控,防攻击者跨端点分散:
loginLimit := r.StrictIPRateLimitMiddleware(ctx, rds, "login", 10.0/60, 5)
v.POST("/user/login", loginLimit, u.login)
v.POST("/user/usernamelogin", loginLimit, u.usernameLogin)
tag 区分不同端点组的 Redis keyspace。同一 tag 的多处调用共享配额(等同"同组"), 不同 tag 互相隔离。必须是稳定字符串(如 "login"、"register"),不要用随机值 或与请求相关的数据,否则滚动部署后会重置配额。
fail-closed(IP 缺失):归入同一全局桶,与全局 RateLimitMiddleware 行为一致。 fail-open(Redis 故障):Redis 调用失败时放行 + 告警,与其余中间件保持一致。
func (*WKHttp) UIDRateLimitMiddleware ¶
func (l *WKHttp) UIDRateLimitMiddleware(ctx context.Context, client *rd.Client, rps float64, burst int) HandlerFunc
UIDRateLimitMiddleware 按登录用户 uid 限流。
⚠️ 挂载要求:必须挂在 AuthMiddleware 之后,且只用于认证路由组。
r.Group("/v1/foo", r.AuthMiddleware(cache, "token:"), r.UIDRateLimitMiddleware(ctx, rds, 1, 2))
Fail-open 语义(uid 缺失):读不到 uid(未经 AuthMiddleware 或 token 无效)时直接放行, 不按任何维度限流。这意味着本中间件**不具备**未认证场景的防护能力,需配合全局 per-IP RateLimitMiddleware 作为底线。错误的挂载顺序会导致限流静默失效,请务必用 AuthMiddleware 前置并在测试中验证。
Fail-open 语义(Redis 故障):Redis 调用失败时放行 + 告警,不降级为内存桶, 避免"挂了就回到有 bug 的状态"把攻击面悄悄放大。
func (*WKHttp) WKHttpHandler ¶
func (l *WKHttp) WKHttpHandler(handlerFunc HandlerFunc) gin.HandlerFunc
WKHttpHandler WKHttpHandler