1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 02:17:05 +00:00

new feature: handle different param types in the exact same path pattern

implements https://github.com/kataras/iris/issues/1315


Former-commit-id: 3e9276f2a95d6fc7c10fbf91186d041dcba72611
This commit is contained in:
Gerasimos (Makis) Maropoulos
2019-07-29 23:09:22 +03:00
parent c44fc6e1de
commit 700dcc8005
10 changed files with 168 additions and 28 deletions

View File

@@ -19,15 +19,16 @@
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. 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 - Different parameter types in the same path (done).
- https://github.com/kataras/iris/issues/1207 - [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: 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. - 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. - 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 # Tu, 23 July 2019 | v11.2.0
Read about the new release at: https://dev.to/kataras/iris-version-11-2-released-22bc Read about the new release at: https://dev.to/kataras/iris-version-11-2-released-22bc

View File

@@ -13,7 +13,9 @@ func main() {
ctx.HTML("<h1> Please click <a href='/debug/pprof'>here</a>") ctx.HTML("<h1> Please click <a href='/debug/pprof'>here</a>")
}) })
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")) app.Run(iris.Addr(":8080"))
} }

View File

@@ -6,6 +6,7 @@ import (
func main() { func main() {
app := iris.New() app := iris.New()
app.Logger().SetLevel("debug")
// registers a custom handler for 404 not found http (error) status code, // registers a custom handler for 404 not found http (error) status code,
// fires when route not found or manually by ctx.StatusCode(iris.StatusNotFound). // 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", "/", [...])`) 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) app.Get("/donate", donateHandler, donateFinishHandler)
// Pssst, don't forget dynamic-path example for more "magic"! // 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/api/users/blabla
// http://localhost:8080/wontfound // 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: // if hosts edited:
// http://v1.localhost:8080 // http://v1.localhost:8080
// http://v1.localhost:8080/api/users // http://v1.localhost:8080/api/users

View File

@@ -10,6 +10,7 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/errors"
"github.com/kataras/iris/macro" "github.com/kataras/iris/macro"
macroHandler "github.com/kataras/iris/macro/handler"
) )
// MethodNone is a Virtual method // MethodNone is a Virtual method
@@ -109,6 +110,20 @@ func (repo *repository) get(routeName string) *Route {
return nil 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 { func (repo *repository) getByPath(tmplPath string) *Route {
if repo.pos != nil { if repo.pos != nil {
if idx, ok := repo.pos[tmplPath]; ok { 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. var route *Route // the last one is returned.
for _, route = range routes { for _, route = range routes {
// global // global
route.topLink = api.routes.getRelative(route)
api.routes.register(route) api.routes.register(route)
} }

View File

@@ -5,11 +5,12 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/kataras/golog"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/netutil" "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. // 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 { for _, r := range registeredRoutes {
// build the r.Handlers based on begin and done handlers, if any. if r.topLink != nil {
r.BuildHandlers() bindMultiParamTypesHandler(r.topLink, r)
}
}
for _, r := range registeredRoutes {
if r.Subdomain != "" { if r.Subdomain != "" {
h.hosts = true h.hosts = true
} }
// the only "bad" with this is if the user made an error if r.topLink == nil {
// on route, it will be stacked shown in this build state // build the r.Handlers based on begin and done handlers, if any.
// and no in the lines of the user's action, they should read r.BuildHandlers()
// the docs better. Or TODO: add a link here in order to help new users.
if err := h.addRoute(r); err != nil { // the only "bad" with this is if the user made an error
// node errors: // on route, it will be stacked shown in this build state
rp.Add("%v -> %s", err, r.String()) // and no in the lines of the user's action, they should read
continue // 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() 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) { func (h *routerHandler) HandleRequest(ctx context.Context) {
method := ctx.Method() method := ctx.Method()
path := ctx.Path() path := ctx.Path()

View File

@@ -40,6 +40,8 @@ type Route struct {
// route, manually or automatic by the framework, // route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`. // get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites []context.StaticSite `json:"staticSites"` StaticSites []context.StaticSite `json:"staticSites"`
topLink *Route
} }
// NewRoute returns a new route based on its method, // NewRoute returns a new route based on its method,

4
doc.go
View File

@@ -38,13 +38,13 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
11.2.2 11.2.3
Installation Installation
The only requirement is the Go Programming Language, at least version 1.12. 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: Wiki:

View File

@@ -37,7 +37,7 @@ import (
var ( var (
// Version is the current version number of the Iris Web Framework. // 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. // HTTP status codes as registered with IANA.

View File

@@ -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 // If the template does not contain any dynamic attributes and a special handler is NOT required
// then it returns a nil handler. // then it returns a nil handler.
func MakeHandler(tmpl macro.Template) context.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) { if !CanMakeHandler(tmpl) {
return nil return nil
} }
return func(ctx context.Context) { return func(ctx context.Context) bool {
for _, p := range tmpl.Params { for _, p := range tmpl.Params {
if !p.CanEval() { if !p.CanEval() {
continue // allow. 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.StatusCode(p.ErrCode)
ctx.StopExecution() return false
return
} }
} }
// if all passed, just continue.
ctx.Next() return true
} }
} }

View File

@@ -20,6 +20,11 @@ type Template struct {
Params []TemplateParam `json:"params"` 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 // TemplateParam is the parsed macro parameter's template
// they are being used to describe the param's syntax result. // they are being used to describe the param's syntax result.
type TemplateParam struct { type TemplateParam struct {