From b3442920475ce38587bb1fbb2cb6090c1545fac4 Mon Sep 17 00:00:00 2001 From: Quentin Renard Date: Mon, 19 Oct 2020 11:51:12 +0200 Subject: [PATCH] Added http sender SendJSON --- http.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ http_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/http.go b/http.go index d401bb8..03cf821 100644 --- a/http.go +++ b/http.go @@ -1,7 +1,10 @@ package astikit import ( + "bytes" "context" + "encoding/json" + "errors" "fmt" "io" "net" @@ -14,6 +17,8 @@ import ( "time" ) +var ErrHTTPSenderUnmarshaledError = errors.New("astikit: unmarshaled error") + // ServeHTTPOptions represents serve options type ServeHTTPOptions struct { Addr string @@ -179,6 +184,68 @@ func (s *HTTPSender) SendWithTimeout(req *http.Request, timeout time.Duration) ( return } +// HTTPSendJSONOptions represents SendJSON options +type HTTPSendJSONOptions struct { + BodyError interface{} + BodyIn interface{} + BodyOut interface{} + Method string + URL string +} + +// SendJSON sends a new JSON HTTP request +func (s *HTTPSender) SendJSON(o HTTPSendJSONOptions) (err error) { + // Marshal body in + var bi io.Reader + if o.BodyIn != nil { + bb := &bytes.Buffer{} + if err = json.NewEncoder(bb).Encode(o.BodyIn); err != nil { + err = fmt.Errorf("astikit: marshaling body in failed: %w", err) + return + } + bi = bb + } + + // Create request + var req *http.Request + if req, err = http.NewRequest(o.Method, o.URL, bi); err != nil { + err = fmt.Errorf("astikit: creating request failed: %w", err) + return + } + + // Send request + var resp *http.Response + if resp, err = s.Send(req); err != nil { + err = fmt.Errorf("astikit: sending request failed: %w", err) + return + } + defer resp.Body.Close() + + // Process status code + if code := resp.StatusCode; code < 200 || code > 299 { + // Try unmarshaling error + if o.BodyError != nil { + if err2 := json.NewDecoder(resp.Body).Decode(o.BodyError); err2 == nil { + err = ErrHTTPSenderUnmarshaledError + return + } + } + + // Default error + err = fmt.Errorf("astikit: invalid status code %d", code) + return + } + + // Unmarshal body out + if o.BodyOut != nil { + if err = json.NewDecoder(resp.Body).Decode(o.BodyOut); err != nil { + err = fmt.Errorf("astikit: unmarshaling failed: %w", err) + return + } + } + return +} + // HTTPResponseFunc is a func that can process an $http.Response type HTTPResponseFunc func(resp *http.Response) error diff --git a/http_test.go b/http_test.go index dd9defd..b480485 100644 --- a/http_test.go +++ b/http_test.go @@ -3,6 +3,8 @@ package astikit import ( "bytes" "context" + "encoding/json" + "errors" "io/ioutil" "net" "net/http" @@ -60,8 +62,7 @@ func TestHTTPSender(t *testing.T) { }), RetryMax: 3, }) - _, err := s.Send(&http.Request{}) - if err == nil { + if _, err := s.Send(&http.Request{}); err == nil { t.Error("expected error, got nil") } if e := 4; c != e { @@ -86,8 +87,7 @@ func TestHTTPSender(t *testing.T) { }), RetryMax: 3, }) - _, err = s.Send(&http.Request{}) - if err != nil { + if _, err := s.Send(&http.Request{}); err != nil { t.Errorf("expected no error, got %+v", err) } if e := 3; c != e { @@ -103,10 +103,66 @@ func TestHTTPSender(t *testing.T) { return }), }) - _, err = s.SendWithTimeout(&http.Request{}, time.Millisecond) - if err == nil { + if _, err := s.SendWithTimeout(&http.Request{}, time.Millisecond); err == nil { t.Error("expected error, got nil") } + + // JSON + const ( + ebe = "error" + ebi = "body-in" + ebo = "body-out" + eu = "https://domain.com/url" + ) + var gu, gbi string + s = NewHTTPSender(HTTPSenderOptions{ + Client: mockedHTTPClient(func(req *http.Request) (resp *http.Response, err error) { + switch req.Method { + case http.MethodHead: + gu = req.URL.String() + resp = &http.Response{Body: ioutil.NopCloser(&bytes.Buffer{}), StatusCode: http.StatusBadRequest} + case http.MethodPost: + json.NewDecoder(req.Body).Decode(&gbi) + resp = &http.Response{Body: ioutil.NopCloser(bytes.NewBuffer([]byte("\"" + ebe + "\""))), StatusCode: http.StatusBadRequest} + case http.MethodGet: + resp = &http.Response{Body: ioutil.NopCloser(bytes.NewBuffer([]byte("\"" + ebo + "\""))), StatusCode: http.StatusOK} + } + return + }), + }) + if err := s.SendJSON(HTTPSendJSONOptions{ + Method: http.MethodHead, + URL: eu, + }); err == nil { + t.Error("expected error, got nil") + } + if gu != eu { + t.Errorf("expected %s, got %s", eu, gu) + } + var gbe string + if err := s.SendJSON(HTTPSendJSONOptions{ + BodyError: &gbe, + BodyIn: ebi, + Method: http.MethodPost, + }); !errors.Is(err, ErrHTTPSenderUnmarshaledError) { + t.Errorf("expected ErrHTTPSenderUnmarshaledError, got %s", err) + } + if gbe != ebe { + t.Errorf("expected %s, got %s", ebe, gbe) + } + if gbi != ebi { + t.Errorf("expected %s, got %s", ebi, gbi) + } + var gbo string + if err := s.SendJSON(HTTPSendJSONOptions{ + BodyOut: &gbo, + Method: http.MethodGet, + }); err != nil { + t.Errorf("expected no error, got %s", err) + } + if gbo != ebo { + t.Errorf("expected %s, go %s", ebo, gbo) + } } func TestHTTPDownloader(t *testing.T) {