Implement the Status Method Not Allowed for gorilla mux too and add some tests

Former-commit-id: b157bacfa15567b8c98f959f3359e025fdf1b205
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-02-21 14:20:31 +02:00
parent 6fca78d12a
commit b25d99870e
5 changed files with 164 additions and 10 deletions

View File

@ -34,6 +34,15 @@ import (
const dynamicSymbol = '{' const dynamicSymbol = '{'
func staticPath(path string) string {
i := strings.IndexByte(path, dynamicSymbol)
if i > -1 {
return path[0:i]
}
return path
}
// New returns a new gorilla mux router which can be plugged inside iris. // New returns a new gorilla mux router which can be plugged inside iris.
// This is magic. // This is magic.
func New() iris.Policies { func New() iris.Policies {
@ -46,14 +55,7 @@ func New() iris.Policies {
}}, }},
RouterReversionPolicy: iris.RouterReversionPolicy{ RouterReversionPolicy: iris.RouterReversionPolicy{
// path normalization done on iris' side // path normalization done on iris' side
StaticPath: func(path string) string { StaticPath: staticPath,
i := strings.IndexByte(path, dynamicSymbol)
if i > -1 {
return path[0:i]
}
return path
},
WildcardPath: func(requestPath string, paramName string) string { WildcardPath: func(requestPath string, paramName string) string {
return requestPath + "/{" + paramName + ":.*}" return requestPath + "/{" + paramName + ":.*}"
}, },
@ -94,10 +96,48 @@ func New() iris.Policies {
router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Acquire(w, r) ctx := context.Acquire(w, r)
// to catch custom 404 not found http errors may registered by user // gorilla mux doesn't supports fire method not allowed like iris
ctx.EmitError(iris.StatusNotFound) // so this is my hack to support it:
if ctx.Framework().Config.FireMethodNotAllowed {
stopVisitor := false
repo.Visit(func(route iris.RouteInfo) {
if stopVisitor {
return
}
// this is not going to work 100% for all routes especially the coblex
// but this is the best solution, to check via static path, subdomain and cors to find the 'correct' route to
// compare its method in order to implement the status method not allowed in gorilla mux which doesn't support it
// and if I edit its internal implementation it will be complicated for new releases to be updated.
p := staticPath(route.Path())
if route.Subdomain() == "" || route.Subdomain() == ctx.Subdomain() {
if p == ctx.Path() {
// we don't care about this route because it has cors and this method is options
// or its method is equal with the requests but the router didn't select this route
// that means that the dynamic path didn't match, so we skip it.
if (route.HasCors() && ctx.Method() == iris.MethodOptions) || ctx.Method() == route.Method() {
return
}
if ctx.Method() != route.Method() {
// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
ctx.SetHeader("Allow", route.Method())
ctx.EmitError(iris.StatusMethodNotAllowed)
stopVisitor = true
return
}
}
}
})
} else {
// to catch custom 404 not found http errors may registered by user
ctx.EmitError(iris.StatusNotFound)
}
context.Release(ctx) context.Release(ctx)
}) })
return router return router
}, },
} }

View File

@ -702,12 +702,17 @@ func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler {
} }
// https://github.com/kataras/iris/issues/469 // https://github.com/kataras/iris/issues/469
if context.Framework().Config.FireMethodNotAllowed { if context.Framework().Config.FireMethodNotAllowed {
var methodAllowed string
for i := range mux.garden { for i := range mux.garden {
tree := mux.garden[i] tree := mux.garden[i]
methodAllowed = tree.method // keep track of the allowed method of the last checked tree
if !mux.methodEqual(context.Method(), tree.method) { if !mux.methodEqual(context.Method(), tree.method) {
continue continue
} }
} }
// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
context.SetHeader("Allow", methodAllowed)
context.EmitError(iris.StatusMethodNotAllowed) context.EmitError(iris.StatusMethodNotAllowed)
return return
} }

61
handler_test.go Normal file
View File

@ -0,0 +1,61 @@
// black-box
package iris_test
import (
"testing"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/httptest"
)
const testCustomHandlerParamName = "myparam"
type myTestHandlerData struct {
Sysname string // this will be the same for all requests
Version int // this will be the same for all requests
URLParameter string // this will be different for each request
}
type myTestCustomHandler struct {
data myTestHandlerData
}
func (m *myTestCustomHandler) Serve(ctx *iris.Context) {
data := &m.data
data.URLParameter = ctx.URLParam(testCustomHandlerParamName)
ctx.JSON(iris.StatusOK, data)
}
func TestCustomHandler(t *testing.T) {
app := iris.New()
app.Adapt(newTestNativeRouter())
myData := myTestHandlerData{
Sysname: "Redhat",
Version: 1,
}
app.Handle("GET", "/custom_handler_1", &myTestCustomHandler{myData})
app.Handle("GET", "/custom_handler_2", &myTestCustomHandler{myData})
e := httptest.New(app, t, httptest.Debug(true))
// two times per testRoute
param1 := "thisimyparam1"
expectedData1 := myData
expectedData1.URLParameter = param1
e.GET("/custom_handler_1/").WithQuery(testCustomHandlerParamName, param1).Expect().Status(iris.StatusOK).JSON().Equal(expectedData1)
param2 := "thisimyparam2"
expectedData2 := myData
expectedData2.URLParameter = param2
e.GET("/custom_handler_1/").WithQuery(testCustomHandlerParamName, param2).Expect().Status(iris.StatusOK).JSON().Equal(expectedData2)
param3 := "thisimyparam3"
expectedData3 := myData
expectedData3.URLParameter = param3
e.GET("/custom_handler_2/").WithQuery(testCustomHandlerParamName, param3).Expect().Status(iris.StatusOK).JSON().Equal(expectedData3)
param4 := "thisimyparam4"
expectedData4 := myData
expectedData4.URLParameter = param4
e.GET("/custom_handler_2/").WithQuery(testCustomHandlerParamName, param4).Expect().Status(iris.StatusOK).JSON().Equal(expectedData4)
}

View File

@ -255,3 +255,27 @@ func TestGorillaMuxRouteURLPath(t *testing.T) {
t.Fatalf("gorillamux' reverse routing 'URLPath' error: expected %s but got %s", expected, got) t.Fatalf("gorillamux' reverse routing 'URLPath' error: expected %s but got %s", expected, got)
} }
} }
func TestGorillaMuxFireMethodNotAllowed(t *testing.T) {
app := iris.New()
app.Adapt(gorillamux.New())
app.Config.FireMethodNotAllowed = true
h := func(ctx *iris.Context) {
ctx.WriteString(ctx.Method())
}
app.OnError(iris.StatusMethodNotAllowed, func(ctx *iris.Context) {
ctx.WriteString("Hello from my custom 405 page")
})
app.Get("/mypath", h)
app.Put("/mypath", h)
e := httptest.New(app, t)
e.GET("/mypath").Expect().Status(iris.StatusOK).Body().Equal("GET")
e.PUT("/mypath").Expect().Status(iris.StatusOK).Body().Equal("PUT")
// this should fail with 405 and catch by the custom http error
e.POST("/mypath").Expect().Status(iris.StatusMethodNotAllowed).Body().Equal("Hello from my custom 405 page")
}

View File

@ -268,3 +268,27 @@ func TestHTTPRouterRouteURLPath(t *testing.T) {
t.Fatalf("httprouter's reverse routing 'URLPath' error: expected %s but got %s", expected, got) t.Fatalf("httprouter's reverse routing 'URLPath' error: expected %s but got %s", expected, got)
} }
} }
func TestHTTPRouterFireMethodNotAllowed(t *testing.T) {
app := iris.New()
app.Adapt(httprouter.New())
app.Config.FireMethodNotAllowed = true
h := func(ctx *iris.Context) {
ctx.WriteString(ctx.Method())
}
app.OnError(iris.StatusMethodNotAllowed, func(ctx *iris.Context) {
ctx.WriteString("Hello from my custom 405 page")
})
app.Get("/mypath", h)
app.Put("/mypath", h)
e := httptest.New(app, t)
e.GET("/mypath").Expect().Status(iris.StatusOK).Body().Equal("GET")
e.PUT("/mypath").Expect().Status(iris.StatusOK).Body().Equal("PUT")
// this should fail with 405 and catch by the custom http error
e.POST("/mypath").Expect().Status(iris.StatusMethodNotAllowed).Body().Equal("Hello from my custom 405 page")
}