mirror of
https://github.com/nabbar/golib.git
synced 2025-10-05 07:46:56 +08:00
- Pkg HTTP Server : add config option to disabled a server
- Pkg HTTP Server : add option to force TLS as mandatory to start and server - Pkg HTTP Server : add function to listen server with multiple handler (map[key string]http.handler) - Pkg HTTP Server : add option to define an handler key to associate one server with one handler
This commit is contained in:
@@ -31,6 +31,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -132,6 +133,18 @@ type ServerConfig struct {
|
||||
getTLSDefault func() libtls.TLSConfig
|
||||
getParentContext func() context.Context
|
||||
|
||||
// Enabled allow to disable a server without clean his configuration
|
||||
Disabled bool
|
||||
|
||||
// Mandatory defined if the component for status is mandatory or not
|
||||
Mandatory bool
|
||||
|
||||
// TimeoutCacheInfo defined the validity time of cache for info (name, version, hash)
|
||||
TimeoutCacheInfo time.Duration
|
||||
|
||||
// TimeoutCacheHealth defined the validity time of cache for healthcheck of this server
|
||||
TimeoutCacheHealth time.Duration
|
||||
|
||||
/*** http options ***/
|
||||
|
||||
// ReadTimeout is the maximum duration for reading the entire
|
||||
@@ -219,12 +232,44 @@ type ServerConfig struct {
|
||||
// Expose is the address use to call this server. This can be allow to use a single fqdn to multiple server"
|
||||
Expose string `mapstructure:"expose" json:"expose" yaml:"expose" toml:"expose" validate:"required,url"`
|
||||
|
||||
// HandlerKeys is an options to associate current server with a specifc handler defined by the key
|
||||
// This allow to defined multiple server in only one config for different handler to start multiple api
|
||||
HandlerKeys string
|
||||
|
||||
// TLSMandatory is a flag to defined that TLS must be valid to start current server.
|
||||
TLSMandatory bool
|
||||
|
||||
// TLS is the tls configuration for this server.
|
||||
// To allow tls on this server, at least the TLS Config option InheritDefault must be at true and the default TLS config must be set.
|
||||
// If you don't want any tls config, just omit or set an empty struct.
|
||||
TLS libtls.Config `mapstructure:"tls" json:"tls" yaml:"tls" toml:"tls"`
|
||||
}
|
||||
|
||||
func (c *ServerConfig) Clone() ServerConfig {
|
||||
return ServerConfig{
|
||||
Disabled: c.Disabled,
|
||||
getTLSDefault: c.getTLSDefault,
|
||||
getParentContext: c.getParentContext,
|
||||
ReadTimeout: c.ReadTimeout,
|
||||
ReadHeaderTimeout: c.ReadHeaderTimeout,
|
||||
WriteTimeout: c.WriteTimeout,
|
||||
MaxHeaderBytes: c.MaxHeaderBytes,
|
||||
MaxHandlers: c.MaxHandlers,
|
||||
MaxConcurrentStreams: c.MaxConcurrentStreams,
|
||||
MaxReadFrameSize: c.MaxReadFrameSize,
|
||||
PermitProhibitedCipherSuites: c.PermitProhibitedCipherSuites,
|
||||
IdleTimeout: c.IdleTimeout,
|
||||
MaxUploadBufferPerConnection: c.MaxUploadBufferPerConnection,
|
||||
MaxUploadBufferPerStream: c.MaxUploadBufferPerStream,
|
||||
Name: c.Name,
|
||||
Listen: c.Listen,
|
||||
Expose: c.Expose,
|
||||
HandlerKeys: strings.ToLower(c.HandlerKeys),
|
||||
TLSMandatory: c.TLSMandatory,
|
||||
TLS: c.TLS,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ServerConfig) SetDefaultTLS(f func() libtls.TLSConfig) {
|
||||
c.getTLSDefault = f
|
||||
}
|
||||
@@ -317,6 +362,10 @@ func (c ServerConfig) GetExpose() *url.URL {
|
||||
return add
|
||||
}
|
||||
|
||||
func (c ServerConfig) GetHandlerKey() string {
|
||||
return c.HandlerKeys
|
||||
}
|
||||
|
||||
func (c ServerConfig) Validate() liberr.Error {
|
||||
val := validator.New()
|
||||
err := val.Struct(c)
|
||||
|
@@ -28,6 +28,7 @@ package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -35,6 +36,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/nabbar/golib/status"
|
||||
|
||||
"github.com/nabbar/golib/logger"
|
||||
|
||||
"github.com/nabbar/golib/semaphore"
|
||||
@@ -45,7 +48,8 @@ import (
|
||||
type FieldType uint8
|
||||
|
||||
const (
|
||||
FieldName FieldType = iota
|
||||
HandlerDefault = "default"
|
||||
FieldName FieldType = iota
|
||||
FieldBind
|
||||
FieldExpose
|
||||
)
|
||||
@@ -72,8 +76,13 @@ type PoolServer interface {
|
||||
WaitNotify(ctx context.Context, cancel context.CancelFunc)
|
||||
|
||||
Listen(handler http.Handler) liberr.Error
|
||||
ListenMultiHandler(handler map[string]http.Handler) liberr.Error
|
||||
Restart()
|
||||
Shutdown()
|
||||
|
||||
StatusInfo(bindAddress string) (name string, release string, hash string)
|
||||
StatusHealth(bindAddress string) error
|
||||
StatusRoute(prefix string, fctMessage status.FctMessage, sts status.RouteStatus)
|
||||
}
|
||||
|
||||
func NewPool(srv ...Server) PoolServer {
|
||||
@@ -333,6 +342,10 @@ func (p pool) WaitNotify(ctx context.Context, cancel context.CancelFunc) {
|
||||
}
|
||||
|
||||
func (p pool) Listen(handler http.Handler) liberr.Error {
|
||||
return p.ListenMultiHandler(map[string]http.Handler{HandlerDefault: handler})
|
||||
}
|
||||
|
||||
func (p pool) ListenMultiHandler(handler map[string]http.Handler) liberr.Error {
|
||||
if p.Len() < 1 {
|
||||
return nil
|
||||
}
|
||||
@@ -341,9 +354,23 @@ func (p pool) Listen(handler http.Handler) liberr.Error {
|
||||
|
||||
e = ErrorPoolListen.Error(nil)
|
||||
logger.InfoLevel.Log("Calling listen for All Servers")
|
||||
|
||||
p.MapRun(func(srv Server) {
|
||||
e.AddParentError(srv.Listen(handler))
|
||||
if len(handler) < 1 {
|
||||
e.AddParentError(srv.Listen(nil))
|
||||
} else {
|
||||
for k := range handler {
|
||||
if len(handler) == 1 {
|
||||
e.AddParentError(srv.Listen(handler[k]))
|
||||
break
|
||||
} else if strings.ToLower(k) == srv.GetHandlerKey() {
|
||||
e.AddParentError(srv.Listen(handler[k]))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.InfoLevel.Log("End of Calling listen for All Servers")
|
||||
|
||||
if !e.HasParent() {
|
||||
@@ -412,3 +439,26 @@ func (p pool) Shutdown() {
|
||||
|
||||
_ = s.WaitAll()
|
||||
}
|
||||
|
||||
func (p pool) StatusInfo(bindAddress string) (name string, release string, hash string) {
|
||||
if s := p.Get(bindAddress); s != nil {
|
||||
return s.StatusInfo()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("missing server '%s'", bindAddress), "", ""
|
||||
}
|
||||
|
||||
func (p pool) StatusHealth(bindAddress string) error {
|
||||
if s := p.Get(bindAddress); s != nil {
|
||||
return s.StatusHealth()
|
||||
}
|
||||
|
||||
return fmt.Errorf("missing server '%s'", bindAddress)
|
||||
}
|
||||
|
||||
func (p pool) StatusRoute(keyPrefix string, fctMessage status.FctMessage, sts status.RouteStatus) {
|
||||
p.MapRun(func(srv Server) {
|
||||
bind := srv.GetBindable()
|
||||
sts.ComponentNew(fmt.Sprintf("%s-%s", keyPrefix, bind), srv.StatusComponent(fctMessage))
|
||||
})
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ package httpserver
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -45,6 +46,7 @@ import (
|
||||
)
|
||||
|
||||
type srvRun struct {
|
||||
err *atomic.Value
|
||||
run *atomic.Value
|
||||
snm string
|
||||
srv *http.Server
|
||||
@@ -54,6 +56,7 @@ type srvRun struct {
|
||||
|
||||
type run interface {
|
||||
IsRunning() bool
|
||||
GetError() error
|
||||
WaitNotify()
|
||||
Listen(cfg *ServerConfig, handler http.Handler) liberr.Error
|
||||
Restart(cfg *ServerConfig)
|
||||
@@ -62,6 +65,7 @@ type run interface {
|
||||
|
||||
func newRun() run {
|
||||
return &srvRun{
|
||||
err: new(atomic.Value),
|
||||
run: new(atomic.Value),
|
||||
srv: nil,
|
||||
}
|
||||
@@ -87,7 +91,37 @@ func (s *srvRun) IsRunning() bool {
|
||||
return s.getRunning()
|
||||
}
|
||||
|
||||
func (s *srvRun) getErr() error {
|
||||
if s.err == nil {
|
||||
return nil
|
||||
} else if i := s.err.Load(); i == nil {
|
||||
return nil
|
||||
} else if e, ok := i.(error); !ok {
|
||||
return nil
|
||||
} else if e.Error() == "" {
|
||||
return nil
|
||||
} else {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
func (s *srvRun) setErr(e error) {
|
||||
if e != nil {
|
||||
s.err.Store(e)
|
||||
} else {
|
||||
s.err.Store(errors.New(""))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *srvRun) GetError() error {
|
||||
return s.getErr()
|
||||
}
|
||||
|
||||
func (s *srvRun) WaitNotify() {
|
||||
if !s.IsRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 5 seconds.
|
||||
quit := make(chan os.Signal, 1)
|
||||
@@ -113,6 +147,7 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
|
||||
return err
|
||||
}
|
||||
|
||||
sTls := cfg.TLSMandatory
|
||||
bind := cfg.GetListen().Host
|
||||
name := cfg.Name
|
||||
if name == "" {
|
||||
@@ -181,6 +216,7 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
|
||||
}
|
||||
|
||||
if e := http2.ConfigureServer(srv, s2); e != nil {
|
||||
s.setErr(e)
|
||||
return ErrorHTTP2Configure.ErrorParent(e)
|
||||
}
|
||||
|
||||
@@ -206,7 +242,7 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
|
||||
s.snm = name
|
||||
s.srv = srv
|
||||
|
||||
go func(name, host string) {
|
||||
go func(name, host string, tlsMandatory bool) {
|
||||
|
||||
defer func() {
|
||||
if s.ctx != nil && s.cnl != nil && s.ctx.Err() == nil {
|
||||
@@ -226,6 +262,8 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
|
||||
|
||||
s.setRunning(true)
|
||||
err = s.srv.ListenAndServeTLS("", "")
|
||||
} else if tlsMandatory {
|
||||
err = fmt.Errorf("missing valid server certificates")
|
||||
} else {
|
||||
liblog.InfoLevel.Logf("Server '%s' is starting with bindable: %s", name, host)
|
||||
|
||||
@@ -238,9 +276,10 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
|
||||
} else if err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
} else if err != nil {
|
||||
s.setErr(err)
|
||||
liblog.ErrorLevel.LogErrorCtxf(liblog.NilLevel, "Listen Server '%s'", err, name)
|
||||
}
|
||||
}(name, bind)
|
||||
}(name, bind, sTls)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -27,11 +27,15 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nabbar/golib/status"
|
||||
|
||||
liberr "github.com/nabbar/golib/errors"
|
||||
)
|
||||
|
||||
@@ -42,8 +46,7 @@ const (
|
||||
|
||||
type server struct {
|
||||
run *atomic.Value
|
||||
cfg *ServerConfig
|
||||
cnl context.CancelFunc
|
||||
cfg ServerConfig
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
@@ -53,6 +56,7 @@ type Server interface {
|
||||
GetName() string
|
||||
GetBindable() string
|
||||
GetExpose() string
|
||||
GetHandlerKey() string
|
||||
|
||||
IsRunning() bool
|
||||
IsTLS() bool
|
||||
@@ -62,13 +66,16 @@ type Server interface {
|
||||
Listen(handler http.Handler) liberr.Error
|
||||
Restart()
|
||||
Shutdown()
|
||||
|
||||
StatusInfo() (name string, release string, hash string)
|
||||
StatusHealth() error
|
||||
StatusComponent(message status.FctMessage) status.Component
|
||||
}
|
||||
|
||||
func NewServer(cfg *ServerConfig) Server {
|
||||
return &server{
|
||||
cfg: cfg,
|
||||
cfg: cfg.Clone(),
|
||||
run: new(atomic.Value),
|
||||
cnl: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +95,20 @@ func (s *server) setRun(r run) {
|
||||
s.run.Store(r)
|
||||
}
|
||||
|
||||
func (s *server) getErr() error {
|
||||
if r := s.getRun(); r == nil {
|
||||
return nil
|
||||
} else {
|
||||
return r.GetError()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) GetConfig() *ServerConfig {
|
||||
return s.cfg
|
||||
return &s.cfg
|
||||
}
|
||||
|
||||
func (s *server) SetConfig(cfg *ServerConfig) {
|
||||
s.cfg = cfg
|
||||
s.cfg = cfg.Clone()
|
||||
}
|
||||
|
||||
func (s server) GetName() string {
|
||||
@@ -112,6 +127,10 @@ func (s *server) GetExpose() string {
|
||||
return s.cfg.GetExpose().String()
|
||||
}
|
||||
|
||||
func (s *server) GetHandlerKey() string {
|
||||
return s.cfg.GetHandlerKey()
|
||||
}
|
||||
|
||||
func (s *server) IsRunning() bool {
|
||||
return s.getRun().IsRunning()
|
||||
}
|
||||
@@ -122,8 +141,9 @@ func (s *server) IsTLS() bool {
|
||||
|
||||
func (s *server) Listen(handler http.Handler) liberr.Error {
|
||||
r := s.getRun()
|
||||
e := r.Listen(s.cfg, handler)
|
||||
e := r.Listen(&s.cfg, handler)
|
||||
s.setRun(r)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -150,3 +170,27 @@ func (s *server) Merge(srv Server) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *server) StatusInfo() (name string, release string, hash string) {
|
||||
vers := strings.TrimLeft(runtime.Version(), "go")
|
||||
vers = strings.TrimLeft(vers, "Go")
|
||||
vers = strings.TrimLeft(vers, "GO")
|
||||
|
||||
return fmt.Sprintf("%s [%s]", s.GetName(), s.GetBindable()), vers, ""
|
||||
}
|
||||
|
||||
func (s *server) StatusHealth() error {
|
||||
if !s.cfg.Disabled && s.IsRunning() {
|
||||
return nil
|
||||
} else if s.cfg.Disabled {
|
||||
return fmt.Errorf("server disabled")
|
||||
} else if e := s.getErr(); e != nil {
|
||||
return e
|
||||
} else {
|
||||
return fmt.Errorf("server is offline -- missing error")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) StatusComponent(message status.FctMessage) status.Component {
|
||||
return status.NewComponent(s.cfg.Mandatory, s.StatusInfo, s.StatusHealth, message, s.cfg.TimeoutCacheInfo, s.cfg.TimeoutCacheHealth)
|
||||
}
|
||||
|
@@ -46,17 +46,27 @@ var tlsConfigSrv = libtls.Config{
|
||||
}
|
||||
|
||||
var cfgSrv01 = libsrv.ServerConfig{
|
||||
Name: "test-01",
|
||||
Listen: "0.0.0.0:61001",
|
||||
Expose: "0.0.0.0:61000",
|
||||
TLS: tlsConfigSrv,
|
||||
Name: "test-01",
|
||||
Listen: "0.0.0.0:61001",
|
||||
Expose: "0.0.0.0:61000",
|
||||
TLSMandatory: false,
|
||||
TLS: tlsConfigSrv,
|
||||
}
|
||||
|
||||
var cfgSrv02 = libsrv.ServerConfig{
|
||||
Name: "test-02",
|
||||
Listen: "0.0.0.0:61002",
|
||||
Expose: "0.0.0.0:61000",
|
||||
TLS: tlsConfigSrv,
|
||||
Name: "test-02",
|
||||
Listen: "0.0.0.0:61002",
|
||||
Expose: "0.0.0.0:61000",
|
||||
TLSMandatory: false,
|
||||
TLS: tlsConfigSrv,
|
||||
}
|
||||
|
||||
var cfgSrv03 = libsrv.ServerConfig{
|
||||
Name: "test-03",
|
||||
Listen: "0.0.0.0:61003",
|
||||
Expose: "0.0.0.0:61000",
|
||||
TLSMandatory: true,
|
||||
TLS: tlsConfigSrv,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -77,7 +87,7 @@ func init() {
|
||||
|
||||
ctx, cnl = context.WithCancel(context.Background())
|
||||
|
||||
cfgPool = libsrv.PoolServerConfig{cfgSrv01, cfgSrv02}
|
||||
cfgPool = libsrv.PoolServerConfig{cfgSrv01, cfgSrv02, cfgSrv03}
|
||||
cfgPool.MapUpdate(func(cfg libsrv.ServerConfig) libsrv.ServerConfig {
|
||||
cfg.SetParentContext(func() context.Context {
|
||||
return ctx
|
||||
@@ -112,6 +122,24 @@ func main() {
|
||||
|
||||
go pool.WaitNotify(ctx, cnl)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
pool.MapRun(func(srv libsrv.Server) {
|
||||
n, v, _ := srv.StatusInfo()
|
||||
if e := srv.StatusHealth(); e != nil {
|
||||
fmt.Printf("%s - %s : %v\n", n, v, e)
|
||||
} else {
|
||||
fmt.Printf("%s - %s : OK\n", n, v)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
var i = 0
|
||||
for {
|
||||
|
||||
|
Reference in New Issue
Block a user