package api import ( "encoding/json" "net/http" "reflect" "strings" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "github.com/vapor/errors" "github.com/vapor/toolbar/federation/config" ) type Server struct { cfg *config.Config db *gorm.DB engine *gin.Engine } func NewServer(db *gorm.DB, cfg *config.Config) *Server { server := &Server{ cfg: cfg, db: db, } if cfg.API.IsReleaseMode { gin.SetMode(gin.ReleaseMode) } server.setupRouter() return server } func (server *Server) setupRouter() { r := gin.Default() r.Use(server.middleware()) v1 := r.Group("/api/v1") v1.POST("/federation/list-crosschain-txs", handlerMiddleware(server.ListCrosschainTxs)) server.engine = r } func (s *Server) Run() { s.engine.Run(":9886") } func (s *Server) middleware() gin.HandlerFunc { return func(c *gin.Context) { // add Access-Control-Allow-Origin c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Headers", "Content-Type") c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusOK) return } c.Set(serverLabel, s) c.Next() } } type handlerFun interface{} func handlerMiddleware(handleFunc interface{}) func(*gin.Context) { if err := validateFuncType(handleFunc); err != nil { panic(err) } return func(context *gin.Context) { handleRequest(context, handleFunc) } } func validateFuncType(fun handlerFun) error { ft := reflect.TypeOf(fun) if ft.Kind() != reflect.Func || ft.IsVariadic() { return errors.New("need nonvariadic func in " + ft.String()) } if ft.NumIn() < 1 || ft.NumIn() > 3 { return errors.New("need one or two or three parameters in " + ft.String()) } if ft.In(0) != contextType { return errors.New("the first parameter must point of context in " + ft.String()) } if ft.NumIn() == 2 && ft.In(1).Kind() != reflect.Ptr { return errors.New("the second parameter must point in " + ft.String()) } if ft.NumIn() == 3 && ft.In(2) != paginationQueryType { return errors.New("the third parameter of pagination must point of paginationQuery in " + ft.String()) } if ft.NumOut() < 1 || ft.NumOut() > 2 { return errors.New("the size of return value must one or two in " + ft.String()) } // if has pagination, the first return value must slice or array if ft.NumIn() == 3 && ft.Out(0).Kind() != reflect.Slice && ft.Out(0).Kind() != reflect.Array { return errors.New("the first return value of pagination must slice of array in " + ft.String()) } if !ft.Out(ft.NumOut() - 1).Implements(errorType) { return errors.New("the last return value must error in " + ft.String()) } return nil } // handleRequest get a handler function to process the request by request url func handleRequest(context *gin.Context, fun handlerFun) { args, err := buildHandleFuncArgs(fun, context) if err != nil { respondErrorResp(context, err) return } result := callHandleFunc(fun, args...) if err := result[len(result)-1]; err != nil { respondErrorResp(context, err.(error)) return } if exist := processPaginationIfPresent(fun, args, result, context); exist { return } if len(result) == 1 { respondSuccessResp(context, nil) return } respondSuccessResp(context, result[0]) } func buildHandleFuncArgs(fun handlerFun, context *gin.Context) ([]interface{}, error) { args := []interface{}{context} req, err := createHandleReqArg(fun, context) if err != nil { return nil, errors.Wrap(err, "createHandleReqArg") } if err := checkDisplayOrder(req); err != nil { return nil, err } if req != nil { args = append(args, req) } ft := reflect.TypeOf(fun) // no pagination exists if ft.NumIn() != 3 { return args, nil } query, err := parsePagination(context) if err != nil { return nil, errors.Wrap(err, "ParsePagination") } args = append(args, query) return args, nil } func createHandleReqArg(fun handlerFun, context *gin.Context) (interface{}, error) { ft := reflect.TypeOf(fun) if ft.NumIn() == 1 { return nil, nil } argType := ft.In(1).Elem() reqArg := reflect.New(argType).Interface() if err := context.ShouldBindJSON(reqArg); err != nil { return nil, errors.Wrap(err, "bind reqArg") } b, err := json.Marshal(reqArg) if err != nil { return nil, errors.Wrap(err, "json marshal") } context.Set(reqBodyLabel, string(b)) return reqArg, nil } func checkDisplayOrder(req interface{}) error { if req == nil { return nil } reqType := reflect.TypeOf(req).Elem() reqVal := reflect.ValueOf(req).Elem() for i := 0; i < reqType.NumField(); i++ { field := reqType.Field(i) if field.Type != reflect.TypeOf(Display{}) { continue } display := reqVal.Field(i).Interface().(Display) order := strings.Trim(display.Sorter.Order, "") if order != "desc" && order != "asc" { reqVal.Field(i).Set(reflect.ValueOf(Display{Filter: display.Filter, Sorter: Sorter{By: display.Sorter.By, Order: "desc"}})) } } return nil } func callHandleFunc(fun handlerFun, args ...interface{}) []interface{} { fv := reflect.ValueOf(fun) params := make([]reflect.Value, len(args)) for i, arg := range args { params[i] = reflect.ValueOf(arg) } rs := fv.Call(params) result := make([]interface{}, len(rs)) for i, r := range rs { result[i] = r.Interface() } return result }