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) +}