package roundtrip import ( "os" "strconv" "time" log "github.com/sirupsen/logrus" ) // Environment variable names for tuning the backend HTTP transport. const ( EnvMaxIdleConns = "NB_PROXY_MAX_IDLE_CONNS" EnvMaxIdleConnsPerHost = "NB_PROXY_MAX_IDLE_CONNS_PER_HOST" EnvMaxConnsPerHost = "NB_PROXY_MAX_CONNS_PER_HOST" EnvIdleConnTimeout = "NB_PROXY_IDLE_CONN_TIMEOUT" EnvTLSHandshakeTimeout = "NB_PROXY_TLS_HANDSHAKE_TIMEOUT" EnvExpectContinueTimeout = "NB_PROXY_EXPECT_CONTINUE_TIMEOUT" EnvResponseHeaderTimeout = "NB_PROXY_RESPONSE_HEADER_TIMEOUT" EnvWriteBufferSize = "NB_PROXY_WRITE_BUFFER_SIZE" EnvReadBufferSize = "NB_PROXY_READ_BUFFER_SIZE" EnvDisableCompression = "NB_PROXY_DISABLE_COMPRESSION" EnvMaxInflight = "NB_PROXY_MAX_INFLIGHT" EnvForceAttemptHTTP2 = "NB_PROXY_FORCE_ATTEMPT_HTTP2" ) // transportConfig holds tunable parameters for the per-account HTTP transport. type transportConfig struct { maxIdleConns int maxIdleConnsPerHost int maxConnsPerHost int idleConnTimeout time.Duration tlsHandshakeTimeout time.Duration expectContinueTimeout time.Duration responseHeaderTimeout time.Duration writeBufferSize int readBufferSize int disableCompression bool // maxInflight limits per-backend concurrent requests. 0 means unlimited. maxInflight int // forceAttemptHTTP2 sets http.Transport.ForceAttemptHTTP2. Both proxy // transports dial through a custom DialContext, which makes net/http // disable HTTP/2 unless it is forced, so this defaults to true. // Setting it to false restores that conservative default, leaving // HTTPS upstreams on HTTP/1.1 for backends whose h2 support is // advertised but unusable. forceAttemptHTTP2 bool } func defaultTransportConfig() transportConfig { return transportConfig{ maxIdleConns: 100, maxIdleConnsPerHost: 100, maxConnsPerHost: 0, // unlimited idleConnTimeout: 90 * time.Second, tlsHandshakeTimeout: 10 * time.Second, expectContinueTimeout: 1 * time.Second, forceAttemptHTTP2: true, } } func loadTransportConfig(logger *log.Logger) transportConfig { cfg := defaultTransportConfig() if v, ok := envInt(EnvMaxIdleConns, logger); ok { cfg.maxIdleConns = v } if v, ok := envInt(EnvMaxIdleConnsPerHost, logger); ok { cfg.maxIdleConnsPerHost = v } if v, ok := envInt(EnvMaxConnsPerHost, logger); ok { cfg.maxConnsPerHost = v } if v, ok := envDuration(EnvIdleConnTimeout, logger); ok { cfg.idleConnTimeout = v } if v, ok := envDuration(EnvTLSHandshakeTimeout, logger); ok { cfg.tlsHandshakeTimeout = v } if v, ok := envDuration(EnvExpectContinueTimeout, logger); ok { cfg.expectContinueTimeout = v } if v, ok := envDuration(EnvResponseHeaderTimeout, logger); ok { cfg.responseHeaderTimeout = v } if v, ok := envInt(EnvWriteBufferSize, logger); ok { cfg.writeBufferSize = v } if v, ok := envInt(EnvReadBufferSize, logger); ok { cfg.readBufferSize = v } if v, ok := envBool(EnvDisableCompression, logger); ok { cfg.disableCompression = v } if v, ok := envInt(EnvMaxInflight, logger); ok { cfg.maxInflight = v } if v, ok := envBool(EnvForceAttemptHTTP2, logger); ok { cfg.forceAttemptHTTP2 = v } logger.WithFields(log.Fields{ "max_idle_conns": cfg.maxIdleConns, "max_idle_conns_per_host": cfg.maxIdleConnsPerHost, "max_conns_per_host": cfg.maxConnsPerHost, "idle_conn_timeout": cfg.idleConnTimeout, "tls_handshake_timeout": cfg.tlsHandshakeTimeout, "expect_continue_timeout": cfg.expectContinueTimeout, "response_header_timeout": cfg.responseHeaderTimeout, "write_buffer_size": cfg.writeBufferSize, "read_buffer_size": cfg.readBufferSize, "disable_compression": cfg.disableCompression, "max_inflight": cfg.maxInflight, "force_attempt_http2": cfg.forceAttemptHTTP2, }).Debug("backend transport configuration") return cfg } func envInt(key string, logger *log.Logger) (int, bool) { s := os.Getenv(key) if s == "" { return 0, false } v, err := strconv.Atoi(s) if err != nil { logger.Warnf("failed to parse %s=%q as int: %v", key, s, err) return 0, false } if v < 0 { logger.Warnf("ignoring negative value for %s=%d", key, v) return 0, false } return v, true } func envDuration(key string, logger *log.Logger) (time.Duration, bool) { s := os.Getenv(key) if s == "" { return 0, false } v, err := time.ParseDuration(s) if err != nil { logger.Warnf("failed to parse %s=%q as duration: %v", key, s, err) return 0, false } if v < 0 { logger.Warnf("ignoring negative value for %s=%s", key, v) return 0, false } return v, true } func envBool(key string, logger *log.Logger) (bool, bool) { s := os.Getenv(key) if s == "" { return false, false } v, err := strconv.ParseBool(s) if err != nil { logger.Warnf("failed to parse %s=%q as bool: %v", key, s, err) return false, false } return v, true }