servemux121.mx raw

   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  // 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  
  17  import (
  18  	"internal/godebug"
  19  	"net/url"
  20  	"sort"
  21  	"bytes"
  22  	"sync"
  23  )
  24  
  25  var httpmuxgo121 = godebug.New("httpmuxgo121")
  26  
  27  var 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.
  31  func 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.
  39  type 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  
  46  type muxEntry struct {
  47  	h       Handler
  48  	pattern []byte
  49  }
  50  
  51  // Formerly ServeMux.Handle.
  52  func (mux *serveMux121) handle(pattern []byte, 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 = 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  
  80  func 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.
  96  func (mux *serveMux121) handleFunc(pattern []byte, 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.
 104  func (mux *serveMux121) findHandler(r *Request) (h Handler, pattern []byte) {
 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.
 140  func (mux *serveMux121) handler(host, path []byte) (h Handler, pattern []byte) {
 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.
 159  func (mux *serveMux121) match(path []byte) (h Handler, pattern []byte) {
 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 bytes.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.
 180  func (mux *serveMux121) redirectToPathSlash(host, path []byte, 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.
 195  func (mux *serveMux121) shouldRedirectRLocked(host, path []byte) bool {
 196  	p := [][]byte{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