Skip to content

Commit

Permalink
✨ feat: httpreq - add PostJSON() method, update some req build logic
Browse files Browse the repository at this point in the history
- add more unit test cases
  • Loading branch information
inhere committed Jul 11, 2023
1 parent b217736 commit 4768e7d
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 52 deletions.
52 changes: 36 additions & 16 deletions netutil/httpreq/client.go
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net/http"
"strings"
"time"

"github.com/gookit/goutil/netutil/httpctype"
"github.com/gookit/goutil/strutil"
Expand All @@ -25,16 +26,20 @@ type Client struct {
afterSend AfterSendFn
}

// New instance with base URL
// New instance with base URL and use http.Client as default http client
func New(baseURL ...string) *Client {
h := NewWithDoer(&http.Client{})

h := NewWithDoer(&http.Client{Timeout: defaultTimeout})
if len(baseURL) > 0 && baseURL[0] != "" {
h.baseURL = baseURL[0]
}
return h
}

// NewWithTimeout new instance use http.Client and with custom timeout(ms)
func NewWithTimeout(ms int) *Client {
return NewWithDoer(&http.Client{Timeout: time.Duration(ms) * time.Millisecond})
}

// NewWithDoer instance with custom http client
func NewWithDoer(d Doer) *Client {
return &Client{
Expand All @@ -50,12 +55,21 @@ func (h *Client) Doer() Doer {
return h.client
}

// Client custom http client doer
func (h *Client) Client(c Doer) *Client {
// SetClient custom set http client doer
func (h *Client) SetClient(c Doer) *Client {
h.client = c
return h
}

// SetTimeout set default timeout for http client doer
func (h *Client) SetTimeout(ms int) *Client {
h.timeout = ms
if hc, ok := h.client.(*http.Client); ok {
hc.Timeout = time.Duration(ms) * time.Millisecond
}
return h
}

// BaseURL set request base URL
func (h *Client) BaseURL(baseURL string) *Client {
h.baseURL = baseURL
Expand Down Expand Up @@ -110,55 +124,55 @@ func (h *Client) WithOption(optFns ...OptionFn) *Option {
return NewOption(optFns).WithClient(h)
}

func newOptWithClient(cli *Client) *Option {
func optWithClient(cli *Client) *Option {
return &Option{cli: cli}
}

// WithData with custom request data
func (h *Client) WithData(data any) *Option {
return newOptWithClient(h).WithData(data)
return optWithClient(h).WithData(data)
}

// WithBody with custom body
func (h *Client) WithBody(r io.Reader) *Option {
return newOptWithClient(h).WithBody(r)
return optWithClient(h).WithBody(r)
}

// BytesBody with custom bytes body
func (h *Client) BytesBody(bs []byte) *Option {
return newOptWithClient(h).BytesBody(bs)
return optWithClient(h).BytesBody(bs)
}

// StringBody with custom string body
func (h *Client) StringBody(s string) *Option {
return newOptWithClient(h).StringBody(s)
return optWithClient(h).StringBody(s)
}

// FormBody with custom form data body
func (h *Client) FormBody(data any) *Option {
return newOptWithClient(h).FormBody(data)
return optWithClient(h).FormBody(data)
}

// JSONBody with custom JSON data body
func (h *Client) JSONBody(data any) *Option {
return newOptWithClient(h).WithJSON(data)
return optWithClient(h).WithJSON(data)
}

// JSONBytesBody with custom bytes body, and set JSON content type
func (h *Client) JSONBytesBody(bs []byte) *Option {
return newOptWithClient(h).JSONBytesBody(bs)
return optWithClient(h).JSONBytesBody(bs)
}

// AnyBody with custom body.
//
// Allow type:
// - string, []byte, map[string][]string/url.Values, io.Reader(eg: bytes.Buffer, strings.Reader)
func (h *Client) AnyBody(data any) *Option {
return newOptWithClient(h).AnyBody(data)
return optWithClient(h).AnyBody(data)
}

//
// send request with options
// ------------ send request with options ------------
//

// Get send GET request with options, return http response
Expand All @@ -172,6 +186,12 @@ func (h *Client) Post(url string, data any, optFns ...OptionFn) (*http.Response,
return h.SendWithOpt(url, opt)
}

// PostJSON send JSON POST request with options, return http response
func (h *Client) PostJSON(url string, data any, optFns ...OptionFn) (*http.Response, error) {
opt := NewOption(optFns).WithMethod(http.MethodPost).WithJSON(data)
return h.SendWithOpt(url, opt)
}

// Put send PUT request with options, return http response
func (h *Client) Put(url string, data any, optFns ...OptionFn) (*http.Response, error) {
opt := NewOption(optFns).WithMethod(http.MethodPut).AnyBody(data)
Expand Down Expand Up @@ -208,7 +228,7 @@ func (h *Client) SendWithOpt(url string, opt *Option) (*http.Response, error) {
}
}

opt = MakeOpt(opt)
opt = OptOrNew(opt)
ctx := opt.Context
if ctx == nil {
ctx = context.Background()
Expand Down
3 changes: 3 additions & 0 deletions netutil/httpreq/client_test.go
Expand Up @@ -24,6 +24,7 @@ func TestClient_Send(t *testing.T) {
})

assert.NoErr(t, err)

sc := resp.StatusCode
assert.True(t, httpreq.IsOK(sc))
assert.True(t, httpreq.IsSuccessful(sc))
Expand All @@ -47,6 +48,8 @@ func TestClient_RSET(t *testing.T) {
"custom2": "value2",
})

assert.NotNil(t, cli.Doer())

t.Run("Get", func(t *testing.T) {
resp, err := cli.Get("/get", httpreq.WithData("name=inhere&age=18"))
assert.NoErr(t, err)
Expand Down
7 changes: 6 additions & 1 deletion netutil/httpreq/default.go
Expand Up @@ -2,7 +2,7 @@ package httpreq

import "net/http"

// default standard client instance
// default standard client instance, with 500ms timeout
var std = NewClient(500)

// Std instance
Expand Down Expand Up @@ -34,6 +34,11 @@ func Post(url string, data any, optFns ...OptionFn) (*http.Response, error) {
return std.Post(url, data, optFns...)
}

// PostJSON quick send a POST request by default client, with JSON content type
func PostJSON(url string, data any, optFns ...OptionFn) (*http.Response, error) {
return std.PostJSON(url, data, optFns...)
}

// Put quick send a PUT request by default client
func Put(url string, data any, optFns ...OptionFn) (*http.Response, error) {
return std.Put(url, data, optFns...)
Expand Down
9 changes: 4 additions & 5 deletions netutil/httpreq/httpreq.go
Expand Up @@ -31,6 +31,8 @@ type ReqLogger interface {
Errorf(format string, args ...any)
}

const defaultTimeout = 500 * time.Millisecond

var (
// global lock
_gl = sync.Mutex{}
Expand All @@ -47,10 +49,7 @@ func NewClient(timeout int) *Client {
cli, ok := cs[timeout]

if !ok {
cli = NewWithDoer(&http.Client{
Timeout: time.Duration(timeout) * time.Millisecond,
})
cli.timeout = timeout
cli = NewWithTimeout(timeout)
cs[timeout] = cli
}

Expand Down Expand Up @@ -79,7 +78,7 @@ func WithJSONType(opt *Option) {
opt.ContentType = httpctype.JSON
}

// WithData set request data
// WithData set request data, will auto convert to body data or query string
func WithData(data any) OptionFn {
return func(opt *Option) {
opt.Data = data
Expand Down
47 changes: 33 additions & 14 deletions netutil/httpreq/httpreq_test.go
Expand Up @@ -2,7 +2,9 @@ package httpreq_test

import (
"fmt"
"net/http"
"testing"
"time"

"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/netutil/httpreq"
Expand All @@ -22,23 +24,31 @@ func TestMain(m *testing.M) {
}

func TestStdClient(t *testing.T) {
resp, err := httpreq.Send("head", testSrvAddr+"/head")
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
rr := testutil.ParseRespToReply(resp)
// dump.P(rr)
assert.Equal(t, "HEAD", rr.Method)
assert.NotNil(t, httpreq.Std())
httpreq.SetTimeout(300)
httpreq.Config(func(hc *http.Client) {
hc.Timeout = 400 * time.Millisecond
})

resp = httpreq.MustSend("options", testSrvAddr+"/options")
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
rr = testutil.ParseRespToReply(resp)
dump.P(rr)
assert.Eq(t, "OPTIONS", rr.Method)
t.Run("head", func(t *testing.T) {
resp, err := httpreq.Send("head", testSrvAddr+"/head")
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
rr := testutil.ParseRespToReply(resp)
// dump.P(rr)
assert.Equal(t, "HEAD", rr.Method)
})

t.Run("options", func(t *testing.T) {
resp := httpreq.MustSend("options", testSrvAddr+"/options")
assert.Equal(t, 200, resp.StatusCode)
rr := testutil.ParseRespToReply(resp)
dump.P(rr)
assert.Eq(t, "OPTIONS", rr.Method)
})

t.Run("get", func(t *testing.T) {
resp, err := httpreq.Get(testSrvAddr + "/get")
assert.NoError(t, err)
resp := httpreq.MustResp(httpreq.Get(testSrvAddr + "/get"))
assert.Equal(t, 200, resp.StatusCode)
rr := testutil.ParseBodyToReply(resp.Body)
assert.Equal(t, "GET", rr.Method)
Expand All @@ -53,6 +63,15 @@ func TestStdClient(t *testing.T) {
assert.Equal(t, "hi", rr.Body)
})

t.Run("post JSON", func(t *testing.T) {
resp, err := httpreq.PostJSON(testSrvAddr+"/post", map[string]string{"name": "inhere"})
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
rr := testutil.ParseBodyToReply(resp.Body)
assert.Equal(t, "POST", rr.Method)
assert.StrContains(t, rr.Body, `{"name":"inhere"}`)
})

t.Run("put", func(t *testing.T) {
resp, err := httpreq.Put(testSrvAddr+"/put", "hi")
assert.NoError(t, err)
Expand Down
31 changes: 21 additions & 10 deletions netutil/httpreq/options.go
Expand Up @@ -16,7 +16,8 @@ type Options = Option

// Option struct
type Option struct {
cli *Client
cli *Client
sent bool
// Timeout for request. unit: ms
Timeout int
// Method for request
Expand Down Expand Up @@ -45,15 +46,15 @@ type Option struct {
// OptionFn option func type
type OptionFn func(opt *Option)

// MakeOpt create a new Option
func MakeOpt(opt *Option) *Option {
// OptOrNew create a new Option if opt is nil
func OptOrNew(opt *Option) *Option {
if opt == nil {
opt = &Option{}
}
return opt
}

// NewOpt create a new Option
// NewOpt create a new Option and with option func
func NewOpt(fns ...OptionFn) *Option {
return NewOption(fns)
}
Expand Down Expand Up @@ -85,6 +86,14 @@ func (o *Option) WithClient(cli *Client) *Option {
return o
}

// Copy option for new request, use for repeat send request
func (o *Option) Copy() *Option {
o.Body = nil
on := *o
on.sent = false
return &on
}

// WithMethod set method
func (o *Option) WithMethod(method string) *Option {
if method != "" {
Expand Down Expand Up @@ -125,12 +134,6 @@ func (o *Option) WithData(data any) *Option {
return o
}

// WithBody with custom body
func (o *Option) WithBody(r io.Reader) *Option {
o.Body = r
return o
}

// AnyBody with custom body.
//
// Allow type:
Expand All @@ -140,6 +143,12 @@ func (o *Option) AnyBody(data any) *Option {
return o
}

// WithBody with custom body
func (o *Option) WithBody(r io.Reader) *Option {
o.Body = r
return o
}

// BytesBody with custom bytes body
func (o *Option) BytesBody(bs []byte) *Option {
o.Body = bytes.NewReader(bs)
Expand Down Expand Up @@ -199,6 +208,7 @@ func (o *Option) Delete(url string, fns ...OptionFn) (*http.Response, error) {
// Send request and return http response
func (o *Option) Send(method, url string, fns ...OptionFn) (*http.Response, error) {
cli := basefn.OrValue(o.cli != nil, o.cli, std)
o.sent = true
o.WithOptionFns(fns).WithMethod(method)

return cli.SendWithOpt(url, o)
Expand All @@ -207,6 +217,7 @@ func (o *Option) Send(method, url string, fns ...OptionFn) (*http.Response, erro
// MustSend request, will panic on error
func (o *Option) MustSend(method, url string, fns ...OptionFn) *http.Response {
cli := basefn.OrValue(o.cli != nil, o.cli, std)
o.sent = true
o.WithOptionFns(fns).WithMethod(method)

resp, err := cli.SendWithOpt(url, o)
Expand Down

0 comments on commit 4768e7d

Please sign in to comment.