Initial commit
This commit is contained in:
37
router/mux/http.go
Normal file
37
router/mux/http.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package mux
|
||||
|
||||
import "net/url"
|
||||
import "net/http"
|
||||
|
||||
type HTTP struct {
|
||||
Mux[http.Handler]
|
||||
}
|
||||
|
||||
func NewHTTP (resolver Resolver) *HTTP {
|
||||
mux := &HTTP { }
|
||||
mux.Mux.Redirect = mux.newRedirect
|
||||
mux.Mux.NotFound = mux.newNotFound
|
||||
mux.Mux.Resolver = resolver
|
||||
return mux
|
||||
}
|
||||
|
||||
func (mux *HTTP) ServeHTTP (res http.ResponseWriter, req *http.Request) {
|
||||
if req.RequestURI == "*" {
|
||||
if req.ProtoAtLeast(1, 1) {
|
||||
res.Header().Set("Connection", "close")
|
||||
}
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
handler, _ := mux.Handler(req.URL)
|
||||
handler.ServeHTTP(res, req)
|
||||
}
|
||||
|
||||
func (mux *HTTP) newNotFound (where *url.URL) http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
}
|
||||
|
||||
func (mux *HTTP) newRedirect (where *url.URL) http.Handler {
|
||||
return http.RedirectHandler(where.String(), http.StatusMovedPermanently)
|
||||
}
|
||||
245
router/mux/mux.go
Normal file
245
router/mux/mux.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package mux
|
||||
|
||||
import "net"
|
||||
import "sync"
|
||||
import "path"
|
||||
import "sort"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "net/url"
|
||||
|
||||
type Resolver interface {
|
||||
ResolveAlias (alias string) string
|
||||
}
|
||||
|
||||
// Mux is a generic request multiplexer based off of http.ServeMux. It can be
|
||||
// adapted to any protocol where requests to a URL are handled.
|
||||
type Mux[HANDLER any] struct {
|
||||
// These must be non-nil!
|
||||
Redirect func (*url.URL) HANDLER
|
||||
NotFound func (*url.URL) HANDLER
|
||||
Resolver Resolver
|
||||
|
||||
mutex sync.RWMutex
|
||||
exactEntries map[string]muxEntry[HANDLER]
|
||||
sortedEntries []muxEntry[HANDLER] // slice of entries sorted from longest to shortest.
|
||||
}
|
||||
|
||||
type muxEntry[HANDLER any] struct {
|
||||
handler HANDLER
|
||||
pattern string
|
||||
}
|
||||
|
||||
func cleanPath (p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
|
||||
np := path.Clean(p)
|
||||
// path.Clean removes trailing slash except for root;
|
||||
// put the trailing slash back if necessary.
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
// Fast path for common case of p being the string we want:
|
||||
if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
|
||||
np = p
|
||||
} else {
|
||||
np += "/"
|
||||
}
|
||||
}
|
||||
|
||||
return np
|
||||
}
|
||||
|
||||
func stripHostPort (h string) string {
|
||||
// If no port on host, return unchanged
|
||||
if !strings.Contains(h, ":") {
|
||||
return h
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return h // on error, return unchanged
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) Handler (where *url.URL) (h HANDLER, pattern string) {
|
||||
// All other requests have any port stripped and path cleaned
|
||||
// before passing to mux.handler.
|
||||
host := stripHostPort(where.Host)
|
||||
path := cleanPath(where.Path)
|
||||
host = mux.Resolver.ResolveAlias(host)
|
||||
pat := host + path
|
||||
|
||||
// If the given path is /tree and its handler is not registered,
|
||||
// redirect for /tree/.
|
||||
u, shouldRedirect := mux.redirectToPathSlash(host, path, where)
|
||||
if shouldRedirect {
|
||||
return mux.Redirect(u), u.Path
|
||||
}
|
||||
|
||||
if path != where.Path {
|
||||
_, pattern = mux.match(pat, where)
|
||||
u := &url.URL{Path: path, RawQuery: where.RawQuery}
|
||||
return mux.Redirect(u), pattern
|
||||
}
|
||||
|
||||
return mux.match(pat, where)
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) redirectToPathSlash (host, path string, u *url.URL) (*url.URL, bool) {
|
||||
mux.mutex.RLock()
|
||||
shouldRedirect := mux.shouldRedirectRLocked(host, path)
|
||||
mux.mutex.RUnlock()
|
||||
if !shouldRedirect {
|
||||
return u, false
|
||||
}
|
||||
path = path + "/"
|
||||
u = &url.URL{Path: path, RawQuery: u.RawQuery}
|
||||
return u, true
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) shouldRedirectRLocked (host, path string) bool {
|
||||
p := []string{path, host + path}
|
||||
|
||||
for _, c := range p {
|
||||
if _, exist := mux.exactEntries[c]; exist {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
n := len(path)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
for _, c := range p {
|
||||
if _, exist := mux.exactEntries[c+"/"]; exist {
|
||||
return path[n-1] != '/'
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) match (path string, original *url.URL) (h HANDLER, pattern string) {
|
||||
// Check for exact match first.
|
||||
entry, matchExists := mux.exactEntries[path]
|
||||
if matchExists {
|
||||
return entry.handler, entry.pattern
|
||||
}
|
||||
|
||||
// Check for longest valid match. mux.es contains all patterns
|
||||
// that end in / sorted from longest to shortest.
|
||||
for _, entry := range mux.sortedEntries {
|
||||
if strings.HasPrefix(path, entry.pattern) {
|
||||
return entry.handler, entry.pattern
|
||||
}
|
||||
}
|
||||
|
||||
return mux.NotFound(original), ""
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) Handle (pattern string, handler HANDLER) error {
|
||||
mux.mutex.Lock()
|
||||
defer mux.mutex.Unlock()
|
||||
|
||||
if pattern == "" {
|
||||
return errors.New(
|
||||
"mux: invalid pattern " + pattern +
|
||||
", cannot be empty.")
|
||||
}
|
||||
|
||||
if pattern[0] == '/' {
|
||||
return errors.New(
|
||||
"mux: invalid pattern " + pattern +
|
||||
", must be host specific.")
|
||||
}
|
||||
|
||||
if _, exist := mux.exactEntries[pattern]; exist {
|
||||
return errors.New("mux: existing mount on " + pattern)
|
||||
}
|
||||
|
||||
if mux.exactEntries == nil {
|
||||
mux.exactEntries = make(map[string] muxEntry[HANDLER])
|
||||
}
|
||||
|
||||
entry := muxEntry[HANDLER] { handler: handler, pattern: pattern }
|
||||
mux.exactEntries[pattern] = entry
|
||||
if pattern[len(pattern)-1] == '/' {
|
||||
mux.sortedEntries = appendSorted(mux.sortedEntries, entry)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) Unhandle (pattern string) error {
|
||||
mux.mutex.Lock()
|
||||
defer mux.mutex.Unlock()
|
||||
|
||||
// delete from exact match list
|
||||
if _, registered := mux.exactEntries[pattern]; !registered {
|
||||
return errors.New(
|
||||
"mux: pattern " + pattern + " is not mounted")
|
||||
}
|
||||
delete(mux.exactEntries, pattern)
|
||||
|
||||
// delete from sorted list, if its in there.
|
||||
newLen := 0
|
||||
for index, entry := range mux.sortedEntries {
|
||||
if entry.pattern != pattern {
|
||||
mux.sortedEntries[newLen] = mux.sortedEntries[index]
|
||||
newLen++
|
||||
}
|
||||
}
|
||||
mux.sortedEntries = mux.sortedEntries[:newLen]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mux *Mux[HANDLER]) OverHandlers (callback func (pattern string, handler HANDLER) bool) {
|
||||
overSorted (mux.exactEntries, func (pattern string, entry muxEntry[HANDLER]) bool {
|
||||
return callback(pattern, entry.handler)
|
||||
})
|
||||
}
|
||||
|
||||
func appendSorted[HANDLER any] (entries []muxEntry[HANDLER], entry muxEntry[HANDLER]) []muxEntry[HANDLER] {
|
||||
entriesLen := len(entries)
|
||||
index := sort.Search(entriesLen, func(index int) bool {
|
||||
return len(entries[index].pattern) < len(entry.pattern)
|
||||
})
|
||||
|
||||
if index == entriesLen {
|
||||
return append(entries, entry)
|
||||
}
|
||||
|
||||
// we now know that i points at where we want to insert
|
||||
// try to grow the slice in place, any entry works
|
||||
entries = append(entries, muxEntry[HANDLER] { })
|
||||
copy(entries[index+1:], entries[index:]) // Move shorter entries down
|
||||
entries[index] = entry
|
||||
return entries
|
||||
}
|
||||
|
||||
func overSorted[V any] (unsorted map[string] V, fn func (string, V) bool) {
|
||||
keys := sortKeys(unsorted)
|
||||
for _, key := range keys {
|
||||
if !fn(key, unsorted[key]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sortKeys[V any] (unsorted map[string] V) []string {
|
||||
sortedKeys := make([]string, len(unsorted))
|
||||
index := 0
|
||||
for key, _ := range unsorted {
|
||||
sortedKeys[index] = key
|
||||
index ++
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
return sortedKeys
|
||||
}
|
||||
Reference in New Issue
Block a user