refactor(api): streamline WebSocket and Pico URL construction to prefer forwarded host and omit port when unspecified
This commit is contained in:
parent
01705249af
commit
051a737eeb
2 changed files with 70 additions and 39 deletions
|
|
@ -75,6 +75,38 @@ func requestHostName(r *http.Request) string {
|
|||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// clientFacingAuthorityFromRequest returns host[:port] for URLs returned to the
|
||||
// browser. If Host / X-Forwarded-Host has no explicit port, the listen port is
|
||||
// omitted so default ports for http/https/ws/wss apply (reverse proxy, FRP, etc.).
|
||||
func clientFacingAuthorityFromRequest(r *http.Request, listenPort int) string {
|
||||
raw := strings.TrimSpace(r.Header.Get("X-Forwarded-Host"))
|
||||
if raw != "" {
|
||||
raw = strings.TrimSpace(strings.Split(raw, ",")[0])
|
||||
} else {
|
||||
raw = strings.TrimSpace(r.Host)
|
||||
}
|
||||
if raw == "" {
|
||||
return net.JoinHostPort("127.0.0.1", strconv.Itoa(listenPort))
|
||||
}
|
||||
host, port, err := net.SplitHostPort(raw)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
func (h *Handler) picoURLAuthority(r *http.Request, cfg *config.Config) string {
|
||||
webPort := h.serverPort
|
||||
if webPort == 0 {
|
||||
webPort = 18800
|
||||
}
|
||||
bind := h.effectiveGatewayBindHost(cfg)
|
||||
if bind != "" && bind != "0.0.0.0" {
|
||||
return net.JoinHostPort(bind, strconv.Itoa(webPort))
|
||||
}
|
||||
return clientFacingAuthorityFromRequest(r, webPort)
|
||||
}
|
||||
|
||||
// forwardedRFC7239Proto returns the proto value from the first Forwarded element (RFC 7239), or "".
|
||||
func forwardedRFC7239Proto(r *http.Request) string {
|
||||
raw := strings.TrimSpace(r.Header.Get("Forwarded"))
|
||||
|
|
@ -149,39 +181,16 @@ func requestHTTPScheme(r *http.Request) string {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// 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"
|
||||
auth := h.picoURLAuthority(r, cfg)
|
||||
return requestWSScheme(r) + "://" + auth + "/pico/ws"
|
||||
}
|
||||
|
||||
func (h *Handler) buildPicoEventsURL(r *http.Request, cfg *config.Config) string {
|
||||
host := h.effectiveGatewayBindHost(cfg)
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = requestHostName(r)
|
||||
}
|
||||
webPort := h.serverPort
|
||||
if webPort == 0 {
|
||||
webPort = 18800
|
||||
}
|
||||
return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/events"
|
||||
auth := h.picoURLAuthority(r, cfg)
|
||||
return requestHTTPScheme(r) + "://" + auth + "/pico/events"
|
||||
}
|
||||
|
||||
func (h *Handler) buildPicoSendURL(r *http.Request, cfg *config.Config) string {
|
||||
host := h.effectiveGatewayBindHost(cfg)
|
||||
if host == "" || host == "0.0.0.0" {
|
||||
host = requestHostName(r)
|
||||
}
|
||||
webPort := h.serverPort
|
||||
if webPort == 0 {
|
||||
webPort = 18800
|
||||
}
|
||||
return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/send"
|
||||
auth := h.picoURLAuthority(r, cfg)
|
||||
return requestHTTPScheme(r) + "://" + auth + "/pico/send"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ func TestBuildWsURLUsesWSSWhenForwardedProtoIsHTTPS(t *testing.T) {
|
|||
req.Host = "chat.example.com"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
|
||||
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:18800/pico/ws")
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +164,8 @@ func TestBuildWsURLUsesWSSWhenRequestIsTLS(t *testing.T) {
|
|||
req.Host = "secure.example.com"
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
|
||||
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:18800/pico/ws")
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://secure.example.com/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://secure.example.com/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,8 +182,8 @@ func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) {
|
|||
req.TLS = &tls.ConnectionState{}
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
|
||||
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:18800/pico/ws")
|
||||
if got := h.buildWsURL(req, cfg); got != "ws://chat.example.com/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +199,8 @@ func TestBuildWsURLUsesWSSWhenForwardedHeaderProtoIsHTTPS(t *testing.T) {
|
|||
req.Host = "chat.example.com"
|
||||
req.Header.Set("Forwarded", `for=203.0.113.1;proto=https;host=chat.example.com`)
|
||||
|
||||
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:18800/pico/ws")
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +216,29 @@ func TestBuildWsURLUsesWSSWhenXForwardedSslOn(t *testing.T) {
|
|||
req.Host = "chat.example.com"
|
||||
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||
|
||||
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:18800/pico/ws")
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPicoURLsPreferForwardedHostAndOmitPortWhenUnspecified(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
h.SetServerOptions(8080, false, false, nil)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "http://127.0.0.1:8080/api/pico/token", nil)
|
||||
req.Host = "127.0.0.1:8080"
|
||||
req.Header.Set("X-Forwarded-Host", "piclaw02-3rho55-8080.svc-usw2.nicegpu.com")
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
|
||||
if got := h.buildPicoEventsURL(req, cfg); got != "http://piclaw02-3rho55-8080.svc-usw2.nicegpu.com/pico/events" {
|
||||
t.Fatalf("buildPicoEventsURL() = %q, want %q", got, "http://piclaw02-3rho55-8080.svc-usw2.nicegpu.com/pico/events")
|
||||
}
|
||||
if got := h.buildPicoSendURL(req, cfg); got != "http://piclaw02-3rho55-8080.svc-usw2.nicegpu.com/pico/send" {
|
||||
t.Fatalf("buildPicoSendURL() = %q, want %q", got, "http://piclaw02-3rho55-8080.svc-usw2.nicegpu.com/pico/send")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue