Browse Source

Updated/added comments.

Benjamin Shelton 4 months ago
2 changed files with 56 additions and 13 deletions
  1. +13
  2. +43

+ 13
- 0
internal/api/application.go View File

@ -5,8 +5,21 @@ import (
// ApplicationLoader definition. Loaders providing multi-application support
// must implement this API.
type ApplicationLoader interface {
// AddApplicationHandler assigns an http.Handler to the specified URI.
// Whenever a URI matches the pattern passed here, `handler` will be called
// instead.
// Not all types implementing ApplicationLoader handle URIs equivalently.
AddApplicationHandler(uri string, handler http.Handler) error
// Handler returns the http.Handler assigned to the specified URI. This is
// usually called by multiAppProxy.
Handler(uri url.URL) (http.Handler, bool)
// DefaultHandler returns an http.Handler to use whenever no handler is
// assigned for a given URI pattern via AddApplicationHandler.
DefaultHandler() http.Handler

+ 43
- 13
proxy.go View File

@ -11,12 +11,33 @@ import (
// ProxyHandler is the interface that must be implemented by types that intend
// to be used as proxy handlers. Proxy handlers may be comparatively simple,
// such as the `proxy` type, or they may be more complex and implement
// host-lookup functionality such as the `multiAppProxy` type.
// Proxies are required in order to support rebinding and endpoint deletion
// since go-chi doesn't currently allow us to overwrite or delete endpoints
// directly. So, what we do instead, is to regenerate the go-chi bindings when a
// rebind or endpoint deletion is requested, call Switch() on the proxy, and
// "switch" to the new chi.Router.
// Multiple proxies are arranged in a hierarchical structure, such as for
// multiapp support. In this case, the multiapp proxy handles dispatching
// requests based on the incoming domain, path, or domain + path, and then
// passes it to the underlying `proxy` which performs the rest of the work. This
// allows us to Switch() on a per-application bases, as required, while still
// supporting multiple applications within the same Capstan-hosted instance.
type ProxyHandler interface {
// ServeHTTP allows ProxyHandler to implement http.Handler.
ServeHTTP(http.ResponseWriter, *http.Request)
// Switch the current router to a new router instance.
SwitchFunc() func(chi.Router)
// MultiAppProxyHandler defines the interface to expose for types supporting
// multi-application loading.
type MultiAppProxyHandler interface {
Loader() api.ApplicationLoader
@ -35,6 +56,7 @@ type proxy struct {
mux *chi.Mux
// `proxy` type configuration options.
type proxyOptions struct {
ListenAddr string
Hostname string
@ -43,11 +65,6 @@ type proxyOptions struct {
MaxRequestDuration int
type handlerFinder interface {
FindHandlerByDomain(string) http.Handler
FindHandlerByName(string) http.Handler
// Returns a new proxy object with its router and mux unset.
// TODO: Replace or add feature for using a radix trie to determine the
@ -93,10 +110,6 @@ func (d *proxy) Switch(router chi.Router) {
func (d *proxy) SwitchFunc() func(chi.Router) {
return d.Switch
type multiappProxy struct {
loader api.ApplicationLoader
@ -128,9 +141,6 @@ func (m *multiappProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (m *multiappProxy) Switch(router chi.Router) {}
func (m *multiappProxy) SwitchFunc() func(chi.Router) {
return m.Switch
// mappedLoader is a map-backed loader for multi-application support.
type mappedLoader struct {
@ -141,6 +151,9 @@ type mappedLoader struct {
// NewMappedLoader returns a map-backed loader for multi-application support.
// Map-backed loaders can only map hostnames to handlers and cannot map base
// paths.
// The application loader interface is defined as an internal API interface (see
// internal/api/application.go for ApplicationLoader).
func NewMappedLoader(def http.Handler) *mappedLoader {
return &mappedLoader{
def: def,
@ -171,6 +184,20 @@ type radixLoader struct {
def http.Handler
// NewRadixLoader returns a loader backed by a radix trie providing longest
// matching prefix support. If you need to match both the hostname and the base
// path for routing incoming requests per-application, use this loader.
// Be aware that there are some limitations with assigning handlers via longest
// matching prefixes. In particular, the radix loader is NOT path aware, meaning
// that a request containing the hostname + base path assignment of
// "" will, by its nature, also match "" and
// any derivative thereafter.
// The intent behind this loader is to match the first hostname + base path
// segment of the incoming request, pass it along to the assigned application,
// and allow that application to determine whether the request should be handled
// or an error should be returned.
func NewRadixLoader(def http.Handler) *radixLoader {
return &radixLoader{
trie: NewProxyTrie(),
@ -196,6 +223,9 @@ func (r *radixLoader) AddApplicationHandler(uri string, handler http.Handler) er
func (r *radixLoader) Handler(uri url.URL) (http.Handler, bool) {
u := uri.String()[2:]
// We don't include the trailing slash here since attached handlers should
// not include it either.
if strings.HasSuffix(u, "/") {
u = u[:len(u)-1]