使用Golang reflect 对 gin handler 进行简单封装
项目中大量使用 gin 作为service API的 http framework. 大部分时候我们的代码结构类似这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func main() { engine := gin.New() engine.GET("/hi", hiHandler) engine.Run(":8083") } func hiHandler(c *gin.Context) { rsp, err := businessLogicProcess() if err != nil { c.AbortWithStatus(http.StatusBadRequest) } else { c.JSON(http.StatusOK, rsp) } } func businessLogicProcess() { ... } |
数据流:hiHandler -> businessLogicProcess -> hiHandler
. 这本身没有什么严重的问题,但是当你注册的API越来越多的时候,你的项目中会出现大量重复且类似hiHandler
结构的胶水层handler
: hiHandler只做了一个 http request 数据与 businessLogicProcess 的粘合,再将返回数据塞回 http response.
从数据流来看,这个胶水层是无法避免的。大量重复的 hiHandler
并不符合 write reusable code
原则。因此,我们可以尝试对该层统一抽象进行封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
func main() { engine := gin.New() engine.GET("/time", WrapHandler(GetTime)) engine.Run(":8083") } type GetTimeReq struct { Nation string `form:"nation" binding:"required" json:"nation"` } func GetTime(req *GetTimeReq) (*OKRsp, *ErrRsp) { ... } // WrapHandler wrap gin handler, the handler signature should be // func(req *ReqStruct) (*OKRspStruct, *ErrorRspStruct) or // func(c *gin.Context, req *ReqStruct) (*OKRspStruct, *ErrorRspStruct) func WrapHandler(f interface{}) gin.HandlerFunc { t := reflect.TypeOf(f) if t.Kind() != reflect.Func { panic("handdler should be function type") } fnumIn := t.NumIn() if fnumIn == 0 || fnumIn > 2 { panic("handler function require 1 or 2 input parameters") } if fnumIn == 2 { tc := reflect.TypeOf(&gin.Context{}) if t.In(0) != tc { panic("handler function first paramter should by type of *gin.Context if you have 2 input parameters") } } if t.NumOut() != 2 { panic("handler function return values should contain response data & error") } // errorInterface := reflect.TypeOf((*error)(nil)).Elem() // if !t.Out(1).Implements(errorInterface) { // panic("handler function second return value should by type of error") // } return func(c *gin.Context) { var req interface{} if fnumIn == 1 { req = newReqInstance(t.In(0)) } else { req = newReqInstance(t.In(1)) } if !c.Bind(req) { err := c.LastError() log.Warning("bind parameter error:%v", err) GinErrRsp(c, ErrCodeParamInvalid, err.Error()) return } var inValues []reflect.Value if fnumIn == 1 { inValues = []reflect.Value{reflect.ValueOf(req)} } else { inValues = []reflect.Value{reflect.ValueOf(c), reflect.ValueOf(req)} } ret := reflect.ValueOf(f).Call(inValues) if ret[1].IsNil() { GinRsp(c, http.StatusOK, ret[0].Interface()) } else { GinRsp(c, http.StatusOK, ret[1].Interface()) } } } func newReqInstance(t reflect.Type) interface{} { switch t.Kind() { case reflect.Ptr, reflect.Interface: return newReqInstance(t.Elem()) default: return reflect.New(t).Interface() } } |
这样做还有一个额外的好处:实现了业务处理函数 (GetTime) 与gin的解耦,使得业务处理函数复用性更强。