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  // 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  	"strings"
    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 string
    49  }
    50  
    51  // Formerly ServeMux.Handle.
    52  func (mux *serveMux121) handle(pattern string, 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 = make(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 string, 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 string) {
   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 string) (h Handler, pattern string) {
   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 string) (h Handler, pattern string) {
   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 strings.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 string, 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 string) bool {
   196  	p := []string{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  

View as plain text