// 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" "net/url" "regexp" "testing" "time" ) func TestServerTLSHandshakeTimeout(t *testing.T) { tests := []struct { s *Server want time.Duration }{ { s: &Server{}, want: 0, }, { s: &Server{ ReadTimeout: -1, }, want: 0, }, { s: &Server{ ReadTimeout: 5 * time.Second, }, want: 5 * time.Second, }, { s: &Server{ ReadTimeout: 5 * time.Second, WriteTimeout: -1, }, want: 5 * time.Second, }, { s: &Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 4 * time.Second, }, want: 4 * time.Second, }, { s: &Server{ ReadTimeout: 5 * time.Second, ReadHeaderTimeout: 2 * time.Second, WriteTimeout: 4 * time.Second, }, want: 2 * time.Second, }, } for i, tt := range tests { got := tt.s.tlsHandshakeTimeout() if got != tt.want { t.Errorf("%d. got %v; want %v", i, got, tt.want) } } } type handler struct{ i int } func (handler) ServeHTTP(ResponseWriter, *Request) {} func TestFindHandler(t *testing.T) { mux := NewServeMux() for _, ph := range []struct { pat string h Handler }{ {"/", &handler{1}}, {"/foo/", &handler{2}}, {"/foo", &handler{3}}, {"/bar/", &handler{4}}, {"//foo", &handler{5}}, } { mux.Handle(ph.pat, ph.h) } for _, test := range []struct { method string path string wantHandler string }{ {"GET", "/", "&http.handler{i:1}"}, {"GET", "//", `&http.redirectHandler{url:"/", code:301}`}, {"GET", "/foo/../bar/./..//baz", `&http.redirectHandler{url:"/baz", code:301}`}, {"GET", "/foo", "&http.handler{i:3}"}, {"GET", "/foo/x", "&http.handler{i:2}"}, {"GET", "/bar/x", "&http.handler{i:4}"}, {"GET", "/bar", `&http.redirectHandler{url:"/bar/", code:301}`}, {"CONNECT", "/", "&http.handler{i:1}"}, {"CONNECT", "//", "&http.handler{i:1}"}, {"CONNECT", "//foo", "&http.handler{i:5}"}, {"CONNECT", "/foo/../bar/./..//baz", "&http.handler{i:2}"}, {"CONNECT", "/foo", "&http.handler{i:3}"}, {"CONNECT", "/foo/x", "&http.handler{i:2}"}, {"CONNECT", "/bar/x", "&http.handler{i:4}"}, {"CONNECT", "/bar", `&http.redirectHandler{url:"/bar/", code:301}`}, } { var r Request r.Method = test.method r.Host = "example.com" r.URL = &url.URL{Path: test.path} gotH, _, _, _ := mux.findHandler(&r) got := fmt.Sprintf("%#v", gotH) if got != test.wantHandler { t.Errorf("%s %q: got %q, want %q", test.method, test.path, got, test.wantHandler) } } } func TestEmptyServeMux(t *testing.T) { // Verify that a ServeMux with nothing registered // doesn't panic. mux := NewServeMux() var r Request r.Method = "GET" r.Host = "example.com" r.URL = &url.URL{Path: "/"} _, p := mux.Handler(&r) if p != "" { t.Errorf(`got %q, want ""`, p) } } func TestRegisterErr(t *testing.T) { mux := NewServeMux() h := &handler{} mux.Handle("/a", h) for _, test := range []struct { pattern string handler Handler wantRegexp string }{ {"", h, "invalid pattern"}, {"/", nil, "nil handler"}, {"/", HandlerFunc(nil), "nil handler"}, {"/{x", h, `parsing "/\{x": at offset 1: bad wildcard segment`}, {"/a", h, `conflicts with pattern.* \(registered at .*/server_test.go:\d+`}, } { t.Run(fmt.Sprintf("%s:%#v", test.pattern, test.handler), func(t *testing.T) { err := mux.registerErr(test.pattern, test.handler) if err == nil { t.Fatal("got nil error") } re := regexp.MustCompile(test.wantRegexp) if g := err.Error(); !re.MatchString(g) { t.Errorf("\ngot %q\nwant string matching %q", g, test.wantRegexp) } }) } } func TestExactMatch(t *testing.T) { for _, test := range []struct { pattern string path string want bool }{ {"", "/a", false}, {"/", "/a", false}, {"/a", "/a", true}, {"/a/{x...}", "/a/b", false}, {"/a/{x}", "/a/b", true}, {"/a/b/", "/a/b/", true}, {"/a/b/{$}", "/a/b/", true}, {"/a/", "/a/b/", false}, } { var n *routingNode if test.pattern != "" { pat := mustParsePattern(t, test.pattern) n = &routingNode{pattern: pat} } got := exactMatch(n, test.path) if got != test.want { t.Errorf("%q, %s: got %t, want %t", test.pattern, test.path, got, test.want) } } } func TestEscapedPathsAndPatterns(t *testing.T) { matches := []struct { pattern string paths []string // paths that match the pattern paths121 []string // paths that matched the pattern in Go 1.21. }{ { "/a", // this pattern matches a path that unescapes to "/a" []string{"/a", "/%61"}, []string{"/a", "/%61"}, }, { "/%62", // patterns are unescaped by segment; matches paths that unescape to "/b" []string{"/b", "/%62"}, []string{"/%2562"}, // In 1.21, patterns were not unescaped but paths were. }, { "/%7B/%7D", // the only way to write a pattern that matches '{' or '}' []string{"/{/}", "/%7b/}", "/{/%7d", "/%7B/%7D"}, []string{"/%257B/%257D"}, // In 1.21, patterns were not unescaped. }, { "/%x", // patterns that do not unescape are left unchanged []string{"/%25x"}, []string{"/%25x"}, }, } run := func(t *testing.T, test121 bool) { defer func(u bool) { use121 = u }(use121) use121 = test121 mux := NewServeMux() for _, m := range matches { mux.HandleFunc(m.pattern, func(w ResponseWriter, r *Request) {}) } for _, m := range matches { paths := m.paths if use121 { paths = m.paths121 } for _, p := range paths { u, err := url.ParseRequestURI(p) if err != nil { t.Fatal(err) } req := &Request{ URL: u, } _, gotPattern := mux.Handler(req) if g, w := gotPattern, m.pattern; g != w { t.Errorf("%s: pattern: got %q, want %q", p, g, w) } } } } t.Run("latest", func(t *testing.T) { run(t, false) }) t.Run("1.21", func(t *testing.T) { run(t, true) }) } 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++ { r, err := NewRequest("GET", "http://example.com/"+paths[i%len(paths)], nil) if err != nil { b.Fatal(err) } if h, p, _, _ := mux.findHandler(r); h != nil && p == "" { b.Error("impossible") } } b.StopTimer() }