next: upd code
This commit is contained in:
parent
ac5a96fada
commit
05eec75222
4
go.mod
4
go.mod
|
@ -4,14 +4,14 @@ go 1.23.3
|
|||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.73.3
|
||||
github.com/AdguardTeam/golibs v0.30.2
|
||||
// TODO(a.garipov): !! Update to tag!
|
||||
github.com/AdguardTeam/golibs v0.30.3-0.20241108140605-ec84c9f86663
|
||||
github.com/AdguardTeam/urlfilter v0.20.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||
github.com/digineo/go-ipset/v2 v2.2.1
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
|
|
6
go.sum
6
go.sum
|
@ -1,7 +1,7 @@
|
|||
github.com/AdguardTeam/dnsproxy v0.73.3 h1:aacr6Wu0ed94DDD+gSB6EwF8nvyq0+DAc7oFOgtgUpA=
|
||||
github.com/AdguardTeam/dnsproxy v0.73.3/go.mod h1:18ssqhDgOCiVIwYmmVuXVM05wSwrzkO2yjKhVRWJX/g=
|
||||
github.com/AdguardTeam/golibs v0.30.2 h1:urU/NAyIvQOeArBqDmKCDpaRkfTCJ26uSiSuDMKQfuY=
|
||||
github.com/AdguardTeam/golibs v0.30.2/go.mod h1:FkwcNQEJoGsgDGXcalrVa/4gWbE68KsmE2guXWtBQUE=
|
||||
github.com/AdguardTeam/golibs v0.30.3-0.20241108140605-ec84c9f86663 h1:2uhyDq3f4BV48TjXDS0itBU2iwyuWKk1HGLujIJmbd0=
|
||||
github.com/AdguardTeam/golibs v0.30.3-0.20241108140605-ec84c9f86663/go.mod h1:Ir9dlHfb8nRQsG3Qgo1zoGL+k1qMbcBtb8tcnsvzdAE=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
|
@ -25,8 +25,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
|
||||
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
|
|
|
@ -146,16 +146,6 @@ func IsOpenWrt() (ok bool) {
|
|||
return isOpenWrt()
|
||||
}
|
||||
|
||||
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
|
||||
func NotifyReconfigureSignal(c chan<- os.Signal) {
|
||||
notifyReconfigureSignal(c)
|
||||
}
|
||||
|
||||
// IsReconfigureSignal returns true if sig is a reconfigure signal.
|
||||
func IsReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return isReconfigureSignal(sig)
|
||||
}
|
||||
|
||||
// SendShutdownSignal sends the shutdown signal to the channel.
|
||||
func SendShutdownSignal(c chan<- os.Signal) {
|
||||
sendShutdownSignal(c)
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
//go:build darwin || freebsd || linux || openbsd
|
||||
//go:build unix
|
||||
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGHUP)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == unix.SIGHUP
|
||||
}
|
||||
|
||||
func sendShutdownSignal(_ chan<- os.Signal) {
|
||||
// On Unix we are already notified by the system.
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ package aghos
|
|||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func setRlimit(val uint64) (err error) {
|
||||
func setRlimit(_ uint64) (err error) {
|
||||
return Unsupported("setrlimit")
|
||||
}
|
||||
|
||||
|
@ -38,14 +37,6 @@ func isOpenWrt() (ok bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, windows.SIGHUP)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == windows.SIGHUP
|
||||
}
|
||||
|
||||
func sendShutdownSignal(c chan<- os.Signal) {
|
||||
c <- os.Interrupt
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ func (w *FSWatcher) Add(name string) (err error) {
|
|||
|
||||
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
|
||||
type ServiceWithConfig[ConfigType any] struct {
|
||||
OnStart func() (err error)
|
||||
OnStart func(ctx context.Context) (err error)
|
||||
OnShutdown func(ctx context.Context) (err error)
|
||||
OnConfig func() (c ConfigType)
|
||||
}
|
||||
|
@ -68,8 +68,8 @@ var _ agh.ServiceWithConfig[struct{}] = (*ServiceWithConfig[struct{}])(nil)
|
|||
|
||||
// Start implements the [agh.ServiceWithConfig] interface for
|
||||
// *ServiceWithConfig.
|
||||
func (s *ServiceWithConfig[_]) Start() (err error) {
|
||||
return s.OnStart()
|
||||
func (s *ServiceWithConfig[_]) Start(ctx context.Context) (err error) {
|
||||
return s.OnStart(ctx)
|
||||
}
|
||||
|
||||
// Shutdown implements the [agh.ServiceWithConfig] interface for
|
||||
|
|
|
@ -82,7 +82,7 @@ type Empty struct{}
|
|||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||
|
||||
// Start implements the [Service] interface for Empty.
|
||||
func (Empty) Start() (err error) { return nil }
|
||||
func (Empty) Start(_ context.Context) (err error) { return nil }
|
||||
|
||||
// Shutdown implements the [Service] interface for Empty.
|
||||
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||
|
|
|
@ -1,36 +1,9 @@
|
|||
// Package agh contains common entities and interfaces of AdGuard Home.
|
||||
package agh
|
||||
|
||||
import "context"
|
||||
|
||||
// Service is the interface for API servers.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a context to Start.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a Wait method or making an extension
|
||||
// interface for that.
|
||||
type Service interface {
|
||||
// Start starts the service. It does not block.
|
||||
Start() (err error)
|
||||
|
||||
// Shutdown gracefully stops the service. ctx is used to determine
|
||||
// a timeout before trying to stop the service less gracefully.
|
||||
Shutdown(ctx context.Context) (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Service = EmptyService{}
|
||||
|
||||
// EmptyService is a [Service] that does nothing.
|
||||
//
|
||||
// TODO(a.garipov): Remove if unnecessary.
|
||||
type EmptyService struct{}
|
||||
|
||||
// Start implements the [Service] interface for EmptyService.
|
||||
func (EmptyService) Start() (err error) { return nil }
|
||||
|
||||
// Shutdown implements the [Service] interface for EmptyService.
|
||||
func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
||||
import (
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
)
|
||||
|
||||
// ServiceWithConfig is an extension of the [Service] interface for services
|
||||
// that can return their configuration.
|
||||
|
@ -38,7 +11,7 @@ func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
|||
// TODO(a.garipov): Consider removing this generic interface if we figure out
|
||||
// how to make it testable in a better way.
|
||||
type ServiceWithConfig[ConfigType any] interface {
|
||||
Service
|
||||
service.Interface
|
||||
|
||||
Config() (c ConfigType)
|
||||
}
|
||||
|
@ -51,7 +24,7 @@ var _ ServiceWithConfig[struct{}] = (*EmptyServiceWithConfig[struct{}])(nil)
|
|||
//
|
||||
// TODO(a.garipov): Remove if unnecessary.
|
||||
type EmptyServiceWithConfig[ConfigType any] struct {
|
||||
EmptyService
|
||||
service.Empty
|
||||
|
||||
Conf ConfigType
|
||||
}
|
||||
|
|
|
@ -12,11 +12,15 @@ import (
|
|||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
)
|
||||
|
||||
// Main is the entry point of AdGuard Home.
|
||||
func Main(embeddedFrontend fs.FS) {
|
||||
ctx := context.Background()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
cmdName := os.Args[0]
|
||||
|
@ -26,21 +30,28 @@ func Main(embeddedFrontend fs.FS) {
|
|||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
err = setLog(opts)
|
||||
check(err)
|
||||
baseLogger := newBaseLogger(opts)
|
||||
|
||||
log.Info("starting adguard home, version %s, pid %d", version.Version(), os.Getpid())
|
||||
baseLogger.InfoContext(
|
||||
ctx,
|
||||
"starting adguard home",
|
||||
"version", version.Version(),
|
||||
"pid", os.Getpid(),
|
||||
)
|
||||
|
||||
if opts.workDir != "" {
|
||||
log.Info("changing working directory to %q", opts.workDir)
|
||||
baseLogger.InfoContext(ctx, "changing working directory", "dir", opts.workDir)
|
||||
|
||||
err = os.Chdir(opts.workDir)
|
||||
check(err)
|
||||
errors.Check(err)
|
||||
}
|
||||
|
||||
frontend, err := frontendFromOpts(opts, embeddedFrontend)
|
||||
check(err)
|
||||
frontend, err := frontendFromOpts(ctx, baseLogger, opts, embeddedFrontend)
|
||||
errors.Check(err)
|
||||
|
||||
confMgrConf := &configmgr.Config{
|
||||
BaseLogger: baseLogger,
|
||||
Logger: baseLogger.With(slogutil.KeyPrefix, "configmgr"),
|
||||
Frontend: frontend,
|
||||
WebAddr: opts.webAddr,
|
||||
Start: start,
|
||||
|
@ -48,24 +59,25 @@ func Main(embeddedFrontend fs.FS) {
|
|||
}
|
||||
|
||||
confMgr, err := newConfigMgr(confMgrConf)
|
||||
check(err)
|
||||
errors.Check(err)
|
||||
|
||||
web := confMgr.Web()
|
||||
err = web.Start()
|
||||
check(err)
|
||||
err = web.Start(ctx)
|
||||
errors.Check(err)
|
||||
|
||||
dns := confMgr.DNS()
|
||||
err = dns.Start()
|
||||
check(err)
|
||||
err = dns.Start(ctx)
|
||||
errors.Check(err)
|
||||
|
||||
sigHdlr := newSignalHandler(
|
||||
baseLogger.With(slogutil.KeyPrefix, service.SignalHandlerPrefix),
|
||||
confMgrConf,
|
||||
opts.pidFile,
|
||||
web,
|
||||
dns,
|
||||
)
|
||||
|
||||
sigHdlr.handle()
|
||||
os.Exit(sigHdlr.handle(ctx))
|
||||
}
|
||||
|
||||
// defaultTimeout is the timeout used for some operations where another timeout
|
||||
|
@ -86,10 +98,3 @@ func newConfigMgr(c *configmgr.Config) (m *configmgr.Manager, err error) {
|
|||
|
||||
return configmgr.New(ctx, c)
|
||||
}
|
||||
|
||||
// check is a simple error-checking helper. It must only be used within Main.
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
)
|
||||
|
||||
// syslogServiceName is the name of the AdGuard Home service used for writing
|
||||
// logs to the system log.
|
||||
const syslogServiceName = "AdGuardHome"
|
||||
|
||||
// setLog sets up the text logging.
|
||||
//
|
||||
// TODO(a.garipov): Add parameters from configuration file.
|
||||
func setLog(opts *options) (err error) {
|
||||
// newBaseLogger constructs a base logger based on the command-line options.
|
||||
// opts must not be nil.
|
||||
func newBaseLogger(opts *options) (baseLogger *slog.Logger) {
|
||||
var output io.Writer
|
||||
switch opts.confFile {
|
||||
case "stdout":
|
||||
log.SetOutput(os.Stdout)
|
||||
output = os.Stdout
|
||||
case "stderr":
|
||||
log.SetOutput(os.Stderr)
|
||||
output = os.Stderr
|
||||
case "syslog":
|
||||
err = aghos.ConfigureSyslog(syslogServiceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing syslog: %w", err)
|
||||
}
|
||||
// TODO(a.garipov): Add a syslog handler to golibs.
|
||||
default:
|
||||
// TODO(a.garipov): Use the path.
|
||||
}
|
||||
|
||||
lvl := slog.LevelInfo
|
||||
if opts.verbose {
|
||||
log.SetLevel(log.DEBUG)
|
||||
log.Debug("verbose logging enabled")
|
||||
lvl = slog.LevelDebug
|
||||
}
|
||||
|
||||
return nil
|
||||
return slogutil.New(&slogutil.Config{
|
||||
Output: output,
|
||||
// TODO(a.garipov): Get from config?
|
||||
Format: slogutil.FormatText,
|
||||
Level: lvl,
|
||||
// TODO(a.garipov): Get from config.
|
||||
AddTimestamp: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
|
@ -14,7 +16,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
)
|
||||
|
||||
// options contains all command-line options for the AdGuardHome(.exe) binary.
|
||||
|
@ -372,13 +374,13 @@ func processOptions(
|
|||
) (exitCode int, needExit bool) {
|
||||
if parseErr != nil {
|
||||
// Assume that usage has already been printed.
|
||||
return statusArgumentError, true
|
||||
return osutil.ExitCodeArgumentError, true
|
||||
}
|
||||
|
||||
if opts.help {
|
||||
usage(cmdName, os.Stdout)
|
||||
|
||||
return statusSuccess, true
|
||||
return osutil.ExitCodeSuccess, true
|
||||
}
|
||||
|
||||
if opts.version {
|
||||
|
@ -388,7 +390,7 @@ func processOptions(
|
|||
fmt.Printf("AdGuard Home %s\n", version.Version())
|
||||
}
|
||||
|
||||
return statusSuccess, true
|
||||
return osutil.ExitCodeSuccess, true
|
||||
}
|
||||
|
||||
if opts.checkConfig {
|
||||
|
@ -396,21 +398,26 @@ func processOptions(
|
|||
if err != nil {
|
||||
_, _ = io.WriteString(os.Stdout, err.Error()+"\n")
|
||||
|
||||
return statusError, true
|
||||
return osutil.ExitCodeFailure, true
|
||||
}
|
||||
|
||||
return statusSuccess, true
|
||||
return osutil.ExitCodeSuccess, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// frontendFromOpts returns the frontend to use based on the options.
|
||||
func frontendFromOpts(opts *options, embeddedFrontend fs.FS) (frontend fs.FS, err error) {
|
||||
func frontendFromOpts(
|
||||
ctx context.Context,
|
||||
logger *slog.Logger,
|
||||
opts *options,
|
||||
embeddedFrontend fs.FS,
|
||||
) (frontend fs.FS, err error) {
|
||||
const frontendSubdir = "build/static"
|
||||
|
||||
if opts.localFrontend {
|
||||
log.Info("warning: using local frontend files")
|
||||
logger.WarnContext(ctx, "using local frontend files")
|
||||
|
||||
return os.DirFS(frontendSubdir), nil
|
||||
}
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
)
|
||||
|
||||
// signalHandler processes incoming signals and shuts services down.
|
||||
type signalHandler struct {
|
||||
// logger is used for logging the operation of the signal handler.
|
||||
logger *slog.Logger
|
||||
|
||||
// confMgrConf contains the configuration parameters for the configuration
|
||||
// manager.
|
||||
confMgrConf *configmgr.Config
|
||||
|
@ -24,114 +32,50 @@ type signalHandler struct {
|
|||
pidFile string
|
||||
|
||||
// services are the services that are shut down before application exiting.
|
||||
services []agh.Service
|
||||
services []service.Interface
|
||||
|
||||
// shutdownTimeout is the timeout for the shutdown operation.
|
||||
shutdownTimeout time.Duration
|
||||
}
|
||||
|
||||
// handle processes OS signals.
|
||||
func (h *signalHandler) handle() {
|
||||
defer log.OnPanic("signalHandler.handle")
|
||||
// handle processes OS signals. It blocks until a termination or a
|
||||
// reconfiguration signal is received, after which it either shuts down all
|
||||
// services or reconfigures them. ctx is used for logging and serves as the
|
||||
// base for the shutdown timeout. status is [osutil.ExitCodeSuccess] on success
|
||||
// and [osutil.ExitCodeFailure] on error.
|
||||
//
|
||||
// TODO(a.garipov): Add reconfiguration logic to golibs.
|
||||
func (h *signalHandler) handle(ctx context.Context) (status osutil.ExitCode) {
|
||||
defer slogutil.RecoverAndLog(ctx, h.logger)
|
||||
|
||||
h.writePID()
|
||||
h.writePID(ctx)
|
||||
|
||||
for sig := range h.signal {
|
||||
log.Info("sighdlr: received signal %q", sig)
|
||||
h.logger.InfoContext(ctx, "received", "signal", sig)
|
||||
|
||||
if aghos.IsReconfigureSignal(sig) {
|
||||
h.reconfigure()
|
||||
} else if osutil.IsShutdownSignal(sig) {
|
||||
status := h.shutdown()
|
||||
h.removePID()
|
||||
|
||||
log.Info("sighdlr: exiting with status %d", status)
|
||||
|
||||
os.Exit(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reconfigure rereads the configuration file and updates and restarts services.
|
||||
func (h *signalHandler) reconfigure() {
|
||||
log.Info("sighdlr: reconfiguring adguard home")
|
||||
|
||||
status := h.shutdown()
|
||||
if status != statusSuccess {
|
||||
log.Info("sighdlr: reconfiguring: exiting with status %d", status)
|
||||
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): This is a very rough way to do it. Some services can be
|
||||
// reconfigured without the full shutdown, and the error handling is
|
||||
// currently not the best.
|
||||
|
||||
confMgr, err := newConfigMgr(h.confMgrConf)
|
||||
check(err)
|
||||
|
||||
web := confMgr.Web()
|
||||
err = web.Start()
|
||||
check(err)
|
||||
|
||||
dns := confMgr.DNS()
|
||||
err = dns.Start()
|
||||
check(err)
|
||||
|
||||
h.services = []agh.Service{
|
||||
dns,
|
||||
web,
|
||||
}
|
||||
|
||||
log.Info("sighdlr: successfully reconfigured adguard home")
|
||||
}
|
||||
|
||||
// Exit status constants.
|
||||
const (
|
||||
statusSuccess = 0
|
||||
statusError = 1
|
||||
statusArgumentError = 2
|
||||
)
|
||||
|
||||
// shutdown gracefully shuts down all services.
|
||||
func (h *signalHandler) shutdown() (status int) {
|
||||
ctx, cancel := ctxWithDefaultTimeout()
|
||||
defer cancel()
|
||||
|
||||
status = statusSuccess
|
||||
|
||||
log.Info("sighdlr: shutting down services")
|
||||
for i, service := range h.services {
|
||||
err := service.Shutdown(ctx)
|
||||
if osutil.IsReconfigureSignal(sig) {
|
||||
err := h.reconfigure(ctx)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: shutting down service at index %d: %s", i, err)
|
||||
status = statusError
|
||||
}
|
||||
h.logger.ErrorContext(ctx, "reconfiguration error", slogutil.KeyError, err)
|
||||
|
||||
return osutil.ExitCodeFailure
|
||||
}
|
||||
} else if osutil.IsShutdownSignal(sig) {
|
||||
status = h.shutdown(ctx)
|
||||
|
||||
h.removePID(ctx)
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// newSignalHandler returns a new signalHandler that shuts down svcs.
|
||||
func newSignalHandler(
|
||||
confMgrConf *configmgr.Config,
|
||||
pidFile string,
|
||||
svcs ...agh.Service,
|
||||
) (h *signalHandler) {
|
||||
h = &signalHandler{
|
||||
confMgrConf: confMgrConf,
|
||||
signal: make(chan os.Signal, 1),
|
||||
pidFile: pidFile,
|
||||
services: svcs,
|
||||
}
|
||||
|
||||
notifier := osutil.DefaultSignalNotifier{}
|
||||
osutil.NotifyShutdownSignal(notifier, h.signal)
|
||||
aghos.NotifyReconfigureSignal(h.signal)
|
||||
|
||||
return h
|
||||
// Shouldn't happen, since h.signal is currently never closed.
|
||||
panic("unexpected close of h.signal")
|
||||
}
|
||||
|
||||
// writePID writes the PID to the file, if needed. Any errors are reported to
|
||||
// log.
|
||||
func (h *signalHandler) writePID() {
|
||||
func (h *signalHandler) writePID(ctx context.Context) {
|
||||
if h.pidFile == "" {
|
||||
return
|
||||
}
|
||||
|
@ -143,26 +87,115 @@ func (h *signalHandler) writePID() {
|
|||
|
||||
err := aghos.WriteFile(h.pidFile, data, 0o644)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: writing pidfile: %s", err)
|
||||
h.logger.ErrorContext(ctx, "writing pidfile", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("sighdlr: wrote pid to %q", h.pidFile)
|
||||
h.logger.DebugContext(ctx, "wrote pid", "file", h.pidFile)
|
||||
}
|
||||
|
||||
// reconfigure rereads the configuration file and updates and restarts services.
|
||||
func (h *signalHandler) reconfigure(ctx context.Context) (err error) {
|
||||
h.logger.InfoContext(ctx, "reconfiguring started")
|
||||
|
||||
status := h.shutdown(ctx)
|
||||
if status != osutil.ExitCodeSuccess {
|
||||
return errors.Error("shutdown failed")
|
||||
}
|
||||
|
||||
// TODO(a.garipov): This is a very rough way to do it. Some services can
|
||||
// be reconfigured without the full shutdown, and the error handling is
|
||||
// currently not the best.
|
||||
|
||||
var errs []error
|
||||
|
||||
confMgr, err := newConfigMgr(h.confMgrConf)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("configuration manager: %w", err))
|
||||
}
|
||||
|
||||
web := confMgr.Web()
|
||||
err = web.Start(ctx)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("starting web: %w", err))
|
||||
}
|
||||
|
||||
dns := confMgr.DNS()
|
||||
err = dns.Start(ctx)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("starting dns: %w", err))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
h.services = []service.Interface{
|
||||
dns,
|
||||
web,
|
||||
}
|
||||
|
||||
h.logger.InfoContext(ctx, "reconfiguring finished")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shutdown gracefully shuts down all services.
|
||||
func (h *signalHandler) shutdown(ctx context.Context) (status int) {
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
status = osutil.ExitCodeSuccess
|
||||
|
||||
h.logger.InfoContext(ctx, "shutting down")
|
||||
for i, svc := range h.services {
|
||||
err := svc.Shutdown(ctx)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, "shutting down service", "idx", i, slogutil.KeyError, err)
|
||||
status = osutil.ExitCodeFailure
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// newSignalHandler returns a new signalHandler that shuts down svcs. logger
|
||||
// and confMgrConf must not be nil.
|
||||
func newSignalHandler(
|
||||
logger *slog.Logger,
|
||||
confMgrConf *configmgr.Config,
|
||||
pidFile string,
|
||||
svcs ...service.Interface,
|
||||
) (h *signalHandler) {
|
||||
h = &signalHandler{
|
||||
logger: logger,
|
||||
confMgrConf: confMgrConf,
|
||||
signal: make(chan os.Signal, 1),
|
||||
pidFile: pidFile,
|
||||
services: svcs,
|
||||
shutdownTimeout: defaultTimeout,
|
||||
}
|
||||
|
||||
notifier := osutil.DefaultSignalNotifier{}
|
||||
osutil.NotifyShutdownSignal(notifier, h.signal)
|
||||
osutil.NotifyReconfigureSignal(notifier, h.signal)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// removePID removes the PID file, if any.
|
||||
func (h *signalHandler) removePID() {
|
||||
func (h *signalHandler) removePID(ctx context.Context) {
|
||||
if h.pidFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err := os.Remove(h.pidFile)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: removing pidfile: %s", err)
|
||||
h.logger.ErrorContext(ctx, "removing pidfile", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("sighdlr: removed pid at %q", h.pidFile)
|
||||
h.logger.DebugContext(ctx, "removed pidfile", "file", h.pidFile)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ import (
|
|||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
)
|
||||
|
||||
// Configuration Structures
|
||||
|
||||
// config is the top-level on-disk configuration structure.
|
||||
type config struct {
|
||||
DNS *dnsConfig `yaml:"dns"`
|
||||
|
@ -19,35 +18,33 @@ type config struct {
|
|||
SchemaVersion int `yaml:"schema_version"`
|
||||
}
|
||||
|
||||
const errNoConf errors.Error = "configuration not found"
|
||||
// type check
|
||||
var _ validator = (*config)(nil)
|
||||
|
||||
// validate returns an error if the configuration structure is invalid.
|
||||
func (c *config) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Add more validations.
|
||||
|
||||
// Keep this in the same order as the fields in the config.
|
||||
validators := []struct {
|
||||
validate func() (err error)
|
||||
name string
|
||||
}{{
|
||||
validate: c.DNS.validate,
|
||||
name: "dns",
|
||||
validators := container.KeyValues[string, validator]{{
|
||||
Key: "dns",
|
||||
Value: c.DNS,
|
||||
}, {
|
||||
validate: c.HTTP.validate,
|
||||
name: "http",
|
||||
Key: "http",
|
||||
Value: c.HTTP,
|
||||
}, {
|
||||
validate: c.Log.validate,
|
||||
name: "log",
|
||||
Key: "log",
|
||||
Value: c.Log,
|
||||
}}
|
||||
|
||||
for _, v := range validators {
|
||||
err = v.validate()
|
||||
for _, kv := range validators {
|
||||
err = kv.Value.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", v.name, err)
|
||||
return fmt.Errorf("%s: %w", kv.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +62,9 @@ type dnsConfig struct {
|
|||
UseDNS64 bool `yaml:"use_dns64"`
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validator = (*dnsConfig)(nil)
|
||||
|
||||
// validate returns an error if the DNS configuration structure is invalid.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
|
@ -72,7 +72,7 @@ func (c *dnsConfig) validate() (err error) {
|
|||
// TODO(a.garipov): Add more validations.
|
||||
switch {
|
||||
case c == nil:
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
case c.UpstreamTimeout.Duration <= 0:
|
||||
return newMustBePositiveError("upstream_timeout", c.UpstreamTimeout)
|
||||
default:
|
||||
|
@ -91,13 +91,16 @@ type httpConfig struct {
|
|||
ForceHTTPS bool `yaml:"force_https"`
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validator = (*httpConfig)(nil)
|
||||
|
||||
// validate returns an error if the HTTP configuration structure is invalid.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
func (c *httpConfig) validate() (err error) {
|
||||
switch {
|
||||
case c == nil:
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
case c.Timeout.Duration <= 0:
|
||||
return newMustBePositiveError("timeout", c.Timeout)
|
||||
default:
|
||||
|
@ -114,7 +117,7 @@ type httpPprofConfig struct {
|
|||
// validate returns an error if the pprof configuration structure is invalid.
|
||||
func (c *httpPprofConfig) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -126,12 +129,15 @@ type logConfig struct {
|
|||
Verbose bool `yaml:"verbose"`
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validator = (*logConfig)(nil)
|
||||
|
||||
// validate returns an error if the HTTP configuration structure is invalid.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
func (c *logConfig) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
|
@ -19,18 +20,22 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Configuration Manager
|
||||
|
||||
// Manager handles full and partial changes in the configuration, persisting
|
||||
// them to disk if necessary.
|
||||
//
|
||||
// TODO(a.garipov): Support missing configs and default values.
|
||||
type Manager struct {
|
||||
// baseLogger is used to create loggers for other entities.
|
||||
baseLogger *slog.Logger
|
||||
|
||||
// logger is used for logging the operation of the configuration manager.
|
||||
logger *slog.Logger
|
||||
|
||||
// updMu makes sure that at most one reconfiguration is performed at a time.
|
||||
// updMu protects all fields below.
|
||||
updMu *sync.RWMutex
|
||||
|
@ -57,12 +62,24 @@ func Validate(fileName string) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return conf.validate()
|
||||
err = conf.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config contains the configuration parameters for the configuration manager.
|
||||
type Config struct {
|
||||
// BaseLogger is used to create loggers for other entities. It must not be
|
||||
// nil.
|
||||
BaseLogger *slog.Logger
|
||||
|
||||
// Logger is used for logging the operation of the configuration manager.
|
||||
// It must not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Frontend is the filesystem with the frontend files.
|
||||
Frontend fs.FS
|
||||
|
||||
|
@ -93,6 +110,8 @@ func New(ctx context.Context, c *Config) (m *Manager, err error) {
|
|||
}
|
||||
|
||||
m = &Manager{
|
||||
baseLogger: c.BaseLogger,
|
||||
logger: c.Logger,
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: c.FileName,
|
||||
|
@ -137,6 +156,7 @@ func (m *Manager) assemble(
|
|||
start time.Time,
|
||||
) (err error) {
|
||||
dnsConf := &dnssvc.Config{
|
||||
Logger: m.baseLogger.With(slogutil.KeyPrefix, "dnssvc"),
|
||||
Addresses: conf.DNS.Addresses,
|
||||
BootstrapServers: conf.DNS.BootstrapDNS,
|
||||
UpstreamServers: conf.DNS.UpstreamDNS,
|
||||
|
@ -151,6 +171,7 @@ func (m *Manager) assemble(
|
|||
}
|
||||
|
||||
webSvcConf := &websvc.Config{
|
||||
Logger: m.baseLogger.With(slogutil.KeyPrefix, "websvc"),
|
||||
Pprof: &websvc.PprofConfig{
|
||||
Port: conf.HTTP.Pprof.Port,
|
||||
Enabled: conf.HTTP.Pprof.Enabled,
|
||||
|
@ -176,7 +197,7 @@ func (m *Manager) assemble(
|
|||
}
|
||||
|
||||
// write writes the current configuration to disk.
|
||||
func (m *Manager) write() (err error) {
|
||||
func (m *Manager) write(ctx context.Context) (err error) {
|
||||
b, err := yaml.Marshal(m.current)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding: %w", err)
|
||||
|
@ -187,7 +208,7 @@ func (m *Manager) write() (err error) {
|
|||
return fmt.Errorf("writing: %w", err)
|
||||
}
|
||||
|
||||
log.Info("configmgr: written to %q", m.fileName)
|
||||
m.logger.InfoContext(ctx, "config file written", "path", m.fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -216,7 +237,7 @@ func (m *Manager) UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
|||
|
||||
m.updateCurrentDNS(c)
|
||||
|
||||
return m.write()
|
||||
return m.write(ctx)
|
||||
}
|
||||
|
||||
// updateDNS recreates the DNS service. m.updMu is expected to be locked.
|
||||
|
@ -270,7 +291,7 @@ func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
|||
|
||||
m.updateCurrentWeb(c)
|
||||
|
||||
return m.write()
|
||||
return m.write(ctx)
|
||||
}
|
||||
|
||||
// updateWeb recreates the web service. m.upd is expected to be locked.
|
||||
|
|
|
@ -3,10 +3,18 @@ package configmgr
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// validator is the interface for configuration entities that can validate
|
||||
// themselves.
|
||||
type validator interface {
|
||||
// validate returns an error if the entity isn't valid.
|
||||
validate() (err error)
|
||||
}
|
||||
|
||||
// numberOrDuration is the constraint for integer types along with
|
||||
// timeutil.Duration.
|
||||
type numberOrDuration interface {
|
||||
|
@ -20,8 +28,8 @@ type numberOrDuration interface {
|
|||
// as well.
|
||||
func newMustBePositiveError[T numberOrDuration](prop string, v T) (err error) {
|
||||
if s, ok := any(v).(fmt.Stringer); ok {
|
||||
return fmt.Errorf("%s must be positive, got %s", prop, s)
|
||||
return fmt.Errorf("%s: %w, got %s", prop, errors.ErrNotPositive, s)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s must be positive, got %d", prop, v)
|
||||
return fmt.Errorf("%s: %w, got %d", prop, errors.ErrNotPositive, v)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dnssvc
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
@ -9,6 +10,10 @@ import (
|
|||
//
|
||||
// TODO(a.garipov): Add timeout for incoming requests.
|
||||
type Config struct {
|
||||
// Logger is used for logging the operation of the web API service. It must
|
||||
// not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Addresses are the addresses on which to serve plain DNS queries.
|
||||
Addresses []netip.AddrPort
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package dnssvc
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
|
@ -28,6 +29,7 @@ import (
|
|||
// TODO(a.garipov): Consider saving a [*proxy.Config] instance for those
|
||||
// fields that are only used in [New] and [Service.Config].
|
||||
type Service struct {
|
||||
logger *slog.Logger
|
||||
proxy *proxy.Proxy
|
||||
bootstraps []string
|
||||
bootstrapResolvers []*upstream.UpstreamResolver
|
||||
|
@ -48,6 +50,7 @@ func New(c *Config) (svc *Service, err error) {
|
|||
}
|
||||
|
||||
svc = &Service{
|
||||
logger: c.Logger,
|
||||
bootstraps: c.BootstrapServers,
|
||||
upstreams: c.UpstreamServers,
|
||||
dns64Prefixes: c.DNS64Prefixes,
|
||||
|
@ -68,6 +71,7 @@ func New(c *Config) (svc *Service, err error) {
|
|||
|
||||
svc.bootstrapResolvers = resolvers
|
||||
svc.proxy, err = proxy.New(&proxy.Config{
|
||||
Logger: svc.logger,
|
||||
UDPListenAddr: udpAddrs(c.Addresses),
|
||||
TCPListenAddr: tcpAddrs(c.Addresses),
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
|
@ -153,12 +157,12 @@ func udpAddrs(addrPorts []netip.AddrPort) (udpAddrs []*net.UDPAddr) {
|
|||
}
|
||||
|
||||
// type check
|
||||
var _ agh.Service = (*Service)(nil)
|
||||
var _ agh.ServiceWithConfig[*Config] = (*Service)(nil)
|
||||
|
||||
// Start implements the [agh.Service] interface for *Service. svc may be nil.
|
||||
// After Start exits, all DNS servers have tried to start, but there is no
|
||||
// guarantee that they did. Errors from the servers are written to the log.
|
||||
func (svc *Service) Start() (err error) {
|
||||
func (svc *Service) Start(ctx context.Context) (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -170,7 +174,7 @@ func (svc *Service) Start() (err error) {
|
|||
svc.running.Store(err == nil)
|
||||
}()
|
||||
|
||||
return svc.proxy.Start(context.Background())
|
||||
return svc.proxy.Start(ctx)
|
||||
}
|
||||
|
||||
// Shutdown implements the [agh.Service] interface for *Service. svc may be
|
||||
|
@ -215,6 +219,7 @@ func (svc *Service) Config() (c *Config) {
|
|||
}
|
||||
|
||||
c = &Config{
|
||||
Logger: svc.logger,
|
||||
Addresses: addrs,
|
||||
BootstrapServers: svc.bootstraps,
|
||||
UpstreamServers: svc.upstreams,
|
||||
|
|
|
@ -6,16 +6,13 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
|
@ -59,6 +56,7 @@ func TestService(t *testing.T) {
|
|||
_, _ = testutil.RequireReceive(t, upstreamStartedCh, testTimeout)
|
||||
|
||||
c := &dnssvc.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Addresses: []netip.AddrPort{netip.MustParseAddrPort(listenAddr)},
|
||||
BootstrapServers: []string{upstreamSrv.PacketConn.LocalAddr().String()},
|
||||
UpstreamServers: []string{upstreamAddr},
|
||||
|
@ -71,7 +69,7 @@ func TestService(t *testing.T) {
|
|||
svc, err := dnssvc.New(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.Start()
|
||||
err = svc.Start(testutil.ContextWithTimeout(t, testTimeout))
|
||||
require.NoError(t, err)
|
||||
|
||||
gotConf := svc.Config()
|
||||
|
|
|
@ -3,12 +3,17 @@ package websvc
|
|||
import (
|
||||
"crypto/tls"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config is the AdGuard Home web service configuration structure.
|
||||
type Config struct {
|
||||
// Logger is used for logging the operation of the web API service. It must
|
||||
// not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Pprof is the configuration for the pprof debug API. It must not be nil.
|
||||
Pprof *PprofConfig
|
||||
|
||||
|
@ -60,15 +65,18 @@ type PprofConfig struct {
|
|||
// finished.
|
||||
func (svc *Service) Config() (c *Config) {
|
||||
c = &Config{
|
||||
Logger: svc.logger,
|
||||
Pprof: &PprofConfig{
|
||||
Port: svc.pprofPort,
|
||||
Enabled: svc.pprof != nil,
|
||||
},
|
||||
ConfigManager: svc.confMgr,
|
||||
Frontend: svc.frontend,
|
||||
TLS: svc.tls,
|
||||
// Leave Addresses and SecureAddresses empty and get the actual
|
||||
// addresses that include the :0 ones later.
|
||||
Start: svc.start,
|
||||
OverrideAddress: svc.overrideAddr,
|
||||
Timeout: svc.timeout,
|
||||
ForceHTTPS: svc.forceHTTPS,
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
)
|
||||
|
||||
// DNS Settings Handlers
|
||||
|
||||
// ReqPatchSettingsDNS describes the request to the PATCH /api/v1/settings/dns
|
||||
// HTTP API.
|
||||
type ReqPatchSettingsDNS struct {
|
||||
|
@ -60,6 +58,7 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
newConf := &dnssvc.Config{
|
||||
Logger: svc.logger,
|
||||
Addresses: req.Addresses,
|
||||
BootstrapServers: req.BootstrapServers,
|
||||
UpstreamServers: req.UpstreamServers,
|
||||
|
@ -78,7 +77,7 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
|
||||
newSvc := svc.confMgr.DNS()
|
||||
err = newSvc.Start()
|
||||
err = newSvc.Start(ctx)
|
||||
if err != nil {
|
||||
aghhttp.WriteJSONResponseError(w, r, fmt.Errorf("starting new service: %w", err))
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
|
|||
confMgr := newConfigManager()
|
||||
confMgr.onDNS = func() (s agh.ServiceWithConfig[*dnssvc.Config]) {
|
||||
return &aghtest.ServiceWithConfig[*dnssvc.Config]{
|
||||
OnStart: func() (err error) {
|
||||
OnStart: func(_ context.Context) (err error) {
|
||||
started.Store(true)
|
||||
|
||||
return nil
|
||||
|
@ -52,7 +52,7 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
|
|||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsDNS,
|
||||
Path: websvc.PathPatternV1SettingsDNS,
|
||||
}
|
||||
|
||||
req := jobj{
|
||||
|
|
|
@ -10,11 +10,9 @@ import (
|
|||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
)
|
||||
|
||||
// HTTP Settings Handlers
|
||||
|
||||
// ReqPatchSettingsHTTP describes the request to the PATCH /api/v1/settings/http
|
||||
// HTTP API.
|
||||
type ReqPatchSettingsHTTP struct {
|
||||
|
@ -53,6 +51,7 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
|
|||
}
|
||||
|
||||
newConf := &Config{
|
||||
Logger: svc.logger,
|
||||
Pprof: &PprofConfig{
|
||||
Port: svc.pprofPort,
|
||||
Enabled: svc.pprof != nil,
|
||||
|
@ -89,13 +88,13 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
|
|||
// relaunch updates the web service in the configuration manager and starts it.
|
||||
// It is intended to be used as a goroutine.
|
||||
func (svc *Service) relaunch(ctx context.Context, cancel context.CancelFunc, newConf *Config) {
|
||||
defer log.OnPanic("websvc: relaunching")
|
||||
defer slogutil.RecoverAndLog(ctx, svc.logger)
|
||||
|
||||
defer cancel()
|
||||
|
||||
err := svc.confMgr.UpdateWeb(ctx, newConf)
|
||||
if err != nil {
|
||||
log.Error("websvc: updating web: %s", err)
|
||||
svc.logger.ErrorContext(ctx, "updating web", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -106,18 +105,18 @@ func (svc *Service) relaunch(ctx context.Context, cancel context.CancelFunc, new
|
|||
var newSvc agh.ServiceWithConfig[*Config]
|
||||
for newSvc = svc.confMgr.Web(); newSvc == svc; {
|
||||
if time.Since(updStart) >= maxUpdDur {
|
||||
log.Error("websvc: failed to update svc after %s", maxUpdDur)
|
||||
svc.logger.ErrorContext(ctx, "failed to update service on time", "duration", maxUpdDur)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("websvc: waiting for new websvc to be configured")
|
||||
svc.logger.DebugContext(ctx, "waiting for new service")
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
err = newSvc.Start()
|
||||
err = newSvc.Start(ctx)
|
||||
if err != nil {
|
||||
log.Error("websvc: new svc failed to start with error: %s", err)
|
||||
svc.logger.ErrorContext(ctx, "new service failed", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -27,6 +28,7 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
|
|||
}
|
||||
|
||||
svc, err := websvc.New(&websvc.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Pprof: &websvc.PprofConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
|
@ -48,7 +50,7 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
|
|||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsHTTP,
|
||||
Path: websvc.PathPatternV1SettingsHTTP,
|
||||
}
|
||||
|
||||
req := jobj{
|
||||
|
|
|
@ -2,15 +2,11 @@ package websvc
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// Middlewares
|
||||
|
||||
// jsonMw sets the content type of the response to application/json.
|
||||
func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -21,18 +17,3 @@ func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
|
|||
|
||||
return http.HandlerFunc(f)
|
||||
}
|
||||
|
||||
// logMw logs the queries with level debug.
|
||||
func logMw(h http.Handler) (wrapped http.HandlerFunc) {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
m, u := r.Method, r.RequestURI
|
||||
|
||||
log.Debug("websvc: %s %s started", m, u)
|
||||
defer func() { log.Debug("websvc: %s %s finished in %s", m, u, time.Since(start)) }()
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(f)
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package websvc
|
||||
|
||||
// Path constants
|
||||
const (
|
||||
PathRoot = "/"
|
||||
PathFrontend = "/*filepath"
|
||||
|
||||
PathHealthCheck = "/health-check"
|
||||
|
||||
PathV1SettingsAll = "/api/v1/settings/all"
|
||||
PathV1SettingsDNS = "/api/v1/settings/dns"
|
||||
PathV1SettingsHTTP = "/api/v1/settings/http"
|
||||
PathV1SystemInfo = "/api/v1/system/info"
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
package websvc
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Path pattern constants.
|
||||
const (
|
||||
PathPatternFrontend = "/"
|
||||
PathPatternHealthCheck = "/health-check"
|
||||
PathPatternV1SettingsAll = "/api/v1/settings/all"
|
||||
PathPatternV1SettingsDNS = "/api/v1/settings/dns"
|
||||
PathPatternV1SettingsHTTP = "/api/v1/settings/http"
|
||||
PathPatternV1SystemInfo = "/api/v1/system/info"
|
||||
)
|
||||
|
||||
// Route pattern constants.
|
||||
const (
|
||||
routePatternFrontend = http.MethodGet + " " + PathPatternFrontend
|
||||
routePatternGetV1SettingsAll = http.MethodGet + " " + PathPatternV1SettingsAll
|
||||
routePatternGetV1SystemInfo = http.MethodGet + " " + PathPatternV1SystemInfo
|
||||
routePatternHealthCheck = http.MethodGet + " " + PathPatternHealthCheck
|
||||
routePatternPatchV1SettingsDNS = http.MethodPatch + " " + PathPatternV1SettingsDNS
|
||||
routePatternPatchV1SettingsHTTP = http.MethodPatch + " " + PathPatternV1SettingsHTTP
|
||||
)
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -39,6 +40,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
|
|||
confMgr := newConfigManager()
|
||||
confMgr.onDNS = func() (s agh.ServiceWithConfig[*dnssvc.Config]) {
|
||||
c, err := dnssvc.New(&dnssvc.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Addresses: wantDNS.Addresses,
|
||||
UpstreamServers: wantDNS.UpstreamServers,
|
||||
BootstrapServers: wantDNS.BootstrapServers,
|
||||
|
@ -51,6 +53,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
|
|||
}
|
||||
|
||||
svc, err := websvc.New(&websvc.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Pprof: &websvc.PprofConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
|
@ -72,7 +75,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
|
|||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SettingsAll,
|
||||
Path: websvc.PathPatternV1SettingsAll,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestService_handleGetV1SystemInfo(t *testing.T) {
|
|||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathV1SystemInfo,
|
||||
Path: websvc.PathPatternV1SystemInfo,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
|
|
|
@ -12,9 +12,11 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -22,10 +24,10 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/mathutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/httputil"
|
||||
httptreemux "github.com/dimfeld/httptreemux/v5"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
)
|
||||
|
||||
// ConfigManager is the configuration manager interface.
|
||||
|
@ -40,18 +42,25 @@ type ConfigManager interface {
|
|||
// Service is the AdGuard Home web service. A nil *Service is a valid
|
||||
// [agh.Service] that does nothing.
|
||||
type Service struct {
|
||||
logger *slog.Logger
|
||||
confMgr ConfigManager
|
||||
frontend fs.FS
|
||||
tls *tls.Config
|
||||
pprof *http.Server
|
||||
pprof *server
|
||||
start time.Time
|
||||
overrideAddr netip.AddrPort
|
||||
servers []*http.Server
|
||||
servers []*server
|
||||
timeout time.Duration
|
||||
pprofPort uint16
|
||||
forceHTTPS bool
|
||||
}
|
||||
|
||||
// server is a wrapper around http.Server with additional information.
|
||||
type server struct {
|
||||
logURL *url.URL
|
||||
http.Server
|
||||
}
|
||||
|
||||
// New returns a new properly initialized *Service. If c is nil, svc is a nil
|
||||
// *Service that does nothing. The fields of c must not be modified after
|
||||
// calling New.
|
||||
|
@ -64,6 +73,7 @@ func New(c *Config) (svc *Service, err error) {
|
|||
}
|
||||
|
||||
svc = &Service{
|
||||
logger: c.Logger,
|
||||
confMgr: c.ConfigManager,
|
||||
frontend: c.Frontend,
|
||||
tls: c.TLS,
|
||||
|
@ -76,14 +86,14 @@ func New(c *Config) (svc *Service, err error) {
|
|||
mux := newMux(svc)
|
||||
|
||||
if svc.overrideAddr != (netip.AddrPort{}) {
|
||||
svc.servers = []*http.Server{newSrv(svc.overrideAddr, nil, mux, c.Timeout)}
|
||||
svc.servers = []*server{newServer(svc.logger, svc.overrideAddr, nil, mux, c.Timeout)}
|
||||
} else {
|
||||
for _, a := range c.Addresses {
|
||||
svc.servers = append(svc.servers, newSrv(a, nil, mux, c.Timeout))
|
||||
svc.servers = append(svc.servers, newServer(svc.logger, a, nil, mux, c.Timeout))
|
||||
}
|
||||
|
||||
for _, a := range c.SecureAddresses {
|
||||
svc.servers = append(svc.servers, newSrv(a, c.TLS, mux, c.Timeout))
|
||||
svc.servers = append(svc.servers, newServer(svc.logger, a, c.TLS, mux, c.Timeout))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,18 +123,31 @@ func (svc *Service) setupPprof(c *PprofConfig) {
|
|||
addr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), c.Port)
|
||||
|
||||
// TODO(a.garipov): Consider making pprof timeout configurable.
|
||||
svc.pprof = newSrv(addr, nil, pprofMux, 10*time.Minute)
|
||||
svc.pprof = newServer(svc.logger, addr, nil, pprofMux, 10*time.Minute)
|
||||
}
|
||||
|
||||
// newSrv returns a new *http.Server with the given parameters.
|
||||
func newSrv(
|
||||
// newServer returns a new *server with the given parameters.
|
||||
func newServer(
|
||||
logger *slog.Logger,
|
||||
addr netip.AddrPort,
|
||||
tlsConf *tls.Config,
|
||||
h http.Handler,
|
||||
timeout time.Duration,
|
||||
) (srv *http.Server) {
|
||||
) (srv *server) {
|
||||
addrStr := addr.String()
|
||||
srv = &http.Server{
|
||||
logURL := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addrStr,
|
||||
}
|
||||
|
||||
if tlsConf != nil {
|
||||
logURL.Scheme = urlutil.SchemeHTTPS
|
||||
}
|
||||
|
||||
l := logger.With("addr", logURL)
|
||||
return &server{
|
||||
logURL: logURL,
|
||||
Server: http.Server{
|
||||
Addr: addrStr,
|
||||
Handler: h,
|
||||
TLSConfig: tlsConf,
|
||||
|
@ -132,64 +155,47 @@ func newSrv(
|
|||
WriteTimeout: timeout,
|
||||
IdleTimeout: timeout,
|
||||
ReadHeaderTimeout: timeout,
|
||||
ErrorLog: slog.NewLogLogger(l.Handler(), slog.LevelError),
|
||||
},
|
||||
}
|
||||
|
||||
if tlsConf == nil {
|
||||
srv.ErrorLog = log.StdLog("websvc: plain http: "+addrStr, log.ERROR)
|
||||
} else {
|
||||
srv.ErrorLog = log.StdLog("websvc: https: "+addrStr, log.ERROR)
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
// newMux returns a new HTTP request multiplexer for the AdGuard Home web
|
||||
// service.
|
||||
func newMux(svc *Service) (mux *httptreemux.ContextMux) {
|
||||
mux = httptreemux.NewContextMux()
|
||||
func newMux(svc *Service) (mux *http.ServeMux) {
|
||||
mux = http.NewServeMux()
|
||||
|
||||
routes := []struct {
|
||||
handler http.HandlerFunc
|
||||
method string
|
||||
pattern string
|
||||
isJSON bool
|
||||
}{{
|
||||
handler: svc.handleGetHealthCheck,
|
||||
method: http.MethodGet,
|
||||
pattern: PathHealthCheck,
|
||||
pattern: routePatternHealthCheck,
|
||||
isJSON: false,
|
||||
}, {
|
||||
handler: http.FileServer(http.FS(svc.frontend)).ServeHTTP,
|
||||
method: http.MethodGet,
|
||||
pattern: PathFrontend,
|
||||
isJSON: false,
|
||||
}, {
|
||||
handler: http.FileServer(http.FS(svc.frontend)).ServeHTTP,
|
||||
method: http.MethodGet,
|
||||
pattern: PathRoot,
|
||||
pattern: routePatternFrontend,
|
||||
isJSON: false,
|
||||
}, {
|
||||
handler: svc.handleGetSettingsAll,
|
||||
method: http.MethodGet,
|
||||
pattern: PathV1SettingsAll,
|
||||
pattern: routePatternGetV1SettingsAll,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handlePatchSettingsDNS,
|
||||
method: http.MethodPatch,
|
||||
pattern: PathV1SettingsDNS,
|
||||
pattern: routePatternPatchV1SettingsDNS,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handlePatchSettingsHTTP,
|
||||
method: http.MethodPatch,
|
||||
pattern: PathV1SettingsHTTP,
|
||||
pattern: routePatternPatchV1SettingsHTTP,
|
||||
isJSON: true,
|
||||
}, {
|
||||
handler: svc.handleGetV1SystemInfo,
|
||||
method: http.MethodGet,
|
||||
pattern: PathV1SystemInfo,
|
||||
pattern: routePatternGetV1SystemInfo,
|
||||
isJSON: true,
|
||||
}}
|
||||
|
||||
logMw := httputil.NewLogMiddleware(svc.logger, slog.LevelDebug)
|
||||
for _, r := range routes {
|
||||
var hdlr http.Handler
|
||||
if r.isJSON {
|
||||
|
@ -198,7 +204,7 @@ func newMux(svc *Service) (mux *httptreemux.ContextMux) {
|
|||
hdlr = r.handler
|
||||
}
|
||||
|
||||
mux.Handle(r.method, r.pattern, logMw(hdlr))
|
||||
mux.Handle(r.pattern, logMw.Wrap(hdlr))
|
||||
}
|
||||
|
||||
return mux
|
||||
|
@ -237,12 +243,14 @@ func (svc *Service) handleGetHealthCheck(w http.ResponseWriter, _ *http.Request)
|
|||
}
|
||||
|
||||
// type check
|
||||
var _ agh.Service = (*Service)(nil)
|
||||
var _ agh.ServiceWithConfig[*Config] = (*Service)(nil)
|
||||
|
||||
// Start implements the [agh.Service] interface for *Service. svc may be nil.
|
||||
// After Start exits, all HTTP servers have tried to start, possibly failing and
|
||||
// writing error messages to the log.
|
||||
func (svc *Service) Start() (err error) {
|
||||
//
|
||||
// TODO(a.garipov): Use the context.
|
||||
func (svc *Service) Start(ctx context.Context) (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -253,11 +261,11 @@ func (svc *Service) Start() (err error) {
|
|||
wg := &sync.WaitGroup{}
|
||||
wg.Add(srvNum)
|
||||
for _, srv := range svc.servers {
|
||||
go serve(srv, wg)
|
||||
go serve(ctx, svc.logger, srv, wg)
|
||||
}
|
||||
|
||||
if pprofEnabled {
|
||||
go serve(svc.pprof, wg)
|
||||
go serve(ctx, svc.logger, svc.pprof, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
@ -266,29 +274,28 @@ func (svc *Service) Start() (err error) {
|
|||
}
|
||||
|
||||
// serve starts and runs srv and writes all errors into its log.
|
||||
func serve(srv *http.Server, wg *sync.WaitGroup) {
|
||||
addr := srv.Addr
|
||||
defer log.OnPanic(addr)
|
||||
func serve(ctx context.Context, logger *slog.Logger, srv *server, wg *sync.WaitGroup) {
|
||||
defer slogutil.RecoverAndLog(ctx, logger)
|
||||
|
||||
var proto string
|
||||
var l net.Listener
|
||||
var err error
|
||||
addr := srv.Addr
|
||||
if srv.TLSConfig == nil {
|
||||
proto = "http"
|
||||
l, err = net.Listen("tcp", addr)
|
||||
} else {
|
||||
proto = "https"
|
||||
l, err = tls.Listen("tcp", addr, srv.TLSConfig)
|
||||
}
|
||||
if err != nil {
|
||||
srv.ErrorLog.Printf("starting srv %s: binding: %s", addr, err)
|
||||
logger.WarnContext(ctx, "binding", "tcp_addr", addr, slogutil.KeyError, err)
|
||||
}
|
||||
|
||||
// Update the server's address in case the address had the port zero, which
|
||||
// would mean that a random available port was automatically chosen.
|
||||
srv.Addr = l.Addr().String()
|
||||
srv.logURL.Host = srv.Addr
|
||||
|
||||
log.Info("websvc: starting srv %s://%s", proto, srv.Addr)
|
||||
logger = logger.With("addr", srv.logURL)
|
||||
logger.InfoContext(ctx, "starting")
|
||||
|
||||
l = &waitListener{
|
||||
Listener: l,
|
||||
|
@ -297,7 +304,7 @@ func serve(srv *http.Server, wg *sync.WaitGroup) {
|
|||
|
||||
err = srv.Serve(l)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
srv.ErrorLog.Printf("starting srv %s: %s", addr, err)
|
||||
logger.WarnContext(ctx, "starting server", "tcp_addr", addr, slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/golibs/testutil/fakefs"
|
||||
|
@ -22,10 +23,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
|
@ -90,6 +87,7 @@ func newTestServer(
|
|||
t.Helper()
|
||||
|
||||
c := &websvc.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Pprof: &websvc.PprofConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
|
@ -108,7 +106,7 @@ func newTestServer(
|
|||
svc, err := websvc.New(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = svc.Start()
|
||||
err = svc.Start(testutil.ContextWithTimeout(t, testTimeout))
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
||||
return svc.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
|
||||
|
@ -184,7 +182,7 @@ func TestService_Start_getHealthCheck(t *testing.T) {
|
|||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: addr.String(),
|
||||
Path: websvc.PathHealthCheck,
|
||||
Path: websvc.PathPatternHealthCheck,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
|
|
Loading…
Reference in New Issue