package ginext import ( "fmt" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/mathext" "gogs.mikescher.com/BlackForestBytes/goext/rext" "net" "net/http" "regexp" "strings" "time" ) type GinWrapper struct { engine *gin.Engine SuppressGinLogs bool allowCors bool ginDebug bool bufferBody bool requestTimeout time.Duration routeSpecs []ginRouteSpec } type ginRouteSpec struct { Method string URL string Middlewares []string Handler string } // NewEngine creates a new (wrapped) ginEngine // Parameters are: // - [allowCors] Add cors handler to allow all CORS requests on the default http methods // - [ginDebug] Set gin.debug to true (adds more logs) // - [bufferBody] Buffers the input body stream, this way the ginext error handler can later include the whole request body // - [timeout] The default handler timeout func NewEngine(allowCors bool, ginDebug bool, bufferBody bool, timeout time.Duration) *GinWrapper { engine := gin.New() wrapper := &GinWrapper{ engine: engine, SuppressGinLogs: false, allowCors: allowCors, ginDebug: ginDebug, bufferBody: bufferBody, requestTimeout: timeout, } engine.RedirectFixedPath = false engine.RedirectTrailingSlash = false if allowCors { engine.Use(CorsMiddleware()) } // do not debug-print routes gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {} if !ginDebug { gin.SetMode(gin.ReleaseMode) ginlogger := gin.Logger() engine.Use(func(context *gin.Context) { if !wrapper.SuppressGinLogs { ginlogger(context) } }) } else { gin.SetMode(gin.DebugMode) } return wrapper } func (w *GinWrapper) ListenAndServeHTTP(addr string, postInit func(port string)) (chan error, *http.Server) { w.DebugPrintRoutes() httpserver := &http.Server{ Addr: addr, Handler: w.engine, } errChan := make(chan error) go func() { ln, err := net.Listen("tcp", httpserver.Addr) if err != nil { errChan <- err return } _, port, err := net.SplitHostPort(ln.Addr().String()) if err != nil { errChan <- err return } log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + port) if postInit != nil { postInit(port) // the net.Listener a few lines above is at this point actually already buffering requests } errChan <- httpserver.Serve(ln) }() return errChan, httpserver } func (w *GinWrapper) DebugPrintRoutes() { if !w.ginDebug { return } lines := make([][4]string, 0) pad := [4]int{0, 0, 0, 0} for _, spec := range w.routeSpecs { line := [4]string{ spec.Method, spec.URL, strings.Join(langext.ArrMap(spec.Middlewares, w.cleanMiddlewareName), " -> "), w.cleanMiddlewareName(spec.Handler), } lines = append(lines, line) pad[0] = mathext.Max(pad[0], len(line[0])) pad[1] = mathext.Max(pad[1], len(line[1])) pad[2] = mathext.Max(pad[2], len(line[2])) pad[3] = mathext.Max(pad[3], len(line[3])) } fmt.Printf("Gin-Routes:\n") fmt.Printf("{\n") for _, line := range lines { fmt.Printf(" %s %s --> %s --> %s\n", langext.StrPadRight("["+line[0]+"]", " ", pad[0]+2), langext.StrPadRight(line[1], " ", pad[1]), langext.StrPadRight(line[2], " ", pad[2]), langext.StrPadRight(line[3], " ", pad[3])) } fmt.Printf("}\n") } func (w *GinWrapper) cleanMiddlewareName(fname string) string { funcSuffix := rext.W(regexp.MustCompile(`\.func[0-9]+(?:\.[0-9]+)*$`)) if match, ok := funcSuffix.MatchFirst(fname); ok { fname = fname[:len(fname)-match.FullMatch().Length()] } if strings.HasSuffix(fname, ".(*GinRoutesWrapper).WithJSONFilter") { fname = "[JSONFilter]" } if fname == "ginext.BodyBuffer" { fname = "[BodyBuffer]" } skipPrefixes := []string{"api.(*Handler).", "api.", "ginext.", "handler.", "admin-app.", "employee-app.", "employer-app."} for _, pfx := range skipPrefixes { if strings.HasPrefix(fname, pfx) { fname = fname[len(pfx):] } } return fname }