forked from deploymenttheory/go-api-sdk-jamfpro
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http_client.go
171 lines (147 loc) · 6.9 KB
/
http_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// http_client.go
/* The `http_client` package provides a configurable HTTP client tailored for interacting with specific APIs.
It supports different authentication methods, including "bearer" and "oauth". The client is designed with a
focus on concurrency management, structured error handling, and flexible configuration options.
The package offers a default timeout, custom backoff strategies, dynamic rate limiting,
and detailed logging capabilities. The main `Client` structure encapsulates all necessary components,
like the baseURL, authentication details, and an embedded standard HTTP client. */
package http_client
import (
"fmt"
"net/http"
"sync"
"time"
)
// Config holds configuration options for the HTTP Client.
type Config struct {
// Required
InstanceName string
Auth AuthConfig // User can either supply these values manually or pass from LoadAuthConfig/Env vars
// Optional
LogLevel LogLevel // Field for defining tiered logging level.
MaxRetryAttempts int // Config item defines the max number of retry request attempts for retryable HTTP methods.
EnableDynamicRateLimiting bool
Logger Logger // Field for the packages initailzed logger
MaxConcurrentRequests int // Field for defining the maximum number of concurrent requests allowed in the semaphore
TokenRefreshBufferPeriod time.Duration
TotalRetryDuration time.Duration
CustomTimeout time.Duration
}
// ClientPerformanceMetrics captures various metrics related to the client's
// interactions with the API, providing insights into its performance and behavior.
type PerformanceMetrics struct {
TotalRequests int64
TotalRetries int64
TotalRateLimitErrors int64
TotalResponseTime time.Duration
TokenWaitTime time.Duration
lock sync.Mutex
}
// ClientAuthConfig represents the structure to read authentication details from a JSON configuration file.
type AuthConfig struct {
InstanceName string `json:"instanceName,omitempty"`
OverrideBaseDomain string `json:"overrideBaseDomain,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
ClientID string `json:"clientID,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
}
// Client represents an HTTP client to interact with a specific API.
type Client struct {
InstanceName string // Website Instance name without the root domain
AuthMethod string // Specifies the authentication method: "bearer" or "oauth"
Token string // Authentication Token
OverrideBaseDomain string // Base domain override used when the default in the api handler isn't suitable
OAuthCredentials OAuthCredentials // ClientID / Client Secret
BearerTokenAuthCredentials BearerTokenAuthCredentials // Username and Password for Basic Authentication
Expiry time.Time // Expiry time set for the auth token
httpClient *http.Client
tokenLock sync.Mutex
config Config
logger Logger
ConcurrencyMgr *ConcurrencyManager
PerfMetrics PerformanceMetrics
}
// NewClient creates a new HTTP client with the provided configuration.
func NewClient(config Config) (*Client, error) {
// Logging to track client setup process
var logger Logger
if config.Logger == nil {
logger = NewDefaultLogger()
}
if config.LogLevel < LogLevelNone || config.LogLevel > LogLevelDebug {
return nil, fmt.Errorf("invalid LogLevel")
} else if config.LogLevel == 0 {
logger.Info("LogLevel not set, setting to default value", "LogLevel", DefaultLogLevel)
config.LogLevel = DefaultLogLevel
}
logger.SetLevel(config.LogLevel)
// Config Validation & Default Value setting
if config.InstanceName == "" {
return nil, fmt.Errorf("instanceName cannot be empty")
}
if config.MaxRetryAttempts < 0 {
logger.Info("MaxRetryAttempts cannot be negative, setting to default value", "MaxRetryAttempts", DefaultMaxRetryAttempts)
config.MaxRetryAttempts = DefaultMaxRetryAttempts
}
if config.MaxConcurrentRequests <= 0 {
logger.Info("MaxConcurrentRequests cannot be negative, setting to default value", "MaxConcurrentRequests", DefaultMaxConcurrentRequests)
config.MaxConcurrentRequests = DefaultMaxConcurrentRequests
}
if config.TokenRefreshBufferPeriod < 0 {
logger.Info("TokenRefreshBufferPeriod cannot be negative, setting to default value", "TokenRefreshBufferPeriod", DefaultTokenBufferPeriod)
config.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
}
if config.TotalRetryDuration < 0 {
logger.Info("TotalRetryDuration cannot be negative, setting to default value", "TotalRetryDuration", DefaultTotalRetryDuration)
return nil, fmt.Errorf("TotalRetryDuration cannot be negative")
}
if config.TokenRefreshBufferPeriod == 0 {
logger.Info("TokenRefreshBufferPeriod not set, setting to default value", "TokenRefreshBufferPeriod", DefaultTokenBufferPeriod)
config.TokenRefreshBufferPeriod = 60 * time.Second
}
if config.TotalRetryDuration == 0 {
logger.Info("TotalRetryDuration not set, setting to default value", "TotalRetryDuration", DefaultTotalRetryDuration)
config.TotalRetryDuration = 60 * time.Second
}
if config.CustomTimeout == 0 {
logger.Info("CustomTimeout not set, setting to default value", "CustomTimeout", DefaultTimeout)
config.CustomTimeout = DefaultTimeout
}
var AuthMethod string
if config.Auth.Username != "" && config.Auth.Password != "" {
AuthMethod = "bearer"
} else if config.Auth.ClientID != "" && config.Auth.ClientSecret != "" {
AuthMethod = "oauth"
} else {
return nil, fmt.Errorf("invalid AuthConfig")
}
client := &Client{
InstanceName: config.InstanceName,
httpClient: &http.Client{Timeout: DefaultTimeout},
AuthMethod: AuthMethod,
config: config,
logger: logger,
ConcurrencyMgr: NewConcurrencyManager(config.MaxConcurrentRequests, logger, config.LogLevel >= LogLevelDebug),
PerfMetrics: PerformanceMetrics{},
}
_, err := client.ValidAuthTokenCheck()
if err != nil {
return nil, fmt.Errorf("failed to validate auth: %w", err)
}
// Start the periodic metric evaluation for adjusting concurrency.
go client.StartMetricEvaluation()
if client.config.LogLevel >= LogLevelDebug {
client.logger.Debug(
"New client initialized with the following details:",
"InstanceName", client.InstanceName,
"Timeout", client.httpClient.Timeout,
"TokenRefreshBufferPeriod", client.config.TokenRefreshBufferPeriod,
"TotalRetryDuration", client.config.TotalRetryDuration,
"MaxRetryAttempts", client.config.MaxRetryAttempts,
"MaxConcurrentRequests", client.config.MaxConcurrentRequests,
"EnableDynamicRateLimiting", client.config.EnableDynamicRateLimiting,
)
}
return client, nil
}