From 0f907a3dae5adefc068c79b98f0dbebd1a32002f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 14 Jun 2019 11:26:56 +0300 Subject: [PATCH] fix https://github.com/kataras/iris/issues/1273 Former-commit-id: 2b48b9b912e9eb8f5f2c6d413907dbfdae857e08 --- core/router/api_builder.go | 4 ++- core/router/fs.go | 2 +- core/router/handler.go | 2 +- core/router/route.go | 3 ++- core/router/router.go | 2 +- hero/di/reflect.go | 12 +++++++++ hero/func_result.go | 50 ++++++++++++++++++++++++++------------ hero/func_result_test.go | 35 ++++++++++++++++++++++++-- 8 files changed, 87 insertions(+), 23 deletions(-) diff --git a/core/router/api_builder.go b/core/router/api_builder.go index d6ca5f3c..4e9f1ab0 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -798,7 +798,9 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route { // // As a special case, the returned file server redirects any request // ending in "/index.html" to the same path, without the final -// "index.html". +// "/index.html", if `index.html` should be served then register a +// new route for it, i.e +// `app.Get("/static", func(ctx iris.Context){ ctx.ServeFile("./static/index.html", false) })`. // // StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`. // diff --git a/core/router/fs.go b/core/router/fs.go index 0c0ca01f..721e2965 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -127,7 +127,7 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error // fileserver := iris.StaticHandler("./static_files", false, false) // h := router.StripPrefix("/static", fileserver) // /* http://mydomain.com/static/css/style.css */ -// app.Get("/static", h) +// app.Get("/static/{file:path}", h) // ... // func StaticHandler(systemPath string, showList bool, gzip bool) context.Handler { diff --git a/core/router/handler.go b/core/router/handler.go index 67ba5845..97aa8a4d 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -82,7 +82,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { registeredRoutes := provider.GetRoutes() h.trees = h.trees[0:0] // reset, inneed when rebuilding. - // sort, subdomains goes first. + // sort, subdomains go first. sort.Slice(registeredRoutes, func(i, j int) bool { first, second := registeredRoutes[i], registeredRoutes[j] lsub1 := len(first.Subdomain) diff --git a/core/router/route.go b/core/router/route.go index 132eb7c7..25adf412 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -28,7 +28,8 @@ type Route struct { // temp storage, they're appended to the Handlers on build. // Execution happens after Begin and main Handler(s), can be empty. doneHandlers context.Handlers - Path string `json:"path"` // "/api/user/:id" + + Path string `json:"path"` // "/api/user/{id:uint64}" // FormattedPath all dynamic named parameters (if any) replaced with %v, // used by Application to validate param values of a Route based on its name. FormattedPath string `json:"formattedPath"` diff --git a/core/router/router.go b/core/router/router.go index f4e9840d..7ff55e86 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -93,7 +93,7 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan } // Downgrade "downgrades", alters the router supervisor service(Router.mainHandler) -// algorithm to a custom one, +// algorithm to a custom one, // be aware to change the global variables of 'ParamStart' and 'ParamWildcardStart'. // can be used to implement a custom proxy or // a custom router which should work with raw ResponseWriter, *Request diff --git a/hero/di/reflect.go b/hero/di/reflect.go index e2c68700..e9ae06af 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -73,6 +73,7 @@ func IndirectValue(v reflect.Value) reflect.Value { if k := v.Kind(); k == reflect.Ptr { //|| k == reflect.Interface { return v.Elem() } + return v } @@ -106,6 +107,17 @@ func IndirectType(typ reflect.Type) reflect.Type { return typ } +// IsNil same as `reflect.IsNil` but a bit safer to use, returns false if not a correct type. +func IsNil(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: + return v.IsNil() + default: + return false + } +} + func goodVal(v reflect.Value) bool { switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice: diff --git a/hero/func_result.go b/hero/func_result.go index e14c5d8e..faf50a0f 100644 --- a/hero/func_result.go +++ b/hero/func_result.go @@ -211,7 +211,6 @@ func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values [ ) for _, v := range values { - // order of these checks matters // for example, first we need to check for status code, // secondly the string (for content type and content)... @@ -310,27 +309,46 @@ func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values [ // it's raw content, get the latest content = value case compatibleErr: - if value != nil { // it's always not nil but keep it here. - if errorHandler != nil { - errorHandler.HandleError(ctx, value) - break - } - - err = value - if statusCode < 400 { - statusCode = DefaultErrStatusCode - } - break // break on first error, error should be in the end but we - // need to know break the dispatcher if any error. - // at the end; we don't want to write anything to the response if error is not nil. + if value == nil || di.IsNil(v) { + continue } + + if errorHandler != nil { + errorHandler.HandleError(ctx, value) + break + } + + err = value + if statusCode < 400 { + statusCode = DefaultErrStatusCode + } + break // break on first error, error should be in the end but we + // need to know break the dispatcher if any error. + // at the end; we don't want to write anything to the response if error is not nil. + default: // else it's a custom struct or a dispatcher, we'll decide later // because content type and status code matters // do that check in order to be able to correctly dispatch: // (customStruct, error) -> customStruct filled and error is nil - if custom == nil && f != nil { - custom = f + if custom == nil { + // if it's a pointer to struct/map. + + if di.IsNil(v) { + // if just a ptr to struct with no content type given + // then try to get the previous response writer's content type, + // and if that is empty too then force-it to application/json + // as the default content type we use for structs/maps. + contentType = ctx.GetContentType() + if contentType == "" { + contentType = context.ContentJSONHeaderValue + } + continue + } + + if value != nil { + custom = value // content type will be take care later on. + } } } } diff --git a/hero/func_result_test.go b/hero/func_result_test.go index 6c602b65..6282dbd9 100644 --- a/hero/func_result_test.go +++ b/hero/func_result_test.go @@ -70,7 +70,7 @@ func GetCustomStructWithContentType() (testCustomStruct, string) { return testCustomStruct{"Iris", 2}, "text/xml" } -func GetCustomStructWithError(ctx iris.Context) (s testCustomStruct, err error) { +func GetCustomStructWithError(ctx context.Context) (s testCustomStruct, err error) { s = testCustomStruct{"Iris", 2} if ctx.URLParamExists("err") { err = errors.New("omit return of testCustomStruct and fire error") @@ -86,7 +86,7 @@ type err struct { Message string `json:"message"` } -func (e err) Dispatch(ctx iris.Context) { +func (e err) Dispatch(ctx context.Context) { // write the status code based on the err's StatusCode. ctx.StatusCode(e.Status) // send to the client the whole object as json @@ -97,6 +97,22 @@ func GetCustomErrorAsDispatcher() err { return err{iris.StatusBadRequest, "this is my error as json"} } +func GetCustomTypedNilEmptyResponse() iris.Map { + return nil +} + +func GetCustomTypedPtrNilEmptyResponse() *iris.Map { + return nil +} + +func GetCustomMapNilEmptyResponse() map[string]interface{} { + return nil +} + +func GetCustomPtrStructNilEmptyResponse() *testCustomStruct { + return nil +} + func TestFuncResult(t *testing.T) { app := iris.New() h := New() @@ -120,6 +136,11 @@ func TestFuncResult(t *testing.T) { app.Get("/custom/struct/with/error", h.Handler(GetCustomStructWithError)) app.Get("/custom/error/as/dispatcher", h.Handler(GetCustomErrorAsDispatcher)) + app.Get("/custom/nil/typed", h.Handler(GetCustomTypedNilEmptyResponse)) + app.Get("/custom/nil/typed/ptr", h.Handler(GetCustomTypedPtrNilEmptyResponse)) + app.Get("/custom/nil/map", h.Handler(GetCustomMapNilEmptyResponse)) + app.Get("/custom/nil/struct", h.Handler(GetCustomPtrStructNilEmptyResponse)) + e := httptest.New(t, app) e.GET("/text").Expect().Status(iris.StatusOK). @@ -172,4 +193,14 @@ func TestFuncResult(t *testing.T) { // the content should be not JSON it should be the status code's text // it will fire the error's text JSON().Equal(err{iris.StatusBadRequest, "this is my error as json"}) + + // its result is nil should give an empty response but content-type is set correctly. + e.GET("/custom/nil/typed").Expect(). + Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() + e.GET("/custom/nil/typed/ptr").Expect(). + Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() + e.GET("/custom/nil/map").Expect(). + Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() + e.GET("/custom/nil/struct").Expect(). + Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() }