Files
golib/httpserver/config.go
Nicolas JUHEL 22b364593e Package certificates:
- rework package to allow convert config to model and retrive config from model
- add sub package to manage cipher, curves, auth client, tls version, certificates, root ca...
- add some small test (can be expande to having more coverage)
- optimize some code

Package httpcli:
- update code following change in certificates

Package httpserver:
- update code following change in certificates

Package Config/Components:
- update code following change in certificates

Package FTPClient:
- update code following change in certificates

Package Nats:
- update code following change in certificates
2024-12-04 17:30:22 +01:00

520 lines
16 KiB
Go

/*
* MIT License
*
* Copyright (c) 2022 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
package httpserver
import (
"context"
"fmt"
"net"
"net/url"
"strings"
libval "github.com/go-playground/validator/v10"
libtls "github.com/nabbar/golib/certificates"
libctx "github.com/nabbar/golib/context"
libdur "github.com/nabbar/golib/duration"
srvtps "github.com/nabbar/golib/httpserver/types"
liblog "github.com/nabbar/golib/logger"
logcfg "github.com/nabbar/golib/logger/config"
moncfg "github.com/nabbar/golib/monitor/types"
)
const (
cfgConfig = "cfgConfig"
cfgName = "cfgName"
cfgListen = "cfgListen"
cfgExpose = "cfgExpose"
cfgHandler = "cfgHandler"
cfgHandlerKey = "cfgHandlerKey"
cfgDisabled = "cfgDisabled"
cfgMonitor = "cfgMonitor"
cfgTLS = "cfgTLS"
cfgTLSMandatory = "cfgTLSMandatory"
cfgServerOptions = "cfgServerOptions"
)
// nolint #maligned
type Config struct {
// Name is the name of the current srv
// the configuration allow multipke srv, which each one must be identify by a name
// If not defined, will use the listen address
Name string `mapstructure:"name" json:"name" yaml:"name" toml:"name" validate:"required"`
// Listen is the local address (ip, hostname, unix socket, ...) with a port
// The srv will bind with this address only and listen for the port defined
Listen string `mapstructure:"listen" json:"listen" yaml:"listen" toml:"listen" validate:"required,hostname_port"`
// Expose is the address use to call this srv. This can be allow to use a single fqdn to multiple srv"
Expose string `mapstructure:"expose" json:"expose" yaml:"expose" toml:"expose" validate:"required,url"`
// HandlerKey is an options to associate current srv with a specifc handler defined by the key
// This key allow to defined multiple srv in only one config for different handler to start multiple api
HandlerKey string `mapstructure:"handler_key" json:"handler_key" yaml:"handler_key" toml:"handler_key"`
//private
getTLSDefault libtls.FctTLSDefault
//private
getParentContext libctx.FuncContext
//private
getHandlerFunc srvtps.FuncHandler
// Enabled allow to disable a srv without clean his configuration
Disabled bool `mapstructure:"disabled" json:"disabled" yaml:"disabled" toml:"disabled"`
// Monitor defined the monitoring options to monitor the status & metrics about the health of this srv
Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor"`
// TLSMandatory is a flag to defined that TLS must be valid to start current srv.
TLSMandatory bool `mapstructure:"tls_mandatory" json:"tls_mandatory" yaml:"tls_mandatory" toml:"tls_mandatory"`
// TLS is the tls configuration for this srv.
// To allow tls on this srv, 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"`
/*** http options ***/
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout libdur.Duration `mapstructure:"read_timeout" json:"read_timeout" yaml:"read_timeout" toml:"read_timeout"`
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body. If ReadHeaderTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
ReadHeaderTimeout libdur.Duration `mapstructure:"read_header_timeout" json:"read_header_timeout" yaml:"read_header_timeout" toml:"read_header_timeout"`
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout libdur.Duration `mapstructure:"write_timeout" json:"write_timeout" yaml:"write_timeout" toml:"write_timeout"`
// MaxHeaderBytes controls the maximum number of bytes the
// srv will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int `mapstructure:"max_header_bytes" json:"max_header_bytes" yaml:"max_header_bytes" toml:"max_header_bytes"`
/*** http2 options ***/
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
// which may run at a time over all connections.
// Negative or zero no limit.
MaxHandlers int `mapstructure:"max_handlers" json:"max_handlers" yaml:"max_handlers" toml:"max_handlers"`
// MaxConcurrentStreams optionally specifies the number of
// concurrent streams that each client may have open at a
// time. This is unrelated to the number of http.Handler goroutines
// which may be active globally, which is MaxHandlers.
// If zero, MaxConcurrentStreams defaults to at least 100, per
// the HTTP/2 spec's recommendations.
MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams" json:"max_concurrent_streams" yaml:"max_concurrent_streams" toml:"max_concurrent_streams"`
// MaxReadFrameSize optionally specifies the largest frame
// this srv is willing to read. A valid value is between
// 16k and 16M, inclusive. If zero or otherwise invalid, a
// default value is used.
MaxReadFrameSize uint32 `mapstructure:"max_read_frame_size" json:"max_read_frame_size" yaml:"max_read_frame_size" toml:"max_read_frame_size"`
// PermitProhibitedCipherSuites, if true, permits the use of
// cipher suites prohibited by the HTTP/2 spec.
PermitProhibitedCipherSuites bool `mapstructure:"permit_prohibited_cipher_suites" json:"permit_prohibited_cipher_suites" yaml:"permit_prohibited_cipher_suites" toml:"permit_prohibited_cipher_suites"`
// IdleTimeout specifies how long until idle clients should be
// closed with a GOAWAY frame. PING frames are not considered
// activity for the purposes of IdleTimeout.
IdleTimeout libdur.Duration `mapstructure:"idle_timeout" json:"idle_timeout" yaml:"idle_timeout" toml:"idle_timeout"`
// MaxUploadBufferPerConnection is the size of the initial flow
// control window for each connections. The HTTP/2 spec does not
// allow this to be smaller than 65535 or larger than 2^32-1.
// If the value is outside this range, a default value will be
// used instead.
MaxUploadBufferPerConnection int32 `mapstructure:"max_upload_buffer_per_connection" json:"max_upload_buffer_per_connection" yaml:"max_upload_buffer_per_connection" toml:"max_upload_buffer_per_connection"`
// MaxUploadBufferPerStream is the size of the initial flow control
// window for each stream. The HTTP/2 spec does not allow this to
// be larger than 2^32-1. If the value is zero or larger than the
// maximum, a default value will be used instead.
MaxUploadBufferPerStream int32 `mapstructure:"max_upload_buffer_per_stream" json:"max_upload_buffer_per_stream" yaml:"max_upload_buffer_per_stream" toml:"max_upload_buffer_per_stream"`
// DisableKeepAlive controls whether HTTP keep-alives are disabled.
// By default, keep-alives are always enabled. Only very
// resource-constrained environments or servers in the process of
// shutting down should disable them.
DisableKeepAlive bool `mapstructure:"disable_keep_alive" json:"disable_keep_alive" yaml:"disable_keep_alive" toml:"disable_keep_alive"`
// Logger is used to define the logger options.
Logger logcfg.Options `mapstructure:"logger" json:"logger" yaml:"logger" toml:"logger"`
}
func (c *Config) Clone() Config {
return Config{
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,
DisableKeepAlive: c.DisableKeepAlive,
Name: c.Name,
Listen: c.Listen,
Expose: c.Expose,
HandlerKey: strings.ToLower(c.HandlerKey),
TLSMandatory: c.TLSMandatory,
TLS: libtls.Config{
CurveList: c.TLS.CurveList,
CipherList: c.TLS.CipherList,
RootCA: c.TLS.RootCA,
ClientCA: c.TLS.ClientCA,
Certs: c.TLS.Certs,
VersionMin: c.TLS.VersionMin,
VersionMax: c.TLS.VersionMax,
AuthClient: c.TLS.AuthClient,
InheritDefault: c.TLS.InheritDefault,
DynamicSizingDisable: c.TLS.DynamicSizingDisable,
SessionTicketDisable: c.TLS.SessionTicketDisable,
},
Monitor: c.Monitor.Clone(),
}
}
func (c *Config) RegisterHandlerFunc(hdl srvtps.FuncHandler) {
c.getHandlerFunc = hdl
}
func (c *Config) SetDefaultTLS(f libtls.FctTLSDefault) {
c.getTLSDefault = f
}
func (c *Config) SetContext(f libctx.FuncContext) {
c.getParentContext = f
}
func (c *Config) GetTLS() (libtls.TLSConfig, error) {
var def libtls.TLSConfig
if c.TLS.InheritDefault && c.getTLSDefault != nil {
def = c.getTLSDefault()
}
if cfg := c.TLS.NewFrom(def); cfg != nil {
return cfg, nil
}
return nil, fmt.Errorf("no tls configuration found")
}
func (c *Config) CheckTLS() (libtls.TLSConfig, error) {
if ssl, err := c.GetTLS(); err != nil {
return nil, err
} else if ssl == nil || ssl.LenCertificatePair() < 1 {
return nil, ErrorServerValidate.Error(fmt.Errorf("not certificates defined"))
} else {
return ssl, nil
}
}
func (c *Config) IsTLS() bool {
if _, err := c.CheckTLS(); err == nil {
return true
}
return false
}
func (c *Config) context() context.Context {
var ctx context.Context
if c.getParentContext != nil {
ctx = c.getParentContext()
}
if ctx == nil {
return context.Background()
}
return ctx
}
func (c *Config) GetListen() *url.URL {
var (
err error
add *url.URL
)
if c.Listen != "" {
if add, err = url.Parse(c.Listen); err != nil {
if host, prt, e := net.SplitHostPort(c.Listen); e == nil {
add = &url.URL{
Host: fmt.Sprintf("%s:%s", host, prt),
}
} else {
add = nil
}
}
}
if add == nil && c.Expose != "" {
if add, err = url.Parse(c.Expose); err != nil {
add = nil
}
}
return add
}
func (c *Config) GetExpose() *url.URL {
var (
err error
add *url.URL
)
if c.Expose != "" {
if add, err = url.Parse(c.Expose); err != nil {
add = nil
}
}
if add == nil {
if add = c.GetListen(); add != nil {
if c.IsTLS() {
add.Scheme = "https"
} else {
add.Scheme = "http"
}
}
}
return add
}
func (c *Config) GetHandlerKey() string {
return c.HandlerKey
}
func (c *Config) Validate() error {
err := ErrorServerValidate.Error(nil)
if er := libval.New().Struct(c); er != nil {
if e, ok := er.(*libval.InvalidValidationError); ok {
err.Add(e)
}
for _, e := range er.(libval.ValidationErrors) {
//nolint goerr113
err.Add(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Namespace(), e.ActualTag()))
}
}
if err.HasParent() {
return err
}
return nil
}
func (c *Config) Server(defLog liblog.FuncLog) (Server, error) {
return New(*c, defLog)
}
func (o *srv) GetConfig() *Config {
if i, l := o.c.Load(cfgConfig); !l {
return nil
} else if v, k := i.(Config); !k {
return nil
} else {
return &v
}
}
func (o *srv) makeOptServer(cfg Config) *optServer {
return &optServer{
ReadTimeout: cfg.ReadTimeout.Time(),
ReadHeaderTimeout: cfg.ReadHeaderTimeout.Time(),
WriteTimeout: cfg.WriteTimeout.Time(),
MaxHeaderBytes: cfg.MaxHeaderBytes,
MaxHandlers: cfg.MaxHandlers,
MaxConcurrentStreams: cfg.MaxConcurrentStreams,
MaxReadFrameSize: cfg.MaxReadFrameSize,
PermitProhibitedCipherSuites: cfg.PermitProhibitedCipherSuites,
IdleTimeout: cfg.IdleTimeout.Time(),
MaxUploadBufferPerConnection: cfg.MaxUploadBufferPerConnection,
MaxUploadBufferPerStream: cfg.MaxUploadBufferPerStream,
DisableKeepAlive: cfg.DisableKeepAlive,
}
}
func (o *srv) SetConfig(cfg Config, defLog liblog.FuncLog) error {
if e := o.cfgSetTLS(&cfg); e != nil {
return e
} else if e = o.setLogger(defLog, cfg.Logger); e != nil {
return e
}
if o.HandlerHas(cfg.HandlerKey) {
o.HandlerStoreFct(cfg.HandlerKey)
} else {
return ErrorServerValidate.Error(fmt.Errorf("handler is missing or not existing"))
}
o.c.Store(cfgName, cfg.Name)
o.c.Store(cfgListen, cfg.GetListen())
o.c.Store(cfgExpose, cfg.GetExpose())
o.c.Store(cfgDisabled, cfg.Disabled)
o.c.Store(cfgServerOptions, o.makeOptServer(cfg))
o.c.Store(cfgConfig, cfg)
return nil
}
func (o *srv) setLogger(def liblog.FuncLog, opt logcfg.Options) error {
o.m.Lock()
defer o.m.Unlock()
var (
l liblog.Logger
f = func() liblog.Logger {
return liblog.New(o.c.GetContext)
}
)
if def != nil {
if n := def(); n != nil {
l = n
}
}
if l == nil {
l = f()
}
if e := l.SetOptions(&opt); e == nil {
o.l = func() liblog.Logger {
return l
}
return nil
} else if o.l == nil {
o.l = func() liblog.Logger {
return l
}
return e
} else {
return e
}
}
func (o *srv) logger() liblog.Logger {
o.m.RLock()
defer o.m.RUnlock()
var log liblog.Logger
if o.l != nil {
log = o.l()
} else {
log = liblog.New(o.c.GetContext)
o.m.RUnlock()
o.m.Lock()
o.l = func() liblog.Logger {
return log
}
o.m.Unlock()
o.m.RLock()
}
log.SetFields(log.GetFields().Add("bind", o.GetBindable()))
return log
}
func (o *srv) cfgSetTLS(cfg *Config) error {
o.c.Store(cfgTLSMandatory, cfg.TLSMandatory)
if t, e := cfg.CheckTLS(); e != nil && cfg.TLSMandatory {
return e
} else if e != nil {
o.c.Delete(cfgTLS)
return nil
} else {
o.c.Store(cfgTLS, t)
return nil
}
}
func (o *srv) cfgGetTLS() libtls.TLSConfig {
if i, l := o.c.Load(cfgTLS); !l {
return nil
} else if v, k := i.(libtls.TLSConfig); !k {
return nil
} else {
return v
}
}
func (o *srv) cfgTLSMandatory() bool {
if i, l := o.c.Load(cfgTLSMandatory); !l {
return false
} else if v, k := i.(bool); !k {
return false
} else {
return v
}
}
func (o *srv) cfgGetServer() *optServer {
if i, l := o.c.Load(cfgServerOptions); !l {
return &optServer{}
} else if v, k := i.(*optServer); !k {
return &optServer{}
} else {
return v
}
}