Skip to content

Instantly share code, notes, and snippets.

@alexaandru
Created February 14, 2024 16:40
Show Gist options
  • Save alexaandru/747f9d7bdfb1fa35140b359bf23fa820 to your computer and use it in GitHub Desktop.
Save alexaandru/747f9d7bdfb1fa35140b359bf23fa820 to your computer and use it in GitHub Desktop.
Chi-like syntactic sugar layer on top of stdlib http.ServeMux
// Chi-like syntactic sugar layer on top of stdlib http.ServeMux.
package main
import (
"net/http"
"slices"
)
type (
middleware func(http.Handler) http.Handler
router struct {
*http.ServeMux
chain []middleware
}
)
func NewRouter(mx ...middleware) *router {
return &router{ServeMux: &http.ServeMux{}, chain: mx}
}
func (r *router) Use(mx ...middleware) {
r.chain = append(r.chain, mx...)
}
func (r *router) Group(fn func(r *router)) {
fn(&router{ServeMux: r.ServeMux, chain: slices.Clone(r.chain)})
}
func (r *router) Get(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodGet, path, fn, mx)
}
func (r *router) Post(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodPost, path, fn, mx)
}
func (r *router) Put(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodPut, path, fn, mx)
}
func (r *router) Delete(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodDelete, path, fn, mx)
}
func (r *router) Head(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodHead, path, fn, mx)
}
func (r *router) Options(path string, fn http.HandlerFunc, mx ...middleware) {
r.handle(http.MethodOptions, path, fn, mx)
}
func (r *router) handle(method, path string, fn http.HandlerFunc, mx []middleware) {
r.Handle(method+" "+path, r.wrap(fn, mx))
}
func (r *router) wrap(fn http.HandlerFunc, mx []middleware) (out http.Handler) {
out, mx = http.Handler(fn), append(slices.Clone(r.chain), mx...)
slices.Reverse(mx)
for _, m := range mx {
out = m(out)
}
return
}
package main
import (
"fmt"
"log"
"net/http"
)
func mid(i int) middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("mid", i, "start")
next.ServeHTTP(w, r)
fmt.Println("mid", i, "done")
})
}
}
func someHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("[the handler ran here]")
fmt.Fprintln(w, "Hello world of", r.URL.Path)
}
func main() {
r := NewRouter(mid(0))
r.Group(func(r *router) {
r.Use(mid(1), mid(2))
r.Get("/foo", someHandler)
})
r.Group(func(r *router) {
r.Use(mid(3))
r.Get("/bar", someHandler, mid(4))
r.Get("/baz", someHandler, mid(5))
})
r.Post("/foobar", someHandler)
log.Fatal(http.ListenAndServe(":3000", r))
}
@eltimn
Copy link

eltimn commented Feb 23, 2024

I believe on line 54 you meant to call http.Handle

@alexaandru
Copy link
Author

I believe on line 54 you meant to call http.Handle

No, I wanted exactly what I wrote: to use router.ServeMux when building the routes, not http.DefaultServeMux.

@eltimn
Copy link

eltimn commented Feb 24, 2024

I figured that out after I posted this. I ended up using r.ServeMux.Handle on line 54 and then passed r.ServeMux to ListenAndServe, but your original code works fine.

Anyway, thanks for posting this. I built a working example with error handling: https://github.com/eltimn/todo-plus/blob/main/cmd/server/router/router.go

@alexaandru
Copy link
Author

You're most welcome!

@enahs
Copy link

enahs commented Oct 29, 2024

https://github.com/eltimn/todo-plus/blob/main/pkg/router/router.go

I figured that out after I posted this. I ended up using r.ServeMux.Handle on line 54 and then passed r.ServeMux to ListenAndServe, but your original code works fine.

Anyway, thanks for posting this. I built a working example with error handling: https://github.com/eltimn/todo-plus/blob/main/cmd/server/router/router.go

https://github.com/eltimn/todo-plus/blob/main/pkg/router/router.go updated link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment