wkhttp

package
v0.0.0-...-79f7884 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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

func GetLoginUID(token string, tokenPrefix string, cache cache.Cache) string

GetLoginUID GetLoginUID

func IsOriginAllowed

func IsOriginAllowed(origin string, allowed []string) bool

IsOriginAllowed 判断 origin 是否命中白名单。 支持精确匹配以及 "*.host" / "scheme://*.host" 的严格子域通配(不匹配裸主机)。 scheme 与 host 比较均大小写不敏感(遵循 RFC 6454 §4)。 注意:不带 scheme 的通配("*.host")会同时匹配 http 与 https 来源; 要限制为 https,请使用 "https://*.host"。

func ParseAllowedOrigins

func ParseAllowedOrigins(raw string) []string

ParseAllowedOrigins 解析逗号分隔的来源白名单。空项被忽略,前后空白被裁剪。 裸 "*" 会被显式丢弃并打 warning——CORS 规范不允许 "*" 与 Access-Control-Allow-Credentials: true 同时出现,此处也不应接受这种语义。 运维若想允许全部来源,应在反代层显式授权或使用精确域名。

func ParseBurstFromEnv

func ParseBurstFromEnv(key string, def int) int

ParseBurstFromEnv 解析 int 环境变量;语义同 ParseRPSFromEnv。

func ParseRPSFromEnv

func ParseRPSFromEnv(key string, def float64) float64

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 原生签名。

func WithUser

func WithUser(ctx context.Context, info UserInfo) context.Context

WithUser 把 UserInfo 写入 context;下游中间件/handler 通过 UserFromCtx 读取。 这是 c.Set("uid", ...) 的 context.Context 等价物,便于把用户信息透传给 不持有 *gin.Context 的下游(如 service 层、goroutine 派发)。

Types

type Context

type Context struct {
	*gin.Context
	// contains filtered or unexported fields
}

Context Context

func (*Context) CheckLoginRole

func (c *Context) CheckLoginRole() error

CheckLoginRole 检查登录角色权限

func (*Context) CheckLoginRoleIsSuperAdmin

func (c *Context) CheckLoginRoleIsSuperAdmin() error

CheckLoginRoleIsSuperAdmin 检查登录用户为超级管理员

func (*Context) GetAppID

func (c *Context) GetAppID() string

GetAppID appID

func (*Context) GetLoginName

func (c *Context) GetLoginName() string

GetLoginName 获取当前登录的用户名字

func (*Context) GetLoginRole

func (c *Context) GetLoginRole() string

GetLoginRole 获取当前登录用户的角色

func (*Context) GetLoginUID

func (c *Context) GetLoginUID() string

GetLoginUID 获取当前登录的用户uid

func (*Context) GetPage

func (c *Context) GetPage() (pageIndex int64, pageSize int64)

GetPage 获取页参数

func (*Context) GetSpanContext

func (c *Context) GetSpanContext() opentracing.SpanContext

GetSpanContext 获取当前请求的span context

func (*Context) RenderError

func (c *Context) RenderError(spec ErrorSpec)

RenderError 通过所属 WKHttp 的 renderer 渲染错误。 调用方仍需自行 c.Abort()(与 gin 中间件惯例一致),以确保后续 handler 不再执行。

func (*Context) Response

func (c *Context) Response(data interface{})

Response Response

func (*Context) ResponseError

func (c *Context) ResponseError(err error)

ResponseError ResponseError

func (*Context) ResponseErrorWithStatus

func (c *Context) ResponseErrorWithStatus(err error, status int)

ResponseErrorWithStatus ResponseErrorWithStatus

func (*Context) ResponseErrorf

func (c *Context) ResponseErrorf(msg string, err error)

ResponseErrorf ResponseErrorf

func (*Context) ResponseOK

func (c *Context) ResponseOK()

ResponseOK 返回成功

func (*Context) ResponseWithStatus

func (c *Context) ResponseWithStatus(status int, data interface{})

ResponseWithStatus ResponseWithStatus

type ErrorRenderer

type ErrorRenderer interface {
	Render(c *Context, spec ErrorSpec)
}

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

type TokenParser interface {
	Parse(ctx context.Context, token string) (UserInfo, error)
}

TokenParser 由下游服务注入,负责把 token 字符串解析为 UserInfo。 octo-lib 不感知 token 的具体格式(JWT、自定义 envelope 等),通过此接口解耦。 未注入时 AuthMiddleware 回退到 legacyTokenParser(基于 cache + uid@name@role 的旧实现)。

type UserInfo

type UserInfo struct {
	UID      string
	Name     string
	Role     string
	Language string
}

UserInfo 是 AuthMiddleware 解析后的登录用户信息 primitive。 Language 为可选字段,由 TokenParser 在解析 token 时填充(或为空), 下游 ErrorRenderer 用它来决定输出语言。

func UserFromCtx

func UserFromCtx(ctx context.Context) (UserInfo, bool)

UserFromCtx 从 context 读取 UserInfo;未注入时返回零值与 false。

type UserRole

type UserRole string

UserRole 用户角色

const (
	// Admin 管理员
	Admin UserRole = "admin"
	// SuperAdmin 超级管理员
	SuperAdmin UserRole = "superAdmin"
)

type WKHttp

type WKHttp struct {
	// contains filtered or unexported fields
}

WKHttp WKHttp

func New

func New() *WKHttp

New New

func (*WKHttp) Any

func (l *WKHttp) Any(relativePath string, handlers ...HandlerFunc)

Any Any

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) GET

func (l *WKHttp) GET(relativePath string, handlers ...HandlerFunc)

GET GET

func (*WKHttp) Group

func (l *WKHttp) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup

Group Group

func (*WKHttp) HandleContext

func (l *WKHttp) HandleContext(c *Context)

HandleContext HandleContext

func (*WKHttp) LoadHTMLGlob

func (l *WKHttp) LoadHTMLGlob(pattern string)

LoadHTMLGlob LoadHTMLGlob

func (*WKHttp) POST

func (l *WKHttp) POST(relativePath string, handlers ...HandlerFunc)

POST POST

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) Run

func (l *WKHttp) Run(addr ...string) error

Run Run

func (*WKHttp) RunTLS

func (l *WKHttp) RunTLS(addr, certFile, keyFile string) error

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) Static

func (l *WKHttp) Static(relativePath string, root string)

Static Static

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) Use

func (l *WKHttp) Use(handlers ...HandlerFunc)

Use Use

func (*WKHttp) UseGin

func (l *WKHttp) UseGin(handlers ...gin.HandlerFunc)

UseGin UseGin

func (*WKHttp) WKHttpHandler

func (l *WKHttp) WKHttpHandler(handlerFunc HandlerFunc) gin.HandlerFunc

WKHttpHandler WKHttpHandler

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL