2024-01-13 08:50:14 -06:00

166 lines
3.9 KiB
Go

package httpclient
import (
"bytes"
"compress/gzip"
"compress/zlib"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"net/http"
)
// HTTPClient is an interface for initializing the http client library.
type HTTPClient struct {
Client *http.Client
Data *bytes.Buffer
Headers map[string]string
Username string
Password string
}
// DefaultClient is a function for defining a basic HTTP client with standard timeouts.
func DefaultClient() *HTTPClient {
return &HTTPClient{
Client: &http.Client{
Timeout: 60 * time.Second,
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
IdleConnTimeout: 300 * time.Second,
},
},
}
}
// NewClient Create an HTTPClient with a user-provided net/http.Client
func NewClient(httpClient *http.Client) *HTTPClient {
return &HTTPClient{Client: httpClient}
}
// SetBasicAuth is a chaining function to set the username and password for basic
// authentication
func (c *HTTPClient) SetBasicAuth(username, password string) *HTTPClient {
c.Username = username
c.Password = password
return c
}
// SetPostData is a chaining function to set POST/PUT/PATCH data
func (c *HTTPClient) SetPostData(data string) *HTTPClient {
c.Data = bytes.NewBufferString(data)
return c
}
// SetHeader is a chaining function to set arbitrary HTTP Headers
func (c *HTTPClient) SetHeader(label string, value string) *HTTPClient {
if c.Headers == nil {
c.Headers = map[string]string{}
}
c.Headers[label] = value
return c
}
// Get calls the net.http GET operation
func (c *HTTPClient) Get(url string) ([]byte, error) {
return c.do(url, http.MethodGet)
}
// Patch calls the net.http PATCH operation
func (c *HTTPClient) Patch(url string) ([]byte, error) {
return c.do(url, http.MethodPatch)
}
// Post calls the net.http POST operation
func (c *HTTPClient) Post(url string) ([]byte, error) {
return c.do(url, http.MethodPost)
}
// Put calls the net.http PUT operation
func (c *HTTPClient) Put(url string) ([]byte, error) {
return c.do(url, http.MethodPut)
}
func (c *HTTPClient) do(url string, method string) ([]byte, error) {
var (
req *http.Request
res *http.Response
output []byte
err error
)
// NewRequest knows that c.data is typed *bytes.Buffer and will SEGFAULT
// if c.data is nil. So we create a request using nil when c.data is nil
if c.Data != nil {
req, err = http.NewRequest(method, url, c.Data)
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
return nil, err
}
if (len(c.Username) > 0) && (len(c.Password) > 0) {
req.SetBasicAuth(c.Username, c.Password)
}
if c.Headers != nil {
for label, value := range c.Headers {
req.Header.Set(label, value)
}
}
if res, err = c.Client.Do(req); err != nil {
return nil, err
}
defer res.Body.Close()
if output, err = io.ReadAll(res.Body); err != nil {
return nil, err
}
// check status
if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, errors.New("non-successful status code received [" + strconv.Itoa(res.StatusCode) + "]")
}
// gzip encoding
if strings.EqualFold(res.Header.Get("Content-Encoding"), "gzip") || strings.EqualFold(res.Header.Get("Content-Encoding"), "x-gzip") {
compHandler, err := gzip.NewReader(bytes.NewReader(output))
if err != nil {
return nil, fmt.Errorf("unable to uncompress response: %v", err)
}
output, err = io.ReadAll(compHandler)
if err != nil {
return nil, fmt.Errorf("unable to uncompress response: %v", err)
}
}
// deflate encoding
if strings.EqualFold(res.Header.Get("Content-Encoding"), "deflate") {
compHandler, err := zlib.NewReader(bytes.NewReader(output))
if err != nil {
return nil, fmt.Errorf("unable to uncompress response: %v", err)
}
output, err = io.ReadAll(compHandler)
if err != nil {
return nil, fmt.Errorf("unable to uncompress response: %v", err)
}
}
return output, nil
}