Source file 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  }
   212  

View as plain text