package servers import ( "context" "errors" "fmt" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" ginprometheus "github.com/zsais/go-gin-prometheus" "gogs.tyduyong.com/duyong/dy-admin/internal/iam/config" "gogs.tyduyong.com/duyong/dy-pkg/app/middleware" "gogs.tyduyong.com/duyong/dy-pkg/app/response" "gogs.tyduyong.com/duyong/dy-pkg/app/version" "gogs.tyduyong.com/duyong/dy-pkg/logs" "log" "net" "net/http" "strconv" "strings" "time" ) // CertKey 证书结构 type CertKey struct { // CertFile is a file containing a PEM-encoded certificate, and possibly the complete certificate chain CertFile string // KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile KeyFile string } // SecureServeInfo 开启https时的结构 type SecureServeInfo struct { BindAddress string BindPort int CertKey CertKey } func (s *SecureServeInfo) Address() string { return net.JoinHostPort(s.BindAddress, strconv.Itoa(s.BindPort)) } // httpAPIServer Http&Https封装 type httpAPIServer struct { *gin.Engine middlewares []string healthz bool enableMetrics bool enableProfiling bool insecureAddress string secureInfo *SecureServeInfo insecureServer, secureServer *http.Server } // newHttpServer 构建http服务配置 func newHttpServer(cfg *config.Config) *httpAPIServer { gin.SetMode(cfg.ServerRunOptions.Mode) httpServer := &httpAPIServer{ Engine: gin.New(), middlewares: cfg.ServerRunOptions.Middlewares, healthz: cfg.ServerRunOptions.Healthz, enableMetrics: cfg.FeatureOptions.EnableMetrics, enableProfiling: cfg.FeatureOptions.EnableProfiling, insecureAddress: net.JoinHostPort(cfg.InsecureServeOptions.BindAddress, strconv.Itoa(cfg.InsecureServeOptions.BindPort)), secureInfo: &SecureServeInfo{ BindAddress: cfg.SecureServingOptions.BindAddress, BindPort: cfg.SecureServingOptions.BindPort, CertKey: CertKey{ CertFile: cfg.SecureServingOptions.ServerCert.CertKey.CertFile, KeyFile: cfg.SecureServingOptions.ServerCert.CertKey.KeyFile, }, }, } httpServer.Setup() httpServer.InstallMiddlewares() httpServer.InstallAPIs() return httpServer } func (s *httpAPIServer) Setup() { gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { logs.Infof("%-6s %-s --> %s (%d handlers)", httpMethod, absolutePath, handlerName, nuHandlers) } } // InstallMiddlewares 根据配置,选定路由中间件 func (s *httpAPIServer) InstallMiddlewares() { // 必选中间件 s.Use(middleware.RequestID()) s.Use(middleware.Context()) // install custom middlewares for _, m := range s.middlewares { mw, ok := middleware.Middlewares[m] if !ok { logs.Warnf("can not find middleware: %s", m) continue } logs.Infof("install middleware: %s", m) s.Use(mw) } } // InstallAPIs 根据配置,设置制定的接口 func (s *httpAPIServer) InstallAPIs() { // 健康检查接口 if s.healthz { s.GET("/healthz", func(c *gin.Context) { response.WriteResponse(c, nil, map[string]string{"status": "ok"}) }) } // 监控接口 if s.enableMetrics { prometheus := ginprometheus.NewPrometheus("gin") prometheus.Use(s.Engine) } // install pprof handler if s.enableProfiling { pprof.Register(s.Engine) } s.GET("/version", func(c *gin.Context) { response.WriteResponse(c, nil, version.Get()) }) } // Run 启动服务 func (s *httpAPIServer) Run() { s.insecureServer = &http.Server{ Addr: s.insecureAddress, Handler: s, } s.secureServer = &http.Server{ Addr: s.secureInfo.Address(), Handler: s, } go func() { logs.Infof("Start to listening the incoming requests on http address: %s", s.insecureAddress) if err := s.insecureServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logs.Fatal(err.Error()) return } logs.Infof("Server on %s stopped", s.insecureAddress) }() go func() { key, cert := s.secureInfo.CertKey.KeyFile, s.secureInfo.CertKey.CertFile if cert == "" || key == "" || s.secureInfo.BindPort == 0 { return } logs.Infof("Start to listening the incoming requests on https address: %s", s.secureInfo.Address()) if err := s.secureServer.ListenAndServeTLS(cert, key); err != nil && !errors.Is(err, http.ErrServerClosed) { logs.Fatal(err.Error()) return } logs.Infof("Server on %s stopped", s.secureInfo.Address()) }() // Ping the server to make sure the router is working. go func() { if s.healthz { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if s.healthz { if err := s.ping(ctx); err != nil { return } } } }() return } // Close 关闭服务 func (s *httpAPIServer) Close() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.secureServer.Shutdown(ctx); err != nil { logs.Warnf("Shutdown secure server failed: %s", err.Error()) } if err := s.insecureServer.Shutdown(ctx); err != nil { logs.Warnf("Shutdown insecure server failed: %s", err.Error()) } } func (s *httpAPIServer) ping(ctx context.Context) error { url := fmt.Sprintf("http://%s/healthz", s.insecureAddress) if strings.Contains(s.insecureAddress, "0.0.0.0") { url = fmt.Sprintf("http://127.0.0.1:%s/healthz", strings.Split(s.insecureAddress, ":")[1]) } for { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return err } resp, err := http.DefaultClient.Do(req) if err == nil && resp.StatusCode == http.StatusOK { logs.Info("The router has been deployed successfully.") resp.Body.Close() return nil } // Sleep for a second to continue the next ping. logs.Info("Waiting for the router, retry in 1 second.") time.Sleep(1 * time.Second) select { case <-ctx.Done(): log.Fatal("can not ping http server with in the specified time interval.") default: } } }