-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
🔥 feat: Add StreamResponseBody support for the Client #3711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2345,3 +2345,65 @@ func Benchmark_Client_Request_Send_ContextCancel(b *testing.B) { | |
| require.ErrorIs(b, <-errCh, ErrTimeoutOrCancel) | ||
| } | ||
| } | ||
|
|
||
| func Test_Client_StreamResponseBody(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| t.Run("default value", func(t *testing.T) { | ||
| t.Parallel() | ||
| client := New() | ||
| require.False(t, client.StreamResponseBody()) | ||
| }) | ||
|
|
||
| t.Run("enable streaming", func(t *testing.T) { | ||
| t.Parallel() | ||
| client := New() | ||
| result := client.SetStreamResponseBody(true) | ||
| require.True(t, client.StreamResponseBody()) | ||
| require.Equal(t, client, result) | ||
| }) | ||
|
|
||
| t.Run("disable streaming", func(t *testing.T) { | ||
| t.Parallel() | ||
| client := New() | ||
| client.SetStreamResponseBody(true) | ||
| require.True(t, client.StreamResponseBody()) | ||
| client.SetStreamResponseBody(false) | ||
| require.False(t, client.StreamResponseBody()) | ||
| }) | ||
|
|
||
| t.Run("with standard client", func(t *testing.T) { | ||
| t.Parallel() | ||
| client := New() | ||
| client.SetStreamResponseBody(true) | ||
| require.True(t, client.StreamResponseBody()) | ||
| }) | ||
|
Comment on lines
+2375
to
+2380
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| t.Run("with host client", func(t *testing.T) { | ||
| t.Parallel() | ||
| hostClient := &fasthttp.HostClient{} | ||
| client := NewWithHostClient(hostClient) | ||
| client.SetStreamResponseBody(true) | ||
| require.True(t, client.StreamResponseBody()) | ||
| require.True(t, hostClient.StreamResponseBody) | ||
| }) | ||
|
|
||
| t.Run("with lb client", func(t *testing.T) { | ||
| t.Parallel() | ||
| lbClient := &fasthttp.LBClient{ | ||
| Clients: []fasthttp.BalancingClient{ | ||
| &fasthttp.HostClient{Addr: "example.com:80"}, | ||
| }, | ||
| } | ||
| client := NewWithLBClient(lbClient) | ||
| client.SetStreamResponseBody(true) | ||
| require.True(t, client.StreamResponseBody()) | ||
| }) | ||
|
|
||
| t.Run("getter with standard client without setter", func(t *testing.T) { | ||
| t.Parallel() | ||
| client := New() | ||
| // Test getter directly without calling setter | ||
| require.False(t, client.StreamResponseBody()) | ||
| }) | ||
|
Comment on lines
+2403
to
+2408
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,7 +11,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "sync" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/gofiber/utils/v2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utils "github.com/gofiber/utils/v2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/valyala/fasthttp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -89,22 +89,38 @@ func (r *Response) Body() []byte { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return r.RawResponse.Body() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // BodyStream returns the response body as a stream reader. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: When using BodyStream(), the response body is not copied to memory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // so calling Body() afterwards may return an empty slice. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+94
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: When using BodyStream(), the response body is not copied to memory, | |
| // so calling Body() afterwards may return an empty slice. | |
| // Note: When streaming is enabled (i.e., RawResponse.BodyStream() is non-nil) and BodyStream() is used, | |
| // the response body stream is consumed directly. Subsequent calls to Body() may return incomplete or empty data. | |
| // If streaming is not enabled, Body() will still return the full response body. |
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling streaming vs. non-streaming bodies is duplicated for case string and case io.Writer. This can be simplified since BodyStream() already provides an io.Reader for both cases. You can directly use io.Copy with r.BodyStream() in both branches, which makes the code more concise and maintainable.
| if r.IsStreaming() { | |
| _, err = io.Copy(outFile, r.BodyStream()) | |
| } else { | |
| _, err = io.Copy(outFile, bytes.NewReader(r.Body())) | |
| } | |
| if err != nil { | |
| return fmt.Errorf("failed to write response body to file: %w", err) | |
| } | |
| return nil | |
| case io.Writer: | |
| if _, err := io.Copy(p, bytes.NewReader(r.Body())); err != nil { | |
| return fmt.Errorf("failed to write response body to io.Writer: %w", err) | |
| var err error | |
| if r.IsStreaming() { | |
| _, err = io.Copy(p, r.BodyStream()) | |
| } else { | |
| _, err = io.Copy(p, bytes.NewReader(r.Body())) | |
| } | |
| defer func() { | |
| if pc, ok := p.(io.WriteCloser); ok { | |
| _ = pc.Close() //nolint:errcheck // not needed | |
| } | |
| }() | |
| if err != nil { | |
| return fmt.Errorf("failed to write response body to writer: %w", err) | |
| } | |
| if _, err = io.Copy(outFile, r.BodyStream()); err != nil { | |
| return fmt.Errorf("failed to write response body to file: %w", err) | |
| } | |
| return nil | |
| case io.Writer: | |
| if _, err := io.Copy(p, r.BodyStream()); err != nil { | |
| return fmt.Errorf("failed to write response body to writer: %w", err) | |
| } |
Uh oh!
There was an error while loading. Please reload this page.