@@ -8,15 +8,18 @@ import (
8
8
"fmt"
9
9
"io"
10
10
"io/fs"
11
- "log"
12
11
"net"
13
12
"net/http"
13
+ "net/http/httputil"
14
+ "net/url"
14
15
"os"
15
16
"path"
16
17
"strings"
17
18
"time"
18
19
19
20
"github.com/gorilla/mux"
21
+ "github.com/gorilla/websocket"
22
+ logging "github.com/ipfs/go-log/v2"
20
23
"go.opencensus.io/tag"
21
24
22
25
"github.com/filecoin-project/curio/deps"
@@ -25,35 +28,37 @@ import (
25
28
"github.com/filecoin-project/lotus/metrics"
26
29
)
27
30
31
+ var log = logging .Logger ("web" )
32
+
28
33
//go:embed static
29
34
var static embed.FS
30
35
31
36
var basePath = "/static/"
32
37
33
- // An dev mode hack for no-restart changes to static and templates.
34
- // You still need to recomplie the binary for changes to go code.
38
+ // A dev mode hack for no-restart changes to static and templates.
39
+ // You still need to recompile the binary for changes to go code.
35
40
var webDev = os .Getenv ("CURIO_WEB_DEV" ) == "1"
36
41
37
- func GetSrv (ctx context.Context , deps * deps.Deps ) (* http.Server , error ) {
42
+ func GetSrv (ctx context.Context , deps * deps.Deps , devMode bool ) (* http.Server , error ) {
38
43
mx := mux .NewRouter ()
39
- api .Routes (mx .PathPrefix ("/api" ).Subrouter (), deps , webDev )
44
+ if ! devMode {
45
+ api .Routes (mx .PathPrefix ("/api" ).Subrouter (), deps , webDev )
46
+ } else {
47
+ if err := setupDevModeProxy (mx ); err != nil {
48
+ return nil , fmt .Errorf ("failed to setup dev mode proxy: %v" , err )
49
+ }
50
+ }
40
51
41
52
var static fs.FS = static
42
- if webDev {
53
+ if webDev || devMode {
43
54
basePath = ""
44
55
static = os .DirFS ("web/static" )
45
56
mx .Use (func (next http.Handler ) http.Handler {
46
57
return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
47
- // Log the request
48
- log .Printf ("Rcv request: %s %s" , r .Method , r .URL .Path )
49
-
58
+ log .Debugf ("Rcv request: %s %s" , r .Method , r .URL .Path )
50
59
x := & interceptResponseWriter {ResponseWriter : w }
51
-
52
- // Call the next handler
53
60
next .ServeHTTP (x , r )
54
-
55
- // Log the response
56
- log .Printf ("HTTP %s %s returned %d bytes with status code %d" , r .Method , r .URL .Path , x .Length (), x .StatusCode ())
61
+ log .Debugf ("HTTP %s %s returned %d bytes with status code %d" , r .Method , r .URL .Path , x .Length (), x .StatusCode ())
57
62
})
58
63
})
59
64
}
@@ -131,3 +136,139 @@ func (w *interceptResponseWriter) Length() int {
131
136
func (w * interceptResponseWriter ) StatusCode () int {
132
137
return w .status
133
138
}
139
+
140
+ func setupDevModeProxy (mx * mux.Router ) error {
141
+ log .Debugf ("Setting up dev mode proxy" )
142
+ apiSrv := os .Getenv ("CURIO_API_SRV" )
143
+ if apiSrv == "" {
144
+ return fmt .Errorf ("CURIO_API_SRV environment variable is not set" )
145
+ }
146
+
147
+ apiURL , err := url .Parse (apiSrv )
148
+ if err != nil {
149
+ return fmt .Errorf ("invalid CURIO_API_SRV URL: %v" , err )
150
+ }
151
+ log .Debugf ("Parsed API URL: %s" , apiURL .String ())
152
+
153
+ proxy := createReverseProxy (apiURL )
154
+
155
+ mx .PathPrefix ("/api" ).HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
156
+ log .Debugf ("Received request: %s %s" , r .Method , r .URL .Path )
157
+ if websocket .IsWebSocketUpgrade (r ) {
158
+ websocketProxy (apiURL , w , r )
159
+ } else {
160
+ proxy .ServeHTTP (w , r )
161
+ }
162
+ })
163
+
164
+ log .Infof ("Dev mode proxy setup complete" )
165
+ return nil
166
+ }
167
+
168
+ func createReverseProxy (target * url.URL ) * httputil.ReverseProxy {
169
+ log .Debugf ("Creating reverse proxy" )
170
+ proxy := httputil .NewSingleHostReverseProxy (target )
171
+
172
+ originalDirector := proxy .Director
173
+ proxy .Director = func (req * http.Request ) {
174
+ log .Debugf ("Directing request: %s %s" , req .Method , req .URL .Path )
175
+ originalDirector (req )
176
+ req .URL .Path = path .Join (target .Path , req .URL .Path )
177
+
178
+ if ! strings .HasPrefix (req .URL .Path , "/" ) {
179
+ req .URL .Path = "/" + req .URL .Path
180
+ }
181
+
182
+ req .URL .Scheme = target .Scheme
183
+ req .URL .Host = target .Host
184
+ }
185
+
186
+ proxy .ModifyResponse = func (resp * http.Response ) error {
187
+ log .Debugf ("Modifying response: %d %s" , resp .StatusCode , resp .Status )
188
+ resp .Header .Del ("Connection" )
189
+ resp .Header .Del ("Upgrade" )
190
+ return nil
191
+ }
192
+
193
+ proxy .Transport = & http.Transport {
194
+ Proxy : http .ProxyFromEnvironment ,
195
+ DialContext : (& net.Dialer {
196
+ Timeout : 30 * time .Second ,
197
+ KeepAlive : 30 * time .Second ,
198
+ }).DialContext ,
199
+ ForceAttemptHTTP2 : true ,
200
+ MaxIdleConns : 100 ,
201
+ IdleConnTimeout : 90 * time .Second ,
202
+ TLSHandshakeTimeout : 10 * time .Second ,
203
+ ExpectContinueTimeout : 1 * time .Second ,
204
+ }
205
+
206
+ log .Infof ("Reverse proxy created" )
207
+ return proxy
208
+ }
209
+
210
+ func websocketProxy (target * url.URL , w http.ResponseWriter , r * http.Request ) {
211
+ log .Debugf ("Starting WebSocket proxy" )
212
+ d := websocket.Dialer {
213
+ Proxy : http .ProxyFromEnvironment ,
214
+ HandshakeTimeout : 45 * time .Second ,
215
+ }
216
+
217
+ // Preserve the original path and query
218
+ wsTarget := * target
219
+ wsTarget .Scheme = "ws"
220
+ wsTarget .Path = path .Join (wsTarget .Path , r .URL .Path )
221
+
222
+ if ! strings .HasPrefix (wsTarget .Path , "/" ) {
223
+ wsTarget .Path = "/" + wsTarget .Path
224
+ }
225
+
226
+ wsTarget .RawQuery = r .URL .RawQuery
227
+ backendConn , resp , err := d .Dial (wsTarget .String (), nil )
228
+ if err != nil {
229
+ log .Errorf ("Failed to connect to backend: %v" , err )
230
+ if resp != nil {
231
+ log .Debugf ("Backend response: %d %s" , resp .StatusCode , resp .Status )
232
+ }
233
+ http .Error (w , "Failed to connect to backend" , http .StatusServiceUnavailable )
234
+ return
235
+ }
236
+ defer backendConn .Close ()
237
+
238
+ upgrader := websocket.Upgrader {
239
+ CheckOrigin : func (r * http.Request ) bool {
240
+ return true // Implement a more secure check in production
241
+ },
242
+ }
243
+ clientConn , err := upgrader .Upgrade (w , r , nil )
244
+ if err != nil {
245
+ log .Errorf ("Failed to upgrade connection: %v" , err )
246
+ http .Error (w , "Failed to upgrade connection" , http .StatusInternalServerError )
247
+ return
248
+ }
249
+ defer clientConn .Close ()
250
+
251
+ errc := make (chan error , 2 )
252
+ go proxyCopy (clientConn , backendConn , errc , "client -> backend" )
253
+ go proxyCopy (backendConn , clientConn , errc , "backend -> client" )
254
+
255
+ err = <- errc
256
+ log .Debugf ("WebSocket proxy ended: %v" , err )
257
+ }
258
+
259
+ func proxyCopy (dst , src * websocket.Conn , errc chan <- error , direction string ) {
260
+ for {
261
+ messageType , p , err := src .ReadMessage ()
262
+ if err != nil {
263
+ log .Errorf ("Error reading message (%s): %v" , direction , err )
264
+ errc <- err
265
+ return
266
+ }
267
+ log .Debugf ("Proxying message (%s): type %d, size %d bytes" , direction , messageType , len (p ))
268
+ if err := dst .WriteMessage (messageType , p ); err != nil {
269
+ log .Errorf ("Error writing message (%s): %v" , direction , err )
270
+ errc <- err
271
+ return
272
+ }
273
+ }
274
+ }
0 commit comments