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"
|
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 "".
|
// forwardedRFC7239Proto returns the proto value from the first Forwarded element (RFC 7239), or "".
|
||||||
func forwardedRFC7239Proto(r *http.Request) string {
|
func forwardedRFC7239Proto(r *http.Request) string {
|
||||||
raw := strings.TrimSpace(r.Header.Get("Forwarded"))
|
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 {
|
func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string {
|
||||||
host := h.effectiveGatewayBindHost(cfg)
|
auth := h.picoURLAuthority(r, cfg)
|
||||||
if host == "" || host == "0.0.0.0" {
|
return requestWSScheme(r) + "://" + auth + "/pico/ws"
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) buildPicoEventsURL(r *http.Request, cfg *config.Config) string {
|
func (h *Handler) buildPicoEventsURL(r *http.Request, cfg *config.Config) string {
|
||||||
host := h.effectiveGatewayBindHost(cfg)
|
auth := h.picoURLAuthority(r, cfg)
|
||||||
if host == "" || host == "0.0.0.0" {
|
return requestHTTPScheme(r) + "://" + auth + "/pico/events"
|
||||||
host = requestHostName(r)
|
|
||||||
}
|
|
||||||
webPort := h.serverPort
|
|
||||||
if webPort == 0 {
|
|
||||||
webPort = 18800
|
|
||||||
}
|
|
||||||
return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/events"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) buildPicoSendURL(r *http.Request, cfg *config.Config) string {
|
func (h *Handler) buildPicoSendURL(r *http.Request, cfg *config.Config) string {
|
||||||
host := h.effectiveGatewayBindHost(cfg)
|
auth := h.picoURLAuthority(r, cfg)
|
||||||
if host == "" || host == "0.0.0.0" {
|
return requestHTTPScheme(r) + "://" + auth + "/pico/send"
|
||||||
host = requestHostName(r)
|
|
||||||
}
|
|
||||||
webPort := h.serverPort
|
|
||||||
if webPort == 0 {
|
|
||||||
webPort = 18800
|
|
||||||
}
|
|
||||||
return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/send"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,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: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:18800/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.Host = "secure.example.com"
|
||||||
req.TLS = &tls.ConnectionState{}
|
req.TLS = &tls.ConnectionState{}
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); 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:18800/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.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: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:18800/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.Host = "chat.example.com"
|
||||||
req.Header.Set("Forwarded", `for=203.0.113.1;proto=https;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" {
|
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com/pico/ws" {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18800/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.Host = "chat.example.com"
|
||||||
req.Header.Set("X-Forwarded-Ssl", "on")
|
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||||
|
|
||||||
if got := h.buildWsURL(req, cfg); 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:18800/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