mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(web): refactor pico chat flow and fix proxied websocket URLs (#1639)
- move chat controller, state, protocol, history, and websocket logic into a dedicated chat feature module - improve chat reconnection, session hydration, and send gating based on actual websocket state - preserve gateway status during transient SSE disconnects and update stop state immediately - generate wss websocket URLs behind HTTPS proxies and add backend tests for forwarded proto handling
This commit is contained in:
@@ -57,10 +57,28 @@ func requestHostName(r *http.Request) string {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
func requestWSScheme(r *http.Request) string {
|
||||
if forwarded := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); forwarded != "" {
|
||||
proto := strings.ToLower(strings.TrimSpace(strings.Split(forwarded, ",")[0]))
|
||||
if proto == "https" || proto == "wss" {
|
||||
return "wss"
|
||||
}
|
||||
if proto == "http" || proto == "ws" {
|
||||
return "ws"
|
||||
}
|
||||
}
|
||||
|
||||
if r.TLS != nil {
|
||||
return "wss"
|
||||
}
|
||||
|
||||
return "ws"
|
||||
}
|
||||
|
||||
func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string {
|
||||
host := h.effectiveGatewayBindHost(cfg)
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = requestHostName(r)
|
||||
}
|
||||
return "ws://" + net.JoinHostPort(host, strconv.Itoa(cfg.Gateway.Port)) + "/pico/ws"
|
||||
return requestWSScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(cfg.Gateway.Port)) + "/pico/ws"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -57,3 +58,55 @@ func TestGatewayProbeHostUsesLoopbackForWildcardBind(t *testing.T) {
|
||||
t.Fatalf("gatewayProbeHost() = %q, want %q", got, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWsURLUsesWSSWhenForwardedProtoIsHTTPS(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "http://launcher.local/api/pico/token", nil)
|
||||
req.Host = "chat.example.com"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18790/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18790/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWsURLUsesWSSWhenRequestIsTLS(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "https://launcher.local/api/pico/token", nil)
|
||||
req.Host = "secure.example.com"
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://secure.example.com:18790/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://secure.example.com:18790/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "https://launcher.local/api/pico/token", nil)
|
||||
req.Host = "chat.example.com"
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
|
||||
if got := h.buildWsURL(req, cfg); got != "ws://chat.example.com:18790/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com:18790/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user