-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
🔥 Feature (v3): Add buffered streaming support #3131
base: main
Are you sure you want to change the base?
Changes from all commits
885f807
1ff06dc
c977b38
024ac5e
2282a35
7b238d1
95b1745
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -24,6 +24,7 @@ import ( | |||||||||||
"path/filepath" | ||||||||||||
"strconv" | ||||||||||||
"strings" | ||||||||||||
"sync" | ||||||||||||
"testing" | ||||||||||||
"text/template" | ||||||||||||
"time" | ||||||||||||
|
@@ -4447,6 +4448,73 @@ func Test_Ctx_SendStream(t *testing.T) { | |||||||||||
require.Equal(t, "Hello bufio", string(c.Response().Body())) | ||||||||||||
} | ||||||||||||
|
||||||||||||
// go test -run Test_Ctx_SendStreamWriter | ||||||||||||
func Test_Ctx_SendStreamWriter(t *testing.T) { | ||||||||||||
t.Parallel() | ||||||||||||
app := New() | ||||||||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}) | ||||||||||||
|
||||||||||||
err := c.SendStreamWriter(func(w *bufio.Writer) { | ||||||||||||
w.WriteString("Don't crash please") //nolint:errcheck, revive // It is fine to ignore the error | ||||||||||||
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. Check the error returned by Even in test code, it's good practice to handle errors returned by write operations to ensure any unexpected issues are caught. Apply this diff to check the error: - w.WriteString("Don't crash please") //nolint:errcheck, revive // It is fine to ignore the error
+ if _, err := w.WriteString("Don't crash please"); err != nil {
+ t.Errorf("unexpected error: %s", err)
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||
}) | ||||||||||||
require.NoError(t, err) | ||||||||||||
require.Equal(t, "Don't crash please", string(c.Response().Body())) | ||||||||||||
|
||||||||||||
err = c.SendStreamWriter(func(w *bufio.Writer) { | ||||||||||||
for lineNum := 1; lineNum <= 5; lineNum++ { | ||||||||||||
fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck, revive // It is fine to ignore the error | ||||||||||||
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. Check the error returned by To ensure that any write errors are handled, consider checking the error returned by Apply this diff to check the error: - fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck, revive // It is fine to ignore the error
+ if _, err := fmt.Fprintf(w, "Line %d\n", lineNum); err != nil {
+ t.Errorf("unexpected error: %s", err)
+ return
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||
if err := w.Flush(); err != nil { | ||||||||||||
t.Errorf("unexpected error: %s", err) | ||||||||||||
return | ||||||||||||
} | ||||||||||||
} | ||||||||||||
}) | ||||||||||||
require.NoError(t, err) | ||||||||||||
require.Equal(t, "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n", string(c.Response().Body())) | ||||||||||||
|
||||||||||||
err = c.SendStreamWriter(func(_ *bufio.Writer) {}) | ||||||||||||
require.NoError(t, err) | ||||||||||||
require.Empty(t, c.Response().Body()) | ||||||||||||
} | ||||||||||||
|
||||||||||||
// go test -run Test_Ctx_SendStreamWriter_Interrupted | ||||||||||||
func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) { | ||||||||||||
t.Parallel() | ||||||||||||
app := New() | ||||||||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}) | ||||||||||||
|
||||||||||||
var mutex sync.Mutex | ||||||||||||
startChan := make(chan bool) | ||||||||||||
interruptStreamWriter := func() { | ||||||||||||
<-startChan | ||||||||||||
time.Sleep(5 * time.Millisecond) | ||||||||||||
mutex.Lock() | ||||||||||||
c.Response().CloseBodyStream() //nolint:errcheck // It is fine to ignore the error | ||||||||||||
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. Check the error returned by Even when errors are expected, explicitly handling them improves code robustness and clarity. Apply this diff to check the error: - c.Response().CloseBodyStream() //nolint:errcheck // It is fine to ignore the error
+ if err := c.Response().CloseBodyStream(); err != nil {
+ t.Errorf("failed to close body stream: %s", err)
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||
mutex.Unlock() | ||||||||||||
} | ||||||||||||
err := c.SendStreamWriter(func(w *bufio.Writer) { | ||||||||||||
go interruptStreamWriter() | ||||||||||||
|
||||||||||||
startChan <- true | ||||||||||||
for lineNum := 1; lineNum <= 5; lineNum++ { | ||||||||||||
mutex.Lock() | ||||||||||||
fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck, revive // It is fine to ignore the error | ||||||||||||
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. Check the error returned by As with other write operations, it's advisable to handle the error returned by Apply this diff to check the error: - fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck, revive // It is fine to ignore the error
+ if _, err := fmt.Fprintf(w, "Line %d\n", lineNum); err != nil {
+ if lineNum < 3 {
+ t.Errorf("unexpected error: %s", err)
+ }
+ return
+ }
|
||||||||||||
mutex.Unlock() | ||||||||||||
|
||||||||||||
if err := w.Flush(); err != nil { | ||||||||||||
if lineNum < 3 { | ||||||||||||
t.Errorf("unexpected error: %s", err) | ||||||||||||
} | ||||||||||||
return | ||||||||||||
} | ||||||||||||
|
||||||||||||
time.Sleep(1500 * time.Microsecond) | ||||||||||||
} | ||||||||||||
}) | ||||||||||||
require.NoError(t, err) | ||||||||||||
require.Equal(t, "Line 1\nLine 2\nLine 3\n", string(c.Response().Body())) | ||||||||||||
} | ||||||||||||
|
||||||||||||
// go test -run Test_Ctx_Set | ||||||||||||
func Test_Ctx_Set(t *testing.T) { | ||||||||||||
t.Parallel() | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1852,6 +1852,47 @@ app.Get("/", func(c fiber.Ctx) error { | |
}) | ||
``` | ||
|
||
## SendStreamWriter | ||
|
||
Sets the response body stream writer. | ||
|
||
:::note | ||
The argument `streamWriter` represents a function that populates | ||
the response body using a buffered stream writer. | ||
::: | ||
|
||
```go title="Signature" | ||
func (c Ctx) SendStreamWriter(streamWriter func(*bufio.Writer)) error | ||
``` | ||
|
||
```go title="Example" | ||
app.Get("/", func (c fiber.Ctx) error { | ||
return c.SendStreamWriter(func(w *bufio.Writer) { | ||
fmt.Fprintf(w, "Hello, World!\n") | ||
}) | ||
// => "Hello, World!" | ||
}) | ||
``` | ||
Comment on lines
+1868
to
+1875
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. 🛠️ Refactor suggestion Add error handling to the basic example. The example should demonstrate proper error handling. ```go title="Example"
app.Get("/", func (c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
- fmt.Fprintf(w, "Hello, World!\n")
+ if _, err := fmt.Fprintf(w, "Hello, World!\n"); err != nil {
+ return // Connection closed by client
+ }
})
// => "Hello, World!"
}) |
||
|
||
:::info | ||
To flush data before the function returns, you can call `w.Flush()` | ||
on the provided writer. Otherwise, the buffered stream flushes after | ||
`streamWriter` returns. | ||
::: | ||
|
||
```go title="Example" | ||
app.Get("/wait", func(c fiber.Ctx) error { | ||
return c.SendStreamWriter(func(w *bufio.Writer) { | ||
fmt.Fprintf(w, "Waiting for 10 seconds\n") | ||
if err := w.Flush(); err != nil { | ||
log.Print("User quit early") | ||
} | ||
time.Sleep(10 * time.Second) | ||
fmt.Fprintf(w, "Done!\n") | ||
}) | ||
}) | ||
``` | ||
Comment on lines
+1883
to
+1894
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. 🛠️ Refactor suggestion Improve error handling in the advanced example. The advanced example should demonstrate proper error handling and connection closure detection. ```go title="Example"
app.Get("/wait", func(c fiber.Ctx) error {
return c.SendStreamWriter(func(w *bufio.Writer) {
- fmt.Fprintf(w, "Waiting for 10 seconds\n")
- if err := w.Flush(); err != nil {
- log.Print("User quit early")
+ if _, err := fmt.Fprintf(w, "Waiting for 10 seconds\n"); err != nil {
+ return // Connection closed by client
}
+ if err := w.Flush(); err != nil {
+ return // Connection closed by client
+ }
time.Sleep(10 * time.Second)
- fmt.Fprintf(w, "Done!\n")
+ if _, err := fmt.Fprintf(w, "Done!\n"); err != nil {
+ return // Connection closed by client
+ }
})
}) |
||
|
||
## Set | ||
|
||
Sets the response’s HTTP header field to the specified `key`, `value`. | ||
|
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.
🛠️ Refactor suggestion
Add comprehensive documentation for the new public API method.
The method lacks proper documentation. As this is a new public API method for buffered streaming support, it should include:
Add documentation like this:
📝 Committable suggestion