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.
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.
21 var httpmuxgo121 = godebug.New("httpmuxgo121")
25 // Read httpmuxgo121 once at startup, since dealing with changes to it during
26 // program execution is too complex and error-prone.
28 if httpmuxgo121.Value() == "1" {
30 httpmuxgo121.IncNonDefault()
34 // serveMux121 holds the state of a ServeMux needed for Go 1.21 behavior.
35 type serveMux121 struct {
38 es []muxEntry // slice of entries sorted from longest to shortest.
39 hosts bool // whether any patterns contain hostnames
42 type muxEntry struct {
47 // Formerly ServeMux.Handle.
48 func (mux *serveMux121) handle(pattern string, handler Handler) {
53 panic("http: invalid pattern")
56 panic("http: nil handler")
58 if _, exist := mux.m[pattern]; exist {
59 panic("http: multiple registrations for " + pattern)
63 mux.m = make(map[string]muxEntry)
65 e := muxEntry{h: handler, pattern: pattern}
67 if pattern[len(pattern)-1] == '/' {
68 mux.es = appendSorted(mux.es, e)
71 if pattern[0] != '/' {
76 func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
78 i := sort.Search(n, func(i int) bool {
79 return len(es[i].pattern) < len(e.pattern)
84 // we now know that i points at where we want to insert
85 es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
86 copy(es[i+1:], es[i:]) // Move shorter entries down
91 // Formerly ServeMux.HandleFunc.
92 func (mux *serveMux121) handleFunc(pattern string, handler func(ResponseWriter, *Request)) {
94 panic("http: nil handler")
96 mux.handle(pattern, HandlerFunc(handler))
99 // Formerly ServeMux.Handler.
100 func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern string) {
102 // CONNECT requests are not canonicalized.
103 if r.Method == "CONNECT" {
104 // If r.URL.Path is /tree and its handler is not registered,
105 // the /tree -> /tree/ redirect applies to CONNECT requests
106 // but the path canonicalization does not.
107 if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
108 return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
111 return mux.handler(r.Host, r.URL.Path)
114 // All other requests have any port stripped and path cleaned
115 // before passing to mux.handler.
116 host := stripHostPort(r.Host)
117 path := cleanPath(r.URL.Path)
119 // If the given path is /tree and its handler is not registered,
120 // redirect for /tree/.
121 if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
122 return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
125 if path != r.URL.Path {
126 _, pattern = mux.handler(host, path)
127 u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
128 return RedirectHandler(u.String(), StatusMovedPermanently), pattern
131 return mux.handler(host, r.URL.Path)
134 // handler is the main implementation of findHandler.
135 // The path is known to be in canonical form, except for CONNECT methods.
136 func (mux *serveMux121) handler(host, path string) (h Handler, pattern string) {
138 defer mux.mu.RUnlock()
140 // Host-specific pattern takes precedence over generic ones
142 h, pattern = mux.match(host + path)
145 h, pattern = mux.match(path)
148 h, pattern = NotFoundHandler(), ""
153 // Find a handler on a handler map given a path string.
154 // Most-specific (longest) pattern wins.
155 func (mux *serveMux121) match(path string) (h Handler, pattern string) {
156 // Check for exact match first.
159 return v.h, v.pattern
162 // Check for longest valid match. mux.es contains all patterns
163 // that end in / sorted from longest to shortest.
164 for _, e := range mux.es {
165 if strings.HasPrefix(path, e.pattern) {
166 return e.h, e.pattern
172 // redirectToPathSlash determines if the given path needs appending "/" to it.
173 // This occurs when a handler for path + "/" was already registered, but
174 // not for path itself. If the path needs appending to, it creates a new
175 // URL, setting the path to u.Path + "/" and returning true to indicate so.
176 func (mux *serveMux121) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
178 shouldRedirect := mux.shouldRedirectRLocked(host, path)
184 u = &url.URL{Path: path, RawQuery: u.RawQuery}
188 // shouldRedirectRLocked reports whether the given path and host should be redirected to
189 // path+"/". This should happen if a handler is registered for path+"/" but
190 // not path -- see comments at ServeMux.
191 func (mux *serveMux121) shouldRedirectRLocked(host, path string) bool {
192 p := []string{path, host + path}
194 for _, c := range p {
195 if _, exist := mux.m[c]; exist {
204 for _, c := range p {
205 if _, exist := mux.m[c+"/"]; exist {
206 return path[n-1] != '/'