Skip to content

Devtooling - 995 Golang Https client injection #1023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions resources/sdk/purecloudgo/extensions/abstract_httpclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package platformclientv2

import (
"context"
"net/http"
"time"

"github.com/hashicorp/go-retryablehttp"
)

// AbstractHttpClient defines the interface for an HTTP client with retry capabilities
type AbstractHttpClient interface {
// Do executes an HTTP request with the given options
Do(options *HTTPRequestOptions) (*http.Response, error)

// SetRetryMax sets the maximum number of retries for failed requests
SetRetryMax(max int)

// SetRetryWaitMax sets the maximum wait time between retries
SetRetryWaitMax(duration time.Duration)

// SetRetryWaitMin sets the minimum wait time between retries
SetRetryWaitMin(duration time.Duration)

// SetRequestLogHook sets a logging hook that is called before each retry attempt
SetRequestLogHook(hook func(retryablehttp.Logger, *http.Request, int))

// SetResponseLogHook sets a logging hook that is called after each response
SetResponseLogHook(hook func(retryablehttp.Logger, *http.Response))

// SetCheckRetry sets the retry policy function to determine if a request should be retried
SetCheckRetry(checkRetry func(ctx context.Context, resp *http.Response, err error) (bool, error))

// SetTransport sets the underlying HTTP transport for the client
SetTransport(transport *http.Transport)

// SetHttpsAgent
SetHttpsAgent(proxy *ProxyAgent)
}

// ProxyAgent holds proxy configuration
type ProxyAgent struct {
ProxyURL string
}
88 changes: 88 additions & 0 deletions resources/sdk/purecloudgo/extensions/default_httpclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package platformclientv2

import (
"context"
"net/http"
"net/url"
"time"

"github.com/hashicorp/go-retryablehttp"
)

// DefaultHttpClient wraps retryablehttp.Client to provide HTTP functionality with automatic retries
type DefaultHttpClient struct {
client retryablehttp.Client
proxyAgent *ProxyAgent
}

// SetRetryMax sets the maximum number of retries for failed requests
func (c *DefaultHttpClient) SetRetryMax(max int) {
c.client.RetryMax = max
}

// SetRetryWaitMax sets the maximum time to wait between retries
func (c *DefaultHttpClient) SetRetryWaitMax(duration time.Duration) {
c.client.RetryWaitMax = duration
}

// SetRetryWaitMin sets the minimum time to wait between retries
func (c *DefaultHttpClient) SetRetryWaitMin(duration time.Duration) {
c.client.RetryWaitMin = duration
}

// SetRequestLogHook sets a logging hook that runs before each retry
func (c *DefaultHttpClient) SetRequestLogHook(hook func(retryablehttp.Logger, *http.Request, int)) {
c.client.RequestLogHook = hook
}

// SetResponseLogHook sets a logging hook that runs after each retry
func (c *DefaultHttpClient) SetResponseLogHook(hook func(retryablehttp.Logger, *http.Response)) {
c.client.ResponseLogHook = hook
}

// SetCheckRetry sets the retry policy function to determine if a request should be retried
func (c *DefaultHttpClient) SetCheckRetry(checkRetry func(ctx context.Context, resp *http.Response, err error) (bool, error)) {
c.client.CheckRetry = checkRetry
}

// SetTransport sets the underlying HTTP transport for the client
func (c *DefaultHttpClient) SetTransport(transport *http.Transport) {
c.client.HTTPClient.Transport = transport
}

func (c *DefaultHttpClient) SetHttpsAgent(agent *ProxyAgent) {
c.proxyAgent = agent
}

// NewDefaultHttpClient creates and returns a new DefaultHttpClient instance with default settings
func NewDefaultHttpClient(proxyAgent *ProxyAgent) *DefaultHttpClient {
_, err := time.ParseDuration("16s")
if err != nil {
panic(err)
}

client := retryablehttp.NewClient()
if proxyAgent != nil && proxyAgent.ProxyURL != "" {
proxyURL, err := url.Parse(proxyAgent.ProxyURL)
if err == nil {
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client.HTTPClient.Transport = transport
}
}

return &DefaultHttpClient{
client: *client,
proxyAgent: proxyAgent,
}
}

// Do executes an HTTP request with the configured retry settings
func (c *DefaultHttpClient) Do(options *HTTPRequestOptions) (*http.Response, error) {
request, err := options.ToRetryableRequest()
if err != nil {
return nil, err
}
return c.client.Do(request)
}
168 changes: 168 additions & 0 deletions resources/sdk/purecloudgo/extensions/http_request_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package platformclientv2

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/hashicorp/go-retryablehttp"
)

// HTTPRequestOptions contains the configuration for making HTTP requests
type HTTPRequestOptions struct {
url *url.URL // The request URL
method string // HTTP method (GET, POST, etc)
headers map[string][]string // HTTP headers
queryParams map[string]string // URL query parameters
body interface{} // Request body
formParams url.Values // Form parameters
}

// GetUrl returns the request URL
func (o *HTTPRequestOptions) GetUrl() *url.URL {
return o.url
}

// GetMethod returns the HTTP method
func (o *HTTPRequestOptions) GetMethod() string {
return o.method
}

// GetHeaders returns the HTTP headers
func (o *HTTPRequestOptions) GetHeaders() map[string][]string {
return o.headers
}

// GetQueryParams returns the URL query parameters
func (o *HTTPRequestOptions) GetQueryParams() map[string]string {
return o.queryParams
}

// GetBody returns the request body
func (o *HTTPRequestOptions) GetBody() interface{} {
return o.body
}

// GetFormParams returns the form parameters
func (o *HTTPRequestOptions) GetFormParams() url.Values {
return o.formParams
}

// SetUrl sets the request URL, returns error if URL is nil
func (o *HTTPRequestOptions) SetUrl(url *url.URL) error {
if url == nil {
return fmt.Errorf("Invalid URL")
}
o.url = url
return nil
}

// SetMethod sets the HTTP method, validates against allowed methods
func (o *HTTPRequestOptions) SetMethod(method string) error {
if method == "" {
return fmt.Errorf("Method cannot be empty")
}

method = strings.ToUpper(method)

// Define valid HTTP methods
validMethods := map[string]bool{
"GET": true,
"HEAD": true,
"POST": true,
"PUT": true,
"PATCH": true,
"DELETE": true,
"OPTIONS": true,
}

if validMethods[method] {
o.method = method
return nil
}
return fmt.Errorf("Invalid method: %s", method)
}

// SetHeaders sets a single header key-value pair
func (o *HTTPRequestOptions) SetHeaders(key string, value string) error {
if key == "" {
return fmt.Errorf("header key cannot be empty")
}
if o.headers == nil {
o.headers = make(map[string][]string)
}
o.headers[key] = []string{value}
return nil
}

// SetQueryParams sets the URL query parameters
func (o *HTTPRequestOptions) SetQueryParams(queryParams map[string]string) error {
if queryParams == nil {
return fmt.Errorf("Query Params cannot be empty")
}
o.queryParams = queryParams
return nil
}

// SetBody sets the request body
func (o *HTTPRequestOptions) SetBody(body interface{}) error {
if body == nil {
return fmt.Errorf("Body cannot be empty")
}
o.body = body
return nil
}

// SetFormParams sets the form parameters
func (o *HTTPRequestOptions) SetFormParams(formParams url.Values) error {
if formParams == nil {
return fmt.Errorf("Form Params cannot be empty")
}
o.formParams = formParams
return nil
}

// ToRetryableRequest converts HTTPRequestOptions to a retryablehttp.Request
func (o *HTTPRequestOptions) ToRetryableRequest() (*retryablehttp.Request, error) {
// Validate required fields
if o.GetUrl() == nil {
return nil, fmt.Errorf("URL is required")
}
if o.GetMethod() == "" {
return nil, fmt.Errorf("Method is required")
}

// Create base request
request := retryablehttp.Request{
Request: &http.Request{
URL: o.GetUrl(),
Close: true,
Method: strings.ToUpper(o.GetMethod()),
Header: o.GetHeaders(),
},
}

// Set form data if present
if len(o.GetFormParams()) > 0 {
request.SetBody(ioutil.NopCloser(strings.NewReader(o.GetFormParams().Encode())))
}

// Set request body if present
if o.GetBody() != nil {
// Handle string body type differently
if body, ok := o.GetBody().(*string); ok {
j := []byte(*body)
request.SetBody(ioutil.NopCloser(bytes.NewReader(j)))
} else {
// Marshal non-string body to JSON
j, _ := json.Marshal(o.GetBody())
request.SetBody(ioutil.NopCloser(bytes.NewReader(j)))
}
}

return &request, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-----BEGIN CERTIFICATE-----
MIIFeTCCA2GgAwIBAgIDEAISMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlVT
MQ8wDQYDVQQIDAZEZW5pYWwxFDASBgNVBAcMC1NwcmluZ2ZpZWxkMQwwCgYDVQQK
DANEaXMxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNDExMjExMTE1NDVaFw0zNDEx
MTkxMTE1NDVaMEAxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIDAZEZW5pYWwxDDAKBgNV
BAoMA0RpczESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOC
Ag8AMIICCgKCAgEAuqkQkp9XMye38B9Fo9miym2LdHlc89UV02nQ1y5L2Wx7id4p
xmF+fmb+LWSJZKwGEi8v0f8/FvBbnssxy1IZCe1GCEG5o9ZOb2xML6B22jPwNBrs
o9ZwEWfF7nmRN9naUQtakOnsuV8MaK6h3fe7s9xHkjCfvlY0fp1saLsDGj+J4pK0
FWwvVNzRcxYadUQyFZN/knGhm9M86/RA6S0OOG274R/0KaWD1F/85fsjbAYZEh4V
6dZ+8TSdWz2u1oOjQUGmW9muUzYzEDAbz4BjFTTnnDUJuWOrB3ISRHL1eCJkjgHr
x53BV53SiOQ3YpK6ZqHyBgxNVB6AdkpQrnlqItarQ1HPbvoTgY+DuYS52xNwBx0Y
kcaRyKrLQEMQws+brixtA7mIzOVn2uhl8s+7nPegtovqvKPl1EUHzhF6cXH1eifb
hnBQ2qILEXrPmpxSup9Mk3cQbmtZqXLTs/qUHOczdjRxk+UgYrimVQD+vFujeiir
G5N0ktkle7pOig9nDd1XCWvFyeMCsBW4opyndNqf2DS9mrksTTpuqXZxlYrDyAre
x8LmPsxFUfldDIQn5iyYiIXrVAupBHraR3g+jSee9UetkR92mY0HHVJ990KxzTo1
2/WmiYYIzX7k+b+/9ITFP6uiascYPefZFQM3h+44Uwkk3ROFKRfUK9Ux2nMCAwEA
AaNmMGQwHQYDVR0OBBYEFNisXWoSWQRxs/uI50LqNkV1nkZ5MB8GA1UdIwQYMBaA
FIVNUSQsKppHYNYdR9vd1PjirYmKMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P
AQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBfaMA1uzPCpqzeF9Nn6NmhlMmn
q82Bkv3ujAbOJ1wjTkT7Xpte/4run64z6/0wa3pmjdCOAbxlGydFJtH18g/BbIj4
pEfgj+TXaB6iZEs24XyV20B2tAi3bhIg+WoG+wa8cSIYgiqAiXVSzlNeGLPkHCif
LcDUbntDCIPDkFsFRGhU6BxrjS05y0C8iiC/51A4OL9e8kPtDj3tJRELm39qGvvK
QFqVBoO6woUR3STFP9Rz7u3tbr3/6IRW6Qfz8OCIaRr2SK2hPJ/6klVNlOhgp9KC
mFbuWx31h0+2XETZsJmsoqfH/aXCvr5a/rV7/FhaUXATwqI/l0rd5NSyn48fzC9B
LYdpFQtdIwKieHpwg1fJTH2D9ZEqDV1DPCBTHEvh3oDsyHcMHGYL3PR3pI2ErAY3
Wil4rxx0bMq+LKjQ5cVHJYrfEnguykgcnCrbCcV2yuIetz1qX/YMvvh/ryhnJAm0
FWOZqDLZIlFevfpFQLftjjJd9Ly9Z7oGv/fW16wETYp4+y1sfTsFbMM+wTh1UKxZ
zbLd9MjI/ohXqFVQTowPSTbvEF25TA/JyEjXJGEZ3Uv933qRpRO0btjS1DlUInjg
JPp3vtuv+XD438VPPhcqQhJMIYAZTKlPt72Gb9ZygILzS65wsBHxXTUflgXSOndU
9JcJKnHjuBXUggHIdg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFnTCCA4WgAwIBAgIURqDeNAeTfgAupz6l6w1abPDMwvkwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By
aW5nZmllbGQxDDAKBgNVBAoMA0RpczESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0
MTEyMTExMTU0NFoXDTQ0MTExNjExMTU0NFowVjELMAkGA1UEBhMCVVMxDzANBgNV
BAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNVBAoMA0RpczES
MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAizgE65i89PDzcfnyr/dZqS61NyTgZsqmkJAunc3lyJ4P5Uu3pPaANVWL3sJR
qeyNUDAv3PvfhXobKuXkiKT1lX8HitlqTro4qVgWxtlnlaanKvs5Li36GR54VRyi
ozk9/KrRqYcO+bC53mmiI9kaD0+sEr/351kvI8jPUFMtkAF8OGhUi/z32OpJjtru
6tiOt8kj0+MvsxxHSljZRTeqrCAAePqbYmx9/n6a+IBGukXjZOEgOPdd+mId7RwM
ZwDU0T5FGL5exGpkMvsAOIgb7Q3vPsIhaqedbFSfQa+m89rUCa8RTZyUd12mPTsD
RyZx1/RJnOlgkT+f0FVlc+gANezkLb2+lW0RsYw5jE94/o30dpQgTLA8JpYgvaPP
mbBADef4KOJ9Z8pVnTjw9wGYl66Hpq8jHSGVKQ0lpFTX1Cwd5ssft5gaS5Fe2C+f
B5fcoF9dV1EHB5nlzGtuw9g9CulN4HIq0r5hbWVguj5TlVu6qL6b7MKWCX6wLNW9
x3SaNmoKKEqVKn+UCUOlJfuAbpXF2ubSG0agSJ+AFuY7uGwZah9MNvZX6xPlhP/m
Aks1XfkFE8dTLkxCrLDz5iK4RWVU5zGsjVd9nIqFKJ0Z0ksf9avdRVwXs0P8x+ab
dtPJmvP4uf3IkHsrpa3wwTNZBo84IW1yEDhdqV7TxYCSLR8CAwEAAaNjMGEwHQYD
VR0OBBYEFIVNUSQsKppHYNYdR9vd1PjirYmKMB8GA1UdIwQYMBaAFIVNUSQsKppH
YNYdR9vd1PjirYmKMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0G
CSqGSIb3DQEBCwUAA4ICAQCCk7nlg7bchZZp3mUYgxcT9Pxz/MyqzmYRwP8YaWlQ
+P6eZcO/ba4LkpNqkQsjlH+IlFDKo7X2TmrS/nMt8nlqhJbSgN7IZ1wh9ghh1095
Y0SBcF9O2kKKiCshlwtKT2q189old0KQT66sGB69gyMU6EGciFubHZ4eXGCiI/Ue
l4qHONWKKP1N/FZg5HmNb182bQASV5/su3GKR1POJvf/ieSPoOV8OdaD8ndDeK1a
Ei5I07jobRVNxcxOuVce1OL+2h/MFWbYbGKNtTax0Gcm+R7lO8qabMEIoSuoD761
h/VCdeCU3+V5Koy9+Mwz7aHLK7PgtJIj+FQgUzQ4LxI2uasdnVV4K+eNYRJEvVXI
S4niOjoVajzXCZUixtnuOQH+yl3Pn2+YYS5j/PCkfVhLHHuo4fYXe++6lOesLXkU
cBE4J7aN4bb0QyYlph9zsLvYFPrq4PiH9Jy5beQCMvEbQ93E1NgMniqYJLaVLeoZ
i3ypsRdcYd+Ig9bQqixAhP84URAFtfhZBpkf7L9IvSqJHoaLpd9tHYE9aj0nVnx7
+fwCZVWvNHFAQn0TWJThPDXgRo1yf7c6twDlp1DfsLZzfFz6ZS1BIXUFcgWQCoKw
wQHe610yrrA4FiUSrZaheZkwrucYn2Z/9U33Sd1TTYZBMMWmtS1vayrtS1l4fHPE
NQ==
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIE2TCCAsGgAwIBAgIDEAITMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNVBAYTAlVT
MQ8wDQYDVQQIDAZEZW5pYWwxDDAKBgNVBAoMA0RpczESMBAGA1UEAwwJbG9jYWxo
b3N0MB4XDTI0MTEyMTExMTU1N1oXDTI1MTIwMTExMTU1N1owVjELMAkGA1UEBhMC
VVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNV
BAoMA0RpczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAwJKlWwldUOtsl8B8VSwUy+HuDFccLbh6sxpayNo7qKTvqLDN
xGyXauuxZfplt/UTe2kkce642I48wlYe3jDAkVK9HQru4Al+fjacleI9n3mSjQJ9
cR+3+n+bThEWBGgGbskC+/AWtax3RgS6f3VCtnOMwTO3gZUdhL/D8Mn/OxEMFix9
dfla3Vm9RkeYnBNCQ+j2uLPf/8XW6BAGqvb+RdO/2H2G8HhyF+LsKU/ervI1ccTn
pFlNuC6HsRpgW8ssHUSgKh2kpXXiTXJjhvvhEbpE6HWNN8GB5U5WcQrP2FrLZ1PJ
l01ACdOob/MpHfYOE3NdGTpjpH/3myy/ILTLbQIDAQABo4HFMIHCMAkGA1UdEwQC
MAAwEQYJYIZIAYb4QgEBBAQDAgWgMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdl
bmVyYXRlZCBDbGllbnQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFFkrskief6mCBsYg
+KJh8SQcnfDEMB8GA1UdIwQYMBaAFNisXWoSWQRxs/uI50LqNkV1nkZ5MA4GA1Ud
DwEB/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwDQYJKoZI
hvcNAQELBQADggIBAB7hec1SrjNqPDfNmSc18RFXrUot9hfFj0sJsdUTHkTDC+Nl
rM8fOgWbtNgwmlPBZQm+UOZPNOzSoPPXVacV4yfua7NLb2zOnUSECW+CGIx2UOj4
buBptFa/FNfaayFmG/EAO1XdJuf7GVNGuYypyfDLipiGtaV/lg9B8Ig1wflvG0T1
MM/WwN8quj8hUrFLa6M05bP1uFXllrhx16VhjD8nXe4xTv5k6lLWbJy4wJ3x+AUa
Gg1KjSeabQVEWm8BiimkKKOyEEDdLXW+6IKz4W4N0Y/vFuuWUQ89qaUWiNuvLSru
WPQ7COWu6GQ0qJU0MwEI14q0XqIfRpUMwqwFSi54y7v6+9P9HPbhOArYrpc8Ghb0
B/VSYjAH9tp8+Fe3RnG5nEYvgu63oKSBxmgIlqubOSIc9E8ZVd3IbFYL+ASK4/lI
p6KaCUt/XEJrZ/Qnozms6VyV4W2ODoXGNYuKKqIw64eud6pwbnMiYVWHap7Hzq/W
8me62mVfmNzNTm8MbMc9WL05AAu9pwyUkeQyx8cmaFNVfyO4FXxDXpVcl23oZmVl
Tfg8s22Mtcz2wj3bDDMhyB9SoUdhityOo0JxMUdakdxZUvFKeas0Eot6XHNdhAPZ
Hph1w1q6VPWqtjhTDOfnjP4P7Gx+YM9fFS14yK7ufiVT4pDTHmZQ0Bucw6IQ
-----END CERTIFICATE-----
Loading