From b25d99870e0abd19238a178853bae4bed980068e Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 21 Feb 2017 14:20:31 +0200 Subject: [PATCH] Implement the Status Method Not Allowed for gorilla mux too and add some tests Former-commit-id: b157bacfa15567b8c98f959f3359e025fdf1b205 --- adaptors/gorillamux/gorillamux.go | 60 +++++++++++++++++++++++++----- adaptors/httprouter/httprouter.go | 5 +++ handler_test.go | 61 +++++++++++++++++++++++++++++++ policy_gorillamux_test.go | 24 ++++++++++++ policy_httprouter_test.go | 24 ++++++++++++ 5 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 handler_test.go diff --git a/adaptors/gorillamux/gorillamux.go b/adaptors/gorillamux/gorillamux.go index 5da6d074..e094d105 100644 --- a/adaptors/gorillamux/gorillamux.go +++ b/adaptors/gorillamux/gorillamux.go @@ -34,6 +34,15 @@ import ( 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. // This is magic. func New() iris.Policies { @@ -46,14 +55,7 @@ func New() iris.Policies { }}, RouterReversionPolicy: iris.RouterReversionPolicy{ // path normalization done on iris' side - StaticPath: func(path string) string { - i := strings.IndexByte(path, dynamicSymbol) - if i > -1 { - return path[0:i] - } - - return path - }, + StaticPath: staticPath, WildcardPath: func(requestPath string, paramName string) string { return requestPath + "/{" + paramName + ":.*}" }, @@ -94,10 +96,48 @@ func New() iris.Policies { router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.Acquire(w, r) - // to catch custom 404 not found http errors may registered by user - ctx.EmitError(iris.StatusNotFound) + // gorilla mux doesn't supports fire method not allowed like iris + // 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) }) + return router }, } diff --git a/adaptors/httprouter/httprouter.go b/adaptors/httprouter/httprouter.go index 14e99d80..4757547b 100644 --- a/adaptors/httprouter/httprouter.go +++ b/adaptors/httprouter/httprouter.go @@ -702,12 +702,17 @@ func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler { } // https://github.com/kataras/iris/issues/469 if context.Framework().Config.FireMethodNotAllowed { + var methodAllowed string for i := range mux.garden { 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) { 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) return } diff --git a/handler_test.go b/handler_test.go new file mode 100644 index 00000000..ad54fa46 --- /dev/null +++ b/handler_test.go @@ -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) +} diff --git a/policy_gorillamux_test.go b/policy_gorillamux_test.go index 55b97a2d..86fae5aa 100644 --- a/policy_gorillamux_test.go +++ b/policy_gorillamux_test.go @@ -255,3 +255,27 @@ func TestGorillaMuxRouteURLPath(t *testing.T) { 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") +} diff --git a/policy_httprouter_test.go b/policy_httprouter_test.go index 87b4d718..e3dc83ac 100644 --- a/policy_httprouter_test.go +++ b/policy_httprouter_test.go @@ -268,3 +268,27 @@ func TestHTTPRouterRouteURLPath(t *testing.T) { 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") +}