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

🤗 [Question]: How to implement single connection close detection for Sever Sent Events (SSE) streams using Hijack #3307

Open
3 tasks done
Tibz-Dankan opened this issue Feb 13, 2025 · 3 comments

Comments

@Tibz-Dankan
Copy link

Question Description

I'm trying to implement single connection close detection for server-sent event streams using Hijack but am currently facing the following challenges:

  • when HijackSetNoResponse is true, the connection close detection works fine but events are NOT sent to the client.
  • when HijackSetNoResponse is false(default), the connection close detection DOESN'T work but events are successfully sent to the client.

So how can I get both single connection close detection and sending events to the client to work together simultaneously? any help is highly appreciated

I have provided minimal reproducible code below.

Code Snippet (optional)

package main

import (
	"log"

	"bufio"
	"context"
	"fmt"
	"net"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/valyala/fasthttp"
)

var GetLive = func(c *fiber.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")

	keepAliveTicker := time.NewTicker(5 * time.Second)
	ctx, cancel := context.WithCancel(context.Background())

	c.Context().HijackSetNoResponse(true) //Currenly set to true

	c.Context().Hijack(func(conn net.Conn) {
		defer conn.Close()

		buf := make([]byte, 1)
		for {
			if _, err := conn.Read(buf); err != nil {
				cancel()
				log.Println("Connection closed")
				return
			}
		}
	})

	c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
		for {
			select {
			case <-ctx.Done():
				log.Println("Stream stopped")
				keepAliveTicker.Stop()
				return

			case <-keepAliveTicker.C:
				msg := fmt.Sprintf("data: %v\n\n", "keepalive")

				if _, err := fmt.Fprintf(w, "%s", msg); err != nil {
					log.Printf("Error writing keep-alive message: %v\n", err)
					return
				}

				if err := w.Flush(); err != nil {
					log.Printf("Error flushing keep-alive message: %v\n", err)
					return
				}
				log.Println("Keep-alive sent")
				// case <-otherChannel:
			}
		}
	}))

	return nil
}

func main() {
	app := fiber.New()
	app.Use(cors.New(cors.Config{
		AllowOrigins:  "*",
		AllowHeaders:  "Origin, Content-Type, Accept, Authorization",
		ExposeHeaders: "Content-Length",
	}))
	app.Use(logger.New())

	app.Get("/live", GetLive)

	log.Fatal(app.Listen(":5000"))
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my questions prior to opening this one.
  • I understand that improperly formatted questions may be closed without explanation.
Copy link

welcome bot commented Feb 13, 2025

Thanks for opening your first issue here! 🎉 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

@JIeJaitt
Copy link
Contributor

@Tibz-Dankan The issue occurs because HijackSetNoResponse(true) prevents fasthttp from sending HTTP headers automatically while allowing connection close detection, but SSE requires proper HTTP headers to work. When it's set to false, headers are sent correctly but connection close detection doesn't work. This creates a conflict between these two requirements. A solution would be to manually send the HTTP headers after setting HijackSetNoResponse(true), thus achieving both connection close detection and proper SSE functionality. I'll try to submit a PR that provides a simple SSE helper function to handle this automatically. What do you think? @ReneWerner87 @efectn @gaby

@Tibz-Dankan
Copy link
Author

ooh, okay now I understand why it is behaving that way

Thanks, @JIeJaitt, but how would manually sending the HTTP headers after setting HijackSetNoResponse(true) look like?

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

No branches or pull requests

2 participants