Skip to content
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

📝 [Proposal]: Add buffered streaming support #3127

Open
3 tasks done
grivera64 opened this issue Sep 9, 2024 · 1 comment · May be fixed by #3131
Open
3 tasks done

📝 [Proposal]: Add buffered streaming support #3127

grivera64 opened this issue Sep 9, 2024 · 1 comment · May be fixed by #3131

Comments

@grivera64
Copy link
Contributor

grivera64 commented Sep 9, 2024

Feature Proposal Description

This feature proposal proposes to add buffered streaming support to Fiber v3. By adding this feature, Fiber users can send shorter segments of content over persistent HTTP connections. This functionality is important for several web-based applications such as:

  • Server-Side Events (SSE)
  • Large File Downloads

To implement this feature, this would involve using the underlying *fasthttp.RequestCtx struct. Specifically, this feature can leverage the RequestCtx.SetBodyStreamWriter method.

// File: https://github.com/valyala/fasthttp/server.go
// Note: type StreamWriter func(w *bufio.Writer)
func (ctx *RequestCtx) SetBodyStreamWriter(sw StreamWriter) {
	ctx.Response.SetBodyStreamWriter(sw)
}

The StreamWriter function passed into RequestCtx.SetBodyStreamWriter describes what a handler should write to an endpoint's response body as a buffered stream.

Within Fiber's existing codebase, implementing this feature is the equivalent of:

c.SendStream(fasthttp.NewStreamReader(func(w *bufio.Writer) {
    // ...
}))

It is also possible to implement this feature by directly using the fasthttp API:

// From a gofiber/recipe example:
// https://github.com/gofiber/recipes/blob/master/sse/main.go#L59
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
    // ...
})

For the feature's API, please view Feature Examples below.

Alignment with Express API

Using the Express API, buffered streaming is supported for req argument in an express route handler by using the write, and end methods. Below is an example usage of this feature for Server-Side Events (SSE):

app.get('/sse', function(req, res) {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.flushHeaders();
	
    // Send messages every 2 seconds
    let intervalIndex = 0;
    const intervalId = setInterval(function() {
        intervalIndex++;

        // Format message using Server-Side Event specification
        // `data: ${content}\n\n`
        const currTime = new Date().toLocaleTimeString();
        const msg = `data: Message: ${i} - the time is ${currTime}\n\n`;

        // Write the message to the response stream (auto-flushes)
        res.write(msg);

        // Handle errors during writing
        res.on('error', function(err) {
            console.error('Error writing SSE message:', err);
            clearInterval(intervalId);
            res.end(); // Close the connection
        });
    }, 2000);
	
    // Handle client disconnection
    req.on('close', function() {
        clearInterval(intervalId);
        res.end();
    });
});

By providing a way to support buffered streams in Fiber, this will help make transitioning from Express much easier for more advanced use cases such as SSE as shown above.

To view the same Express example in Fiber with this new feature, please view Feature Examples.

HTTP RFC Standards Compliance

This feature does not modify how Fiber or fasthttp complies with HTTP RFC standards, as it just provides simplified access to an existing fasthttp feature.

API Stability

This feature's API will be introduced in v3 as a separate Ctx method, augmenting its interface without modifying existing methods. This will ensure that there are no breaking changes to old and new features for Fiber v3. (For more information, please view Feature Examples below.)

Besides potential naming changes, this API feature will require very few changes in the future due to fasthttp methods it relies on have been stable since 9 years ago. Unless fasthttp makes big API changes to their fasthttp.StreamWriter type, maintenance will be limited.

On the rare circumstance that the StreamWriter type does change, we can use an alias for that type and have no code change required. The only change that we would need is to update documentation.

type StreamHandler fasthttp.StreamWriter

Feature Examples

Function Signature:

func (c Ctx) SendStreamWriter(streamWriter func(*bufio.Writer)) error

Note: The name of this method is still being finalized. A few other options are:

  • WriteStream
  • SendWritableStream
  • SendBufferedStream

Use in Server-Side Events (SSE):

// Modified version of SSE example in gofiber/recipes
// https://github.com/gofiber/recipes/blob/master/sse/main.go#L59
app.Get("/sse", func (c Ctx) error {
    c.Set("Content-Type", "text/event-stream")
    c.Set("Cache-Control", "no-cache")
    c.Set("Connection", "keep-alive")
    c.Set("Transfer-Encoding", "chunked")

    return c.SendStreamWriter(func (w *bufio.Writer) {
        intervalIndex := 0
        for {
            intervalIndex++

            // Format message using Server-Side Event specification
            // `data: ${content}\n\n`
            currTime := time.Now().Local()
            msg := fmt.Sprintf("%d - the time is %v", i, currTime)    
            fmt.Fprintf(w, "data: Message: %s\n\n", msg)    // write the message

            // Handle errors while writing (after flushing)
            err := w.Flush()    // flush the message
            if err != nil {
                break    // close the connection
            }
            time.Sleep(2 * time.Second)    // Send messages every 2 seconds
        }
    })
})

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have searched for existing issues that describe my proposal before opening this one.
  • I understand that a proposal that does not meet these guidelines may be closed without explanation.
@grivera64
Copy link
Contributor Author

I am currently working on a PR for this feature proposal.

Also: After considering the different names, I believe that either SendStreamWriter() or SendWritableStream() best fit the feature's API. Though since the code base maps SendStream() to fasthttp'sSetBodyStream() method, I will keep the name SendStreamWriter() in the PR to map to fasthttp's SetBodyStreamWriter() method.

What are your thoughts on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant