fix: proxy WebSocket through web server port
- Modify buildWsURL to use web server port (18800) instead of gateway port (18790) - Add WebSocket proxy handler to forward /pico/ws to gateway - Gateway port is read from config (cfg.Gateway.Port), defaults to 18790 - This allows WebSocket connections through the same port as the web UI, avoiding the need to expose extra ports for Tailscale/Docker
This commit is contained in:
parent
79b0568d75
commit
e12d44ae01
3 changed files with 50 additions and 9 deletions
|
|
@ -80,5 +80,11 @@ func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string {
|
||||||
if host == "" || host == "0.0.0.0" {
|
if host == "" || host == "0.0.0.0" {
|
||||||
host = requestHostName(r)
|
host = requestHostName(r)
|
||||||
}
|
}
|
||||||
return requestWSScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(cfg.Gateway.Port)) + "/pico/ws"
|
// Use web server port instead of gateway port to avoid exposing extra ports
|
||||||
|
// The WebSocket connection will be proxied by the backend to the gateway
|
||||||
|
wsPort := h.serverPort
|
||||||
|
if wsPort == 0 {
|
||||||
|
wsPort = 18800 // default web server port
|
||||||
|
}
|
||||||
|
return requestWSScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(wsPort)) + "/pico/ws"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ func TestBuildWsURLUsesRequestHostWhenLauncherPublicSaved(t *testing.T) {
|
||||||
req := httptest.NewRequest("GET", "http://launcher.local/api/pico/token", nil)
|
req := httptest.NewRequest("GET", "http://launcher.local/api/pico/token", nil)
|
||||||
req.Host = "192.168.1.9:18800"
|
req.Host = "192.168.1.9:18800"
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); got != "ws://192.168.1.9:18790/pico/ws" {
|
if got := h.buildWsURL(req, cfg); got != "ws://192.168.1.9:18800/pico/ws" {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://192.168.1.9:18790/pico/ws")
|
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://192.168.1.9:18800/pico/ws")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,8 +71,8 @@ func TestBuildWsURLUsesWSSWhenForwardedProtoIsHTTPS(t *testing.T) {
|
||||||
req.Host = "chat.example.com"
|
req.Host = "chat.example.com"
|
||||||
req.Header.Set("X-Forwarded-Proto", "https")
|
req.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18790/pico/ws" {
|
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18800/pico/ws" {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18790/pico/ws")
|
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18800/pico/ws")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,8 +88,8 @@ func TestBuildWsURLUsesWSSWhenRequestIsTLS(t *testing.T) {
|
||||||
req.Host = "secure.example.com"
|
req.Host = "secure.example.com"
|
||||||
req.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); got != "wss://secure.example.com:18790/pico/ws" {
|
if got := h.buildWsURL(req, cfg); got != "wss://secure.example.com:18800/pico/ws" {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://secure.example.com:18790/pico/ws")
|
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://secure.example.com:18800/pico/ws")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) {
|
||||||
req.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
req.Header.Set("X-Forwarded-Proto", "http")
|
req.Header.Set("X-Forwarded-Proto", "http")
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); got != "ws://chat.example.com:18790/pico/ws" {
|
if got := h.buildWsURL(req, cfg); got != "ws://chat.example.com:18800/pico/ws" {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com:18790/pico/ws")
|
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com:18800/pico/ws")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
|
@ -16,6 +18,39 @@ func (h *Handler) registerPicoRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("GET /api/pico/token", h.handleGetPicoToken)
|
mux.HandleFunc("GET /api/pico/token", h.handleGetPicoToken)
|
||||||
mux.HandleFunc("POST /api/pico/token", h.handleRegenPicoToken)
|
mux.HandleFunc("POST /api/pico/token", h.handleRegenPicoToken)
|
||||||
mux.HandleFunc("POST /api/pico/setup", h.handlePicoSetup)
|
mux.HandleFunc("POST /api/pico/setup", h.handlePicoSetup)
|
||||||
|
|
||||||
|
// WebSocket proxy: forward /pico/ws to gateway
|
||||||
|
// This allows the frontend to connect via the same port as the web UI,
|
||||||
|
// avoiding the need to expose extra ports for WebSocket communication.
|
||||||
|
wsProxy := h.createWsProxy()
|
||||||
|
mux.HandleFunc("GET /pico/ws", h.handleWebSocketProxy(wsProxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// createWsProxy creates a reverse proxy to the gateway WebSocket endpoint.
|
||||||
|
// The gateway port is read from the configuration.
|
||||||
|
func (h *Handler) createWsProxy() *httputil.ReverseProxy {
|
||||||
|
cfg, err := config.LoadConfig(h.configPath)
|
||||||
|
gatewayPort := 18790 // default
|
||||||
|
if err == nil && cfg.Gateway.Port != 0 {
|
||||||
|
gatewayPort = cfg.Gateway.Port
|
||||||
|
}
|
||||||
|
gatewayURL, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", gatewayPort))
|
||||||
|
wsProxy := httputil.NewSingleHostReverseProxy(gatewayURL)
|
||||||
|
wsProxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
http.Error(w, "Gateway unavailable: "+err.Error(), http.StatusBadGateway)
|
||||||
|
}
|
||||||
|
return wsProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleWebSocketProxy wraps a reverse proxy to handle WebSocket connections.
|
||||||
|
// It ensures the Connection and Upgrade headers are properly forwarded.
|
||||||
|
func (h *Handler) handleWebSocketProxy(proxy *httputil.ReverseProxy) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Set headers for WebSocket upgrade
|
||||||
|
r.Header.Set("Connection", "upgrade")
|
||||||
|
r.Header.Set("Upgrade", "websocket")
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetPicoToken returns the current WS token and URL for the frontend.
|
// handleGetPicoToken returns the current WS token and URL for the frontend.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue