/* * 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 } }