yao/service/service.go
Max b39397ded0 feat(monitor): integrate monitor service start and stop in load and unload processes
- Added functionality to start the monitor service during the loading process, ensuring that watchers are registered.
- Implemented the stopping of the monitor service before unloading other services to allow for event handling.
- Updated middleware to utilize access logging for improved request tracking.

Made-with: Cursor
2026-03-15 10:18:55 +08:00

165 lines
3.8 KiB
Go

package service
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/yaoapp/gou/api"
"github.com/yaoapp/gou/server/http"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/openapi"
servicelog "github.com/yaoapp/yao/service/log"
"github.com/yaoapp/yao/share"
)
// Router holds the active gin.Engine so the gRPC API proxy can forward
// requests internally without an HTTP round-trip.
var Router *gin.Engine
// ServerHooks allows the caller to inject gRPC (or other) server lifecycle
// without creating import cycles.
type ServerHooks struct {
Start func(cfg config.Config) error // called before HTTP starts; nil = skip
Stop func() // called on shutdown; nil = skip
Addrs func() []string // returns listen addresses; nil = skip
}
// Service manages HTTP and optional gRPC servers as a single unit.
type Service struct {
http *http.Server
hooks ServerHooks
}
// Start launches optional hook servers (e.g. gRPC) and the HTTP server.
// Returns a Service handle for shutdown coordination.
func Start(cfg config.Config, hooks ...ServerHooks) (*Service, error) {
if cfg.AllowFrom == nil {
cfg.AllowFrom = []string{}
}
err := prepare()
if err != nil {
return nil, err
}
var h ServerHooks
if len(hooks) > 0 {
h = hooks[0]
}
// Start hook server (gRPC, etc.)
if h.Start != nil {
if err := h.Start(cfg); err != nil {
return nil, err
}
}
router := gin.New()
Router = router
router.Use(Middlewares...)
var apiRoot string
if openapi.Server != nil {
apiRoot = openapi.Server.Config.BaseURL
api.SetGuards(OpenAPIGuards())
router.Any(apiRoot+"/api/*path", DynamicAPIHandler)
api.SetRoutes(router, apiRoot, cfg.AllowFrom...)
api.BuildRouteTable()
openapi.Server.Attach(router)
} else {
apiRoot = "/api"
api.SetGuards(Guards)
api.SetRoutes(router, "/api", cfg.AllowFrom...)
}
srv := http.New(router, http.Option{
Host: cfg.Host,
Port: cfg.Port,
Root: apiRoot,
Allows: cfg.AllowFrom,
Timeout: 5 * time.Second,
})
// Start HTTP in background; wait for the first event to confirm
// the port is bound before returning.
go func() {
srv.Start()
}()
// Block until HTTP reports READY or ERROR
ev := <-srv.Event()
if ev != http.READY {
if h.Stop != nil {
h.Stop()
}
return nil, fmt.Errorf("HTTP server failed to start on %s:%d", cfg.Host, cfg.Port)
}
return &Service{http: srv, hooks: h}, nil
}
// Event returns the HTTP server event channel (READY, CLOSED, ERROR).
func (s *Service) Event() chan uint8 {
return s.http.Event()
}
// Stop shuts down hook servers (gRPC, etc.) then signals the HTTP server to close.
func (s *Service) Stop() {
if s.hooks.Stop != nil {
s.hooks.Stop()
}
s.http.Stop()
}
// HookAddrs returns the hook server listen addresses (e.g. gRPC addresses).
func (s *Service) HookAddrs() []string {
if s.hooks.Addrs != nil {
return s.hooks.Addrs()
}
return nil
}
// Watch starts file watching in development mode. Blocking; run in a goroutine.
func (s *Service) Watch(done chan uint8) {
watch(s, done)
}
// Restart the HTTP server with a fresh router (hook servers stay running).
func Restart(svc *Service, cfg config.Config) error {
router := gin.New()
Router = router
router.Use(Middlewares...)
if openapi.Server != nil {
baseURL := openapi.Server.Config.BaseURL
api.SetGuards(OpenAPIGuards())
router.Any(baseURL+"/api/*path", DynamicAPIHandler)
api.SetRoutes(router, baseURL, cfg.AllowFrom...)
api.BuildRouteTable()
openapi.Server.Attach(router)
} else {
api.SetGuards(Guards)
api.SetRoutes(router, "/api", cfg.AllowFrom...)
}
svc.http.Reset(router)
return svc.http.Restart()
}
func prepare() error {
servicelog.InitAccessLog(config.Conf.Root)
err := share.SessionStart()
if err != nil {
return err
}
err = SetupStatic()
if err != nil {
return err
}
return nil
}