github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/devutil/mux.go (about)

     1  package devutil
     2  
     3  import (
     4  	"net/http"
     5  	"path"
     6  )
     7  
     8  /*
     9  
    10  Example use:
    11  wc := devutil.NewWasmCompiler().SetDir(".")
    12  mux := devutil.NewMux()
    13  //mux.Exact("/", devutil.DefaultIndex)
    14  mux.Match(devutil.NoFileExt, devutil.DefaultIndex)
    15  //mux.Match(devutil.NoFileExt, devutil.StaticFilePath("index.html"))
    16  mux.Exact("/main.wasm", devutil.MainWasmHandler(wc))
    17  mux.Exact("/wasm_exec.js", devutil.WasmExecJSHandler(wc))
    18  mux.Default(devutil.NewFileServer().SetDir("."))
    19  
    20  */
    21  
    22  // RequestMatcher describes something that can say yes/no if a request matches.
    23  // We use it here to mean "should this path be followed in order to answer this request".
    24  type RequestMatcher interface {
    25  	RequestMatch(r *http.Request) bool
    26  }
    27  
    28  // RequestMatcherFunc implements RequestMatcher as a function.
    29  type RequestMatcherFunc func(r *http.Request) bool
    30  
    31  // NoFileExt is a RequestMatcher that will return true for all paths which do not have a file extension.
    32  var NoFileExt = RequestMatcherFunc(func(r *http.Request) bool {
    33  	return path.Ext(path.Clean("/"+r.URL.Path)) == ""
    34  })
    35  
    36  // RequestMatch implements RequestMatcher.
    37  func (f RequestMatcherFunc) RequestMatch(r *http.Request) bool { return f(r) }
    38  
    39  // Mux is simple HTTP request multiplexer that has more generally useful behavior for Vugu development than http.ServeMux.
    40  // Routes are considered in the order they are added.
    41  type Mux struct {
    42  	routeList      []muxRoute
    43  	defaultHandler http.Handler
    44  }
    45  
    46  type muxRoute struct {
    47  	rm RequestMatcher
    48  	h  http.Handler
    49  }
    50  
    51  // NewMux returns a new Mux.
    52  func NewMux() *Mux {
    53  	return &Mux{}
    54  }
    55  
    56  // Exact adds an exact route match.  If path.Clean("/"+r.URL.Path)==name then h is called to handle the request.
    57  func (m *Mux) Exact(name string, h http.Handler) *Mux {
    58  	m.Match(RequestMatcherFunc(func(r *http.Request) bool {
    59  		return path.Clean("/"+r.URL.Path) == name
    60  	}), h)
    61  	return m
    62  }
    63  
    64  // Match adds a route that is used if the provided RequestMatcher returns true.
    65  func (m *Mux) Match(rm RequestMatcher, h http.Handler) *Mux {
    66  	m.routeList = append(m.routeList, muxRoute{rm: rm, h: h})
    67  	return m
    68  }
    69  
    70  // Default sets the defualt handler to be called if no other matches were found.
    71  func (m *Mux) Default(h http.Handler) *Mux {
    72  	m.defaultHandler = h
    73  	return m
    74  }
    75  
    76  // ServeHTTP implements http.Handler.
    77  func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    78  
    79  	for _, rt := range m.routeList {
    80  		if rt.rm.RequestMatch(r) {
    81  			rt.h.ServeHTTP(w, r)
    82  			return
    83  		}
    84  	}
    85  
    86  	if m.defaultHandler != nil {
    87  		m.defaultHandler.ServeHTTP(w, r)
    88  		return
    89  	}
    90  
    91  	http.NotFound(w, r)
    92  }