diff --git a/HISTORY.md b/HISTORY.md index 95eed0fa6..3b7bc94ef 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,15 +19,16 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. -**How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris@v11.2.0`. +**How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris@master`. +# Tu, 30 July 2019 | v11.2.3 -# We, 24 July 2019 | v11.2.1 +TODO: -- https://github.com/kataras/iris/issues/1298 -- https://github.com/kataras/iris/issues/1207 +- Different parameter types in the same path (done). +- [Content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) (in-progress) -## v11.2.2 +# We, 24 July 2019 | v11.2.2 Sessions as middleware: @@ -47,6 +48,11 @@ app.Get("/path", func(ctx iris.Context){ - Add `Session.Len() int` to return the total number of stored values/entries. - Make `Context.HTML` and `Context.Text` to accept an optional, variadic, `args ...interface{}` input arg(s) too. +## v11.1.1 + +- https://github.com/kataras/iris/issues/1298 +- https://github.com/kataras/iris/issues/1207 + # Tu, 23 July 2019 | v11.2.0 Read about the new release at: https://dev.to/kataras/iris-version-11-2-released-22bc diff --git a/_examples/miscellaneous/pprof/main.go b/_examples/miscellaneous/pprof/main.go index 2b2a79e94..80cb9429e 100644 --- a/_examples/miscellaneous/pprof/main.go +++ b/_examples/miscellaneous/pprof/main.go @@ -13,7 +13,9 @@ func main() { ctx.HTML("

Please click here") }) - app.Any("/debug/pprof/{action:path}", pprof.New()) + p := pprof.New() + app.Any("/debug/pprof", p) + app.Any("/debug/pprof/{action:path}", p) // ___________ app.Run(iris.Addr(":8080")) } diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index d03ea46c0..8a6c29f1a 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -6,6 +6,7 @@ import ( func main() { app := iris.New() + app.Logger().SetLevel("debug") // registers a custom handler for 404 not found http (error) status code, // fires when route not found or manually by ctx.StatusCode(iris.StatusNotFound). @@ -26,6 +27,42 @@ func main() { ctx.Writef(`Same as app.Handle("GET", "/", [...])`) }) + // Different path parameters types in the same path. + app.Get("/u/{username:string}", func(ctx iris.Context) { + ctx.Writef("before username (string), current route name: %s\n", ctx.RouteName()) + ctx.Next() + }, func(ctx iris.Context) { + ctx.Writef("username (string): %s", ctx.Params().Get("username")) + }) + + app.Get("/u/{id:int}", func(ctx iris.Context) { + ctx.Writef("before id (int), current route name: %s\n", ctx.RouteName()) + ctx.Next() + }, func(ctx iris.Context) { + ctx.Writef("id (int): %d", ctx.Params().GetIntDefault("id", 0)) + }) + + app.Get("/u/{uid:uint}", func(ctx iris.Context) { + ctx.Writef("before uid (uint), current route name: %s\n", ctx.RouteName()) + ctx.Next() + }, func(ctx iris.Context) { + ctx.Writef("uid (uint): %d", ctx.Params().GetUintDefault("uid", 0)) + }) + + app.Get("/u/{firstname:alphabetical}", func(ctx iris.Context) { + ctx.Writef("before firstname (alphabetical), current route name: %s\n", ctx.RouteName()) + ctx.Next() + }, func(ctx iris.Context) { + ctx.Writef("firstname (alphabetical): %s", ctx.Params().Get("firstname")) + }) + + /* + /u/abcd maps to :alphabetical (if :alphabetical registered otherwise :string) + /u/42 maps to :uint (if :uint registered otherwise :int) + /u/-1 maps to :int (if :int registered otherwise :string) + /u/abcd123 maps to :string + */ + app.Get("/donate", donateHandler, donateFinishHandler) // Pssst, don't forget dynamic-path example for more "magic"! @@ -128,6 +165,11 @@ func main() { // http://localhost:8080/api/users/blabla // http://localhost:8080/wontfound // + // http://localhost:8080/u/abcd + // http://localhost:8080/u/42 + // http://localhost:8080/u/-1 + // http://localhost:8080/u/abcd123 + // // if hosts edited: // http://v1.localhost:8080 // http://v1.localhost:8080/api/users diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 35005f873..b5aee7a72 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -10,6 +10,7 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/macro" + macroHandler "github.com/kataras/iris/macro/handler" ) // MethodNone is a Virtual method @@ -109,6 +110,20 @@ func (repo *repository) get(routeName string) *Route { return nil } +func (repo *repository) getRelative(r *Route) *Route { + if r.tmpl.IsTrailing() || !macroHandler.CanMakeHandler(r.tmpl) { + return nil + } + + for _, route := range repo.routes { + if r.Subdomain == route.Subdomain && r.Method == route.Method && r.FormattedPath == route.FormattedPath && !route.tmpl.IsTrailing() { + return route + } + } + + return nil +} + func (repo *repository) getByPath(tmplPath string) *Route { if repo.pos != nil { if idx, ok := repo.pos[tmplPath]; ok { @@ -345,6 +360,8 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co var route *Route // the last one is returned. for _, route = range routes { // global + + route.topLink = api.routes.getRelative(route) api.routes.register(route) } diff --git a/core/router/handler.go b/core/router/handler.go index 4091e2415..894fefd0f 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -5,11 +5,12 @@ import ( "sort" "strings" - "github.com/kataras/golog" - "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" + macroHandler "github.com/kataras/iris/macro/handler" + + "github.com/kataras/golog" ) // RequestHandler the middle man between acquiring a context and releasing it. @@ -116,29 +117,63 @@ func (h *routerHandler) Build(provider RoutesProvider) error { }) for _, r := range registeredRoutes { - // build the r.Handlers based on begin and done handlers, if any. - r.BuildHandlers() + if r.topLink != nil { + bindMultiParamTypesHandler(r.topLink, r) + } + } + for _, r := range registeredRoutes { if r.Subdomain != "" { h.hosts = true } - // the only "bad" with this is if the user made an error - // on route, it will be stacked shown in this build state - // and no in the lines of the user's action, they should read - // the docs better. Or TODO: add a link here in order to help new users. - if err := h.addRoute(r); err != nil { - // node errors: - rp.Add("%v -> %s", err, r.String()) - continue + if r.topLink == nil { + // build the r.Handlers based on begin and done handlers, if any. + r.BuildHandlers() + + // the only "bad" with this is if the user made an error + // on route, it will be stacked shown in this build state + // and no in the lines of the user's action, they should read + // the docs better. Or TODO: add a link here in order to help new users. + if err := h.addRoute(r); err != nil { + // node errors: + rp.Add("%v -> %s", err, r.String()) + continue + } } - golog.Debugf(r.Trace()) + golog.Debugf(r.Trace()) // keep log different parameter types in the same path as different routes. } return rp.Return() } +func bindMultiParamTypesHandler(top *Route, r *Route) { + r.BuildHandlers() + + h := r.Handlers[1:] // remove the macro evaluator handler as we manually check below. + f := macroHandler.MakeFilter(r.tmpl) + if f == nil { + return // should never happen, previous checks made to set the top link. + } + + decisionHandler := func(ctx context.Context) { + currentRouteName := ctx.RouteName() + if f(ctx) { + ctx.SetCurrentRouteName(r.Name) + ctx.HandlerIndex(0) + ctx.Do(h) + return + } + + ctx.SetCurrentRouteName(currentRouteName) + ctx.StatusCode(http.StatusOK) + ctx.Next() + } + + r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...) +} + func (h *routerHandler) HandleRequest(ctx context.Context) { method := ctx.Method() path := ctx.Path() diff --git a/core/router/route.go b/core/router/route.go index a7f53a09e..9746e4231 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -40,6 +40,8 @@ type Route struct { // route, manually or automatic by the framework, // get the route by `Application#GetRouteByPath(staticSite.RequestPath)`. StaticSites []context.StaticSite `json:"staticSites"` + + topLink *Route } // NewRoute returns a new route based on its method, diff --git a/doc.go b/doc.go index fca949ed2..f9593e802 100644 --- a/doc.go +++ b/doc.go @@ -38,13 +38,13 @@ Source code and other details for the project are available at GitHub: Current Version -11.2.2 +11.2.3 Installation The only requirement is the Go Programming Language, at least version 1.12. - $ go get github.com/kataras/iris@v11.2.2 + $ go get github.com/kataras/iris@master Wiki: diff --git a/iris.go b/iris.go index a5aae2296..8cb0f36c4 100644 --- a/iris.go +++ b/iris.go @@ -37,7 +37,7 @@ import ( var ( // Version is the current version number of the Iris Web Framework. - Version = "11.2.2" + Version = "11.2.3" ) // HTTP status codes as registered with IANA. diff --git a/macro/handler/handler.go b/macro/handler/handler.go index ea36c7d56..1cf744500 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -34,23 +34,54 @@ func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) { // If the template does not contain any dynamic attributes and a special handler is NOT required // then it returns a nil handler. func MakeHandler(tmpl macro.Template) context.Handler { + filter := MakeFilter(tmpl) + + return func(ctx context.Context) { + if !filter(ctx) { + ctx.StopExecution() + return + } + + // if all passed, just continue. + ctx.Next() + } +} + +// MakeFilter returns a Filter which reports whether a specific macro template +// and its parameters pass the serve-time validation. +func MakeFilter(tmpl macro.Template) context.Filter { if !CanMakeHandler(tmpl) { return nil } - return func(ctx context.Context) { + return func(ctx context.Context) bool { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. } - if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) { + // 07-29-2019 + // changed to retrieve by param index in order to support + // different parameter names for routes with + // different param types (and probably different param names i.e {name:string}, {id:uint64}) + // in the exact same path pattern. + // + // Same parameter names are not allowed, different param types in the same path + // should have different name e.g. {name} {id:uint64}; + // something like {name} and {name:uint64} + // is bad API design and we do NOT allow it by-design. + entry, found := ctx.Params().Store.GetEntryAt(p.Index) + if !found { + // should never happen. + return false + } + + if !p.Eval(entry.String(), &ctx.Params().Store) { ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return + return false } } - // if all passed, just continue. - ctx.Next() + + return true } } diff --git a/macro/template.go b/macro/template.go index 8e766cb5d..fcdb0b096 100644 --- a/macro/template.go +++ b/macro/template.go @@ -20,6 +20,11 @@ type Template struct { Params []TemplateParam `json:"params"` } +// IsTrailing reports whether this Template is a traling one. +func (t *Template) IsTrailing() bool { + return len(t.Params) > 0 && ast.IsTrailing(t.Params[len(t.Params)-1].Type) +} + // TemplateParam is the parsed macro parameter's template // they are being used to describe the param's syntax result. type TemplateParam struct {