From d4e8530b11e1709e6ff7d7cd5a399d16c247e865 Mon Sep 17 00:00:00 2001 From: ch3nnn Date: Tue, 23 Jul 2024 02:03:23 +0800 Subject: [PATCH 1/2] feat(handler): implement duplicate read closer for request body This change introduces a utility function `dupReadCloser` that allows duplicating the incoming request body reader. It is now used in the request handling loop to ensure that the request body can be consumed by multiple handlers or middleware without losing the original data. --- handler.go | 10 ++++++++++ handler_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/handler.go b/handler.go index 7aa6278..6e36f5a 100644 --- a/handler.go +++ b/handler.go @@ -22,14 +22,24 @@ func ChainHandlers(handlers ...ResponseHandler) ResponseHandler { if h == nil { continue } + + var dup io.ReadCloser + r.Body, dup = dupReadCloser(r.Body) if err := h(r); err != nil { return err } + r.Body = dup } return nil } } +func dupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) { + var buf bytes.Buffer + tee := io.TeeReader(reader, &buf) + return io.NopCloser(tee), io.NopCloser(&buf) +} + func consumeBody(res *http.Response) (err error) { const maxDiscardSize = 640 * 1 << 10 if _, err = io.CopyN(io.Discard, res.Body, maxDiscardSize); err == io.EOF { diff --git a/handler_test.go b/handler_test.go index 7e74c16..cbfe096 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1,8 +1,10 @@ package requests_test import ( + "bytes" "context" "fmt" + "io" "net/http" "os" "path/filepath" @@ -32,3 +34,37 @@ func BenchmarkBuilder_ToFile(b *testing.B) { be.NilErr(b, err) } } + +// TestChainHandlers tests the ChainHandlers function. +func TestChainHandlers(t *testing.T) { + type Common struct { + ID int `json:"id"` + } + + type Book struct { + Common + Name string `json:"name"` + } + + var ( + book Book + common Common + str string + ) + + handler := requests.ChainHandlers( + requests.ToJSON(&common), + requests.ToJSON(&book), + requests.ToString(&str), + ) + + err := handler(&http.Response{ + Body: io.NopCloser(bytes.NewReader([]byte(`{"id":1, "name":"孙子兵法"}`))), + }) + + be.NilErr(t, err) + be.Equal(t, 1, common.ID) + be.Equal(t, 1, book.ID) + be.Equal(t, "孙子兵法", book.Name) + be.Equal(t, `{"id":1, "name":"孙子兵法"}`, str) +} From 8f56781851d5a3ca48cb84894e734bd8ed03497c Mon Sep 17 00:00:00 2001 From: ch3nnn Date: Tue, 23 Jul 2024 16:35:09 +0800 Subject: [PATCH 2/2] feat(handler): implement KeepRespBodyHandlers --- handler.go | 15 +++++++++++++++ handler_test.go | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/handler.go b/handler.go index 6e36f5a..512b317 100644 --- a/handler.go +++ b/handler.go @@ -17,6 +17,21 @@ type ResponseHandler = func(*http.Response) error // ChainHandlers allows for the composing of validators or response handlers. func ChainHandlers(handlers ...ResponseHandler) ResponseHandler { + return func(r *http.Response) error { + for _, h := range handlers { + if h == nil { + continue + } + if err := h(r); err != nil { + return err + } + } + return nil + } +} + +// KeepRespBodyHandlers Combine multiple ResponseHandler and ensure that each processor has access to the original response body +func KeepRespBodyHandlers(handlers ...ResponseHandler) ResponseHandler { return func(r *http.Response) error { for _, h := range handlers { if h == nil { diff --git a/handler_test.go b/handler_test.go index cbfe096..adcf952 100644 --- a/handler_test.go +++ b/handler_test.go @@ -35,8 +35,8 @@ func BenchmarkBuilder_ToFile(b *testing.B) { } } -// TestChainHandlers tests the ChainHandlers function. -func TestChainHandlers(t *testing.T) { +// TestKeepRespBodyHandlers tests the KeepRespBodyHandlers function. +func TestKeepRespBodyHandlers(t *testing.T) { type Common struct { ID int `json:"id"` } @@ -52,7 +52,7 @@ func TestChainHandlers(t *testing.T) { str string ) - handler := requests.ChainHandlers( + handler := requests.KeepRespBodyHandlers( requests.ToJSON(&common), requests.ToJSON(&book), requests.ToString(&str),