Pull request 2303: AGDNS-2505-upd-next
Squashed commit of the following: commit 586b0eb180afc22d06d673756dd916c17a173361 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Nov 12 19:58:56 2024 +0300 next: upd more commit d729aa150f7ac367255830cceca40b8880c51015 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Nov 12 16:53:15 2024 +0300 next/websvc: upd more commit 0c64e6cfc66b9212f077b2de7450586fd4d02802 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 11 21:08:51 2024 +0300 next: upd more commit 05eec75222360708621c99d3eeac7c8d9f2a5080 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 8 19:20:02 2024 +0300 next: upd code
This commit is contained in:
156
internal/next/websvc/server.go
Normal file
156
internal/next/websvc/server.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
)
|
||||
|
||||
// server contains an *http.Server as well as entities and data associated with
|
||||
// it.
|
||||
//
|
||||
// TODO(a.garipov): Join with similar structs in other projects and move to
|
||||
// golibs/netutil/httputil.
|
||||
//
|
||||
// TODO(a.garipov): Once the above standardization is complete, consider
|
||||
// merging debugsvc and websvc into a single httpsvc.
|
||||
type server struct {
|
||||
// mu protects http, logger, tcpListener, and url.
|
||||
mu *sync.Mutex
|
||||
http *http.Server
|
||||
logger *slog.Logger
|
||||
tcpListener *net.TCPListener
|
||||
url *url.URL
|
||||
|
||||
tlsConf *tls.Config
|
||||
initialAddr netip.AddrPort
|
||||
}
|
||||
|
||||
// loggerKeyServer is the key used by [server] to identify itself.
|
||||
const loggerKeyServer = "server"
|
||||
|
||||
// newServer returns a *server that is ready to serve HTTP queries. The TCP
|
||||
// listener is not started. handler must not be nil.
|
||||
func newServer(
|
||||
baseLogger *slog.Logger,
|
||||
initialAddr netip.AddrPort,
|
||||
tlsConf *tls.Config,
|
||||
handler http.Handler,
|
||||
timeout time.Duration,
|
||||
) (s *server) {
|
||||
u := &url.URL{
|
||||
Scheme: urlutil.SchemeHTTP,
|
||||
Host: initialAddr.String(),
|
||||
}
|
||||
|
||||
if tlsConf != nil {
|
||||
u.Scheme = urlutil.SchemeHTTPS
|
||||
}
|
||||
|
||||
logger := baseLogger.With(loggerKeyServer, u)
|
||||
|
||||
return &server{
|
||||
mu: &sync.Mutex{},
|
||||
http: &http.Server{
|
||||
Handler: handler,
|
||||
ReadTimeout: timeout,
|
||||
ReadHeaderTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
IdleTimeout: timeout,
|
||||
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
|
||||
},
|
||||
logger: logger,
|
||||
url: u,
|
||||
|
||||
tlsConf: tlsConf,
|
||||
initialAddr: initialAddr,
|
||||
}
|
||||
}
|
||||
|
||||
// localAddr returns the local address of the server if the server has started
|
||||
// listening; otherwise, it returns nil.
|
||||
func (s *server) localAddr() (addr net.Addr) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if l := s.tcpListener; l != nil {
|
||||
return l.Addr()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serve starts s. baseLogger is used as a base logger for s. If s fails to
|
||||
// serve with anything other than [http.ErrServerClosed], it causes an unhandled
|
||||
// panic. It is intended to be used as a goroutine.
|
||||
//
|
||||
// TODO(a.garipov): Improve error handling.
|
||||
func (s *server) serve(ctx context.Context, baseLogger *slog.Logger) {
|
||||
l, err := net.ListenTCP("tcp", net.TCPAddrFromAddrPort(s.initialAddr))
|
||||
if err != nil {
|
||||
s.logger.ErrorContext(ctx, "listening tcp", slogutil.KeyError, err)
|
||||
|
||||
panic(fmt.Errorf("websvc: listening tcp: %w", err))
|
||||
}
|
||||
|
||||
func() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.tcpListener = l
|
||||
|
||||
// Reassign the address in case the port was zero.
|
||||
s.url.Host = l.Addr().String()
|
||||
s.logger = baseLogger.With(loggerKeyServer, s.url)
|
||||
s.http.ErrorLog = slog.NewLogLogger(s.logger.Handler(), slog.LevelError)
|
||||
}()
|
||||
|
||||
s.logger.InfoContext(ctx, "starting")
|
||||
defer s.logger.InfoContext(ctx, "started")
|
||||
|
||||
err = s.http.Serve(l)
|
||||
if err == nil || errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.ErrorContext(ctx, "serving", slogutil.KeyError, err)
|
||||
|
||||
panic(fmt.Errorf("websvc: serving: %w", err))
|
||||
}
|
||||
|
||||
// shutdown shuts s down.
|
||||
func (s *server) shutdown(ctx context.Context) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
err = s.http.Shutdown(ctx)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("shutting down server %s: %w", s.url, err))
|
||||
}
|
||||
|
||||
// Close the listener separately, as it might not have been closed if the
|
||||
// context has been canceled.
|
||||
//
|
||||
// NOTE: The listener could remain uninitialized if [net.ListenTCP] failed
|
||||
// in [s.serve].
|
||||
if l := s.tcpListener; l != nil {
|
||||
err = l.Close()
|
||||
if err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs = append(errs, fmt.Errorf("closing listener for server %s: %w", s.url, err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
Reference in New Issue
Block a user