Files
go-omni/transport/nethttp/router.go

109 lines
2.9 KiB
Go

// Package nethttp provides GoOmni's standard-library router. Register routes
// before serving requests.
package nethttp
import (
"net/http"
"sort"
"strings"
"git.yuntuo.hk/k2safe/go-omni/httpx"
"git.yuntuo.hk/k2safe/go-omni/transport/internal/routing"
)
type Router struct{ routes []route }
type route struct {
method string
pattern string
segments []routing.Segment
handler http.Handler
}
func New() *Router { return &Router{} }
func (r *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var selected *route
var selectedParams map[string]string
allow := make(map[string]bool)
for index := range r.routes {
candidate := &r.routes[index]
params, matched := routing.Match(candidate.segments, request.URL.Path)
if !matched {
continue
}
if candidate.method != request.Method {
allow[candidate.method] = true
continue
}
if selected == nil || moreSpecific(candidate.segments, selected.segments) {
selected, selectedParams = candidate, params
}
}
if selected == nil {
if len(allow) > 0 {
methods := make([]string, 0, len(allow))
for method := range allow {
methods = append(methods, method)
}
sort.Strings(methods)
writer.Header().Set("Allow", strings.Join(methods, ", "))
http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
} else {
http.NotFound(writer, request)
}
return
}
ctx := httpx.WithRouteParams(request.Context(), selectedParams)
ctx = httpx.WithRoutePattern(ctx, selected.pattern)
selected.handler.ServeHTTP(writer, request.WithContext(ctx))
}
func (r *Router) Handle(method, path string, handler http.Handler) {
segments := routing.Parse(method, path, handler)
for _, existing := range r.routes {
if existing.method == method {
routing.CheckConflict(existing.segments, segments)
}
}
r.routes = append(r.routes, route{method, path, segments, handler})
}
func (r *Router) Any(path string, handler http.Handler) {
for _, method := range routing.Methods {
r.Handle(method, path, handler)
}
}
func (r *Router) Group(path string) httpx.RouterGroup {
return &group{router: r, prefix: routing.Join("", path)}
}
type group struct {
router *Router
prefix string
}
func (g *group) Handle(method, path string, handler http.Handler) {
g.router.Handle(method, routing.Join(g.prefix, path), handler)
}
func (g *group) Any(path string, handler http.Handler) {
g.router.Any(routing.Join(g.prefix, path), handler)
}
func (g *group) Group(path string) httpx.RouterGroup {
return &group{router: g.router, prefix: routing.Join(g.prefix, path)}
}
// Compare from the left: an earlier static segment wins even if another
// matching route has more static segments later in its path.
func moreSpecific(a, b []routing.Segment) bool {
for i := 0; i < len(a) && i < len(b); i++ {
if (a[i].Name == "") != (b[i].Name == "") {
return a[i].Name == ""
}
}
return false
}