server: Switch to custom mux
This commit is contained in:
parent
a1069f115e
commit
ab9998881b
13 changed files with 313 additions and 62 deletions
154
pkg/mux/mux.go
Normal file
154
pkg/mux/mux.go
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// Package mux provides mux
|
||||
package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux represents a mux that can handle different requests
|
||||
type Mux[C any] struct {
|
||||
prefixes map[string][]handler
|
||||
exacts map[string][]handler
|
||||
|
||||
Context func(r *http.Request) C // called to set context on the given request
|
||||
|
||||
Panic func(panic any, w http.ResponseWriter, r *http.Request) // called on panic
|
||||
NotFound http.Handler // optional handler to be called in case of a not found
|
||||
}
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var theContextKey = contextKey{}
|
||||
|
||||
type handler struct {
|
||||
Predicate Predicate
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) Prepare(r *http.Request) *http.Request {
|
||||
if mux == nil || mux.Context == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), theContextKey, mux.Context(r))
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) ContextOf(r *http.Request) (t T) {
|
||||
value, ok := r.Context().Value(theContextKey).(T)
|
||||
if !ok {
|
||||
return t
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Add adds a handler for the given path
|
||||
func (mux *Mux[T]) Add(path string, predicate Predicate, exact bool, h http.Handler) {
|
||||
if mux.exacts == nil {
|
||||
mux.exacts = make(map[string][]handler)
|
||||
}
|
||||
if mux.prefixes == nil {
|
||||
mux.prefixes = make(map[string][]handler)
|
||||
}
|
||||
|
||||
mPath := normalizePath(path)
|
||||
mHandler := handler{Predicate: predicate, Handler: h}
|
||||
if exact {
|
||||
mux.exacts[mPath] = append(mux.exacts[mPath], mHandler)
|
||||
} else {
|
||||
mux.prefixes[mPath] = append(mux.prefixes[mPath], mHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns the handler to be applied for the given request.
|
||||
func (mux *Mux[T]) Match(r *http.Request, prepare bool) (http.Handler, bool) {
|
||||
if mux == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if prepare {
|
||||
r = mux.Prepare(r)
|
||||
}
|
||||
|
||||
candidate := normalizePath(r.URL.Path)
|
||||
|
||||
// match the exact path first
|
||||
for _, h := range mux.exacts[candidate] {
|
||||
if h.Predicate.Call(r) {
|
||||
return h.Handler, true
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over path segment candidates
|
||||
for {
|
||||
// check the current candidate
|
||||
for _, h := range mux.prefixes[candidate] {
|
||||
if h.Predicate.Call(r) {
|
||||
return h.Handler, true
|
||||
}
|
||||
}
|
||||
|
||||
// if the candidate is the root url, we can bail out now
|
||||
if len(candidate) == 0 || candidate == "/" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// move to the parent segment
|
||||
candidate = parentSegment(candidate)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (mux *Mux[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// handle panics with the panic handler
|
||||
defer func() {
|
||||
caught := recover()
|
||||
if caught == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if mux == nil || mux.Panic == nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// silently ignore any panic()s in the panic handler
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
// call the panic handler
|
||||
mux.Panic(caught, w, r)
|
||||
}()
|
||||
|
||||
// prepare the request
|
||||
r = mux.Prepare(r)
|
||||
|
||||
// find the right handler
|
||||
// or go into 404 mode
|
||||
handler, ok := mux.Match(r, false)
|
||||
if !ok {
|
||||
if mux == nil || mux.NotFound == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mux.NotFound.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// call the actual handling
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Predicate represents a matching predicate for a given request.
|
||||
// The nil predicate always matches
|
||||
type Predicate func(r *http.Request) bool
|
||||
|
||||
// Call checks if this predicate matches the given request.
|
||||
func (p Predicate) Call(r *http.Request) bool {
|
||||
if p == nil {
|
||||
return true
|
||||
}
|
||||
return p(r)
|
||||
}
|
||||
28
pkg/mux/path.go
Normal file
28
pkg/mux/path.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package mux
|
||||
|
||||
import (
|
||||
"path"
|
||||
)
|
||||
|
||||
// normalizePath normalizes the provided path.
|
||||
// It ensures that there is both a leading and trailing slash.
|
||||
func normalizePath(value string) string {
|
||||
value = path.Clean(value)
|
||||
if value != "/" {
|
||||
value = value + "/"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// parentSegment returns the parent segment of the provided path
|
||||
// it assumes that normalizePath has been called on value.
|
||||
func parentSegment(value string) string {
|
||||
if value == "" || value == "/" {
|
||||
return "/"
|
||||
}
|
||||
parent := path.Dir(value[:len(value)-1])
|
||||
if parent != "/" {
|
||||
parent = parent + "/"
|
||||
}
|
||||
return parent
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue