From 2b280de481ad38947a114a4eb2778447ae623788 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Mon, 12 Dec 2022 10:26:55 +0000 Subject: [PATCH] Correct handling of multiple packfiles During the 1.3.1 update, a change was made to stop using http.Handle (and ServeMux underneath), in order to have manual control over the http.Server object. Unfortunately, it was overlooked that nothing was doing routing / multiplexing, so when using multiple packfiles only one handler (picked arbitrarily due to map) would actually be active on any given invocation. Correct this by adding an explicit handler. We don't use ServeMux so as to avoid bringing in any of its more complex behaviours like path cleaning etc. --- cmd/packserver/main.go | 58 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/cmd/packserver/main.go b/cmd/packserver/main.go index 2000102..641a100 100644 --- a/cmd/packserver/main.go +++ b/cmd/packserver/main.go @@ -12,6 +12,7 @@ import ( "os" "os/signal" "path/filepath" + "sort" "strings" "sync/atomic" "syscall" @@ -192,7 +193,7 @@ func run(c *cobra.Command, args []string) error { } // load packfiles, registering handlers as we go - var handler http.Handler + router := &routerHandler{} for prefix, packfile := range packPaths { packHandler, err := htpack.New(packfile) if err != nil { @@ -207,7 +208,7 @@ func run(c *cobra.Command, args []string) error { } packHandler.SetHeader("X-Frame-Options", framesHeader) - handler = &addHeaders{ + var handler http.Handler = &addHeaders{ extraHeaders: extraHeaders, handler: packHandler, } @@ -215,12 +216,13 @@ func run(c *cobra.Command, args []string) error { if prefix != "/" { handler = http.StripPrefix(prefix, handler) } + router.AddRoute(prefix, handler) } // HTTP server object setup sv := &http.Server{ Addr: bindAddr, - Handler: handler, + Handler: router, } // register SIGINT, SIGTERM handler @@ -309,3 +311,53 @@ func (ah *addHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) { } ah.handler.ServeHTTP(w, r) } + +// routeEntry is used within routerHandler to map a specific prefix to a +// specific handler. +type routeEntry struct { + // prefix is a path prefix with trailing "/" such as "/foo/". + prefix string + + // handler for the request if prefix matches. + handler http.Handler +} + +// routerHandler holds a list of routes sorted by longest-prefix-first. +type routerHandler struct { + // entries are the list of prefixes, with longest prefix strings first. + // The sorting ensures we can iterate through from the start and match + // "/dir/subdir/" in preference to just "/dir/". + entries []routeEntry +} + +// AddRoute adds a new entry into the handler. It is not concurrency safe; the +// handler should not be in use. +func (rh *routerHandler) AddRoute(prefix string, handler http.Handler) { + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + rh.entries = append(rh.entries, routeEntry{ + prefix: prefix, + handler: handler, + }) + sort.Slice(rh.entries, func(i, j int) bool { + l1, l2 := len(rh.entries[i].prefix), len(rh.entries[j].prefix) + if l1 > l2 { + return true + } + if l1 == l2 { + return rh.entries[i].prefix < rh.entries[j].prefix + } + return false + }) +} + +func (rh *routerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + for _, entry := range rh.entries { + if strings.HasPrefix(r.URL.Path, entry.prefix) { + entry.handler.ServeHTTP(w, r) + return + } + } + http.NotFound(w, r) +}