使用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的解耦,使得业务处理函数复用性更强。