570 - transport wrapper initial checkin

This commit is contained in:
Mayuresh Nirhali
2025-12-08 12:23:36 +05:30
parent 4da6ce6bde
commit 20a924d43e
5 changed files with 79 additions and 40 deletions

View File

@@ -22,6 +22,7 @@ type PluginManager interface {
Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error) Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error)
Registry(ctx context.Context, cfg *plugin.Config) (definition.RegistryLookup, error) Registry(ctx context.Context, cfg *plugin.Config) (definition.RegistryLookup, error)
KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error) KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error)
TransportWrapper(ctx context.Context, cfg *plugin.Config) (definition.TransportWrapper, error)
SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error)
} }
@@ -35,16 +36,17 @@ const (
// PluginCfg holds the configuration for various plugins. // PluginCfg holds the configuration for various plugins.
type PluginCfg struct { type PluginCfg struct {
SchemaValidator *plugin.Config `yaml:"schemaValidator,omitempty"` SchemaValidator *plugin.Config `yaml:"schemaValidator,omitempty"`
SignValidator *plugin.Config `yaml:"signValidator,omitempty"` SignValidator *plugin.Config `yaml:"signValidator,omitempty"`
Publisher *plugin.Config `yaml:"publisher,omitempty"` Publisher *plugin.Config `yaml:"publisher,omitempty"`
Signer *plugin.Config `yaml:"signer,omitempty"` Signer *plugin.Config `yaml:"signer,omitempty"`
Router *plugin.Config `yaml:"router,omitempty"` Router *plugin.Config `yaml:"router,omitempty"`
Cache *plugin.Config `yaml:"cache,omitempty"` Cache *plugin.Config `yaml:"cache,omitempty"`
Registry *plugin.Config `yaml:"registry,omitempty"` Registry *plugin.Config `yaml:"registry,omitempty"`
KeyManager *plugin.Config `yaml:"keyManager,omitempty"` KeyManager *plugin.Config `yaml:"keyManager,omitempty"`
Middleware []plugin.Config `yaml:"middleware,omitempty"` TransportWrapper *plugin.Config `yaml:"transportWrapper,omitempty"`
Steps []plugin.Config Middleware []plugin.Config `yaml:"middleware,omitempty"`
Steps []plugin.Config
} }
// HttpClientConfig defines the configuration for the HTTP transport layer. // HttpClientConfig defines the configuration for the HTTP transport layer.

View File

@@ -17,22 +17,23 @@ import (
// stdHandler orchestrates the execution of defined processing steps. // stdHandler orchestrates the execution of defined processing steps.
type stdHandler struct { type stdHandler struct {
signer definition.Signer signer definition.Signer
steps []definition.Step steps []definition.Step
signValidator definition.SignValidator signValidator definition.SignValidator
cache definition.Cache cache definition.Cache
registry definition.RegistryLookup registry definition.RegistryLookup
km definition.KeyManager km definition.KeyManager
schemaValidator definition.SchemaValidator schemaValidator definition.SchemaValidator
router definition.Router router definition.Router
publisher definition.Publisher publisher definition.Publisher
SubscriberID string transportWrapper definition.TransportWrapper
role model.Role SubscriberID string
httpClient *http.Client role model.Role
httpClient *http.Client
} }
// newHTTPClient creates a new HTTP client with a custom transport configuration. // newHTTPClient creates a new HTTP client with a custom transport configuration.
func newHTTPClient(cfg *HttpClientConfig) *http.Client { func newHTTPClient(cfg *HttpClientConfig, wrapper definition.TransportWrapper) *http.Client {
// Clone the default transport to inherit its sensible defaults. // Clone the default transport to inherit its sensible defaults.
transport := http.DefaultTransport.(*http.Transport).Clone() transport := http.DefaultTransport.(*http.Transport).Clone()
@@ -50,7 +51,12 @@ func newHTTPClient(cfg *HttpClientConfig) *http.Client {
if cfg.ResponseHeaderTimeout > 0 { if cfg.ResponseHeaderTimeout > 0 {
transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout
} }
return &http.Client{Transport: transport} var finalTransport http.RoundTripper = transport
if wrapper != nil {
log.Debugf(context.Background(), "Applying custom transport wrapper")
finalTransport = wrapper.Wrap(transport)
}
return &http.Client{Transport: finalTransport}
} }
// NewStdHandler initializes a new processor with plugins and steps. // NewStdHandler initializes a new processor with plugins and steps.
@@ -59,12 +65,13 @@ func NewStdHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Ha
steps: []definition.Step{}, steps: []definition.Step{},
SubscriberID: cfg.SubscriberID, SubscriberID: cfg.SubscriberID,
role: cfg.Role, role: cfg.Role,
httpClient: newHTTPClient(&cfg.HttpClientConfig),
} }
// Initialize plugins. // Initialize plugins.
if err := h.initPlugins(ctx, mgr, &cfg.Plugins); err != nil { if err := h.initPlugins(ctx, mgr, &cfg.Plugins); err != nil {
return nil, fmt.Errorf("failed to initialize plugins: %w", err) return nil, fmt.Errorf("failed to initialize plugins: %w", err)
} }
// Initialize HTTP client after plugins so transport wrapper can be applied.
h.httpClient = newHTTPClient(&cfg.HttpClientConfig, h.transportWrapper)
// Initialize steps. // Initialize steps.
if err := h.initSteps(ctx, mgr, cfg); err != nil { if err := h.initSteps(ctx, mgr, cfg); err != nil {
return nil, fmt.Errorf("failed to initialize steps: %w", err) return nil, fmt.Errorf("failed to initialize steps: %w", err)
@@ -244,6 +251,9 @@ func (h *stdHandler) initPlugins(ctx context.Context, mgr PluginManager, cfg *Pl
if h.signer, err = loadPlugin(ctx, "Signer", cfg.Signer, mgr.Signer); err != nil { if h.signer, err = loadPlugin(ctx, "Signer", cfg.Signer, mgr.Signer); err != nil {
return err return err
} }
if h.transportWrapper, err = loadPlugin(ctx, "TransportWrapper", cfg.TransportWrapper, mgr.TransportWrapper); err != nil {
return err
}
log.Debugf(ctx, "All required plugins successfully loaded for stdHandler") log.Debugf(ctx, "All required plugins successfully loaded for stdHandler")
return nil return nil

View File

@@ -55,8 +55,8 @@ func TestNewHTTPClient(t *testing.T) {
{ {
name: "partial configuration", name: "partial configuration",
config: HttpClientConfig{ config: HttpClientConfig{
MaxIdleConns: 500, MaxIdleConns: 500,
IdleConnTimeout: 180 * time.Second, IdleConnTimeout: 180 * time.Second,
}, },
expected: struct { expected: struct {
maxIdleConns int maxIdleConns int
@@ -74,8 +74,8 @@ func TestNewHTTPClient(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
client := newHTTPClient(&tt.config) client := newHTTPClient(&tt.config, nil)
if client == nil { if client == nil {
t.Fatal("newHTTPClient returned nil") t.Fatal("newHTTPClient returned nil")
} }
@@ -107,15 +107,15 @@ func TestNewHTTPClient(t *testing.T) {
func TestHttpClientConfigDefaults(t *testing.T) { func TestHttpClientConfigDefaults(t *testing.T) {
// Test that zero config values don't override defaults // Test that zero config values don't override defaults
config := &HttpClientConfig{} config := &HttpClientConfig{}
client := newHTTPClient(config) client := newHTTPClient(config, nil)
transport := client.Transport.(*http.Transport) transport := client.Transport.(*http.Transport)
// Verify defaults are preserved when config values are zero // Verify defaults are preserved when config values are zero
if transport.MaxIdleConns == 0 { if transport.MaxIdleConns == 0 {
t.Error("MaxIdleConns should not be zero when using defaults") t.Error("MaxIdleConns should not be zero when using defaults")
} }
// MaxIdleConnsPerHost default is 0 (unlimited), which is correct // MaxIdleConnsPerHost default is 0 (unlimited), which is correct
if transport.MaxIdleConns != 100 { if transport.MaxIdleConns != 100 {
t.Errorf("Expected default MaxIdleConns=100, got %d", transport.MaxIdleConns) t.Errorf("Expected default MaxIdleConns=100, got %d", transport.MaxIdleConns)
@@ -130,24 +130,24 @@ func TestHttpClientConfigPerformanceValues(t *testing.T) {
IdleConnTimeout: 300 * time.Second, IdleConnTimeout: 300 * time.Second,
ResponseHeaderTimeout: 5 * time.Second, ResponseHeaderTimeout: 5 * time.Second,
} }
client := newHTTPClient(config) client := newHTTPClient(config, nil)
transport := client.Transport.(*http.Transport) transport := client.Transport.(*http.Transport)
// Verify performance-optimized values // Verify performance-optimized values
if transport.MaxIdleConns != 1000 { if transport.MaxIdleConns != 1000 {
t.Errorf("Expected MaxIdleConns=1000, got %d", transport.MaxIdleConns) t.Errorf("Expected MaxIdleConns=1000, got %d", transport.MaxIdleConns)
} }
if transport.MaxIdleConnsPerHost != 200 { if transport.MaxIdleConnsPerHost != 200 {
t.Errorf("Expected MaxIdleConnsPerHost=200, got %d", transport.MaxIdleConnsPerHost) t.Errorf("Expected MaxIdleConnsPerHost=200, got %d", transport.MaxIdleConnsPerHost)
} }
if transport.IdleConnTimeout != 300*time.Second { if transport.IdleConnTimeout != 300*time.Second {
t.Errorf("Expected IdleConnTimeout=300s, got %v", transport.IdleConnTimeout) t.Errorf("Expected IdleConnTimeout=300s, got %v", transport.IdleConnTimeout)
} }
if transport.ResponseHeaderTimeout != 5*time.Second { if transport.ResponseHeaderTimeout != 5*time.Second {
t.Errorf("Expected ResponseHeaderTimeout=5s, got %v", transport.ResponseHeaderTimeout) t.Errorf("Expected ResponseHeaderTimeout=5s, got %v", transport.ResponseHeaderTimeout)
} }
} }

View File

@@ -69,6 +69,11 @@ func (m *mockPluginManager) KeyManager(ctx context.Context, cache definition.Cac
return nil, nil return nil, nil
} }
// TransportWrapper returns a mock transport wrapper implementation.
func (m *mockPluginManager) TransportWrapper(ctx context.Context, cfg *plugin.Config) (definition.TransportWrapper, error) {
return nil, nil
}
// SchemaValidator returns a mock schema validator implementation. // SchemaValidator returns a mock schema validator implementation.
func (m *mockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) { func (m *mockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) {
return nil, nil return nil, nil

View File

@@ -196,6 +196,28 @@ func (m *Manager) Middleware(ctx context.Context, cfg *Config) (func(http.Handle
return mwp.New(ctx, cfg.Config) return mwp.New(ctx, cfg.Config)
} }
// TransportWrapper returns a TransportWrapper instance based on the provided configuration.
func (m *Manager) TransportWrapper(ctx context.Context, cfg *Config) (definition.TransportWrapper, error) {
twp, err := provider[definition.TransportWrapperProvider](m.plugins, cfg.ID)
if err != nil {
return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err)
}
config := make(map[string]any, len(cfg.Config))
for k, v := range cfg.Config {
config[k] = v
}
wrapper, closer, err := twp.New(ctx, config)
if err != nil {
return nil, err
}
if closer != nil {
m.closers = append(m.closers, closer)
}
return wrapper, nil
}
// Step returns a Step instance based on the provided configuration. // Step returns a Step instance based on the provided configuration.
func (m *Manager) Step(ctx context.Context, cfg *Config) (definition.Step, error) { func (m *Manager) Step(ctx context.Context, cfg *Config) (definition.Step, error) {
sp, err := provider[definition.StepProvider](m.plugins, cfg.ID) sp, err := provider[definition.StepProvider](m.plugins, cfg.ID)