3
0

add(internal): http client import

This commit is contained in:
Tit Petric
2019-04-03 13:49:40 +02:00
parent 7ea763373e
commit 439100f53e
3 changed files with 230 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
package config
import (
"github.com/namsral/flag"
)
type (
HTTPClient struct {
BaseURL string
Timeout int
}
)
var httpClient *HTTPClient
func (c *HTTPClient) Validate() error {
return nil
}
func (*HTTPClient) Init(prefix ...string) *HTTPClient {
if httpClient != nil {
return httpClient
}
httpClient = new(HTTPClient)
flag.StringVar(&httpClient.BaseURL, "http-client-base-url", "", "HTTP Client Base URL")
flag.IntVar(&httpClient.Timeout, "http-client-timeout", 5, "HTTP Client request timeout (seconds)")
return httpClient
}

166
internal/http/client.go Normal file
View File

@@ -0,0 +1,166 @@
package http
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/config"
)
type (
Client struct {
Transport *http.Transport
Client *http.Client
debugLevel DebugLevel
config *config.HTTPClient
}
Request http.Request
DebugLevel string
)
const (
INFO DebugLevel = "info"
FULL = "full"
)
func New(flags *config.HTTPClient) (*Client, error) {
if err := flags.Validate(); err != nil {
return nil, errors.Wrap(err, "creating http client failed")
}
timeout := time.Duration(flags.Timeout) * time.Second
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: timeout,
}).Dial,
TLSHandshakeTimeout: timeout,
}
client := &http.Client{
Timeout: timeout,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return &Client{
Transport: transport,
Client: client,
config: flags,
}, nil
}
func (c *Client) Debug(level DebugLevel) *Client {
c.debugLevel = level
return c
}
func (c *Client) Get(url string) (*http.Request, error) {
return c.Request("GET", url, nil)
}
func (c *Client) Post(url string, body interface{}) (*http.Request, error) {
return c.Request("POST", url, body)
}
func (c *Client) Patch(url string, body interface{}) (*http.Request, error) {
return c.Request("PATCH", url, body)
}
func (c *Client) Delete(url string) (*http.Request, error) {
return c.Request("DELETE", url, nil)
}
func (c *Client) Request(method, url string, body interface{}) (*http.Request, error) {
if c.config.BaseURL != "" {
url = strings.TrimRight(c.config.BaseURL, "/") + "/" + strings.TrimLeft(url, "/")
}
if c.debugLevel == "info" {
fmt.Println("HTTP >>>", method, url)
}
request := func() (*http.Request, error) {
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
return http.NewRequest(method, url, bytes.NewBuffer(b))
}
return http.NewRequest(method, url, nil)
}
req, err := request()
if err != nil {
return nil, errors.Wrap(err, "creating request failed")
}
req.Header.Add("Content-Type", "application/json")
return req, nil
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
if c.debugLevel == FULL {
fmt.Println("HTTP >>> (request)")
b, err := httputil.DumpRequestOut(req, true)
if err != nil {
fmt.Println("HTTP >>> Error:", err)
} else {
if b != nil {
fmt.Println(strings.TrimSpace(string(b)))
}
}
fmt.Println("---")
}
resp, err := c.Client.Do(req)
if c.debugLevel == FULL {
fmt.Println("HTTP <<< (response)")
if err != nil {
fmt.Println("HTTP <<< Error:", err)
} else {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
fmt.Println("HTTP <<< Error:", err)
} else {
if b != nil {
fmt.Println(string(b))
}
}
}
fmt.Println("-----------------")
}
if err != nil {
if c.debugLevel == INFO {
fmt.Println("HTTP <<< Response error", err)
}
return nil, errors.Wrap(err, "request failed")
}
if c.debugLevel == INFO {
fmt.Println("HTTP <<< Response", resp.StatusCode)
}
return resp, nil
}
func ToError(resp *http.Response) error {
body, err := ioutil.ReadAll(resp.Body)
if body == nil || err != nil {
return errors.Errorf("unexpected response (%d, %s)", resp.StatusCode, err)
}
return errors.New(string(body))
}

View File

@@ -0,0 +1,36 @@
// +build external
package http
import (
"testing"
"github.com/crusttech/crust/internal/config"
"github.com/crusttech/crust/internal/test"
)
func TestHTTPClient(t *testing.T) {
client, err := New(&config.HTTPClient{
Timeout: 10,
})
test.Assert(t, err == nil, "%+v", err)
client.Debug(FULL)
req, err := client.Get("https://api.scene-si.org/fortune.php")
test.Assert(t, err == nil, "%+v", err)
resp, err := client.Do(req)
test.Assert(t, err == nil, "%+v", err)
err = func() error {
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
return nil
default:
return ToError(resp)
}
}()
test.Assert(t, err == nil, "Invalid response: %+v", err)
}