如何将一个新的ServeMux组成一个更大的结构?

In previous golang applications I've made that use the DefaultServeMux, if I had a route like this "/users/" route that was handled by func user(name string), for example, and I sent a request to /users/jim, the request would still be handled by the "/users/" route. In the current application I'm making, I'm not using DefaultServeMux but merely passing a handler that implements ServeHTTP and then switching on the request url. However, now, if I send a request to a route that doesn't exactly match, the handler function doesn't get called. For example, if I send a post request to "/api/jim", I get a 404 error even though "/api" is handled.

I want to keep my application the way it is (with a reference to the DB in the handler) but also be able to handle routes that don't match exactly.

Question: Assuming I could create a new ServeMux to handle routes that don't exactly match, but how can I compose that with my type Handler Struct that has the reference to the database connection?

type Handler struct{
  DB *DB
}

func main() {
    fmt.Println("Hello, playground")


    db, err := sql.Open("postgres", dbinfo)
    defer db.Close()

    h := &Handler{
      DB: db,
    }

    log.Fatal(http.ListenAndServe(":8888", h))
}

func (h *Handler)ServeHTTP(w http.ResponseWriter, r *http.Request){

    switch r.URL.Path{
        case "/":
        h.serveRoot(w, r)
        case "/api/":
        h.apiRouter(w, r)
    }
}

func (h *Handler)serveRoot(w http.ResponseWriter, r *http.Request){
        h.DB.DoSomethingWithDB()
}

func (h *Handler)apiRouter(w http.ResponseWriter, r *http.Request){

       switch r.URL.Path{

           case "/":
               h.serveRoot(w, r)
           case "/api/":
               h.apiRouter(w, r)

       }
}

Update For reasons that aren't relevant to the question, I can't use DefaultServeMux

Your custom handler struct, a ServeMux, a HandlerFunc, and 3rd party routers are all of type http.Handler. You can compose and layer them as needed.

Since a ServeMux is a Handler, you can assign it to a path just like any other handler, and register separate paths for various handlers. Here's an example of using multiple http.ServeMux (which could be defined in separate packages if you choose). This has 3 separate handlers (defined via a HandlerFunc), routed over 2 ServeMux.

Here we have a ServeMux create in a package "router"

var Sub = http.NewServeMux()

func subHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("handled / in /sub")
    w.Write([]byte("/sub/
"))
}    

func init() {
    Sub.HandleFunc("/", subHandler)
}

Now we can import the ServeMux from "router", and use it in our top level Handler, along with some others:

import "router"

func rootHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("handled /")
    w.Write([]byte("/
"))
}

func topHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("handled /top")
    w.Write([]byte("/top/
"))
}
func main() {

    mux := http.NewServeMux()
    mux.HandleFunc("/", rootHandler)
    mux.HandleFunc("/top/", topHandler)

    // now insert the Sub routes under "/top/sub/"
    mux.Handle("/top/sub/", http.StripPrefix("/top", router.Sub))

    server := &http.Server{Addr: ":9999", Handler: mux}
    log.Fatal(server.ListenAndServe())
}

How you choose to register these is up to you, either via importing and routing them like this in the main package, or via some other registration pattern (like e.g the database/sql drivers).

There's also no shortage of 3rd party routing packages to make this easier, higher performance, or provide more advanced methods of pattern matching.