Files
gopeed/pkg/rest/api_server_manager.go
2026-09-05 20:21:39 +08:00

216 lines
5.4 KiB
Go

package rest
import (
"context"
"net"
"net/http"
"time"
"github.com/GopeedLab/gopeed/pkg/base"
"github.com/GopeedLab/gopeed/pkg/rest/model"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/pkg/errors"
)
type apiServerManager struct {
server *http.Server
runningPort int
activeConfig *base.APIServerConfig
lastError string
}
var apiServer = &apiServerManager{}
func GetAPIServerState() (*model.APIServerState, error) {
runtimeMu.Lock()
defer runtimeMu.Unlock()
desired, err := desiredAPIServerConfigLocked()
return apiServer.stateLocked(desired), err
}
func StartAPIServer() (*model.APIServerState, error) {
runtimeMu.Lock()
defer runtimeMu.Unlock()
desired, err := desiredAPIServerConfigLocked()
if err != nil {
return apiServer.stateLocked(nil), err
}
if !desired.Enable {
err = errors.New("API server is disabled in persisted config")
apiServer.lastError = err.Error()
return apiServer.stateLocked(desired), err
}
_, err = apiServer.startLocked(desired)
return apiServer.stateLocked(desired), err
}
func StopAPIServer() (*model.APIServerState, error) {
runtimeMu.Lock()
defer runtimeMu.Unlock()
desired, configErr := desiredAPIServerConfigLocked()
err := apiServer.stopLocked()
if err == nil {
err = configErr
}
return apiServer.stateLocked(desired), err
}
func RestartAPIServer() (*model.APIServerState, error) {
runtimeMu.Lock()
defer runtimeMu.Unlock()
desired, err := desiredAPIServerConfigLocked()
if err != nil {
return apiServer.stateLocked(nil), err
}
if !desired.Enable {
err = errors.New("API server is disabled in persisted config")
apiServer.lastError = err.Error()
return apiServer.stateLocked(desired), err
}
err = apiServer.restartLocked(desired)
return apiServer.stateLocked(desired), err
}
func desiredAPIServerConfigLocked() (*base.APIServerConfig, error) {
if Downloader == nil {
return nil, errors.New("service not started")
}
config, err := Downloader.GetConfig()
if err != nil {
return nil, err
}
if config.API == nil {
return (&base.APIServerConfig{}).Init(), nil
}
return cloneAPIServerConfig(config.API).Init(), nil
}
func (m *apiServerManager) startLocked(config *base.APIServerConfig) (int, error) {
if config == nil {
return 0, errors.New("API server config is nil")
}
if m.server != nil {
if sameAPIServerConfig(m.activeConfig, config) {
m.lastError = ""
return m.runningPort, nil
}
err := errors.New("API server is already running with different config")
m.lastError = err.Error()
return m.runningPort, err
}
return m.startWithConfigLocked(startConfigForAPIServer(config), config)
}
func (m *apiServerManager) startWithConfigLocked(
startConfig *model.StartConfig,
activeConfig *base.APIServerConfig,
) (int, error) {
server, listener, err := buildServer(startConfig, false)
if err != nil {
m.lastError = err.Error()
return 0, err
}
m.server = server
m.runningPort = 0
m.activeConfig = cloneAPIServerConfig(activeConfig)
m.activeConfig.Enable = true
m.lastError = ""
if address, ok := listener.Addr().(*net.TCPAddr); ok {
m.runningPort = address.Port
}
go m.serve(server, listener)
return m.runningPort, nil
}
func (m *apiServerManager) serve(server *http.Server, listener net.Listener) {
err := server.Serve(listener)
if err == nil || errors.Is(err, http.ErrServerClosed) {
return
}
runtimeMu.Lock()
if m.server == server {
m.server = nil
m.runningPort = 0
m.activeConfig = nil
m.lastError = err.Error()
}
downloader := Downloader
runtimeMu.Unlock()
if downloader != nil {
downloader.Logger.Error().Err(err).Msg("API server stopped unexpectedly")
}
}
func (m *apiServerManager) stopLocked() error {
server := m.server
config := cloneAPIServerConfig(m.activeConfig)
if server == nil {
m.runningPort = 0
m.activeConfig = nil
m.lastError = ""
return nil
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
err := server.Shutdown(shutdownCtx)
cancel()
if err != nil {
if closeErr := server.Close(); closeErr != nil {
err = errors.Wrapf(err, "force close API server failed: %v", closeErr)
}
}
m.server = nil
m.runningPort = 0
m.activeConfig = nil
if config != nil && config.Network == "unix" {
util.SafeRemove(config.Address)
}
if err != nil {
m.lastError = err.Error()
return errors.Wrap(err, "shutdown API server failed")
}
m.lastError = ""
return nil
}
func (m *apiServerManager) restartLocked(desired *base.APIServerConfig) error {
if m.server == nil {
_, err := m.startLocked(desired)
return err
}
if sameAPIServerConfig(m.activeConfig, desired) {
m.lastError = ""
return nil
}
if err := m.stopLocked(); err != nil {
return err
}
_, err := m.startLocked(desired)
return err
}
func (m *apiServerManager) stateLocked(desired *base.APIServerConfig) *model.APIServerState {
state := &model.APIServerState{Running: m.server != nil, RunningPort: m.runningPort, LastError: m.lastError}
if desired != nil {
state.Enabled = desired.Enable
}
if m.activeConfig != nil {
state.MCPEnabled = m.activeConfig.MCPEnable
state.Network = m.activeConfig.Network
state.Address = m.activeConfig.Address
}
state.PendingApply = !apiServerConfigApplied(desired, m.activeConfig)
return state
}
func apiServerConfigApplied(desired, active *base.APIServerConfig) bool {
if desired == nil {
return false
}
if !desired.Enable {
return active == nil
}
return active != nil && sameAPIServerConfig(desired, active)
}