Files
cgrates/config/httpcfg.go
2025-10-13 09:57:41 +02:00

347 lines
12 KiB
Go

/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
*/
package config
import (
"crypto/tls"
"net"
"net/http"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/utils"
)
// HTTPCfg is the HTTP config section
type HTTPCfg struct {
JsonRPCURL string // JSON RPC relative URL ("" to disable)
RegistrarSURL string // registrar service relative URL
WSURL string // WebSocket relative URL ("" to disable)
FreeswitchCDRsURL string // Freeswitch CDRS relative URL ("" to disable)
CDRsURL string // CDRS relative URL ("" to disable)
PprofPath string // runtime profiling url path ("" to disable)
UseBasicAuth bool // Use basic auth for HTTP API
AuthUsers map[string]string // Basic auth user:password map (base64 passwords)
ClientOpts *http.Transport
dialer *net.Dialer
}
// loadHTTPCfg loads the Http section of the configuration
func (httpcfg *HTTPCfg) Load(ctx *context.Context, jsnCfg ConfigDB, _ *CGRConfig) (err error) {
jsnHTTPCfg := new(HTTPJsonCfg)
if err = jsnCfg.GetSection(ctx, HTTPJSON, jsnHTTPCfg); err != nil {
return
}
return httpcfg.loadFromJSONCfg(jsnHTTPCfg)
}
func newDialer(dialer *net.Dialer, jsnCfg *HTTPClientOptsJson) (err error) {
if jsnCfg == nil {
return
}
dialer.DualStack = true
if jsnCfg.DialTimeout != nil {
if dialer.Timeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.DialTimeout); err != nil {
return
}
}
if jsnCfg.DialFallbackDelay != nil {
if dialer.FallbackDelay, err = utils.ParseDurationWithNanosecs(*jsnCfg.DialFallbackDelay); err != nil {
return
}
}
if jsnCfg.DialKeepAlive != nil {
if dialer.KeepAlive, err = utils.ParseDurationWithNanosecs(*jsnCfg.DialKeepAlive); err != nil {
return
}
}
return
}
func loadTransportFromJSONCfg(httpOpts *http.Transport, httpDialer *net.Dialer, jsnCfg *HTTPClientOptsJson) (err error) {
if jsnCfg == nil {
return
}
httpOpts.Proxy = http.ProxyFromEnvironment
if jsnCfg.SkipTLSVerification != nil {
httpOpts.TLSClientConfig = &tls.Config{InsecureSkipVerify: *jsnCfg.SkipTLSVerification}
}
if jsnCfg.TLSHandshakeTimeout != nil {
if httpOpts.TLSHandshakeTimeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.TLSHandshakeTimeout); err != nil {
return
}
}
if jsnCfg.DisableKeepAlives != nil {
httpOpts.DisableKeepAlives = *jsnCfg.DisableKeepAlives
}
if jsnCfg.DisableCompression != nil {
httpOpts.DisableCompression = *jsnCfg.DisableCompression
}
if jsnCfg.MaxIdleConns != nil {
httpOpts.MaxIdleConns = *jsnCfg.MaxIdleConns
}
if jsnCfg.MaxIdleConnsPerHost != nil {
httpOpts.MaxIdleConnsPerHost = *jsnCfg.MaxIdleConnsPerHost
}
if jsnCfg.MaxConnsPerHost != nil {
httpOpts.MaxConnsPerHost = *jsnCfg.MaxConnsPerHost
}
if jsnCfg.IdleConnTimeout != nil {
if httpOpts.IdleConnTimeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.IdleConnTimeout); err != nil {
return
}
}
if jsnCfg.ResponseHeaderTimeout != nil {
if httpOpts.ResponseHeaderTimeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.ResponseHeaderTimeout); err != nil {
return
}
}
if jsnCfg.ExpectContinueTimeout != nil {
if httpOpts.ExpectContinueTimeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.ExpectContinueTimeout); err != nil {
return
}
}
if jsnCfg.ForceAttemptHTTP2 != nil {
httpOpts.ForceAttemptHTTP2 = *jsnCfg.ForceAttemptHTTP2
}
httpOpts.DialContext = httpDialer.DialContext
return
}
// loadFromJSONCfg loads Database config from JsonCfg
func (httpcfg *HTTPCfg) loadFromJSONCfg(jsnHTTPCfg *HTTPJsonCfg) (err error) {
if jsnHTTPCfg == nil {
return
}
if jsnHTTPCfg.JSONRPCURL != nil {
httpcfg.JsonRPCURL = *jsnHTTPCfg.JSONRPCURL
}
if jsnHTTPCfg.RegistrarsURL != nil {
httpcfg.RegistrarSURL = *jsnHTTPCfg.RegistrarsURL
}
if jsnHTTPCfg.WSURL != nil {
httpcfg.WSURL = *jsnHTTPCfg.WSURL
}
if jsnHTTPCfg.FreeswitchCDRsURL != nil {
httpcfg.FreeswitchCDRsURL = *jsnHTTPCfg.FreeswitchCDRsURL
}
if jsnHTTPCfg.HTTPCDRs != nil {
httpcfg.CDRsURL = *jsnHTTPCfg.HTTPCDRs
}
if jsnHTTPCfg.PprofPath != nil {
httpcfg.PprofPath = *jsnHTTPCfg.PprofPath
}
if jsnHTTPCfg.UseBasicAuth != nil {
httpcfg.UseBasicAuth = *jsnHTTPCfg.UseBasicAuth
}
if jsnHTTPCfg.AuthUsers != nil {
httpcfg.AuthUsers = *jsnHTTPCfg.AuthUsers
}
if jsnHTTPCfg.ClientOpts != nil {
if err = newDialer(httpcfg.dialer, jsnHTTPCfg.ClientOpts); err != nil {
return
}
err = loadTransportFromJSONCfg(httpcfg.ClientOpts, httpcfg.dialer, jsnHTTPCfg.ClientOpts)
}
return
}
// AsMapInterface returns the config as a map[string]any
func (httpcfg HTTPCfg) AsMapInterface() any {
clientOpts := map[string]any{
utils.HTTPClientSkipTLSVerificationCfg: false,
utils.HTTPClientTLSHandshakeTimeoutCfg: httpcfg.ClientOpts.TLSHandshakeTimeout.String(),
utils.HTTPClientDisableKeepAlivesCfg: httpcfg.ClientOpts.DisableKeepAlives,
utils.HTTPClientDisableCompressionCfg: httpcfg.ClientOpts.DisableCompression,
utils.HTTPClientMaxIdleConnsCfg: httpcfg.ClientOpts.MaxIdleConns,
utils.HTTPClientMaxIdleConnsPerHostCfg: httpcfg.ClientOpts.MaxIdleConnsPerHost,
utils.HTTPClientMaxConnsPerHostCfg: httpcfg.ClientOpts.MaxConnsPerHost,
utils.HTTPClientIdleConnTimeoutCfg: httpcfg.ClientOpts.IdleConnTimeout.String(),
utils.HTTPClientResponseHeaderTimeoutCfg: httpcfg.ClientOpts.ResponseHeaderTimeout.String(),
utils.HTTPClientExpectContinueTimeoutCfg: httpcfg.ClientOpts.ExpectContinueTimeout.String(),
utils.HTTPClientForceAttemptHTTP2Cfg: httpcfg.ClientOpts.ForceAttemptHTTP2,
utils.HTTPClientDialTimeoutCfg: httpcfg.dialer.Timeout.String(),
utils.HTTPClientDialFallbackDelayCfg: httpcfg.dialer.FallbackDelay.String(),
utils.HTTPClientDialKeepAliveCfg: httpcfg.dialer.KeepAlive.String(),
}
if httpcfg.ClientOpts.TLSClientConfig != nil {
clientOpts[utils.HTTPClientSkipTLSVerificationCfg] = httpcfg.ClientOpts.TLSClientConfig.InsecureSkipVerify
}
return map[string]any{
utils.HTTPJsonRPCURLCfg: httpcfg.JsonRPCURL,
utils.RegistrarSURLCfg: httpcfg.RegistrarSURL,
utils.HTTPWSURLCfg: httpcfg.WSURL,
utils.HTTPFreeswitchCDRsURLCfg: httpcfg.FreeswitchCDRsURL,
utils.HTTPCDRsURLCfg: httpcfg.CDRsURL,
utils.PprofPathCfg: httpcfg.PprofPath,
utils.HTTPUseBasicAuthCfg: httpcfg.UseBasicAuth,
utils.HTTPAuthUsersCfg: httpcfg.AuthUsers,
utils.HTTPClientOptsCfg: clientOpts,
}
}
func (HTTPCfg) SName() string { return HTTPJSON }
func (httpcfg HTTPCfg) CloneSection() Section { return httpcfg.Clone() }
// Clone returns a deep copy of HTTPCfg
func (httpcfg HTTPCfg) Clone() (cln *HTTPCfg) {
dialer := &net.Dialer{
Timeout: httpcfg.dialer.Timeout,
DualStack: httpcfg.dialer.DualStack,
KeepAlive: httpcfg.dialer.KeepAlive,
FallbackDelay: httpcfg.dialer.FallbackDelay,
}
cln = &HTTPCfg{
JsonRPCURL: httpcfg.JsonRPCURL,
RegistrarSURL: httpcfg.RegistrarSURL,
WSURL: httpcfg.WSURL,
FreeswitchCDRsURL: httpcfg.FreeswitchCDRsURL,
CDRsURL: httpcfg.CDRsURL,
PprofPath: httpcfg.PprofPath,
UseBasicAuth: httpcfg.UseBasicAuth,
AuthUsers: make(map[string]string),
ClientOpts: httpcfg.ClientOpts.Clone(),
dialer: dialer,
}
for u, a := range httpcfg.AuthUsers {
cln.AuthUsers[u] = a
}
return
}
type HTTPClientOptsJson struct {
SkipTLSVerification *bool `json:"skipTLSVerification"`
TLSHandshakeTimeout *string `json:"tlsHandshakeTimeout"`
DisableKeepAlives *bool `json:"disableKeepAlives"`
DisableCompression *bool `json:"disableCompression"`
MaxIdleConns *int `json:"maxIdleConns"`
MaxIdleConnsPerHost *int `json:"maxIdleConnsPerHost"`
MaxConnsPerHost *int `json:"maxConnsPerHost"`
IdleConnTimeout *string `json:"IdleConnTimeout"`
ResponseHeaderTimeout *string `json:"responseHeaderTimeout"`
ExpectContinueTimeout *string `json:"expectContinueTimeout"`
ForceAttemptHTTP2 *bool `json:"forceAttemptHttp2"`
DialTimeout *string `json:"dialTimeout"`
DialFallbackDelay *string `json:"dialFallbackDelay"`
DialKeepAlive *string `json:"dialKeepAlive"`
}
// HTTP config section
type HTTPJsonCfg struct {
JSONRPCURL *string `json:"json_rpc_url"`
RegistrarsURL *string `json:"registrars_url"`
WSURL *string `json:"ws_url"`
FreeswitchCDRsURL *string `json:"freeswitch_cdrs_url"`
HTTPCDRs *string `json:"http_cdrs"`
PprofPath *string `json:"pprof_path"`
UseBasicAuth *bool `json:"use_basic_auth"`
AuthUsers *map[string]string `json:"auth_users"`
ClientOpts *HTTPClientOptsJson `json:"client_opts"`
}
func diffHTTPClientOptsJsonCfgDialer(d *HTTPClientOptsJson, v1, v2 *net.Dialer) *HTTPClientOptsJson {
if d == nil {
d = new(HTTPClientOptsJson)
}
if v1.Timeout != v2.Timeout {
d.DialTimeout = utils.StringPointer(v2.Timeout.String())
}
if v1.FallbackDelay != v2.FallbackDelay {
d.DialFallbackDelay = utils.StringPointer(v2.FallbackDelay.String())
}
if v1.KeepAlive != v2.KeepAlive {
d.DialKeepAlive = utils.StringPointer(v2.KeepAlive.String())
}
return d
}
func diffHTTPClientOptsJsonCfg(d *HTTPClientOptsJson, v1, v2 *http.Transport) *HTTPClientOptsJson {
if d == nil {
d = new(HTTPClientOptsJson)
}
if v1.TLSClientConfig != nil {
if v1.TLSClientConfig.InsecureSkipVerify != v2.TLSClientConfig.InsecureSkipVerify {
d.SkipTLSVerification = utils.BoolPointer(v2.TLSClientConfig.InsecureSkipVerify)
}
}
if v1.TLSHandshakeTimeout != v2.TLSHandshakeTimeout {
d.TLSHandshakeTimeout = utils.StringPointer(v2.TLSHandshakeTimeout.String())
}
if v1.DisableKeepAlives != v2.DisableKeepAlives {
d.DisableKeepAlives = utils.BoolPointer(v2.DisableKeepAlives)
}
if v1.DisableCompression != v2.DisableCompression {
d.DisableCompression = utils.BoolPointer(v2.DisableCompression)
}
if v1.MaxIdleConns != v2.MaxIdleConns {
d.MaxIdleConns = utils.IntPointer(v2.MaxIdleConns)
}
if v1.MaxIdleConnsPerHost != v2.MaxIdleConnsPerHost {
d.MaxIdleConnsPerHost = utils.IntPointer(v2.MaxIdleConnsPerHost)
}
if v1.MaxConnsPerHost != v2.MaxConnsPerHost {
d.MaxConnsPerHost = utils.IntPointer(v2.MaxConnsPerHost)
}
if v1.IdleConnTimeout != v2.IdleConnTimeout {
d.IdleConnTimeout = utils.StringPointer(v2.IdleConnTimeout.String())
}
if v1.ResponseHeaderTimeout != v2.ResponseHeaderTimeout {
d.ResponseHeaderTimeout = utils.StringPointer(v2.ResponseHeaderTimeout.String())
}
if v1.ExpectContinueTimeout != v2.ExpectContinueTimeout {
d.ExpectContinueTimeout = utils.StringPointer(v2.ExpectContinueTimeout.String())
}
if v1.ForceAttemptHTTP2 != v2.ForceAttemptHTTP2 {
d.ForceAttemptHTTP2 = utils.BoolPointer(v2.ForceAttemptHTTP2)
}
return d
}
func diffHTTPJsonCfg(d *HTTPJsonCfg, v1, v2 *HTTPCfg) *HTTPJsonCfg {
if d == nil {
d = new(HTTPJsonCfg)
}
if v1.JsonRPCURL != v2.JsonRPCURL {
d.JSONRPCURL = utils.StringPointer(v2.JsonRPCURL)
}
if v1.RegistrarSURL != v2.RegistrarSURL {
d.RegistrarsURL = utils.StringPointer(v2.RegistrarSURL)
}
if v1.WSURL != v2.WSURL {
d.WSURL = utils.StringPointer(v2.WSURL)
}
if v1.FreeswitchCDRsURL != v2.FreeswitchCDRsURL {
d.FreeswitchCDRsURL = utils.StringPointer(v2.FreeswitchCDRsURL)
}
if v1.CDRsURL != v2.CDRsURL {
d.HTTPCDRs = utils.StringPointer(v2.CDRsURL)
}
if v1.PprofPath != v2.PprofPath {
d.PprofPath = utils.StringPointer(v2.PprofPath)
}
if v1.UseBasicAuth != v2.UseBasicAuth {
d.UseBasicAuth = utils.BoolPointer(v2.UseBasicAuth)
}
if !utils.MapStringStringEqual(v1.AuthUsers, v2.AuthUsers) {
d.AuthUsers = &v2.AuthUsers
}
d.ClientOpts = diffHTTPClientOptsJsonCfg(d.ClientOpts, v1.ClientOpts, v2.ClientOpts)
d.ClientOpts = diffHTTPClientOptsJsonCfgDialer(d.ClientOpts, v1.dialer, v2.dialer)
return d
}