diff --git a/agent/pkg/handler/proxy_http.go b/agent/pkg/handler/proxy_http.go index d840d456..79fa2c60 100644 --- a/agent/pkg/handler/proxy_http.go +++ b/agent/pkg/handler/proxy_http.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/http/httputil" + "strings" "time" "github.com/yusing/godoxy/agent/pkg/agent" @@ -43,10 +44,22 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) { return } - r.URL.Scheme = "" - r.URL.Host = "" - r.URL.Path = r.URL.Path[agent.HTTPProxyURLPrefixLen:] // strip the {API_BASE}/proxy/http prefix - r.RequestURI = r.URL.String() + // Strip the {API_BASE}/proxy/http prefix while preserving URL escaping. + // + // NOTE: `r.URL.Path` is decoded. If we rewrite it without keeping `RawPath` + // in sync, Go may re-escape the path (e.g. turning "%5B" into "%255B"), + // which breaks urls with percent-encoded characters, like Next.js static chunk URLs. + prefix := agent.APIEndpointBase + agent.EndpointProxyHTTP + r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) + if r.URL.RawPath != "" { + if after, ok := strings.CutPrefix(r.URL.RawPath, prefix); ok { + r.URL.RawPath = after + } else { + // RawPath is no longer a valid encoding for Path; force Go to re-derive it. + r.URL.RawPath = "" + } + } + r.RequestURI = "" rp := &httputil.ReverseProxy{ Director: func(r *http.Request) { diff --git a/internal/api/v1/docs/swagger.json b/internal/api/v1/docs/swagger.json index 9cea7db6..290a8eee 100644 --- a/internal/api/v1/docs/swagger.json +++ b/internal/api/v1/docs/swagger.json @@ -4555,6 +4555,11 @@ "x-nullable": false, "x-omitempty": false }, + "is_excluded": { + "type": "boolean", + "x-nullable": false, + "x-omitempty": false + }, "statuses": { "type": "array", "items": { diff --git a/internal/api/v1/docs/swagger.yaml b/internal/api/v1/docs/swagger.yaml index e6744d88..f6065c92 100644 --- a/internal/api/v1/docs/swagger.yaml +++ b/internal/api/v1/docs/swagger.yaml @@ -1056,6 +1056,8 @@ definitions: type: number is_docker: type: boolean + is_excluded: + type: boolean statuses: items: $ref: '#/definitions/RouteStatus' diff --git a/internal/entrypoint/entrypoint.go b/internal/entrypoint/entrypoint.go index ecaadd73..e7a69426 100644 --- a/internal/entrypoint/entrypoint.go +++ b/internal/entrypoint/entrypoint.go @@ -146,11 +146,18 @@ func findRouteAnyDomain(host string) types.HTTPRoute { if r, ok := routes.HTTP.Get(host); ok { return r } + // try striping the trailing :port from the host + if before, _, ok := strings.Cut(host, ":"); ok { + if r, ok := routes.HTTP.Get(before); ok { + return r + } + } return nil } func findRouteByDomains(domains []string) func(host string) types.HTTPRoute { return func(host string) types.HTTPRoute { + host, _, _ = strings.Cut(host, ":") // strip the trailing :port for _, domain := range domains { if target, ok := strings.CutSuffix(host, domain); ok { if r, ok := routes.HTTP.Get(target); ok { diff --git a/internal/entrypoint/entrypoint_test.go b/internal/entrypoint/entrypoint_test.go index d36f4879..526e878a 100644 --- a/internal/entrypoint/entrypoint_test.go +++ b/internal/entrypoint/entrypoint_test.go @@ -128,3 +128,47 @@ func TestFindRouteByDomainsExactMatch(t *testing.T) { run(t, tests, testsNoMatch) } + +func TestFindRouteWithPort(t *testing.T) { + t.Run("AnyDomain", func(t *testing.T) { + addRoute("app1") + addRoute("app2.com") + + tests := []string{ + "app1:8080", + "app1.domain.com:8080", + "app2.com:8080", + } + testsNoMatch := []string{ + "app11", + "app2.co", + "app2.co:8080", + } + run(t, tests, testsNoMatch) + }) + + t.Run("ByDomains", func(t *testing.T) { + ep.SetFindRouteDomains([]string{ + ".domain.com", + }) + addRoute("app1") + addRoute("app2") + addRoute("app3.domain.com") + + tests := []string{ + "app1.domain.com:8080", + "app2:8080", // exact match fallback + "app3.domain.com:8080", + } + testsNoMatch := []string{ + "app11", + "app1.domain.co", + "app1.domain.co:8080", + "app2.co", + "app2.co:8080", + "app3.domain.co", + "app3.domain.co:8080", + } + run(t, tests, testsNoMatch) + }) +} diff --git a/internal/homepage/homepage.go b/internal/homepage/homepage.go index e4a0c5d2..739496c6 100644 --- a/internal/homepage/homepage.go +++ b/internal/homepage/homepage.go @@ -7,6 +7,7 @@ import ( "github.com/yusing/ds/ordered" "github.com/yusing/godoxy/internal/homepage/widgets" "github.com/yusing/godoxy/internal/serialization" + strutils "github.com/yusing/goutils/strings" ) type ( @@ -146,13 +147,13 @@ func (c *Category) sortByClicks() { return 1 } // fallback to alphabetical - return strings.Compare(a.Name, b.Name) + return strings.Compare(strutils.Title(a.Name), strutils.Title(b.Name)) }) } func (c *Category) sortByAlphabetical() { slices.SortStableFunc(c.Items, func(a, b *Item) int { - return strings.Compare(a.Name, b.Name) + return strings.Compare(strutils.Title(a.Name), strutils.Title(b.Name)) }) } diff --git a/internal/metrics/uptime/uptime.go b/internal/metrics/uptime/uptime.go index 11dc0718..18baf000 100644 --- a/internal/metrics/uptime/uptime.go +++ b/internal/metrics/uptime/uptime.go @@ -33,6 +33,7 @@ type ( Idle float32 `json:"idle"` AvgLatency float32 `json:"avg_latency"` IsDocker bool `json:"is_docker"` + IsExcluded bool `json:"is_excluded"` CurrentStatus types.HealthStatus `json:"current_status" swaggertype:"string" enums:"healthy,unhealthy,unknown,napping,starting"` Statuses []Status `json:"statuses"` } // @name RouteUptimeAggregate @@ -156,6 +157,7 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated { CurrentStatus: status, Statuses: statuses, IsDocker: r != nil && r.IsDocker(), + IsExcluded: r == nil || r.ShouldExclude(), } } return result