fix(server): serve pprof profiles when a BaseURL is configured

net/http/pprof's Index resolves the profile name by trimming "/debug/pprof/" from
the raw request path, which never matches once MountRouter prepends the BasePath.
Requests for any profile without an explicit chi route fell through to the index
page, returning HTML with a 200 instead of the profile. The handler now strips the
BasePath first.
This commit is contained in:
Deluan 2026-08-19 23:45:26 -04:00
parent dafd57ca25
commit 758857b210
2 changed files with 53 additions and 1 deletions

View File

@ -2,6 +2,7 @@ package cmd
import (
"context"
"net/http"
"os"
"os/signal"
"strings"
@ -138,7 +139,7 @@ func startServer(ctx context.Context) func() error {
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, p.GetHandler())
}
if conf.Server.DevEnableProfiler {
a.MountRouter("Profiling", "/debug", middleware.Profiler())
a.MountRouter("Profiling", "/debug", profilerHandler())
}
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
a.MountRouter("Background images", conf.Server.UILoginBackgroundURL, backgrounds.NewHandler())
@ -147,6 +148,12 @@ func startServer(ctx context.Context) func() error {
}
}
// profilerHandler returns the pprof handler. net/http/pprof resolves the profile
// name from the raw request path, so the BasePath has to come off first.
func profilerHandler() http.Handler {
return http.StripPrefix(conf.Server.BasePath, middleware.Profiler())
}
// schedulePeriodicScan schedules a periodic scan of the music library, if configured.
func schedulePeriodicScan(ctx context.Context) func() error {
return func() error {

45
cmd/root_test.go Normal file
View File

@ -0,0 +1,45 @@
package cmd
import (
"net/http"
"net/http/httptest"
"path"
"runtime/pprof"
"github.com/go-chi/chi/v5"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/conf/configtest"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("profilerHandler", func() {
// Mirrors how server.MountRouter mounts the handler.
mount := func() http.Handler {
router := chi.NewRouter()
router.Mount(path.Join(conf.Server.BasePath, "/debug"), profilerHandler())
return router
}
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
if pprof.Lookup("nd-profiler-test") == nil {
pprof.NewProfile("nd-profiler-test")
}
})
DescribeTable("serves a named profile",
func(basePath string) {
conf.Server.BasePath = basePath
w := httptest.NewRecorder()
target := path.Join(basePath, "/debug/pprof/nd-profiler-test") + "?debug=1"
mount().ServeHTTP(w, httptest.NewRequest(http.MethodGet, target, nil))
Expect(w.Code).To(Equal(http.StatusOK))
Expect(w.Body.String()).To(HavePrefix("nd-profiler-test profile: total 0"))
},
Entry("without a BasePath", ""),
Entry("with a BasePath", "/music"),
)
})