]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/http/servemux121.go
net/http: add GODEBUG setting for old ServeMux behavior
[gostls13.git] / src / net / http / servemux121.go
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
5 package 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 import (
14         "internal/godebug"
15         "net/url"
16         "sort"
17         "strings"
18         "sync"
19 )
20
21 var httpmuxgo121 = godebug.New("httpmuxgo121")
22
23 var use121 bool
24
25 // Read httpmuxgo121 once at startup, since dealing with changes to it during
26 // program execution is too complex and error-prone.
27 func init() {
28         if httpmuxgo121.Value() == "1" {
29                 use121 = true
30                 httpmuxgo121.IncNonDefault()
31         }
32 }
33
34 // serveMux121 holds the state of a ServeMux needed for Go 1.21 behavior.
35 type serveMux121 struct {
36         mu    sync.RWMutex
37         m     map[string]muxEntry
38         es    []muxEntry // slice of entries sorted from longest to shortest.
39         hosts bool       // whether any patterns contain hostnames
40 }
41
42 type muxEntry struct {
43         h       Handler
44         pattern string
45 }
46
47 // Formerly ServeMux.Handle.
48 func (mux *serveMux121) handle(pattern string, handler Handler) {
49         mux.mu.Lock()
50         defer mux.mu.Unlock()
51
52         if pattern == "" {
53                 panic("http: invalid pattern")
54         }
55         if handler == nil {
56                 panic("http: nil handler")
57         }
58         if _, exist := mux.m[pattern]; exist {
59                 panic("http: multiple registrations for " + pattern)
60         }
61
62         if mux.m == nil {
63                 mux.m = make(map[string]muxEntry)
64         }
65         e := muxEntry{h: handler, pattern: pattern}
66         mux.m[pattern] = e
67         if pattern[len(pattern)-1] == '/' {
68                 mux.es = appendSorted(mux.es, e)
69         }
70
71         if pattern[0] != '/' {
72                 mux.hosts = true
73         }
74 }
75
76 func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
77         n := len(es)
78         i := sort.Search(n, func(i int) bool {
79                 return len(es[i].pattern) < len(e.pattern)
80         })
81         if i == n {
82                 return append(es, e)
83         }
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
87         es[i] = e
88         return es
89 }
90
91 // Formerly ServeMux.HandleFunc.
92 func (mux *serveMux121) handleFunc(pattern string, handler func(ResponseWriter, *Request)) {
93         if handler == nil {
94                 panic("http: nil handler")
95         }
96         mux.handle(pattern, HandlerFunc(handler))
97 }
98
99 // Formerly ServeMux.Handler.
100 func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern string) {
101
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
109                 }
110
111                 return mux.handler(r.Host, r.URL.Path)
112         }
113
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)
118
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
123         }
124
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
129         }
130
131         return mux.handler(host, r.URL.Path)
132 }
133
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) {
137         mux.mu.RLock()
138         defer mux.mu.RUnlock()
139
140         // Host-specific pattern takes precedence over generic ones
141         if mux.hosts {
142                 h, pattern = mux.match(host + path)
143         }
144         if h == nil {
145                 h, pattern = mux.match(path)
146         }
147         if h == nil {
148                 h, pattern = NotFoundHandler(), ""
149         }
150         return
151 }
152
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.
157         v, ok := mux.m[path]
158         if ok {
159                 return v.h, v.pattern
160         }
161
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
167                 }
168         }
169         return nil, ""
170 }
171
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) {
177         mux.mu.RLock()
178         shouldRedirect := mux.shouldRedirectRLocked(host, path)
179         mux.mu.RUnlock()
180         if !shouldRedirect {
181                 return u, false
182         }
183         path = path + "/"
184         u = &url.URL{Path: path, RawQuery: u.RawQuery}
185         return u, true
186 }
187
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}
193
194         for _, c := range p {
195                 if _, exist := mux.m[c]; exist {
196                         return false
197                 }
198         }
199
200         n := len(path)
201         if n == 0 {
202                 return false
203         }
204         for _, c := range p {
205                 if _, exist := mux.m[c+"/"]; exist {
206                         return path[n-1] != '/'
207                 }
208         }
209
210         return false
211 }