Skip to content

Commit 078b0fe

Browse files
authored
Merge pull request #85 from deploymenttheory/dev
Dev
2 parents 1e5fc57 + e9f4b00 commit 078b0fe

6 files changed

+449
-73
lines changed

errors/http_error_handling.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func TranslateStatusCode(resp *http.Response) string {
124124
if message, exists := messages[resp.StatusCode]; exists {
125125
return message
126126
}
127-
return "An unexpected error occurred. Please try again later."
127+
return fmt.Sprintf("Unknown status code: %d", resp.StatusCode)
128128
}
129129

130130
// IsNonRetryableStatusCode checks if the provided response indicates a non-retryable error.

httpclient/httpclient_client.go

+11-42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ like the baseURL, authentication details, and an embedded standard HTTP client.
88
package httpclient
99

1010
import (
11+
"log"
1112
"net/http"
1213
"sync"
1314
"time"
@@ -82,7 +83,15 @@ type PerformanceMetrics struct {
8283
}
8384

8485
// BuildClient creates a new HTTP client with the provided configuration.
85-
func BuildClient(config ClientConfig) (*Client, error) {
86+
func BuildClient(configFilePath string) (*Client, error) {
87+
88+
// Load the configuration
89+
config, err := SetClientConfiguration(configFilePath)
90+
if err != nil {
91+
log.Printf("Failed to set client configuration: %v", err) // This uses the standard log package before zap logger is initialized
92+
return nil, err
93+
}
94+
8695
// Parse the log level string to logger.LogLevel
8796
parsedLogLevel := logger.ParseLogLevelFromString(config.ClientOptions.LogLevel)
8897

@@ -105,46 +114,6 @@ func BuildClient(config ClientConfig) (*Client, error) {
105114

106115
log.Info("Initializing new HTTP client with the provided configuration")
107116

108-
// Validate and set default values for the configuration
109-
if config.Environment.APIType == "" {
110-
return nil, log.Error("InstanceName cannot be empty")
111-
}
112-
113-
if config.ClientOptions.MaxRetryAttempts < 0 {
114-
config.ClientOptions.MaxRetryAttempts = DefaultMaxRetryAttempts
115-
log.Info("MaxRetryAttempts was negative, set to default value", zap.Int("MaxRetryAttempts", DefaultMaxRetryAttempts))
116-
}
117-
118-
if config.ClientOptions.MaxConcurrentRequests <= 0 {
119-
config.ClientOptions.MaxConcurrentRequests = DefaultMaxConcurrentRequests
120-
log.Info("MaxConcurrentRequests was negative or zero, set to default value", zap.Int("MaxConcurrentRequests", DefaultMaxConcurrentRequests))
121-
}
122-
123-
if config.ClientOptions.TokenRefreshBufferPeriod < 0 {
124-
config.ClientOptions.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
125-
log.Info("TokenRefreshBufferPeriod was negative, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
126-
}
127-
128-
if config.ClientOptions.TotalRetryDuration <= 0 {
129-
config.ClientOptions.TotalRetryDuration = DefaultTotalRetryDuration
130-
log.Info("TotalRetryDuration was negative or zero, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
131-
}
132-
133-
if config.ClientOptions.TokenRefreshBufferPeriod == 0 {
134-
config.ClientOptions.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
135-
log.Info("TokenRefreshBufferPeriod not set, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
136-
}
137-
138-
if config.ClientOptions.TotalRetryDuration == 0 {
139-
config.ClientOptions.TotalRetryDuration = DefaultTotalRetryDuration
140-
log.Info("TotalRetryDuration not set, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
141-
}
142-
143-
if config.ClientOptions.CustomTimeout == 0 {
144-
config.ClientOptions.CustomTimeout = DefaultTimeout
145-
log.Info("CustomTimeout not set, set to default value", zap.Duration("CustomTimeout", DefaultTimeout))
146-
}
147-
148117
// Determine the authentication method using the helper function
149118
authMethod, err := DetermineAuthMethod(config.Auth)
150119
if err != nil {
@@ -159,7 +128,7 @@ func BuildClient(config ClientConfig) (*Client, error) {
159128
AuthMethod: authMethod,
160129
OverrideBaseDomain: config.Environment.OverrideBaseDomain,
161130
httpClient: &http.Client{Timeout: config.ClientOptions.CustomTimeout},
162-
clientConfig: config,
131+
clientConfig: *config,
163132
Logger: log,
164133
ConcurrencyMgr: NewConcurrencyManager(config.ClientOptions.MaxConcurrentRequests, log, true),
165134
PerfMetrics: PerformanceMetrics{},

httpclient/httpclient_client.go.back

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// http_client.go
2+
/* The `http_client` package provides a configurable HTTP client tailored for interacting with specific APIs.
3+
It supports different authentication methods, including "bearer" and "oauth". The client is designed with a
4+
focus on concurrency management, structured error handling, and flexible configuration options.
5+
The package offers a default timeout, custom backoff strategies, dynamic rate limiting,
6+
and detailed logging capabilities. The main `Client` structure encapsulates all necessary components,
7+
like the baseURL, authentication details, and an embedded standard HTTP client. */
8+
package httpclient
9+
10+
import (
11+
"net/http"
12+
"sync"
13+
"time"
14+
15+
"github.com/deploymenttheory/go-api-http-client/logger"
16+
"go.uber.org/zap"
17+
)
18+
19+
// Client represents an HTTP client to interact with a specific API.
20+
type Client struct {
21+
APIHandler APIHandler // APIHandler interface used to define which API handler to use
22+
InstanceName string // Website Instance name without the root domain
23+
AuthMethod string // Specifies the authentication method: "bearer" or "oauth"
24+
Token string // Authentication Token
25+
OverrideBaseDomain string // Base domain override used when the default in the api handler isn't suitable
26+
OAuthCredentials OAuthCredentials // ClientID / Client Secret
27+
BearerTokenAuthCredentials BearerTokenAuthCredentials // Username and Password for Basic Authentication
28+
Expiry time.Time // Expiry time set for the auth token
29+
httpClient *http.Client
30+
tokenLock sync.Mutex
31+
clientConfig ClientConfig
32+
Logger logger.Logger
33+
ConcurrencyMgr *ConcurrencyManager
34+
PerfMetrics PerformanceMetrics
35+
}
36+
37+
// Config holds configuration options for the HTTP Client.
38+
type ClientConfig struct {
39+
Auth AuthConfig // User can either supply these values manually or pass from LoadAuthConfig/Env vars
40+
Environment EnvironmentConfig // User can either supply these values manually or pass from LoadAuthConfig/Env vars
41+
ClientOptions ClientOptions // Optional configuration options for the HTTP Client
42+
}
43+
44+
// EnvironmentConfig represents the structure to read authentication details from a JSON configuration file.
45+
type EnvironmentConfig struct {
46+
InstanceName string `json:"InstanceName,omitempty"`
47+
OverrideBaseDomain string `json:"OverrideBaseDomain,omitempty"`
48+
APIType string `json:"APIType,omitempty"`
49+
}
50+
51+
// AuthConfig represents the structure to read authentication details from a JSON configuration file.
52+
type AuthConfig struct {
53+
Username string `json:"Username,omitempty"`
54+
Password string `json:"Password,omitempty"`
55+
ClientID string `json:"ClientID,omitempty"`
56+
ClientSecret string `json:"ClientSecret,omitempty"`
57+
}
58+
59+
// ClientOptions holds optional configuration options for the HTTP Client.
60+
type ClientOptions struct {
61+
LogLevel string // Field for defining tiered logging level.
62+
LogOutputFormat string // Field for defining the output format of the logs. Use "JSON" for JSON format, "console" for human-readable format
63+
LogConsoleSeparator string // Field for defining the separator in console output format.
64+
HideSensitiveData bool // Field for defining whether sensitive fields should be hidden in logs.
65+
MaxRetryAttempts int // Config item defines the max number of retry request attempts for retryable HTTP methods.
66+
EnableDynamicRateLimiting bool // Field for defining whether dynamic rate limiting should be enabled.
67+
MaxConcurrentRequests int // Field for defining the maximum number of concurrent requests allowed in the semaphore
68+
TokenRefreshBufferPeriod time.Duration
69+
TotalRetryDuration time.Duration
70+
CustomTimeout time.Duration
71+
}
72+
73+
// ClientPerformanceMetrics captures various metrics related to the client's
74+
// interactions with the API, providing insights into its performance and behavior.
75+
type PerformanceMetrics struct {
76+
TotalRequests int64
77+
TotalRetries int64
78+
TotalRateLimitErrors int64
79+
TotalResponseTime time.Duration
80+
TokenWaitTime time.Duration
81+
lock sync.Mutex
82+
}
83+
84+
// BuildClient creates a new HTTP client with the provided configuration.
85+
func BuildClient(config ClientConfig) (*Client, error) {
86+
// Parse the log level string to logger.LogLevel
87+
parsedLogLevel := logger.ParseLogLevelFromString(config.ClientOptions.LogLevel)
88+
89+
// Set default value if none is provided
90+
if config.ClientOptions.LogConsoleSeparator == "" {
91+
config.ClientOptions.LogConsoleSeparator = ","
92+
}
93+
94+
// Initialize the logger with parsed config values
95+
log := logger.BuildLogger(parsedLogLevel, config.ClientOptions.LogOutputFormat, config.ClientOptions.LogConsoleSeparator)
96+
97+
// Set the logger's level (optional if BuildLogger already sets the level based on the input)
98+
log.SetLevel(parsedLogLevel)
99+
100+
// Use the APIType from the config to determine which API handler to load
101+
apiHandler, err := LoadAPIHandler(config.Environment.APIType, log)
102+
if err != nil {
103+
return nil, log.Error("Failed to load API handler", zap.String("APIType", config.Environment.APIType), zap.Error(err))
104+
}
105+
106+
log.Info("Initializing new HTTP client with the provided configuration")
107+
108+
// Validate and set default values for the configuration
109+
if config.Environment.APIType == "" {
110+
return nil, log.Error("InstanceName cannot be empty")
111+
}
112+
113+
if config.ClientOptions.MaxRetryAttempts < 0 {
114+
config.ClientOptions.MaxRetryAttempts = DefaultMaxRetryAttempts
115+
log.Info("MaxRetryAttempts was negative, set to default value", zap.Int("MaxRetryAttempts", DefaultMaxRetryAttempts))
116+
}
117+
118+
if config.ClientOptions.MaxConcurrentRequests <= 0 {
119+
config.ClientOptions.MaxConcurrentRequests = DefaultMaxConcurrentRequests
120+
log.Info("MaxConcurrentRequests was negative or zero, set to default value", zap.Int("MaxConcurrentRequests", DefaultMaxConcurrentRequests))
121+
}
122+
123+
if config.ClientOptions.TokenRefreshBufferPeriod < 0 {
124+
config.ClientOptions.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
125+
log.Info("TokenRefreshBufferPeriod was negative, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
126+
}
127+
128+
if config.ClientOptions.TotalRetryDuration <= 0 {
129+
config.ClientOptions.TotalRetryDuration = DefaultTotalRetryDuration
130+
log.Info("TotalRetryDuration was negative or zero, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
131+
}
132+
133+
if config.ClientOptions.TokenRefreshBufferPeriod == 0 {
134+
config.ClientOptions.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
135+
log.Info("TokenRefreshBufferPeriod not set, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
136+
}
137+
138+
if config.ClientOptions.TotalRetryDuration == 0 {
139+
config.ClientOptions.TotalRetryDuration = DefaultTotalRetryDuration
140+
log.Info("TotalRetryDuration not set, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
141+
}
142+
143+
if config.ClientOptions.CustomTimeout == 0 {
144+
config.ClientOptions.CustomTimeout = DefaultTimeout
145+
log.Info("CustomTimeout not set, set to default value", zap.Duration("CustomTimeout", DefaultTimeout))
146+
}
147+
148+
// Determine the authentication method using the helper function
149+
authMethod, err := DetermineAuthMethod(config.Auth)
150+
if err != nil {
151+
log.Error("Failed to determine authentication method", zap.Error(err))
152+
return nil, err
153+
}
154+
155+
// Create a new HTTP client with the provided configuration.
156+
client := &Client{
157+
APIHandler: apiHandler,
158+
InstanceName: config.Environment.InstanceName,
159+
AuthMethod: authMethod,
160+
OverrideBaseDomain: config.Environment.OverrideBaseDomain,
161+
httpClient: &http.Client{Timeout: config.ClientOptions.CustomTimeout},
162+
clientConfig: config,
163+
Logger: log,
164+
ConcurrencyMgr: NewConcurrencyManager(config.ClientOptions.MaxConcurrentRequests, log, true),
165+
PerfMetrics: PerformanceMetrics{},
166+
}
167+
168+
// Log the client's configuration.
169+
log.Info("New API client initialized",
170+
zap.String("API Type", config.Environment.APIType),
171+
zap.String("Instance Name", client.InstanceName),
172+
zap.String("Override Base Domain", config.Environment.OverrideBaseDomain),
173+
zap.String("Authentication Method", authMethod),
174+
zap.String("Logging Level", config.ClientOptions.LogLevel),
175+
zap.String("Log Encoding Format", config.ClientOptions.LogOutputFormat),
176+
zap.String("Log Separator", config.ClientOptions.LogConsoleSeparator),
177+
zap.Bool("Hide Sensitive Data In Logs", config.ClientOptions.HideSensitiveData),
178+
zap.Int("Max Retry Attempts", config.ClientOptions.MaxRetryAttempts),
179+
zap.Int("Max Concurrent Requests", config.ClientOptions.MaxConcurrentRequests),
180+
zap.Bool("Enable Dynamic Rate Limiting", config.ClientOptions.EnableDynamicRateLimiting),
181+
zap.Duration("Token Refresh Buffer Period", config.ClientOptions.TokenRefreshBufferPeriod),
182+
zap.Duration("Total Retry Duration", config.ClientOptions.TotalRetryDuration),
183+
zap.Duration("Custom Timeout", config.ClientOptions.CustomTimeout),
184+
)
185+
186+
return client, nil
187+
188+
}

0 commit comments

Comments
 (0)