// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http // This file implements ServeMux behavior as in Go 1.21. // The behavior is controlled by a GODEBUG setting. // Most of this code is derived from commit 08e35cc334. // Changes are minimal: aside from the different receiver type, // they mostly involve renaming functions, usually by unexporting them. import ( "internal/godebug" "net/url" "sort" "strings" "sync" ) var httpmuxgo121 = godebug.New("httpmuxgo121") var use121 bool // Read httpmuxgo121 once at startup, since dealing with changes to it during // program execution is too complex and error-prone. func init() { if httpmuxgo121.Value() == "1" { use121 = true httpmuxgo121.IncNonDefault() } } // serveMux121 holds the state of a ServeMux needed for Go 1.21 behavior. type serveMux121 struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } type muxEntry struct { h Handler pattern string } // Formerly ServeMux.Handle. func (mux *serveMux121) handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern") } if handler == nil { panic("http: nil handler") } if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) } if pattern[0] != '/' { mux.hosts = true } } func appendSorted(es []muxEntry, e muxEntry) []muxEntry { n := len(es) i := sort.Search(n, func(i int) bool { return len(es[i].pattern) < len(e.pattern) }) if i == n { return append(es, e) } // we now know that i points at where we want to insert es = append(es, muxEntry{}) // try to grow the slice in place, any entry works. copy(es[i+1:], es[i:]) // Move shorter entries down es[i] = e return es } // Formerly ServeMux.HandleFunc. func (mux *serveMux121) handleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.handle(pattern, HandlerFunc(handler)) } // Formerly ServeMux.Handler. func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern string) { // CONNECT requests are not canonicalized. if r.Method == "CONNECT" { // If r.URL.Path is /tree and its handler is not registered, // the /tree -> /tree/ redirect applies to CONNECT requests // but the path canonicalization does not. if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok { return RedirectHandler(u.String(), StatusMovedPermanently), u.Path } return mux.handler(r.Host, r.URL.Path) } // All other requests have any port stripped and path cleaned // before passing to mux.handler. host := stripHostPort(r.Host) path := cleanPath(r.URL.Path) // If the given path is /tree and its handler is not registered, // redirect for /tree/. if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok { return RedirectHandler(u.String(), StatusMovedPermanently), u.Path } if path != r.URL.Path { _, pattern = mux.handler(host, path) u := &url.URL{Path: path, RawQuery: r.URL.RawQuery} return RedirectHandler(u.String(), StatusMovedPermanently), pattern } return mux.handler(host, r.URL.Path) } // handler is the main implementation of findHandler. // The path is known to be in canonical form, except for CONNECT methods. func (mux *serveMux121) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return } // Find a handler on a handler map given a path string. // Most-specific (longest) pattern wins. func (mux *serveMux121) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" } // redirectToPathSlash determines if the given path needs appending "/" to it. // This occurs when a handler for path + "/" was already registered, but // not for path itself. If the path needs appending to, it creates a new // URL, setting the path to u.Path + "/" and returning true to indicate so. func (mux *serveMux121) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) { mux.mu.RLock() shouldRedirect := mux.shouldRedirectRLocked(host, path) mux.mu.RUnlock() if !shouldRedirect { return u, false } path = path + "/" u = &url.URL{Path: path, RawQuery: u.RawQuery} return u, true } // shouldRedirectRLocked reports whether the given path and host should be redirected to // path+"/". This should happen if a handler is registered for path+"/" but // not path -- see comments at ServeMux. func (mux *serveMux121) shouldRedirectRLocked(host, path string) bool { p := []string{path, host + path} for _, c := range p { if _, exist := mux.m[c]; exist { return false } } n := len(path) if n == 0 { return false } for _, c := range p { if _, exist := mux.m[c+"/"]; exist { return path[n-1] != '/' } } return false }