Skip to content

Commit

Permalink
router: sort allowed methods
Browse files Browse the repository at this point in the history
Fixes #248
  • Loading branch information
julienschmidt committed Sep 29, 2019
1 parent b64ea83 commit bcdc802
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 23 deletions.
38 changes: 23 additions & 15 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ package httprouter
import (
"context"
"net/http"
"strings"
)

// Handle is a function that can be registered to a route to handle HTTP
Expand Down Expand Up @@ -313,18 +314,15 @@ func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
}

func (r *Router) allowed(path, reqMethod string) (allow string) {
allowed := make([]string, 0, 9)

if path == "*" { // server-wide
for method := range r.trees {
if method == http.MethodOptions {
continue
}

// add request method to list of allowed methods
if len(allow) == 0 {
allow = method
} else {
allow += ", " + method
}
// Add request method to list of allowed methods
allowed = append(allowed, method)
}
} else { // specific path
for method := range r.trees {
Expand All @@ -335,17 +333,27 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {

handle, _, _ := r.trees[method].getValue(path)
if handle != nil {
// add request method to list of allowed methods
if len(allow) == 0 {
allow = method
} else {
allow += ", " + method
}
// Add request method to list of allowed methods
allowed = append(allowed, method)
}
}
}
if len(allow) > 0 {
allow += ", OPTIONS"

if len(allowed) > 0 {
// Add request method to list of allowed methods
allowed = append(allowed, http.MethodOptions)

// Sort allowed methods.
// sort.Strings(allowed) unfortunately causes unnecessary allocations
// due to allowed being moved to the heap and interface conversion
for i, l := 1, len(allowed); i < l; i++ {
for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
}
}

// return as comma separated list
return strings.Join(allowed, ", ")
}
return
}
Expand Down
37 changes: 29 additions & 8 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,27 @@ func TestRouterChaining(t *testing.T) {
}
}

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) {}

Expand All @@ -229,7 +250,7 @@ func TestRouterOPTIONS(t *testing.T) {
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 != "POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -239,7 +260,7 @@ func TestRouterOPTIONS(t *testing.T) {
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 != "POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -260,7 +281,7 @@ func TestRouterOPTIONS(t *testing.T) {
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 != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -270,7 +291,7 @@ func TestRouterOPTIONS(t *testing.T) {
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 != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -287,7 +308,7 @@ func TestRouterOPTIONS(t *testing.T) {
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 != "POST, GET, OPTIONS" && allow != "GET, POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "GET, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
if custom {
Expand Down Expand Up @@ -318,7 +339,7 @@ func TestRouterNotAllowed(t *testing.T) {
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 != "POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -332,7 +353,7 @@ func TestRouterNotAllowed(t *testing.T) {
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 != "POST, DELETE, OPTIONS" && allow != "DELETE, POST, OPTIONS" {
} else if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}

Expand All @@ -350,7 +371,7 @@ func TestRouterNotAllowed(t *testing.T) {
if w.Code != http.StatusTeapot {
t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
}
if allow := w.Header().Get("Allow"); allow != "POST, DELETE, OPTIONS" && allow != "DELETE, POST, OPTIONS" {
if allow := w.Header().Get("Allow"); allow != "DELETE, OPTIONS, POST" {
t.Error("unexpected Allow header value: " + allow)
}
}
Expand Down

0 comments on commit bcdc802

Please sign in to comment.