mirror of
https://github.com/luxfi/vm.git
synced 2026-10-30 15:13:40 +00:00
183 lines
4.3 KiB
Go
183 lines
4.3 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/luxfi/math/set"
|
|
)
|
|
|
|
const HTTPHeaderRoute = "Lux-Api-Route"
|
|
|
|
var (
|
|
errUnknownBaseURL = errors.New("unknown base url")
|
|
errUnknownEndpoint = errors.New("unknown endpoint")
|
|
errAlreadyReserved = errors.New("route is either already aliased or already maps to a handle")
|
|
)
|
|
|
|
type router struct {
|
|
lock sync.RWMutex
|
|
router *mux.Router
|
|
|
|
routeLock sync.Mutex
|
|
reservedRoutes set.Set[string] // Reserves routes so that there can't be alias that conflict
|
|
aliases map[string][]string // Maps a route to a set of reserved routes
|
|
// headerRoutes contains routes based on http headers
|
|
// aliasing is not currently supported
|
|
headerRoutes map[string]http.Handler
|
|
// legacy url-based routing
|
|
routes map[string]map[string]http.Handler // Maps routes to a handler
|
|
}
|
|
|
|
func newRouter() *router {
|
|
return &router{
|
|
router: mux.NewRouter(),
|
|
reservedRoutes: make(set.Set[string]),
|
|
aliases: make(map[string][]string),
|
|
headerRoutes: make(map[string]http.Handler),
|
|
routes: make(map[string]map[string]http.Handler),
|
|
}
|
|
}
|
|
|
|
func (r *router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|
r.lock.RLock()
|
|
defer r.lock.RUnlock()
|
|
|
|
route, ok := request.Header[HTTPHeaderRoute]
|
|
if !ok {
|
|
// If there is no routing header, fall-back to the legacy path-based
|
|
// routing
|
|
r.router.ServeHTTP(writer, request)
|
|
return
|
|
}
|
|
|
|
// Request specified the routing header key but did not provide a
|
|
// corresponding value
|
|
if len(route) != 1 {
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
handler, ok := r.headerRoutes[route[0]]
|
|
if !ok {
|
|
writer.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
handler.ServeHTTP(writer, request)
|
|
}
|
|
|
|
func (r *router) GetHandler(base, endpoint string) (http.Handler, error) {
|
|
r.routeLock.Lock()
|
|
defer r.routeLock.Unlock()
|
|
|
|
urlBase, exists := r.routes[base]
|
|
if !exists {
|
|
return nil, errUnknownBaseURL
|
|
}
|
|
handler, exists := urlBase[endpoint]
|
|
if !exists {
|
|
return nil, errUnknownEndpoint
|
|
}
|
|
return handler, nil
|
|
}
|
|
|
|
func (r *router) AddHeaderRoute(route string, handler http.Handler) bool {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
|
|
_, ok := r.headerRoutes[route]
|
|
if ok {
|
|
return false
|
|
}
|
|
|
|
r.headerRoutes[route] = handler
|
|
return true
|
|
}
|
|
|
|
func (r *router) AddRouter(base, endpoint string, handler http.Handler) error {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
r.routeLock.Lock()
|
|
defer r.routeLock.Unlock()
|
|
|
|
return r.addRouter(base, endpoint, handler)
|
|
}
|
|
|
|
func (r *router) addRouter(base, endpoint string, handler http.Handler) error {
|
|
if r.reservedRoutes.Contains(base) {
|
|
return fmt.Errorf("%w: %s", errAlreadyReserved, base)
|
|
}
|
|
|
|
return r.forceAddRouter(base, endpoint, handler)
|
|
}
|
|
|
|
func (r *router) forceAddRouter(base, endpoint string, handler http.Handler) error {
|
|
endpoints := r.routes[base]
|
|
if endpoints == nil {
|
|
endpoints = make(map[string]http.Handler)
|
|
}
|
|
url := base + endpoint
|
|
if _, exists := endpoints[endpoint]; exists {
|
|
return fmt.Errorf("failed to create endpoint as %s already exists", url)
|
|
}
|
|
|
|
endpoints[endpoint] = handler
|
|
r.routes[base] = endpoints
|
|
|
|
// Name routes based on their URL for easy retrieval in the future
|
|
route := r.router.Handle(url, handler)
|
|
if route == nil {
|
|
return fmt.Errorf("failed to create new route for %s", url)
|
|
}
|
|
route.Name(url)
|
|
|
|
var err error
|
|
if aliases, exists := r.aliases[base]; exists {
|
|
for _, alias := range aliases {
|
|
if innerErr := r.forceAddRouter(alias, endpoint, handler); err == nil {
|
|
err = innerErr
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *router) AddAlias(base string, aliases ...string) error {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
r.routeLock.Lock()
|
|
defer r.routeLock.Unlock()
|
|
|
|
for _, alias := range aliases {
|
|
if r.reservedRoutes.Contains(alias) {
|
|
return fmt.Errorf("%w: %s", errAlreadyReserved, alias)
|
|
}
|
|
}
|
|
|
|
for _, alias := range aliases {
|
|
r.reservedRoutes.Add(alias)
|
|
}
|
|
|
|
r.aliases[base] = append(r.aliases[base], aliases...)
|
|
|
|
var err error
|
|
if endpoints, exists := r.routes[base]; exists {
|
|
for endpoint, handler := range endpoints {
|
|
for _, alias := range aliases {
|
|
if innerErr := r.forceAddRouter(alias, endpoint, handler); err == nil {
|
|
err = innerErr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|