"os"
"path"
"runtime"
+ "sort"
"strconv"
"strings"
"sync"
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
- hosts bool // whether any patterns contain hostnames
+ es []muxEntry // slice of entries sorted from longest to shortest.
+ hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
var defaultServeMux ServeMux
-// Does path match pattern?
-func pathMatch(pattern, path string) bool {
- if len(pattern) == 0 {
- // should not happen
- return false
- }
- n := len(pattern)
- if pattern[n-1] != '/' {
- return pattern == path
- }
- return len(path) >= n && path[0:n] == pattern
-}
-
// cleanPath returns the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
if p == "" {
return v.h, v.pattern
}
- // Check for longest valid match.
- var n = 0
- for k, v := range mux.m {
- if !pathMatch(k, path) {
- continue
- }
- if h == nil || len(k) > n {
- n = len(k)
- h = v.h
- pattern = 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
+ return nil, ""
}
// redirectToPathSlash determines if the given path needs appending "/" to it.
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
- mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
+ 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
+}
+
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
--- /dev/null
+// Copyright 2018 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.
+
+// Server unit tests
+
+package http
+
+import (
+ "fmt"
+ "testing"
+)
+
+func BenchmarkServerMatch(b *testing.B) {
+ fn := func(w ResponseWriter, r *Request) {
+ fmt.Fprintf(w, "OK")
+ }
+ mux := NewServeMux()
+ mux.HandleFunc("/", fn)
+ mux.HandleFunc("/index", fn)
+ mux.HandleFunc("/home", fn)
+ mux.HandleFunc("/about", fn)
+ mux.HandleFunc("/contact", fn)
+ mux.HandleFunc("/robots.txt", fn)
+ mux.HandleFunc("/products/", fn)
+ mux.HandleFunc("/products/1", fn)
+ mux.HandleFunc("/products/2", fn)
+ mux.HandleFunc("/products/3", fn)
+ mux.HandleFunc("/products/3/image.jpg", fn)
+ mux.HandleFunc("/admin", fn)
+ mux.HandleFunc("/admin/products/", fn)
+ mux.HandleFunc("/admin/products/create", fn)
+ mux.HandleFunc("/admin/products/update", fn)
+ mux.HandleFunc("/admin/products/delete", fn)
+
+ paths := []string{"/", "/notfound", "/admin/", "/admin/foo", "/contact", "/products",
+ "/products/", "/products/3/image.jpg"}
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if h, p := mux.match(paths[i%len(paths)]); h != nil && p == "" {
+ b.Error("impossible")
+ }
+ }
+ b.StopTimer()
+}