package http import ( "context" "net" "net/http" "net/http/pprof" "net/url" "sync" "git.nobla.cn/golang/aeus/middleware" "git.nobla.cn/golang/aeus/pkg/errors" "git.nobla.cn/golang/aeus/pkg/logger" netutil "git.nobla.cn/golang/aeus/pkg/net" "github.com/gin-gonic/gin" ) type Server struct { ctx context.Context opts *options uri *url.URL serve *http.Server engine *gin.Engine once sync.Once listener net.Listener } func (s *Server) Endpoint(ctx context.Context) (string, error) { if err := s.createListener(); err != nil { return "", err } return s.uri.String(), nil } func (s *Server) GET(pattern string, h HandleFunc) { s.engine.GET(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) PUT(pattern string, h HandleFunc) { s.engine.PUT(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) POST(pattern string, h HandleFunc) { s.engine.POST(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) HEAD(pattern string, h HandleFunc) { s.engine.HEAD(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) PATCH(pattern string, h HandleFunc) { s.engine.PATCH(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) DELETE(pattern string, h HandleFunc) { s.engine.DELETE(pattern, func(ctx *gin.Context) { childCtx := newContext(ctx) h(childCtx) putContext(childCtx) }) } func (s *Server) Use(middlewares ...middleware.Middleware) { for _, m := range middlewares { s.engine.Use(s.warpMiddleware(m)) } } func (s *Server) warpMiddleware(m middleware.Middleware) gin.HandlerFunc { return func(ginCtx *gin.Context) { ctx := ginCtx.Request.Context() handler := func(ctx context.Context) error { ginCtx.Next() if err := ginCtx.Errors.Last(); err != nil { return err.Err } return nil } wrappedHandler := m(handler) if err := wrappedHandler(ctx); err != nil { ginCtx.AbortWithError(http.StatusServiceUnavailable, err) return } } } func (s *Server) wrapHandle(f http.HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { f(c.Writer, c.Request) } } func (s *Server) createListener() (err error) { if s.listener == nil { if s.listener, err = net.Listen(s.opts.network, s.opts.address); err != nil { return } s.uri.Host = netutil.TrulyAddr(s.opts.address, s.listener) } return } func (s *Server) Start(ctx context.Context) (err error) { s.serve = &http.Server{ Addr: s.opts.address, Handler: s.engine, } s.ctx = ctx if s.opts.debug { s.engine.Handle(http.MethodGet, "/debug/pprof/", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/goroutine", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/heap", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/mutex", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/threadcreate", s.wrapHandle(pprof.Index)) s.engine.Handle(http.MethodGet, "/debug/pprof/cmdline", s.wrapHandle(pprof.Cmdline)) s.engine.Handle(http.MethodGet, "/debug/pprof/profile", s.wrapHandle(pprof.Profile)) s.engine.Handle(http.MethodGet, "/debug/pprof/symbol", s.wrapHandle(pprof.Symbol)) s.engine.Handle(http.MethodGet, "/debug/pprof/trace", s.wrapHandle(pprof.Trace)) } if err = s.createListener(); err != nil { return } s.opts.logger.Info(ctx, "http server listen on: %s", s.uri.Host) if s.opts.certFile != "" && s.opts.keyFile != "" { s.uri.Scheme = "https" err = s.serve.ServeTLS(s.listener, s.opts.certFile, s.opts.keyFile) } else { err = s.serve.Serve(s.listener) } if !errors.Is(err, http.ErrServerClosed) { return err } return nil } func (s *Server) Stop(ctx context.Context) (err error) { err = s.serve.Shutdown(ctx) s.opts.logger.Info(ctx, "http server stopped") return } func New(cbs ...Option) *Server { svr := &Server{ uri: &url.URL{Scheme: "http"}, opts: &options{ network: "tcp", logger: logger.Default(), address: ":0", }, } for _, cb := range cbs { cb(svr.opts) } if !svr.opts.debug { gin.SetMode(gin.ReleaseMode) } svr.engine = gin.New(svr.opts.ginOptions...) return svr }