1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package http 6 7// This file implements ServeMux behavior as in Go 1.21. 8// The behavior is controlled by a GODEBUG setting. 9// Most of this code is derived from commit 08e35cc334. 10// Changes are minimal: aside from the different receiver type, 11// they mostly involve renaming functions, usually by unexporting them. 12 13// servemux121.go exists solely to provide a snapshot of 14// the pre-Go 1.22 ServeMux implementation for backwards compatibility. 15// Do not modify this file, it should remain frozen. 16 17import ( 18 "internal/godebug" 19 "net/url" 20 "sort" 21 "strings" 22 "sync" 23) 24 25var httpmuxgo121 = godebug.New("httpmuxgo121") 26 27var use121 bool 28 29// Read httpmuxgo121 once at startup, since dealing with changes to it during 30// program execution is too complex and error-prone. 31func init() { 32 if httpmuxgo121.Value() == "1" { 33 use121 = true 34 httpmuxgo121.IncNonDefault() 35 } 36} 37 38// serveMux121 holds the state of a ServeMux needed for Go 1.21 behavior. 39type serveMux121 struct { 40 mu sync.RWMutex 41 m map[string]muxEntry 42 es []muxEntry // slice of entries sorted from longest to shortest. 43 hosts bool // whether any patterns contain hostnames 44} 45 46type muxEntry struct { 47 h Handler 48 pattern string 49} 50 51// Formerly ServeMux.Handle. 52func (mux *serveMux121) handle(pattern string, handler Handler) { 53 mux.mu.Lock() 54 defer mux.mu.Unlock() 55 56 if pattern == "" { 57 panic("http: invalid pattern") 58 } 59 if handler == nil { 60 panic("http: nil handler") 61 } 62 if _, exist := mux.m[pattern]; exist { 63 panic("http: multiple registrations for " + pattern) 64 } 65 66 if mux.m == nil { 67 mux.m = make(map[string]muxEntry) 68 } 69 e := muxEntry{h: handler, pattern: pattern} 70 mux.m[pattern] = e 71 if pattern[len(pattern)-1] == '/' { 72 mux.es = appendSorted(mux.es, e) 73 } 74 75 if pattern[0] != '/' { 76 mux.hosts = true 77 } 78} 79 80func appendSorted(es []muxEntry, e muxEntry) []muxEntry { 81 n := len(es) 82 i := sort.Search(n, func(i int) bool { 83 return len(es[i].pattern) < len(e.pattern) 84 }) 85 if i == n { 86 return append(es, e) 87 } 88 // we now know that i points at where we want to insert 89 es = append(es, muxEntry{}) // try to grow the slice in place, any entry works. 90 copy(es[i+1:], es[i:]) // Move shorter entries down 91 es[i] = e 92 return es 93} 94 95// Formerly ServeMux.HandleFunc. 96func (mux *serveMux121) handleFunc(pattern string, handler func(ResponseWriter, *Request)) { 97 if handler == nil { 98 panic("http: nil handler") 99 } 100 mux.handle(pattern, HandlerFunc(handler)) 101} 102 103// Formerly ServeMux.Handler. 104func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern string) { 105 106 // CONNECT requests are not canonicalized. 107 if r.Method == "CONNECT" { 108 // If r.URL.Path is /tree and its handler is not registered, 109 // the /tree -> /tree/ redirect applies to CONNECT requests 110 // but the path canonicalization does not. 111 if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok { 112 return RedirectHandler(u.String(), StatusMovedPermanently), u.Path 113 } 114 115 return mux.handler(r.Host, r.URL.Path) 116 } 117 118 // All other requests have any port stripped and path cleaned 119 // before passing to mux.handler. 120 host := stripHostPort(r.Host) 121 path := cleanPath(r.URL.Path) 122 123 // If the given path is /tree and its handler is not registered, 124 // redirect for /tree/. 125 if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok { 126 return RedirectHandler(u.String(), StatusMovedPermanently), u.Path 127 } 128 129 if path != r.URL.Path { 130 _, pattern = mux.handler(host, path) 131 u := &url.URL{Path: path, RawQuery: r.URL.RawQuery} 132 return RedirectHandler(u.String(), StatusMovedPermanently), pattern 133 } 134 135 return mux.handler(host, r.URL.Path) 136} 137 138// handler is the main implementation of findHandler. 139// The path is known to be in canonical form, except for CONNECT methods. 140func (mux *serveMux121) handler(host, path string) (h Handler, pattern string) { 141 mux.mu.RLock() 142 defer mux.mu.RUnlock() 143 144 // Host-specific pattern takes precedence over generic ones 145 if mux.hosts { 146 h, pattern = mux.match(host + path) 147 } 148 if h == nil { 149 h, pattern = mux.match(path) 150 } 151 if h == nil { 152 h, pattern = NotFoundHandler(), "" 153 } 154 return 155} 156 157// Find a handler on a handler map given a path string. 158// Most-specific (longest) pattern wins. 159func (mux *serveMux121) match(path string) (h Handler, pattern string) { 160 // Check for exact match first. 161 v, ok := mux.m[path] 162 if ok { 163 return v.h, v.pattern 164 } 165 166 // Check for longest valid match. mux.es contains all patterns 167 // that end in / sorted from longest to shortest. 168 for _, e := range mux.es { 169 if strings.HasPrefix(path, e.pattern) { 170 return e.h, e.pattern 171 } 172 } 173 return nil, "" 174} 175 176// redirectToPathSlash determines if the given path needs appending "/" to it. 177// This occurs when a handler for path + "/" was already registered, but 178// not for path itself. If the path needs appending to, it creates a new 179// URL, setting the path to u.Path + "/" and returning true to indicate so. 180func (mux *serveMux121) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) { 181 mux.mu.RLock() 182 shouldRedirect := mux.shouldRedirectRLocked(host, path) 183 mux.mu.RUnlock() 184 if !shouldRedirect { 185 return u, false 186 } 187 path = path + "/" 188 u = &url.URL{Path: path, RawQuery: u.RawQuery} 189 return u, true 190} 191 192// shouldRedirectRLocked reports whether the given path and host should be redirected to 193// path+"/". This should happen if a handler is registered for path+"/" but 194// not path -- see comments at ServeMux. 195func (mux *serveMux121) shouldRedirectRLocked(host, path string) bool { 196 p := []string{path, host + path} 197 198 for _, c := range p { 199 if _, exist := mux.m[c]; exist { 200 return false 201 } 202 } 203 204 n := len(path) 205 if n == 0 { 206 return false 207 } 208 for _, c := range p { 209 if _, exist := mux.m[c+"/"]; exist { 210 return path[n-1] != '/' 211 } 212 } 213 214 return false 215} 216