Skip to content
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

Add methods for configuring an APIClient #154

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
245 changes: 12 additions & 233 deletions sdk/client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,14 @@
package client

import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"time"

"github.com/conductor-sdk/conductor-go/sdk/authentication"
"github.com/conductor-sdk/conductor-go/sdk/settings"
"github.com/sirupsen/logrus"
)

const (
Expand All @@ -46,6 +32,9 @@ var (
)

type APIClient struct {
dialer *net.Dialer
netTransport *http.Transport
httpClient *http.Client
httpRequester *HttpRequester
tokenManager authentication.TokenManager
}
Expand Down Expand Up @@ -94,97 +83,6 @@ func NewAPIClientWithTokenManager(
)
}

func newAPIClient(authenticationSettings *settings.AuthenticationSettings, httpSettings *settings.HttpSettings, tokenExpiration *authentication.TokenExpiration, tokenManager authentication.TokenManager) *APIClient {
if httpSettings == nil {
httpSettings = settings.NewHttpDefaultSettings()
}
baseDialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
netTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: baseDialer.DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
DisableCompression: false,
}
client := http.Client{
Transport: netTransport,
CheckRedirect: nil,
Jar: nil,
Timeout: 30 * time.Second,
}
return &APIClient{
httpRequester: NewHttpRequester(
authenticationSettings, httpSettings, &client, tokenExpiration, tokenManager,
),
}
}

// callAPI do the request.
func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) {
return c.httpRequester.httpClient.Do(request)
}

func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) {
if strings.Contains(contentType, "application/xml") {
if err = xml.Unmarshal(b, v); err != nil {
return err
}
return nil
} else if strings.Contains(contentType, "application/json") {
if err = json.Unmarshal(b, v); err != nil {
return err
}
return nil
} else if strings.Contains(contentType, "text/plain") {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return errors.New("undefined response type")
}
rv.Elem().SetString(string(b))
return nil
}
return errors.New("undefined response type")
}

func (c *APIClient) prepareRequest(
ctx context.Context,
path string, method string,
postBody interface{},
headerParams map[string]string,
queryParams url.Values,
formParams url.Values,
fileName string,
fileBytes []byte,
) (localVarRequest *http.Request, err error) {
return c.httpRequester.prepareRequest(
ctx, path, method, postBody, headerParams, queryParams, formParams, fileName, fileBytes,
)
}

// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go
type cacheControl map[string]string

func parseCacheControl(headers http.Header) cacheControl {
cc := cacheControl{}
ccHeader := headers.Get("Cache-Control")
for _, part := range strings.Split(ccHeader, ",") {
part = strings.Trim(part, " ")
if part == "" {
continue
}
if strings.ContainsRune(part, '=') {
keyval := strings.Split(part, "=")
cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
} else {
cc[part] = ""
}
}
return cc
}

// CacheExpires helper function to determine remaining time before repeating a request.
func CacheExpires(r *http.Response) time.Time {
// Figure out when the cache expires.
Expand Down Expand Up @@ -213,139 +111,20 @@ func CacheExpires(r *http.Response) time.Time {
return expires
}

func selectHeaderContentType(contentTypes []string) string {
if len(contentTypes) == 0 {
return ""
}
if contains(contentTypes, "application/json") {
return "application/json"
}
return contentTypes[0] // use the first content type specified in 'consumes'
}
func (client *APIClient) ConfigureDialer(configurer func(dialer *net.Dialer)) *APIClient {
configurer(client.dialer)

// selectHeaderAccept join all accept types and return
func selectHeaderAccept(accepts []string) string {
if len(accepts) == 0 {
return ""
}
if contains(accepts, "application/json") {
return "application/json"
}
return strings.Join(accepts, ",")
return client
}

func contains(haystack []string, needle string) bool {
for _, a := range haystack {
if strings.EqualFold(a, needle) {
return true
}
}
return false
}
func (client *APIClient) ConfigureTransport(configurer func(transport *http.Transport)) *APIClient {
configurer(client.netTransport)

func parameterToString(obj interface{}, collectionFormat string) string {
var delimiter string

switch collectionFormat {
case "pipes":
delimiter = "|"
case "ssv":
delimiter = " "
case "tsv":
delimiter = "\t"
case "csv":
delimiter = ","
}

if reflect.TypeOf(obj).Kind() == reflect.Slice {
return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]")
}

return fmt.Sprintf("%v", obj)
}

func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) {
if bodyBuf == nil {
bodyBuf = &bytes.Buffer{}
}
if reader, ok := body.(io.Reader); ok {
_, err = bodyBuf.ReadFrom(reader)
} else if b, ok := body.([]byte); ok {
_, err = bodyBuf.Write(b)
} else if s, ok := body.(string); ok {
_, err = bodyBuf.WriteString(s)
} else if s, ok := body.(*string); ok {
_, err = bodyBuf.WriteString(*s)
} else if jsonCheck.MatchString(contentType) {
err = json.NewEncoder(bodyBuf).Encode(body)
} else if xmlCheck.MatchString(contentType) {
xml.NewEncoder(bodyBuf).Encode(body)
}

if err != nil {
return nil, err
}

if bodyBuf.Len() == 0 {
err = fmt.Errorf("invalid body type %s", contentType)
return nil, err
}
return bodyBuf, nil
return client
}

func detectContentType(body interface{}) string {
contentType := "text/plain; charset=utf-8"
kind := reflect.TypeOf(body).Kind()

switch kind {
case reflect.Struct, reflect.Map, reflect.Ptr:
contentType = "application/json; charset=utf-8"
case reflect.String:
contentType = "text/plain; charset=utf-8"
default:
if b, ok := body.([]byte); ok {
contentType = http.DetectContentType(b)
} else if kind == reflect.Slice {
contentType = "application/json; charset=utf-8"
}
}

return contentType
}

func getDecompressedBody(response *http.Response) ([]byte, error) {
defer response.Body.Close()
var reader io.ReadCloser
var err error
switch response.Header.Get("Content-Encoding") {
case "gzip":
reader, err = gzip.NewReader(response.Body)
if err != nil {
logrus.Error("Unable to decompress the response ", err.Error())
if err == io.EOF {
return nil, nil
}
return nil, err
}
default:
reader = response.Body
}
defer reader.Close()
return io.ReadAll(reader)
}

func addFile(w *multipart.Writer, fieldName, path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

part, err := w.CreateFormFile(fieldName, filepath.Base(path))
if err != nil {
return err
}
_, err = io.Copy(part, file)
func (client *APIClient) ConfigureHttpClient(configurer func(client *http.Client)) *APIClient {
configurer(client.httpClient)

return err
return client
}
Loading
Loading