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.