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
Feature | SSE | WebSockets |
---|---|---|
Direction | Server → Client | Bidirectional |
Protocol | HTTP | WS/WSS |
Complexity | Simple | More complex |
Proxy support | Excellent | Variable |
Auto-reconnect | Built-in | Manual |
Binary data | No | Yes |
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
- GitHub Repository
- SSE Specification
- MDN SSE Guide
- MCP Sampling Lab - See these patterns in action
Next Steps
After completing this lab:
- Try the MCP Sampling lab to see SSE in production use
- Build a real-time dashboard with SSE
- Implement a chat system using these patterns
- Explore WebSocket alternatives for comparison
Support
- Open issues on GitHub
- Check the CONCEPTS.md file for detailed theory
- Review working examples in the repository