1 Star 0 Fork 0

missyoyo/httprouter

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
router_test.go 18.54 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package httprouter
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
type mockResponseWriter struct{}
func (m *mockResponseWriter) Header() (h http.Header) {
return http.Header{}
}
func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
return len(s), nil
}
func (m *mockResponseWriter) WriteHeader(int) {}
func TestParams(t *testing.T) {
ps := Params{
Param{"param1", "value1"},
Param{"param2", "value2"},
Param{"param3", "value3"},
}
for i := range ps {
if val := ps.ByName(ps[i].Key); val != ps[i].Value {
t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
}
}
if val := ps.ByName("noKey"); val != "" {
t.Errorf("Expected empty string for not found key; got: %s", val)
}
}
func TestRouter(t *testing.T) {
router := New()
routed := false
router.Handle(http.MethodGet, "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
routed = true
want := Params{Param{"name", "gopher"}}
if !reflect.DeepEqual(ps, want) {
t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
}
})
w := new(mockResponseWriter)
req, _ := http.NewRequest(http.MethodGet, "/user/gopher", nil)
router.ServeHTTP(w, req)
if !routed {
t.Fatal("routing failed")
}
}
type handlerStruct struct {
handled *bool
}
func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
*h.handled = true
}
func TestRouterAPI(t *testing.T) {
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
httpHandler := handlerStruct{&handler}
router := New()
router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
get = true
})
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
head = true
})
router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
options = true
})
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
post = true
})
router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
put = true
})
router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
patch = true
})
router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
delete = true
})
router.Handler(http.MethodGet, "/Handler", httpHandler)
router.HandlerFunc(http.MethodGet, "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
handlerFunc = true
})
w := new(mockResponseWriter)
r, _ := http.NewRequest(http.MethodGet, "/GET", nil)
router.ServeHTTP(w, r)
if !get {
t.Error("routing GET failed")
}
r, _ = http.NewRequest(http.MethodHead, "/GET", nil)
router.ServeHTTP(w, r)
if !head {
t.Error("routing HEAD failed")
}
r, _ = http.NewRequest(http.MethodOptions, "/GET", nil)
router.ServeHTTP(w, r)
if !options {
t.Error("routing OPTIONS failed")
}
r, _ = http.NewRequest(http.MethodPost, "/POST", nil)
router.ServeHTTP(w, r)
if !post {
t.Error("routing POST failed")
}
r, _ = http.NewRequest(http.MethodPut, "/PUT", nil)
router.ServeHTTP(w, r)
if !put {
t.Error("routing PUT failed")
}
r, _ = http.NewRequest(http.MethodPatch, "/PATCH", nil)
router.ServeHTTP(w, r)
if !patch {
t.Error("routing PATCH failed")
}
r, _ = http.NewRequest(http.MethodDelete, "/DELETE", nil)
router.ServeHTTP(w, r)
if !delete {
t.Error("routing DELETE failed")
}
r, _ = http.NewRequest(http.MethodGet, "/Handler", nil)
router.ServeHTTP(w, r)
if !handler {
t.Error("routing Handler failed")
}
r, _ = http.NewRequest(http.MethodGet, "/HandlerFunc", nil)
router.ServeHTTP(w, r)
if !handlerFunc {
t.Error("routing HandlerFunc failed")
}
}
func TestRouterInvalidInput(t *testing.T) {
router := New()
handle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
recv := catchPanic(func() {
router.Handle("", "/", handle)
})
if recv == nil {
t.Fatal("registering empty method did not panic")
}
recv = catchPanic(func() {
router.GET("", handle)
})
if recv == nil {
t.Fatal("registering empty path did not panic")
}
recv = catchPanic(func() {
router.GET("noSlashRoot", handle)
})
if recv == nil {
t.Fatal("registering path not beginning with '/' did not panic")
}
recv = catchPanic(func() {
router.GET("/", nil)
})
if recv == nil {
t.Fatal("registering nil handler did not panic")
}
}
func TestRouterChaining(t *testing.T) {
router1 := New()
router2 := New()
router1.NotFound = router2
fooHit := false
router1.POST("/foo", func(w http.ResponseWriter, req *http.Request, _ Params) {
fooHit = true
w.WriteHeader(http.StatusOK)
})
barHit := false
router2.POST("/bar", func(w http.ResponseWriter, req *http.Request, _ Params) {
barHit = true
w.WriteHeader(http.StatusOK)
})
r, _ := http.NewRequest(http.MethodPost, "/foo", nil)
w := httptest.NewRecorder()
router1.ServeHTTP(w, r)
if !(w.Code == http.StatusOK && fooHit) {
t.Errorf("Regular routing failed with router chaining.")
t.FailNow()
}
r, _ = http.NewRequest(http.MethodPost, "/bar", nil)
w = httptest.NewRecorder()
router1.ServeHTTP(w, r)
if !(w.Code == http.StatusOK && barHit) {
t.Errorf("Chained routing failed with router chaining.")
t.FailNow()
}
r, _ = http.NewRequest(http.MethodPost, "/qax", nil)
w = httptest.NewRecorder()
router1.ServeHTTP(w, r)
if !(w.Code == http.StatusNotFound) {
t.Errorf("NotFound behavior failed with router chaining.")
t.FailNow()
}
}
func BenchmarkAllowed(b *testing.B) {
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
router := New()
router.POST("/path", handlerFunc)
router.GET("/path", handlerFunc)
b.Run("Global", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = router.allowed("*", http.MethodOptions)
}
})
b.Run("Path", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = router.allowed("/path", http.MethodOptions)
}
})
}
func TestRouterOPTIONS(t *testing.T) {
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
router := New()
router.POST("/path", handlerFunc)
// test not allowed
// * (server)
r, _ := http.NewRequest(http.MethodOptions, "*", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
// path
r, _ = http.NewRequest(http.MethodOptions, "/path", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
r, _ = http.NewRequest(http.MethodOptions, "/doesnotexist", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNotFound) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
}
// add another method
router.GET("/path", handlerFunc)
// set a global OPTIONS handler
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Adjust status code to 204
w.WriteHeader(http.StatusNoContent)
})
// test again
// * (server)
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
// path
r, _ = http.NewRequest(http.MethodOptions, "/path", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
// custom handler
var custom bool
router.OPTIONS("/path", func(w http.ResponseWriter, r *http.Request, _ Params) {
custom = true
})
// test again
// * (server)
r, _ = http.NewRequest(http.MethodOptions, "*", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNoContent) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
if custom {
t.Error("custom handler called on *")
}
// path
r, _ = http.NewRequest(http.MethodOptions, "/path", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusOK) {
t.Errorf("OPTIONS handling failed: Code=%d, Header=%v", w.Code, w.Header())
}
if !custom {
t.Error("custom handler not called")
}
}
func TestRouterNotAllowed(t *testing.T) {
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
router := New()
router.POST("/path", handlerFunc)
// test not allowed
r, _ := http.NewRequest(http.MethodGet, "/path", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusMethodNotAllowed) {
t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
// add another method
router.DELETE("/path", handlerFunc)
router.OPTIONS("/path", handlerFunc) // must be ignored
// test again
r, _ = http.NewRequest(http.MethodGet, "/path", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusMethodNotAllowed) {
t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
} else if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
// test custom handler
w = httptest.NewRecorder()
responseText := "custom method"
router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusTeapot)
w.Write([]byte(responseText))
})
router.ServeHTTP(w, r)
if got := w.Body.String(); !(got == responseText) {
t.Errorf("unexpected response got %q want %q", got, responseText)
}
if w.Code != http.StatusTeapot {
t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
}
if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
}
func TestRouterNotFound(t *testing.T) {
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
router := New()
router.GET("/path", handlerFunc)
router.GET("/dir/", handlerFunc)
router.GET("/", handlerFunc)
testRoutes := []struct {
route string
code int
location string
}{
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
{"", http.StatusMovedPermanently, "/"}, // TSR +/
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
{"/nope", http.StatusNotFound, ""}, // NotFound
}
for _, tr := range testRoutes {
r, _ := http.NewRequest(http.MethodGet, tr.route, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == tr.code && (w.Code == http.StatusNotFound || fmt.Sprint(w.Header().Get("Location")) == tr.location)) {
t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header().Get("Location"))
}
}
// Test custom not found handler
var notFound bool
router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusNotFound)
notFound = true
})
r, _ := http.NewRequest(http.MethodGet, "/nope", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNotFound && notFound == true) {
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
}
// Test other method than GET (want 308 instead of 301)
router.PATCH("/path", handlerFunc)
r, _ = http.NewRequest(http.MethodPatch, "/path/", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusPermanentRedirect && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
}
// Test special case where no node for the prefix "/" exists
router = New()
router.GET("/a", handlerFunc)
r, _ = http.NewRequest(http.MethodGet, "/", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, r)
if !(w.Code == http.StatusNotFound) {
t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
}
}
func TestRouterPanicHandler(t *testing.T) {
router := New()
panicHandled := false
router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
panicHandled = true
}
router.Handle(http.MethodPut, "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
panic("oops!")
})
w := new(mockResponseWriter)
req, _ := http.NewRequest(http.MethodPut, "/user/gopher", nil)
defer func() {
if rcv := recover(); rcv != nil {
t.Fatal("handling panic failed")
}
}()
router.ServeHTTP(w, req)
if !panicHandled {
t.Fatal("simulating failed")
}
}
func TestRouterLookup(t *testing.T) {
routed := false
wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
routed = true
}
wantParams := Params{Param{"name", "gopher"}}
router := New()
// try empty router first
handle, _, tsr := router.Lookup(http.MethodGet, "/nope")
if handle != nil {
t.Fatalf("Got handle for unregistered pattern: %v", handle)
}
if tsr {
t.Error("Got wrong TSR recommendation!")
}
// insert route and try again
router.GET("/user/:name", wantHandle)
handle, params, _ := router.Lookup(http.MethodGet, "/user/gopher")
if handle == nil {
t.Fatal("Got no handle!")
} else {
handle(nil, nil, nil)
if !routed {
t.Fatal("Routing failed!")
}
}
if !reflect.DeepEqual(params, wantParams) {
t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
}
routed = false
// route without param
router.GET("/user", wantHandle)
handle, params, _ = router.Lookup(http.MethodGet, "/user")
if handle == nil {
t.Fatal("Got no handle!")
} else {
handle(nil, nil, nil)
if !routed {
t.Fatal("Routing failed!")
}
}
if params != nil {
t.Fatalf("Wrong parameter values: want %v, got %v", nil, params)
}
handle, _, tsr = router.Lookup(http.MethodGet, "/user/gopher/")
if handle != nil {
t.Fatalf("Got handle for unregistered pattern: %v", handle)
}
if !tsr {
t.Error("Got no TSR recommendation!")
}
handle, _, tsr = router.Lookup(http.MethodGet, "/nope")
if handle != nil {
t.Fatalf("Got handle for unregistered pattern: %v", handle)
}
if tsr {
t.Error("Got wrong TSR recommendation!")
}
}
func TestRouterParamsFromContext(t *testing.T) {
routed := false
wantParams := Params{Param{"name", "gopher"}}
handlerFunc := func(_ http.ResponseWriter, req *http.Request) {
// get params from request context
params := ParamsFromContext(req.Context())
if !reflect.DeepEqual(params, wantParams) {
t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
}
routed = true
}
var nilParams Params
handlerFuncNil := func(_ http.ResponseWriter, req *http.Request) {
// get params from request context
params := ParamsFromContext(req.Context())
if !reflect.DeepEqual(params, nilParams) {
t.Fatalf("Wrong parameter values: want %v, got %v", nilParams, params)
}
routed = true
}
router := New()
router.HandlerFunc(http.MethodGet, "/user", handlerFuncNil)
router.HandlerFunc(http.MethodGet, "/user/:name", handlerFunc)
w := new(mockResponseWriter)
r, _ := http.NewRequest(http.MethodGet, "/user/gopher", nil)
router.ServeHTTP(w, r)
if !routed {
t.Fatal("Routing failed!")
}
routed = false
r, _ = http.NewRequest(http.MethodGet, "/user", nil)
router.ServeHTTP(w, r)
if !routed {
t.Fatal("Routing failed!")
}
}
func TestRouterMatchedRoutePath(t *testing.T) {
route1 := "/user/:name"
routed1 := false
handle1 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
route := ps.MatchedRoutePath()
if route != route1 {
t.Fatalf("Wrong matched route: want %s, got %s", route1, route)
}
routed1 = true
}
route2 := "/user/:name/details"
routed2 := false
handle2 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
route := ps.MatchedRoutePath()
if route != route2 {
t.Fatalf("Wrong matched route: want %s, got %s", route2, route)
}
routed2 = true
}
route3 := "/"
routed3 := false
handle3 := func(_ http.ResponseWriter, req *http.Request, ps Params) {
route := ps.MatchedRoutePath()
if route != route3 {
t.Fatalf("Wrong matched route: want %s, got %s", route3, route)
}
routed3 = true
}
router := New()
router.SaveMatchedRoutePath = true
router.Handle(http.MethodGet, route1, handle1)
router.Handle(http.MethodGet, route2, handle2)
router.Handle(http.MethodGet, route3, handle3)
w := new(mockResponseWriter)
r, _ := http.NewRequest(http.MethodGet, "/user/gopher", nil)
router.ServeHTTP(w, r)
if !routed1 || routed2 || routed3 {
t.Fatal("Routing failed!")
}
w = new(mockResponseWriter)
r, _ = http.NewRequest(http.MethodGet, "/user/gopher/details", nil)
router.ServeHTTP(w, r)
if !routed2 || routed3 {
t.Fatal("Routing failed!")
}
w = new(mockResponseWriter)
r, _ = http.NewRequest(http.MethodGet, "/", nil)
router.ServeHTTP(w, r)
if !routed3 {
t.Fatal("Routing failed!")
}
}
type mockFileSystem struct {
opened bool
}
func (mfs *mockFileSystem) Open(name string) (http.File, error) {
mfs.opened = true
return nil, errors.New("this is just a mock")
}
func TestRouterServeFiles(t *testing.T) {
router := New()
mfs := &mockFileSystem{}
recv := catchPanic(func() {
router.ServeFiles("/noFilepath", mfs)
})
if recv == nil {
t.Fatal("registering path not ending with '*filepath' did not panic")
}
router.ServeFiles("/*filepath", mfs)
w := new(mockResponseWriter)
r, _ := http.NewRequest(http.MethodGet, "/favicon.ico", nil)
router.ServeHTTP(w, r)
if !mfs.opened {
t.Error("serving file failed")
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/missyoyo/httprouter.git
git@gitee.com:missyoyo/httprouter.git
missyoyo
httprouter
httprouter
master

搜索帮助