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 }