diff --git a/HISTORY.md b/HISTORY.md index 36a95c81..31d68714 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -371,6 +371,8 @@ Other Improvements: ![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png?v=0) +- `Configuration.EnablePathIntelligence | iris.WithPathIntelligence` to enable path intelligence automatic path redirection on the most closest path (if any), [example]((https://github.com/kataras/iris/blob/master/_examples/routing/intelligence/main.go) + - Enhanced cookie security and management through new `Context.AddCookieOptions` method and new cookie options (look on New Package-level functions section below), [securecookie](https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie) example has been updated. - `Context.RemoveCookie` removes also the Request's specific cookie of the same request lifecycle when `iris.CookieAllowReclaim` is set to cookie options, [example](https://github.com/kataras/iris/tree/master/_examples/cookies/options). @@ -494,7 +496,7 @@ Fix [[BUG]Session works incorrectly when meets the multi-level TLDs](https://git # Mo, 16 December 2019 | v12.1.1 -Add [Context.FindClosest(n int) []string](https://github.com/kataras/iris/blob/master/_examples/routing/not-found-suggests/main.go#L22) +Add [Context.FindClosest(n int) []string](https://github.com/kataras/iris/blob/master/_examples/routing/intelligence/manual/main.go#L22) ```go app := iris.New() diff --git a/_examples/README.md b/_examples/README.md index c0b10ba3..11e344d0 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -36,7 +36,8 @@ * [Overview](routing/overview/main.go) * [Basic](routing/basic/main.go) * [Custom HTTP Errors](routing/http-errors/main.go) - * [Not Found - Suggest Closest Paths](routing/not-found-suggests/main.go) + * [Not Found - Intelligence](routing/intelligence/main.go) + * [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go) * [Dynamic Path](routing/dynamic-path/main.go) * [Root Wildcard](routing/dynamic-path/root-wildcard/main.go) * [Implement a Parameter Type](routing/macros/main.go) diff --git a/_examples/routing/intelligence/main.go b/_examples/routing/intelligence/main.go new file mode 100644 index 00000000..dbc2c631 --- /dev/null +++ b/_examples/routing/intelligence/main.go @@ -0,0 +1,24 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.Get("/home", handler) + app.Get("/contact", handler) + app.Get("/contract", handler) + + // http://localhost:8080/home + // http://localhost:8080/hom + // + // http://localhost:8080/contact + // http://localhost:8080/cont + // + // http://localhost:8080/contract + // http://localhost:8080/contr + app.Listen(":8080", iris.WithPathIntelligence) +} + +func handler(ctx iris.Context) { + ctx.Writef("Path: %s", ctx.Path()) +} diff --git a/_examples/routing/not-found-suggests/main.go b/_examples/routing/intelligence/manual/main.go similarity index 100% rename from _examples/routing/not-found-suggests/main.go rename to _examples/routing/intelligence/manual/main.go diff --git a/configuration.go b/configuration.go index cde41866..13bbfe11 100644 --- a/configuration.go +++ b/configuration.go @@ -240,6 +240,13 @@ var WithoutPathCorrection = func(app *Application) { app.config.DisablePathCorrection = true } +// WithPathIntelligence enables the EnablePathIntelligence setting. +// +// See `Configuration`. +var WithPathIntelligence = func(app *Application) { + app.config.EnablePathIntelligence = true +} + // WithoutPathCorrectionRedirection disables the PathCorrectionRedirection setting. // // See `Configuration`. @@ -781,14 +788,21 @@ type Configuration struct { // // Defaults to false. DisablePathCorrection bool `json:"disablePathCorrection,omitempty" yaml:"DisablePathCorrection" toml:"DisablePathCorrection"` - // DisablePathCorrectionRedirection works whenever configuration.DisablePathCorrection is set to false // and if DisablePathCorrectionRedirection set to true then it will fire the handler of the matching route without // the trailing slash ("/") instead of send a redirection status. // // Defaults to false. DisablePathCorrectionRedirection bool `json:"disablePathCorrectionRedirection,omitempty" yaml:"DisablePathCorrectionRedirection" toml:"DisablePathCorrectionRedirection"` - + // EnablePathIntelligence if set to true, + // the router will redirect HTTP "GET" not found pages to the most closest one path(if any). For example + // you register a route at "/contact" path - + // a client tries to reach it by "/cont", the path will be automatic fixed + // and the client will be redirected to the "/contact" path + // instead of getting a 404 not found response back. + // + // Defaults to false. + EnablePathIntelligence bool `json:"enablePathIntelligence,omitempty" yaml:"EnablePathIntelligence" toml:"EnablePathIntelligence"` // EnablePathEscape when is true then its escapes the path and the named parameters (if any). // When do you need to Disable(false) it: // accepts parameters with slash '/' @@ -799,23 +813,21 @@ type Configuration struct { // // Defaults to false. EnablePathEscape bool `json:"enablePathEscape,omitempty" yaml:"EnablePathEscape" toml:"EnablePathEscape"` - // ForceLowercaseRouting if enabled, converts all registered routes paths to lowercase // and it does lowercase the request path too for matching. // // Defaults to false. ForceLowercaseRouting bool `json:"forceLowercaseRouting,omitempty" yaml:"ForceLowercaseRouting" toml:"ForceLowercaseRouting"` + // FireMethodNotAllowed if it's true router checks for StatusMethodNotAllowed(405) and + // fires the 405 error instead of 404 + // Defaults to false. + FireMethodNotAllowed bool `json:"fireMethodNotAllowed,omitempty" yaml:"FireMethodNotAllowed" toml:"FireMethodNotAllowed"` // EnableOptimization when this field is true // then the application tries to optimize for the best performance where is possible. // // Defaults to false. EnableOptimizations bool `json:"enableOptimizations,omitempty" yaml:"EnableOptimizations" toml:"EnableOptimizations"` - // FireMethodNotAllowed if it's true router checks for StatusMethodNotAllowed(405) and - // fires the 405 error instead of 404 - // Defaults to false. - FireMethodNotAllowed bool `json:"fireMethodNotAllowed,omitempty" yaml:"FireMethodNotAllowed" toml:"FireMethodNotAllowed"` - // DisableBodyConsumptionOnUnmarshal manages the reading behavior of the context's body readers/binders. // If set to true then it // disables the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML`. @@ -962,35 +974,40 @@ func (c Configuration) GetDisablePathCorrection() bool { return c.DisablePathCorrection } -// GetDisablePathCorrectionRedirection returns the Configuration#DisablePathCorrectionRedirection field. +// GetDisablePathCorrectionRedirection returns the Configuration.DisablePathCorrectionRedirection field. // If DisablePathCorrectionRedirection set to true then it will fire the handler of the matching route without // the last slash ("/") instead of send a redirection status. func (c Configuration) GetDisablePathCorrectionRedirection() bool { return c.DisablePathCorrectionRedirection } -// GetEnablePathEscape is the Configuration#EnablePathEscape, +// GetEnablePathIntelligence returns the Configuration.EnablePathIntelligence field. +func (c Configuration) GetEnablePathIntelligence() bool { + return c.EnablePathIntelligence +} + +// GetEnablePathEscape is the Configuration.EnablePathEscape, // returns true when its escapes the path, the named parameters (if any). func (c Configuration) GetEnablePathEscape() bool { return c.EnablePathEscape } -// GetForceLowercaseRouting returns the value of the `ForceLowercaseRouting` setting. +// GetForceLowercaseRouting returns the value of the Configuration.ForceLowercaseRouting setting. func (c Configuration) GetForceLowercaseRouting() bool { return c.ForceLowercaseRouting } +// GetFireMethodNotAllowed returns the Configuration.FireMethodNotAllowed. +func (c Configuration) GetFireMethodNotAllowed() bool { + return c.FireMethodNotAllowed +} + // GetEnableOptimizations returns whether // the application has performance optimizations enabled. func (c Configuration) GetEnableOptimizations() bool { return c.EnableOptimizations } -// GetFireMethodNotAllowed returns the Configuration#FireMethodNotAllowed. -func (c Configuration) GetFireMethodNotAllowed() bool { - return c.FireMethodNotAllowed -} - // GetDisableBodyConsumptionOnUnmarshal returns the Configuration#GetDisableBodyConsumptionOnUnmarshal, // manages the reading behavior of the context's body readers/binders. // If returns true then the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML` @@ -1149,6 +1166,10 @@ func WithConfiguration(c Configuration) Configurator { main.DisablePathCorrectionRedirection = v } + if v := c.EnablePathIntelligence; v { + main.EnablePathIntelligence = v + } + if v := c.EnablePathEscape; v { main.EnablePathEscape = v } diff --git a/configuration_test.go b/configuration_test.go index f9df9dc7..c7f0e483 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -146,6 +146,7 @@ func TestConfigurationYAML(t *testing.T) { yamlConfigurationContents := ` DisablePathCorrection: false DisablePathCorrectionRedirection: true +EnablePathIntelligence: true EnablePathEscape: false FireMethodNotAllowed: true EnableOptimizations: true @@ -175,6 +176,10 @@ Other: t.Fatalf("error on TestConfigurationYAML: Expected DisablePathCorrectionRedirection %v but got %v", expected, c.DisablePathCorrectionRedirection) } + if expected := true; c.EnablePathIntelligence != expected { + t.Fatalf("error on TestConfigurationYAML: Expected EnablePathIntelligence %v but got %v", expected, c.EnablePathIntelligence) + } + if expected := false; c.EnablePathEscape != expected { t.Fatalf("error on TestConfigurationYAML: Expected EnablePathEscape %v but got %v", expected, c.EnablePathEscape) } diff --git a/context/configuration.go b/context/configuration.go index cdf310ef..706dbd25 100644 --- a/context/configuration.go +++ b/context/configuration.go @@ -25,26 +25,24 @@ type ConfigurationReadOnly interface { // then the Router checks if /home handler exists, if yes, // (permant)redirects the client to the correct path /home. GetDisablePathCorrection() bool - - // GetDisablePathCorrectionRedirection returns the Configuration#DisablePathCorrectionRedirection field. + // GetDisablePathCorrectionRedirection returns the Configuration.DisablePathCorrectionRedirection field. // If DisablePathCorrectionRedirection set to true then it will handle paths as they are. // it will fire the handler of the matching route without // the last slash ("/") instead of send a redirection status. GetDisablePathCorrectionRedirection() bool - + // GetEnablePathIntelligence returns the Configuration.EnablePathIntelligence field. + GetEnablePathIntelligence() bool // GetEnablePathEscape is the configuration.EnablePathEscape, // returns true when its escapes the path, the named parameters (if any). GetEnablePathEscape() bool - // GetForceLowercaseRouting returns the value of the `ForceLowercaseRouting` setting. GetForceLowercaseRouting() bool + // GetFireMethodNotAllowed returns the configuration.FireMethodNotAllowed. + GetFireMethodNotAllowed() bool // GetEnableOptimizations returns whether // the application has performance optimizations enabled. GetEnableOptimizations() bool - - // GetFireMethodNotAllowed returns the configuration.FireMethodNotAllowed. - GetFireMethodNotAllowed() bool // GetDisableBodyConsumptionOnUnmarshal returns the configuration.GetDisableBodyConsumptionOnUnmarshal, // manages the reading behavior of the context's body readers/binders. // If returns true then the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML` diff --git a/context/context.go b/context/context.go index 89dd6669..ade8532b 100644 --- a/context/context.go +++ b/context/context.go @@ -351,7 +351,7 @@ type Context interface { // this request based on subdomain and request path. // // Order may change. - // Example: https://github.com/kataras/iris/tree/master/_examples/routing/not-found-suggests + // Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual FindClosest(n int) []string // IsWWW returns true if the current subdomain (if any) is www. IsWWW() bool @@ -1812,7 +1812,7 @@ func (ctx *context) Subdomain() (subdomain string) { // this request based on subdomain and request path. // // Order may change. -// Example: https://github.com/kataras/iris/tree/master/_examples/routing/not-found-suggests +// Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual func (ctx *context) FindClosest(n int) []string { return ctx.Application().FindClosestPaths(ctx.Subdomain(), ctx.Path(), n) } diff --git a/core/router/api_container.go b/core/router/api_container.go index 9b9c1175..9c69ba41 100644 --- a/core/router/api_container.go +++ b/core/router/api_container.go @@ -229,3 +229,19 @@ func (api *APIContainer) Any(relativePath string, handlersFn ...interface{}) (ro return } + +// OnErrorCode registers a handlers chain for this `Party` for a specific HTTP status code. +// Read more at: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +// Look `OnAnyErrorCode` too. +func (api *APIContainer) OnErrorCode(statusCode int, handlersFn ...interface{}) []*Route { + handlers := api.convertHandlerFuncs("/{tail:path}", handlersFn...) + return api.Self.OnErrorCode(statusCode, handlers...) +} + +// OnAnyErrorCode registers a handlers chain for all error codes +// (4xxx and 5xxx, change the `ClientErrorCodes` and `ServerErrorCodes` variables to modify those) +// Look `OnErrorCode` too. +func (api *APIContainer) OnAnyErrorCode(handlersFn ...interface{}) []*Route { + handlers := api.convertHandlerFuncs("/{tail:path}", handlersFn...) + return api.Self.OnAnyErrorCode(handlers...) +} diff --git a/core/router/handler.go b/core/router/handler.go index 39846cb2..81adfe22 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -417,6 +417,16 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } + if config.GetEnablePathIntelligence() && method == http.MethodGet { + closestPaths := ctx.FindClosest(1) + if len(closestPaths) > 0 { + u := ctx.Request().URL + u.Path = closestPaths[0] + ctx.Redirect(u.String(), http.StatusMovedPermanently) + return + } + } + ctx.StatusCode(http.StatusNotFound) }