180 lines
4.9 KiB
Go
180 lines
4.9 KiB
Go
package bunrouter
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Router is the main router structure that implements HTTP request routing.
|
|
// It maintains a routing tree and handles incoming HTTP requests.
|
|
type Router struct {
|
|
config // embedded router configuration
|
|
Group // embedded route group
|
|
mu sync.Mutex // protects the routing tree
|
|
tree node // root node of the routing tree
|
|
}
|
|
|
|
// New creates and returns a new Router instance with the given options.
|
|
// Options can include middleware, custom handlers for 404 and 405 responses,
|
|
// and other router configurations.
|
|
func New(opts ...Option) *Router {
|
|
r := &Router{
|
|
tree: node{
|
|
part: "/",
|
|
},
|
|
}
|
|
|
|
r.Group.router = r
|
|
r.config.group = &r.Group
|
|
r.methodNotAllowedHandler = methodNotAllowedHandler
|
|
|
|
for _, opt := range opts {
|
|
opt.apply(&r.config)
|
|
}
|
|
|
|
// Do it after processing middlewares from the options.
|
|
if r.notFoundHandler == nil {
|
|
r.notFoundHandler = r.group.wrap(notFoundHandler)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
var _ http.Handler = (*Router)(nil)
|
|
|
|
// ServeHTTP implements the http.Handler interface.
|
|
// It processes the incoming HTTP request and routes it to the appropriate handler.
|
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
_ = r.ServeHTTPError(w, req)
|
|
}
|
|
|
|
// ServeHTTPError is similar to ServeHTTP but also returns any error
|
|
// that occurred during request handling.
|
|
func (r *Router) ServeHTTPError(w http.ResponseWriter, req *http.Request) error {
|
|
handler, params := r.lookup(w, req)
|
|
return handler(w, newRequestParams(req, params))
|
|
}
|
|
|
|
// lookup finds the appropriate handler and parameters for the given HTTP request.
|
|
// It returns the handler function and parsed route parameters.
|
|
func (r *Router) lookup(w http.ResponseWriter, req *http.Request) (HandlerFunc, Params) {
|
|
path := req.URL.RawPath
|
|
if path == "" {
|
|
path = req.URL.Path
|
|
}
|
|
|
|
node, handler, wildcardLen := r.tree.findRoute(req.Method, path)
|
|
if node == nil {
|
|
if redir := r.redir(req.Method, path); redir != nil {
|
|
return redir, Params{}
|
|
}
|
|
return r.notFoundHandler, Params{}
|
|
}
|
|
|
|
if handler == nil {
|
|
if redir := r.redir(req.Method, path); redir != nil {
|
|
return redir, Params{}
|
|
}
|
|
handler = node.handlerMap.notAllowed
|
|
}
|
|
|
|
return handler.fn, Params{
|
|
node: node,
|
|
handler: handler,
|
|
path: path,
|
|
wildcardLen: uint16(wildcardLen),
|
|
}
|
|
}
|
|
|
|
// redir handles URL redirects for cleaned paths and trailing slash variations.
|
|
// It returns a redirect handler if a redirect is needed, nil otherwise.
|
|
func (r *Router) redir(method, path string) HandlerFunc {
|
|
if path == "/" {
|
|
return nil
|
|
}
|
|
|
|
// Path was not found. Try cleaning it up and search again.
|
|
if cleanPath := CleanPath(path); cleanPath != path {
|
|
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
|
|
return redirectHandler(cleanPath)
|
|
}
|
|
}
|
|
|
|
if strings.HasSuffix(path, "/") {
|
|
// Try path without a slash.
|
|
cleanPath := path[:len(path)-1]
|
|
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
|
|
return redirectHandler(cleanPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Try path with a slash.
|
|
cleanPath := path + "/"
|
|
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
|
|
return redirectHandler(cleanPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// CompatRouter provides compatibility layer for the router.
|
|
type CompatRouter struct {
|
|
*Router
|
|
*CompatGroup
|
|
}
|
|
|
|
// Compat returns a new CompatRouter instance that wraps the current router.
|
|
func (r *Router) Compat() *CompatRouter {
|
|
return &CompatRouter{
|
|
Router: r,
|
|
CompatGroup: r.Group.Compat(),
|
|
}
|
|
}
|
|
|
|
// VerboseRouter provides a verbose interface to the router.
|
|
type VerboseRouter struct {
|
|
*Router
|
|
*VerboseGroup
|
|
}
|
|
|
|
// Verbose returns a new VerboseRouter instance that wraps the current router.
|
|
func (r *Router) Verbose() *VerboseRouter {
|
|
return &VerboseRouter{
|
|
Router: r,
|
|
VerboseGroup: r.Group.Verbose(),
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// redirectHandler creates a handler function that performs HTTP redirects
|
|
// to the specified new path while preserving query parameters and fragments.
|
|
func redirectHandler(newPath string) HandlerFunc {
|
|
return func(w http.ResponseWriter, req Request) error {
|
|
newURL := url.URL{
|
|
Path: newPath,
|
|
RawQuery: req.URL.RawQuery,
|
|
Fragment: req.URL.Fragment,
|
|
}
|
|
http.Redirect(w, req.Request, newURL.String(), http.StatusMovedPermanently)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// methodNotAllowedHandler is the default handler for requests with methods
|
|
// that are not allowed for the matched route.
|
|
func methodNotAllowedHandler(w http.ResponseWriter, r Request) error {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return nil
|
|
}
|
|
|
|
// notFoundHandler is the default handler for requests that don't match any route.
|
|
func notFoundHandler(w http.ResponseWriter, req Request) error {
|
|
http.NotFound(w, req.Request)
|
|
return nil
|
|
}
|