Based on https://github.com/nnevatie/capnwebcpp
1package gocapnweb
2
3import (
4 "bufio"
5 "log"
6 "net/http"
7 "strings"
8
9 "github.com/gorilla/websocket"
10 "github.com/labstack/echo/v4"
11 "github.com/labstack/echo/v4/middleware"
12)
13
14var upgrader = websocket.Upgrader{
15 CheckOrigin: func(r *http.Request) bool {
16 return true // Allow all origins for simplicity
17 },
18}
19
20// SetupRpcEndpoint sets up both WebSocket and HTTP POST endpoints for RPC using Echo.
21func SetupRpcEndpoint(e *echo.Echo, path string, target RpcTarget) {
22 session := NewRpcSession(target)
23
24 // Setup WebSocket endpoint
25 e.GET(path, func(c echo.Context) error {
26 conn, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
27 if err != nil {
28 log.Printf("WebSocket upgrade error: %v", err)
29 return err
30 }
31 defer conn.Close()
32
33 sessionData := NewSessionData(target)
34 session.OnOpen(sessionData)
35 defer session.OnClose(sessionData)
36
37 for {
38 _, message, err := conn.ReadMessage()
39 if err != nil {
40 if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
41 log.Printf("WebSocket error: %v", err)
42 }
43 break
44 }
45
46 response, err := session.HandleMessage(sessionData, string(message))
47 if err != nil {
48 log.Printf("Error processing WebSocket message: %v", err)
49 continue
50 }
51
52 if response != "" {
53 if err := conn.WriteMessage(websocket.TextMessage, []byte(response)); err != nil {
54 log.Printf("Error writing WebSocket response: %v", err)
55 break
56 }
57 }
58 }
59 return nil
60 })
61
62 // Setup HTTP POST endpoint for batch RPC
63 e.POST(path, func(c echo.Context) error {
64 // CORS headers are handled by Echo middleware
65 c.Response().Header().Set("Content-Type", "text/plain")
66
67 defer c.Request().Body.Close()
68 scanner := bufio.NewScanner(c.Request().Body)
69
70 // Create a session data for this HTTP batch request
71 sessionData := NewSessionData(target)
72 var responses []string
73
74 // Process each line as a separate RPC message
75 for scanner.Scan() {
76 line := strings.TrimSpace(scanner.Text())
77 if line == "" {
78 continue
79 }
80
81 response, err := session.HandleMessage(sessionData, line)
82 if err != nil {
83 log.Printf("Error processing HTTP message: %v", err)
84 continue
85 }
86
87 if response != "" {
88 responses = append(responses, response)
89 }
90 }
91
92 if err := scanner.Err(); err != nil {
93 log.Printf("Error reading HTTP body: %v", err)
94 return echo.NewHTTPError(http.StatusInternalServerError, "Error reading request body")
95 }
96
97 // Join responses with newlines
98 responseBody := strings.Join(responses, "\n")
99 return c.String(http.StatusOK, responseBody)
100 })
101
102 // OPTIONS endpoint is handled automatically by Echo CORS middleware
103}
104
105// SetupEchoServer creates and configures an Echo server with common middleware.
106func SetupEchoServer() *echo.Echo {
107 e := echo.New()
108
109 // Add middleware
110 e.Use(middleware.Logger())
111 e.Use(middleware.Recover())
112 e.Use(middleware.CORS())
113
114 // Hide Echo banner for cleaner output
115 e.HideBanner = true
116
117 return e
118}