You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

169 lines
3.3 KiB

package http
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// HttpClient wraps an http.Client and provides methods for GET and POST
type HttpClient struct {
Client *http.Client
Headers map[string]string
BaseURL string
}
type HttpClientOptions struct {
Timeout time.Duration
Headers map[string]string
BaseURL string
}
type HttpResponse struct {
Status string
StatusCode int
Headers http.Header
Body []byte
}
type HttpError struct {
Response *HttpResponse
Message string
}
func (e *HttpError) Error() string {
return fmt.Sprintf("%s (status code %d)", e.Message, e.Response.StatusCode)
}
func NewHttpClient(option ...HttpClientOptions) *HttpClient {
var timeout time.Duration = 30 * time.Second
var headers map[string]string
var baseURL string
if len(option) > 0 {
if option[0].Timeout > 0 {
timeout = option[0].Timeout
}
headers = option[0].Headers
baseURL = option[0].BaseURL
}
return &HttpClient{
Client: &http.Client{Timeout: timeout},
Headers: headers,
BaseURL: baseURL,
}
}
// internal method to apply headers
func (h *HttpClient) applyHeaders(req *http.Request) {
for key, value := range h.Headers {
req.Header.Set(key, value)
}
}
// internal method to validate absolute URL
func validateAbsoluteURL(rawURL string) (bool, error) {
parsed, err := url.Parse(rawURL)
if err != nil {
return false, err
}
if !parsed.IsAbs() {
return false, nil
}
return true, nil
}
func (h *HttpClient) httpRequest(method, url string, body io.Reader) (*http.Request, error) {
// Validate URL
isAbsolute, _ := validateAbsoluteURL(url)
if !isAbsolute {
if h.BaseURL != "" {
url = h.BaseURL + url
}
}
req, err := http.NewRequest(method, url, body)
return req, err
}
// Get performs a GET request and returns full response info
func (h *HttpClient) Get(url string) (*HttpResponse, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
h.applyHeaders(req)
resp, err := h.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
response := &HttpResponse{
Status: resp.Status,
StatusCode: resp.StatusCode,
Headers: resp.Header,
Body: bodyBytes,
}
// Return an error if status code is 4xx or 5xx
if resp.StatusCode >= 400 {
return response, &HttpError{
Response: response,
Message: "unexpected HTTP status",
}
}
return response, nil
}
// Post performs a POST request and returns full response info
func (h *HttpClient) Post(url string, data any) (*HttpResponse, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer([]byte(jsonBytes)))
if err != nil {
return nil, err
}
h.applyHeaders(req)
resp, err := h.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
response := &HttpResponse{
Status: resp.Status,
StatusCode: resp.StatusCode,
Headers: resp.Header,
Body: bodyBytes,
}
// Return an error if status code is 4xx or 5xx
if resp.StatusCode >= 400 {
return response, &HttpError{
Response: response,
Message: "unexpected HTTP status",
}
}
return response, nil
}