gecko/api/router.go

133 lines
3.2 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package api
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/gorilla/mux"
)
var (
errUnknownBaseURL = errors.New("unknown base url")
errUnknownEndpoint = errors.New("unknown endpoint")
)
type router struct {
lock sync.RWMutex
router *mux.Router
routeLock sync.Mutex
reservedRoutes map[string]bool // Reserves routes so that there can't be alias that conflict
aliases map[string][]string // Maps a route to a set of reserved routes
routes map[string]map[string]http.Handler // Maps routes to a handler
}
func newRouter() *router {
return &router{
router: mux.NewRouter(),
reservedRoutes: make(map[string]bool),
aliases: make(map[string][]string),
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()
r.router.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) 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[base] {
return fmt.Errorf("couldn't route to %s as that route is either aliased or already maps to a handler", 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
r.router.Handle(url, handler)
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[alias] {
return fmt.Errorf("couldn't alias to %s as that route is either already aliased or already maps to a handler", alias)
}
}
for _, alias := range aliases {
r.reservedRoutes[alias] = true
}
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
}