SSE & Bidirectional Communication

SSE & Bidirectional Communication

A hands-on learning repository for Server-Sent Events (SSE) and bidirectional real-time communication patterns.

Overview

This lab teaches you how to build real-time communication systems using SSE combined with HTTP POST for bidirectional messaging. You’ll understand the protocol mechanics, implement production patterns, and learn when to use these techniques over alternatives like WebSockets.

What You’ll Learn

SSE Fundamentals

  • Protocol mechanics and headers
  • Connection lifecycle management
  • Event stream format
  • Browser compatibility

Bidirectional Patterns

  • Combining SSE with HTTP POST
  • Session correlation techniques
  • Request/response patterns
  • Real-time workflows

Real-time Architecture

  • When to use SSE vs WebSockets
  • Scaling considerations
  • Error handling strategies
  • Production deployment

Quick Start

# Clone the repository
git clone https://github.com/hardwaylabs/learn-sse-bidirectional
cd learn-sse-bidirectional/go

# Run basic SSE example
go run cmd/basic_sse_server/main.go    # Terminal 1
go run cmd/basic_sse_client/main.go    # Terminal 2
open http://localhost:8080/web          # Browser test

# Run bidirectional example
go run cmd/bidirectional_server/main.go    # Terminal 1
go run cmd/bidirectional_client/main.go    # Terminal 2
go run cmd/demo_trigger/main.go            # Terminal 3

Key Concepts

Server-Sent Events (SSE)

SSE provides a simple, efficient way for servers to push data to clients:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: First message\n\n
data: Second message\n\n
event: custom-event
data: {"type": "update", "value": 42}\n\n

Key characteristics:

  • Text-based protocol over HTTP
  • Automatic reconnection
  • One-way: server to client only
  • Works through proxies and firewalls

Bidirectional Communication Pattern

Combine SSE with HTTP POST for two-way communication:

1. Client → Server: Establish SSE connection
1. Server → Client: Send REQUEST via SSE
1. Client: Process the request
1. Client → Server: Send RESPONSE via HTTP POST
1. Server: Correlate response with request

This pattern is used by:

  • Model Context Protocol (MCP)
  • GitHub Codespaces
  • Various CI/CD systems

Implementation Examples

Basic SSE Server

func sseHandler(w http.ResponseWriter, r *http.Request) {
    // Set SSE headers
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    flusher := w.(http.Flusher)

    // Send events
    for i := 0; i < 10; i++ {
        fmt.Fprintf(w, "data: Message %d\n\n", i)
        flusher.Flush()
        time.Sleep(time.Second)
    }
}

Bidirectional Server

type Server struct {
    clients  map[string]*Client
    requests map[string]*PendingRequest
}

func (s *Server) SendRequest(clientID, method string, params interface{}) (*Response, error) {
    // Generate unique request ID
    requestID := uuid.New().String()

    // Send request via SSE
    client := s.clients[clientID]
    client.Send(Request{
        ID:     requestID,
        Method: method,
        Params: params,
    })

    // Wait for response via channel
    respChan := make(chan *Response)
    s.requests[requestID] = &PendingRequest{
        Response: respChan,
        Timeout:  time.Now().Add(30 * time.Second),
    }

    return <-respChan, nil
}

Real-World Applications

LLM Integration Systems

Server requests LLM analysis, client handles API calls:

  • Server sends text for analysis
  • Client calls OpenAI/Anthropic
  • Returns results to server
  • Used by MCP protocol

Remote Code Execution

IDE sends code to remote environment:

  • Push code changes via SSE
  • Execute and stream output
  • Handle interactive input
  • Return execution results

Live Dashboards

Real-time metrics and monitoring:

  • Stream metrics to browser
  • Accept control commands
  • Update visualizations
  • Handle user interactions

Interactive Workflows

Multi-step processes with human input:

  • Push workflow steps to UI
  • Collect user decisions
  • Update progress in real-time
  • Handle approvals/rejections

Architecture Patterns

Connection Management

type ConnectionManager struct {
    clients    sync.Map
    register   chan *Client
    unregister chan *Client
}

func (cm *ConnectionManager) HandleClient(client *Client) {
    cm.clients.Store(client.ID, client)
    defer cm.clients.Delete(client.ID)

    // Handle disconnection cleanup
    defer func() {
        // Cancel pending requests
        // Clean up resources
    }()

    // Process client messages
    for {
        select {
        case msg := <-client.Send:
            // Send message to client
        case <-client.Done:
            return
        }
    }
}

Session Correlation

type Session struct {
    ID        string
    ClientID  string
    CreatedAt time.Time
    Requests  map[string]*Request
}

func (s *Server) CorrelateResponse(sessionID, requestID string, response interface{}) error {
    session := s.sessions[sessionID]
    request := session.Requests[requestID]

    if request == nil {
        return fmt.Errorf("unknown request: %s", requestID)
    }

    request.ResponseChan <- response
    delete(session.Requests, requestID)

    return nil
}

Best Practices

Server Implementation

  • Track and clean up client connections
  • Use unique IDs for request correlation
  • Implement timeouts for pending requests
  • Handle graceful disconnections
  • Queue messages for reconnecting clients

Client Implementation

  • Implement automatic reconnection
  • Parse SSE messages correctly
  • Send errors for failed requests
  • Maintain session state
  • Handle connection failures gracefully

Production Considerations

  • Use HTTPS for security
  • Implement authentication
  • Add rate limiting
  • Monitor connection health
  • Plan for horizontal scaling

Debugging Tips

Common Issues

Events not appearing: Ensure you’re calling Flush() after writing

Connection drops: Check proxy/firewall timeout settings

Memory leaks: Clean up disconnected clients properly

Race conditions: Use proper synchronization for shared state

Debug Tools

# Test SSE endpoint
curl -N http://localhost:8080/events

# Monitor connections
lsof -i :8080

# Check SSE stream
curl -H "Accept: text/event-stream" http://localhost:8080/sse

Comparison with Alternatives

SSE vs WebSockets

FeatureSSEWebSockets
DirectionServer → ClientBidirectional
ProtocolHTTPWS/WSS
ComplexitySimpleMore complex
Proxy supportExcellentVariable
Auto-reconnectBuilt-inManual
Binary dataNoYes

When to Use SSE + HTTP POST

Choose this pattern when:

  • You need mostly server-to-client communication
  • Working through restrictive proxies
  • Want simple HTTP-based architecture
  • Need automatic reconnection
  • Building request/response workflows

Advanced Topics

Scaling SSE

  • Use sticky sessions for load balancing
  • Implement message queuing for reliability
  • Consider event sourcing patterns
  • Use Redis for pub/sub across servers

Security

  • Implement JWT authentication
  • Use CORS appropriately
  • Validate all incoming data
  • Rate limit connections
  • Monitor for abuse patterns

Resources

Next Steps

After completing this lab:

  1. Try the MCP Sampling lab to see SSE in production use
  2. Build a real-time dashboard with SSE
  3. Implement a chat system using these patterns
  4. Explore WebSocket alternatives for comparison

Support

  • Open issues on GitHub
  • Check the CONCEPTS.md file for detailed theory
  • Review working examples in the repository