blob: e81e3bb6b00f9b9c80bdbba40786351bba02c925 [file] [log] [blame]
// 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()
}