goblin
A golang http router based on trie tree.
Features
- Support Go1.20 >= 1.16
- Easy to use
- Lightweight
- Fully compatible with net/http
- No external dependencies
- Support custom error handler
- Support default OPTIONS handler
- Support method-based routing
- Support variables in URL paths
- Support regexp route patterns
- Support middlewares
Install
go get -u github.com/bmf-san/goblin
Usage
Method-based routing
Goblin supports method-based routing.
GET/POST/PUT/PATCH/DELETE/OPTIONS
You can define routing as follows.
r := goblin.NewRouter()
r.Methods(http.MethodGet).Handler(`/`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "/")
}))
r.Methods(http.MethodGet, http.MethodPost).Handler(`/methods`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
fmt.Fprintf(w, "GET")
}
if r.Method == http.MethodPost {
fmt.Fprintf(w, "POST")
}
}))
http.ListenAndServe(":9999", r)
Variables in URL paths
goblin supports variabled in URL paths.
r := goblin.NewRouter()
r.Methods(http.MethodGet).Handler(`/foo/:id`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := goblin.GetParam(r.Context(), "id")
fmt.Fprintf(w, "/foo/%v", id)
}))
r.Methods(http.MethodGet).Handler(`/foo/:name`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := goblin.GetParam(r.Context(), "name")
fmt.Fprintf(w, "/foo/%v", name)
}))
http.ListenAndServe(":9999", r)
If you use the named parameters without regular expression as in the above case, it is internally interpreted as a wildcard ((.+)
) regular expression.
So :id
is substantially defined as :id[(.+)]
internaly.
Regexp route patterns
goblin support regexp route patterns.
:paramName[pattern]
r.Methods(http.MethodGet).Handler(`/foo/:id[^\d+$]`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := goblin.GetParam(r.Context(), "id")
fmt.Fprintf(w, "/foo/%v", id)
}))
Matching priority
A routing pattern matching priority depends on an order of routing definition.
The one defined earlier takes precedence over the one defined later.
r := goblin.NewRouter()
r.Methods(http.MethodGet).Handler(`/foo/:id`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `/foo/:id`)
}))
r.Methods(http.MethodGet).Handler(`/foo/:id[^\d+$]`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `/foo/:id[^\d+$]`)
}))
r.Methods(http.MethodGet).Handler(`/foo/:id[^\D+$]`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `/foo/:id[^\D+$]`)
}))
http.ListenAndServe(":9999", r)
In the above case, when accessing /foo/1
, it matches the routing defined first.
So it doesn't match the 2nd and 3rd defined routings.
Custom error handler
goblin supports custom error handler.
You can be able to set your customized error handler.
func customMethodNotFound() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "customMethodNotFound")
})
}
func customMethodAllowed() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "customMethodNotAllowed")
})
}
r := goblin.NewRouter()
r.NotFoundHandler = customMethodNotFound()
r.MethodNotAllowedHandler = customMethodAllowed()
http.ListenAndServe(":9999", r)
Default OPTIONS handler
goblin supports default OPTIONS handler.
You can be able to set your default handler for OPTIONS http method.
func DefaultOPTIONSHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
}
r := goblin.NewRouter()
r.DefaultOPTIONSHandler = DefaultOPTIONSHandler()
http.ListenAndServe(":9999", r)
Middlewares
goblin supports middlewares.
You can be able to set one or more middlewares.
There is no problem even if you do not set the middleware.
func global(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "global: before\n")
next.ServeHTTP(w, r)
fmt.Fprintf(w, "global: after\n")
})
}
func first(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "first: before\n")
next.ServeHTTP(w, r)
fmt.Fprintf(w, "first: after\n")
})
}
func second(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "second: before\n")
next.ServeHTTP(w, r)
fmt.Fprintf(w, "second: after\n")
})
}
func third(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "third: before\n")
next.ServeHTTP(w, r)
fmt.Fprintf(w, "third: after\n")
})
}
r := goblin.NewRouter()
r.UseGlobal(global)
r.Methods(http.MethodGet).Handler(`/globalmiddleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "/globalmiddleware\n")
}))
r.Methods(http.MethodGet).Use(first).Handler(`/middleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "middleware\n")
}))
r.Methods(http.MethodGet).Use(second, third).Handler(`/middlewares`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "middlewares\n")
}))
http.ListenAndServe(":9999", r)
Accessing /globalmiddleware
will produce ouput similar to the following:
global: before
/globalmiddleware
global: after
In the above case, accessing /middleware
will produce ouput similar to the following:
global: before
first: before
middleware
first: after
global: after
Accessing /middlewares
will produce ouput similar to the following:
global: before
second: before
third: before
middlewares
third: after
second: after
global: after
Examples
See _examples.
Wiki
See Wiki.
Benchmark tests
Interested in a comparison with other HTTP routers? Please take a look here.
Contribution
We are always accepting issues, pull requests, and other requests and questions.
We look forward to your contribution!
License
This project is licensed under the terms of the MIT license.
Author
bmf - A Web Developer in Japan.