Skip to content

Commit

Permalink
add httpclient pkg (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
veezhang authored Mar 30, 2022
1 parent 55bae23 commit d1df1cc
Show file tree
Hide file tree
Showing 11 changed files with 1,081 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
# Go Common Packages

- [errorx](errorx) - Error extension with code and message.
- [httpclient](httpclient) - HTTP client containing raw `Client`, `BytesClient` and `ObjectClient`.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/vesoft-inc/go-pkg
go 1.17

require (
github.com/go-resty/resty/v2 v2.7.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.1
)
Expand All @@ -11,6 +12,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand All @@ -15,6 +17,18 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
66 changes: 66 additions & 0 deletions httpclient/bytes_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package httpclient

import "github.com/go-resty/resty/v2"

type (
BytesClient interface {
Get(urlPath string, opts ...RequestOption) ([]byte, error)
Post(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error)
Put(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error)
Patch(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error)
Delete(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error)
Execute(method, urlPath string, body interface{}, opts ...RequestOption) ([]byte, error)
}

defaultBytesClient struct {
client Client
}
)

var _ BytesClient = (*defaultBytesClient)(nil)

func NewBytesClient(addr string, opts ...RequestOption) BytesClient {
return NewBytesClientRaw(NewClient(addr, opts...))
}

func NewBytesClientRaw(cli Client) BytesClient {
return &defaultBytesClient{
client: cli,
}
}

func (c *defaultBytesClient) Get(urlPath string, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Get(urlPath, opts...))
}

func (c *defaultBytesClient) Post(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Post(urlPath, body, opts...))
}

func (c *defaultBytesClient) Put(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Put(urlPath, body, opts...))
}

func (c *defaultBytesClient) Patch(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Patch(urlPath, body, opts...))
}

func (c *defaultBytesClient) Delete(urlPath string, body interface{}, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Delete(urlPath, body, opts...))
}

func (c *defaultBytesClient) Execute(method, urlPath string, body interface{}, opts ...RequestOption) ([]byte, error) {
return c.convertResponse(c.client.Execute(method, urlPath, body, opts...))
}

func (*defaultBytesClient) convertResponse(resp *resty.Response, err error) ([]byte, error) {
if err != nil {
return nil, err
}

if err = NewResponseErrorNotSuccess(resp); err != nil {
return nil, err
}

return resp.Body(), err
}
122 changes: 122 additions & 0 deletions httpclient/bytes_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package httpclient

import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/go-resty/resty/v2"
"github.com/stretchr/testify/assert"
)

func TestBytesClient(t *testing.T) {
reqBody := []byte("testReqBody")
respBody := []byte("testRespBody")
reqFuncMap := map[string]func(BytesClient) ([]byte, error){
resty.MethodGet: func(c BytesClient) ([]byte, error) {
return c.Get("")
},
resty.MethodPost: func(c BytesClient) ([]byte, error) {
return c.Post("", reqBody)
},
resty.MethodPut: func(c BytesClient) ([]byte, error) {
return c.Put("", reqBody)
},
resty.MethodPatch: func(c BytesClient) ([]byte, error) {
return c.Patch("", reqBody)
},
resty.MethodDelete: func(c BytesClient) ([]byte, error) {
return c.Delete("", reqBody)
},
}

for _, statusCode := range []int{200, 301, 404, 500, 502} {
for _, method := range []string{resty.MethodGet, resty.MethodPost, resty.MethodPut, resty.MethodPatch, resty.MethodDelete} {
t.Run(fmt.Sprintf("%s:%d", method, statusCode), func(t *testing.T) {
ast := assert.New(t)
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ast.Equal(method, r.Method)
if r.Method != resty.MethodGet {
body, err := ioutil.ReadAll(r.Body)
ast.NoError(err)
ast.Equal(reqBody, body)
}
ast.Equal(url.Values{
"q0": []string{"qv0"},
"q1": []string{"qv1"},
"q2": []string{"qv2"},
}, r.URL.Query())
ast.Equal([]string{"hv0", "hv1", "hv2"}, []string{r.Header.Get("h0"), r.Header.Get("h1"), r.Header.Get("h2")})
ast.Equal("Bearer AuthToken", r.Header.Get("Authorization"))

w.WriteHeader(statusCode)
w.Write(respBody)
}))

defer testServer.Close()

checkHookFunc := func(resp *resty.Response, err error) {
if statusCode == 301 {
ast.Error(err)
ast.Contains(err.Error(), "301 response missing Location header")
} else {
ast.NoError(err)
ast.Equal(statusCode, resp.StatusCode())
ast.Equal(respBody, resp.Body())
}
}
checkFunc := func(respBytes []byte, err error) {
if statusCode == 301 {
ast.Error(err)
ast.Contains(err.Error(), "301 response missing Location header")
} else if statusCode != 200 {
ast.Nil(respBytes)
respErr, ok := AsResponseError(err)
ast.True(ok)
resp := respErr.GetResponse()
ast.NotNil(resp)
ast.Equal(statusCode, resp.StatusCode())
ast.Equal(respBody, resp.Body())
} else {
ast.NoError(err)
ast.Equal(respBody, respBytes)
}
}

c := NewBytesClient(testServer.URL,
WithQueryParam("q0", "qv0"),
WithQueryParams(map[string]string{
"q1": "qv1",
"q2": "qv2",
}),
WithHeader("h0", "hv0"),
WithHeaders(map[string]string{
"h1": "hv1",
"h2": "hv2",
}),
WithAuthToken("AuthToken"),
WithAfterRequestHook(func(_ *resty.Request, resp *resty.Response, err error) {
checkHookFunc(resp, err)
}),
WithAfterRequestHook(func(_ *resty.Request, resp *resty.Response, err error) {
checkHookFunc(resp, err)
}),
)
f := reqFuncMap[method]
if ast.NotNil(f) {
resp, err := f(c)
checkFunc(resp, err)
}
var executeBody interface{}
if method != resty.MethodGet {
executeBody = reqBody
}
resp, err := c.Execute(method, "", executeBody)
checkFunc(resp, err)
})
}
}
}
Loading

0 comments on commit d1df1cc

Please sign in to comment.