k8s.io/apiserver@v0.31.1/pkg/server/mux/pathrecorder.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mux
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"runtime/debug"
    23  	"sort"
    24  	"strings"
    25  	"sync"
    26  	"sync/atomic"
    27  
    28  	"k8s.io/klog/v2"
    29  
    30  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  )
    33  
    34  // PathRecorderMux wraps a mux object and records the registered exposedPaths.
    35  type PathRecorderMux struct {
    36  	// name is used for logging so you can trace requests through
    37  	name string
    38  
    39  	lock            sync.Mutex
    40  	notFoundHandler http.Handler
    41  	pathToHandler   map[string]http.Handler
    42  	prefixToHandler map[string]http.Handler
    43  
    44  	// mux stores a pathHandler and is used to handle the actual serving.
    45  	// Turns out, we want to accept trailing slashes, BUT we don't care about handling
    46  	// everything under them.  This does exactly matches only unless its explicitly requested to
    47  	// do something different
    48  	mux atomic.Value
    49  
    50  	// exposedPaths is the list of paths that should be shown at /
    51  	exposedPaths []string
    52  
    53  	// pathStacks holds the stacks of all registered paths.  This allows us to show a more helpful message
    54  	// before the "http: multiple registrations for %s" panic.
    55  	pathStacks map[string]string
    56  }
    57  
    58  // pathHandler is an http.Handler that will satisfy requests first by exact match, then by prefix,
    59  // then by notFoundHandler
    60  type pathHandler struct {
    61  	// muxName is used for logging so you can trace requests through
    62  	muxName string
    63  
    64  	// pathToHandler is a map of exactly matching request to its handler
    65  	pathToHandler map[string]http.Handler
    66  
    67  	// this has to be sorted by most slashes then by length
    68  	prefixHandlers []prefixHandler
    69  
    70  	// notFoundHandler is the handler to use for satisfying requests with no other match
    71  	notFoundHandler http.Handler
    72  }
    73  
    74  // prefixHandler holds the prefix it should match and the handler to use
    75  type prefixHandler struct {
    76  	// prefix is the prefix to test for a request match
    77  	prefix string
    78  	// handler is used to satisfy matching requests
    79  	handler http.Handler
    80  }
    81  
    82  // NewPathRecorderMux creates a new PathRecorderMux
    83  func NewPathRecorderMux(name string) *PathRecorderMux {
    84  	ret := &PathRecorderMux{
    85  		name:            name,
    86  		pathToHandler:   map[string]http.Handler{},
    87  		prefixToHandler: map[string]http.Handler{},
    88  		mux:             atomic.Value{},
    89  		exposedPaths:    []string{},
    90  		pathStacks:      map[string]string{},
    91  	}
    92  
    93  	ret.mux.Store(&pathHandler{notFoundHandler: http.NotFoundHandler()})
    94  	return ret
    95  }
    96  
    97  // ListedPaths returns the registered handler exposedPaths.
    98  func (m *PathRecorderMux) ListedPaths() []string {
    99  	m.lock.Lock()
   100  	handledPaths := append([]string{}, m.exposedPaths...)
   101  	m.lock.Unlock()
   102  
   103  	sort.Strings(handledPaths)
   104  	return handledPaths
   105  }
   106  
   107  func (m *PathRecorderMux) trackCallers(path string) {
   108  	stack := string(debug.Stack())
   109  	if existingStack, ok := m.pathStacks[path]; ok {
   110  		utilruntime.HandleError(fmt.Errorf("duplicate path registration of %q: original registration from %v\n\nnew registration from %v", path, existingStack, stack))
   111  	}
   112  	m.pathStacks[path] = stack
   113  }
   114  
   115  // refreshMuxLocked creates a new mux and must be called while locked.  Otherwise the view of handlers may
   116  // not be consistent
   117  func (m *PathRecorderMux) refreshMuxLocked() {
   118  	newMux := &pathHandler{
   119  		muxName:         m.name,
   120  		pathToHandler:   map[string]http.Handler{},
   121  		prefixHandlers:  []prefixHandler{},
   122  		notFoundHandler: http.NotFoundHandler(),
   123  	}
   124  	if m.notFoundHandler != nil {
   125  		newMux.notFoundHandler = m.notFoundHandler
   126  	}
   127  	for path, handler := range m.pathToHandler {
   128  		newMux.pathToHandler[path] = handler
   129  	}
   130  
   131  	keys := sets.StringKeySet(m.prefixToHandler).List()
   132  	sort.Sort(sort.Reverse(byPrefixPriority(keys)))
   133  	for _, prefix := range keys {
   134  		newMux.prefixHandlers = append(newMux.prefixHandlers, prefixHandler{
   135  			prefix:  prefix,
   136  			handler: m.prefixToHandler[prefix],
   137  		})
   138  	}
   139  
   140  	m.mux.Store(newMux)
   141  }
   142  
   143  // NotFoundHandler sets the handler to use if there's no match for a give path
   144  func (m *PathRecorderMux) NotFoundHandler(notFoundHandler http.Handler) {
   145  	m.lock.Lock()
   146  	defer m.lock.Unlock()
   147  
   148  	m.notFoundHandler = notFoundHandler
   149  
   150  	m.refreshMuxLocked()
   151  }
   152  
   153  // Unregister removes a path from the mux.
   154  func (m *PathRecorderMux) Unregister(path string) {
   155  	m.lock.Lock()
   156  	defer m.lock.Unlock()
   157  
   158  	delete(m.pathToHandler, path)
   159  	delete(m.prefixToHandler, path)
   160  	delete(m.pathStacks, path)
   161  	for i := range m.exposedPaths {
   162  		if m.exposedPaths[i] == path {
   163  			m.exposedPaths = append(m.exposedPaths[:i], m.exposedPaths[i+1:]...)
   164  			break
   165  		}
   166  	}
   167  
   168  	m.refreshMuxLocked()
   169  }
   170  
   171  // Handle registers the handler for the given pattern.
   172  // If a handler already exists for pattern, Handle panics.
   173  func (m *PathRecorderMux) Handle(path string, handler http.Handler) {
   174  	m.lock.Lock()
   175  	defer m.lock.Unlock()
   176  	m.trackCallers(path)
   177  
   178  	m.exposedPaths = append(m.exposedPaths, path)
   179  	m.pathToHandler[path] = handler
   180  	m.refreshMuxLocked()
   181  }
   182  
   183  // HandleFunc registers the handler function for the given pattern.
   184  // If a handler already exists for pattern, Handle panics.
   185  func (m *PathRecorderMux) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
   186  	m.Handle(path, http.HandlerFunc(handler))
   187  }
   188  
   189  // UnlistedHandle registers the handler for the given pattern, but doesn't list it.
   190  // If a handler already exists for pattern, Handle panics.
   191  func (m *PathRecorderMux) UnlistedHandle(path string, handler http.Handler) {
   192  	m.lock.Lock()
   193  	defer m.lock.Unlock()
   194  	m.trackCallers(path)
   195  
   196  	m.pathToHandler[path] = handler
   197  	m.refreshMuxLocked()
   198  }
   199  
   200  // UnlistedHandleFunc registers the handler function for the given pattern, but doesn't list it.
   201  // If a handler already exists for pattern, Handle panics.
   202  func (m *PathRecorderMux) UnlistedHandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
   203  	m.UnlistedHandle(path, http.HandlerFunc(handler))
   204  }
   205  
   206  // HandlePrefix is like Handle, but matches for anything under the path.  Like a standard golang trailing slash.
   207  func (m *PathRecorderMux) HandlePrefix(path string, handler http.Handler) {
   208  	if !strings.HasSuffix(path, "/") {
   209  		panic(fmt.Sprintf("%q must end in a trailing slash", path))
   210  	}
   211  
   212  	m.lock.Lock()
   213  	defer m.lock.Unlock()
   214  	m.trackCallers(path)
   215  
   216  	m.exposedPaths = append(m.exposedPaths, path)
   217  	m.prefixToHandler[path] = handler
   218  	m.refreshMuxLocked()
   219  }
   220  
   221  // UnlistedHandlePrefix is like UnlistedHandle, but matches for anything under the path.  Like a standard golang trailing slash.
   222  func (m *PathRecorderMux) UnlistedHandlePrefix(path string, handler http.Handler) {
   223  	if !strings.HasSuffix(path, "/") {
   224  		panic(fmt.Sprintf("%q must end in a trailing slash", path))
   225  	}
   226  
   227  	m.lock.Lock()
   228  	defer m.lock.Unlock()
   229  	m.trackCallers(path)
   230  
   231  	m.prefixToHandler[path] = handler
   232  	m.refreshMuxLocked()
   233  }
   234  
   235  // ServeHTTP makes it an http.Handler
   236  func (m *PathRecorderMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   237  	m.mux.Load().(*pathHandler).ServeHTTP(w, r)
   238  }
   239  
   240  // ServeHTTP makes it an http.Handler
   241  func (h *pathHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   242  	if exactHandler, ok := h.pathToHandler[r.URL.Path]; ok {
   243  		klog.V(5).Infof("%v: %q satisfied by exact match", h.muxName, r.URL.Path)
   244  		exactHandler.ServeHTTP(w, r)
   245  		return
   246  	}
   247  
   248  	for _, prefixHandler := range h.prefixHandlers {
   249  		if strings.HasPrefix(r.URL.Path, prefixHandler.prefix) {
   250  			klog.V(5).Infof("%v: %q satisfied by prefix %v", h.muxName, r.URL.Path, prefixHandler.prefix)
   251  			prefixHandler.handler.ServeHTTP(w, r)
   252  			return
   253  		}
   254  	}
   255  
   256  	klog.V(5).Infof("%v: %q satisfied by NotFoundHandler", h.muxName, r.URL.Path)
   257  	h.notFoundHandler.ServeHTTP(w, r)
   258  }
   259  
   260  // byPrefixPriority sorts url prefixes by the order in which they should be tested by the mux
   261  // this has to be sorted by most slashes then by length so that we can iterate straight
   262  // through to match the "best" one first.
   263  type byPrefixPriority []string
   264  
   265  func (s byPrefixPriority) Len() int      { return len(s) }
   266  func (s byPrefixPriority) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   267  func (s byPrefixPriority) Less(i, j int) bool {
   268  	lhsNumParts := strings.Count(s[i], "/")
   269  	rhsNumParts := strings.Count(s[j], "/")
   270  	if lhsNumParts != rhsNumParts {
   271  		return lhsNumParts < rhsNumParts
   272  	}
   273  
   274  	lhsLen := len(s[i])
   275  	rhsLen := len(s[j])
   276  	if lhsLen != rhsLen {
   277  		return lhsLen < rhsLen
   278  	}
   279  
   280  	return strings.Compare(s[i], s[j]) < 0
   281  }