Web
由于gin组件的强大能力,构建web应用也是非常简单的.而woocoo目前专注于服务端API开发.
// 取全局程序配置
cnf := conf.Global()
// 传入web节点配置
webSrv := web.New(web.WithConfiguration(cnf.Sub("web")))
// 采用gin原生的写法
webSrv.Router().GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
首先我们来看一下配置:
# root节点,可自己指定
web:
# 服务器节点
server:
# 服务地址,符合地址要求的字符串
addr: 0.0.0.0:10001
# 如果启动tls时
tls:
cert: ""
key: ""
# 服务引擎配置,子级等同于gin的router
engine:
redirectTrailingSlash: false
remoteIPHeaders:
- X-Forwarded-For
- X-Real-XIP
# 路由组
routerGroups:
# 路由组名. default是默认路径为"/"的默认路由
- default:
# 路由组路径,非default节点必须配置
basePath: "/"
# 中间件配置
middlewares:
# 访问日志中间件
- accessLog:
# 异常处理中间件
- recovery:
# 错误处理中间件
- errorHandle:
engine
engine
配置节实质是指向了gin.Engine
结构体,通过将结构体的公共字段自行引入配置即可初始化常规的服务配置.
路由
路由是web服务中最重要的组件,可配置的路由同样是基于Gin.
由于路由的写法仍然使用gin的,与配置文件配合使用:
// 配置文件:
// group:
// basePath: "/group"
// middlewares: .....
webSrv.Router().GET("/group", func(c *gin.Context) {
c.String(200, "hello world")
})
上面的写法会使用配置文件中的设置,不需要在程序中显式引入分组及中间件.
中间件
由web的配置节点中,在middlawares
中配置中间件,我们开始介绍内置的中间件.具体的代码可查看handler
中间件的加载是有顺序的,因此定制程序配置文件时,请根据实际情况配置. 一般前置的几个中间件如下:
accessLog: #...
recovery: #...
errorHandle: #...
需要特别说明的是设置在default
路由组节点下的中间件是全局的,将会应用到所有的路由中, 由于是顺序加载,因此default
务必放在routerGroups
最前面.
如果子路由组中配置了同名的中间件,两个是间件将同时生效,而不是覆盖.
AccessLog
访问日志借助了高性能的log组件实现HTTP请求日志. 可定义输出格式.
使用方式,在配置中放入:
middlewares:
- accessLog: # 不设置任何使用默认配置
可以自定义格式,支持的tag如下:
- id (Request ID or trace ID)
- remoteIp
- uri
- host
- method
- path
- protocol
- referer
- userAgent
- status
- error
- latency (纳秒)
- latencyHuman (可读性)
- bodyIn (请求体)
- bytesIn (进站大小)
- bytesOut (出站大小)
- header:NAME
- query:NAME
- form:NAME
- context:NAME
accessLog:
format: "id,header:accept,context:tenantID,query:id"
log组件使用的全局的日志核心,因此错误等级与全局的错误等级是一致的.同时也提供了自定义等级,但该等级只能高于全局设置.请参考logger
level: info #错误等级文本
Recovery
Recovery是用于程序从panic中恢复的recover,其结合日志及错误的中间件, 可在日志中输出错误的stack.用法也比较简单.直接在配置中放入.
recovery: ## 目前为空配置.
错误处理
我们提倡通过从中间件和处理程序返回错误来集中处理HTTP错误,统一将错误直接或转换后输入到客户端.
内置的ErrorHandle提供了常规的错误处理机制.可通过配置文件配置.
errorHandle:
accepts: "application/json,application/xml" # http accepts接受的数据类型
message: "系统错误,请联系管理员" # 针对私有错误错误信息
首先我们借用了Gin的错误定义,将错误区分为Public和Private两种类型. Public错误认为是可公开的,可以直接输出到客户端, Private错误将会被记录到日志中,同时以配置文件中的message输出到客户端.对于这类型的Error,Code默认为Http Status.
默认通过gin.Context方法c.Error(error)
方法产生的为private类型的错误.
我们也提供了一个方法来支持错误代码(code)和错误信息(message)的输出.
// SetContextError set the error to Context,and the error will be handled by ErrorHandleMiddleware
func SetContextError(c *gin.Context, code int, err error) {
ce := c.Error(err)
ce.Type = gin.ErrorType(code)
}
最终error的输出格式类似如下:
{
"errors": [
{
"code": 10000,
"message": "自定义错误信息"
},
{
"code": 500,
"message": "系统错误,请联系管理员"
}
]
}
自定义错误映射
内置了Error的映射方法,你可以通过程序化来自定义错误映射,来应不同的需求, 有两类错误映射:
- codeMap: key:int,value:string ,你可以将http.StatusCode配置为错误码来映射错误信息.
- errorMap: key:error,value:string ,你可以将error配置为错误码来映射错误信息.适应于底层返回错误难以被识别的错误.
customCodeMap:= map[int]any{
// init by yourself
500: "系统错误,请联系管理员",
10000: "自定义错误信息",
}
customErrorMap:= map[int]any{
"error pkg error": "系统错误,请联系管理员"
}
handler.SetErrorMap(customCodeMap, customErrorMap)
自定义错误解析
可以通过程序化来自定义处理程序,来应不同的需求:
// ExampleErrorHandleMiddleware_customErrorParser is the example for customer ErrorHandle
func ExampleErrorHandleMiddleware_customErrorParser() {
hdl := handler.ErrorHandle(handler.WithMiddlewareConfig(func(config any) {
cfg := config.(*handler.ErrorHandleConfig)
codeMap := map[uint64]any{
10000: "miss required param",
10001: "invalid param",
}
errorMap := map[interface{ Error() string }]string{
http.ErrBodyNotAllowed: "username/password not correct",
}
cfg.Accepts = "application/json,application/xml"
cfg.Message = "internal error"
cfg.ErrorParser = func(c *gin.Context, public error) (int, any) {
var errs = make([]gin.H, len(c.Errors))
for i, e := range c.Errors {
if txt, ok := codeMap[uint64(e.Type)]; ok {
errs[i] = gin.H{"code": i, "message": txt}
continue
}
if txt, ok := errorMap[e.Err]; ok {
errs[i] = gin.H{"code": i, "message": txt}
continue
}
errs[i] = gin.H{"code": i, "message": e.Error()}
}
return 0, errs
},
}))
gin.Default().Use(hdl.ApplyFunc(nil))
}