From 66209cae4f8e3df669f9be0489f895a35819b9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Meyer?= Date: Wed, 21 Feb 2018 08:18:53 +0300 Subject: [PATCH 01/23] Save Former-commit-id: 592e9cddf3511fc08e87f19ad39fdaac479b453f --- _examples/routing/route-state/main.go | 2 +- core/router/api_builder.go | 19 ++++++++++-- core/router/fallback_stack.go | 44 +++++++++++++++++++++++++++ core/router/handler.go | 9 ++++-- core/router/party.go | 7 +++++ mvc/mvc.go | 7 +++++ 6 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 core/router/fallback_stack.go diff --git a/_examples/routing/route-state/main.go b/_examples/routing/route-state/main.go index f85426f8..ec4f03db 100644 --- a/_examples/routing/route-state/main.go +++ b/_examples/routing/route-state/main.go @@ -8,7 +8,7 @@ func main() { app := iris.New() none := app.None("/invisible/{username}", func(ctx iris.Context) { - ctx.Writef("Hello %s with method: %s", ctx.Values().GetString("username"), ctx.Method()) + ctx.Writef("Hello %s with method: %s", ctx.Params().Get("username"), ctx.Method()) if from := ctx.Values().GetString("from"); from != "" { ctx.Writef("\nI see that you're coming from %s", from) diff --git a/core/router/api_builder.go b/core/router/api_builder.go index e9afefba..e0cfc4d7 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -90,6 +90,8 @@ type APIBuilder struct { doneHandlers context.Handlers // global done handlers, order doesn't matter doneGlobalHandlers context.Handlers + // fallback stack, LIFO order + fallbackStack *FallbackStack // the per-party relativePath string } @@ -106,6 +108,7 @@ func NewAPIBuilder() *APIBuilder { reporter: errors.NewReporter(), relativePath: "/", routes: new(repository), + fallbackStack: NewFallbackStack(), } return api @@ -268,9 +271,10 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P doneGlobalHandlers: api.doneGlobalHandlers, reporter: api.reporter, // per-party/children - middleware: middleware, - doneHandlers: api.doneHandlers, - relativePath: fullpath, + middleware: middleware, + doneHandlers: api.doneHandlers, + fallbackStack: api.fallbackStack, + relativePath: fullpath, } } @@ -422,6 +426,15 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...) } +// Fallback appends Handler(s) to the current Party's fallback stack. +// Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. +// Therefore Handler(s) in Fallback stack could send another thing than NotFound status, +// if `Context.Next()` method is not called. +// Done & DoneGlobal Handlers are not called. +func (api *APIBuilder) Fallback(middleware ...context.Handler) { + api.fallbackStack.add(middleware) +} + // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, // note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. // diff --git a/core/router/fallback_stack.go b/core/router/fallback_stack.go new file mode 100644 index 00000000..499e0896 --- /dev/null +++ b/core/router/fallback_stack.go @@ -0,0 +1,44 @@ +package router + +import ( + "net/http" + "sync" + + "github.com/kataras/iris/context" +) + +type FallbackStack struct { + handlers context.Handlers + m sync.Mutex +} + +func (stk *FallbackStack) add(h context.Handlers) { + stk.m.Lock() + defer stk.m.Unlock() + + stk.handlers = append(stk.handlers, h...) + + copy(stk.handlers[len(h):], stk.handlers) + copy(stk.handlers, h) +} + +func (stk *FallbackStack) list() context.Handlers { + res := make(context.Handlers, len(stk.handlers)) + + stk.m.Lock() + defer stk.m.Unlock() + + copy(res, stk.handlers) + + return res +} + +func NewFallbackStack() *FallbackStack { + return &FallbackStack{ + handlers: context.Handlers{ + func(ctx context.Context) { + ctx.StatusCode(http.StatusNotFound) + }, + }, + } +} diff --git a/core/router/handler.go b/core/router/handler.go index bb00eb3a..bd8aed9a 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -33,8 +33,9 @@ type tree struct { } type routerHandler struct { - trees []*tree - hosts bool // true if at least one route contains a Subdomain. + trees []*tree + hosts bool // true if at least one route contains a Subdomain. + fallbackStack *FallbackStack } var _ RequestHandler = &routerHandler{} @@ -82,6 +83,8 @@ func NewDefaultHandler() RequestHandler { type RoutesProvider interface { // api builder GetRoutes() []*Route GetRoute(routeName string) *Route + + FallBackStack() *FallbackStack } func (h *routerHandler) Build(provider RoutesProvider) error { @@ -242,5 +245,5 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } - ctx.StatusCode(http.StatusNotFound) + ctx.Do(h.fallbackStack.list()) } diff --git a/core/router/party.go b/core/router/party.go index 6f0e814e..a70c6030 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -60,6 +60,13 @@ type Party interface { // If the current Party is the root, then it registers the middleware to all child Parties' routes too. Use(middleware ...context.Handler) + // Fallback appends Handler(s) to the current Party's fallback stack. + // Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. + // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, + // if `Context.Next()` method is not called. + // Done Handler(s) is(are) not called. + Fallback(middleware ...context.Handler) + // Done appends to the very end, Handler(s) to the current Party's routes and child routes. // The difference from .Use is that this/or these Handler(s) are being always running last. Done(handlers ...context.Handler) diff --git a/mvc/mvc.go b/mvc/mvc.go index f11e02d4..35b21cba 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -176,6 +176,13 @@ func (app *Application) Handle(controller interface{}) *Application { return app } +// Fallback is an alias to `app.Router.Fallback(handlers...)` +// +// See `core/router#Party.Fallback` +func (app *Application) Fallback(handlers ...context.Handler) { + app.Router.Fallback(handlers...) +} + // Clone returns a new mvc Application which has the dependencies // of the current mvc Mpplication's dependencies. // From 72b096e156334271b8b54eb8dd6f13b02b2feb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Meyer?= Date: Wed, 21 Feb 2018 12:27:01 +0300 Subject: [PATCH 02/23] Add fallback handlers Former-commit-id: f7e9bd17076a10e1ed1702780d7ce9e89f00b592 --- .../experimental-handlers/cors/simple/main.go | 8 ++ _examples/routing/fallback-handlers/main.go | 29 +++++ context/application.go | 3 + context/context.go | 8 ++ core/router/api_builder.go | 12 +- core/router/fallback_stack.go | 56 +++++++-- core/router/fallback_stack_test.go | 112 ++++++++++++++++++ core/router/handler.go | 64 +++++++++- core/router/router.go | 5 + core/router/router_test.go | 41 +++++++ 10 files changed, 322 insertions(+), 16 deletions(-) create mode 100644 _examples/routing/fallback-handlers/main.go create mode 100644 core/router/fallback_stack_test.go create mode 100644 core/router/router_test.go diff --git a/_examples/experimental-handlers/cors/simple/main.go b/_examples/experimental-handlers/cors/simple/main.go index 8f9cb1bf..611f664b 100644 --- a/_examples/experimental-handlers/cors/simple/main.go +++ b/_examples/experimental-handlers/cors/simple/main.go @@ -5,6 +5,7 @@ package main import ( "github.com/kataras/iris" + "github.com/kataras/iris/core/router" "github.com/iris-contrib/middleware/cors" ) @@ -14,6 +15,7 @@ func main() { app := iris.New() crs := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. + AllowedMethods: router.AllMethods[:], AllowCredentials: true, }) @@ -29,6 +31,12 @@ func main() { v1.Post("/send", func(ctx iris.Context) { ctx.WriteString("sent") }) + v1.Put("/send", func(ctx iris.Context) { + ctx.WriteString("updated") + }) + v1.Delete("/send", func(ctx iris.Context) { + ctx.WriteString("deleted") + }) } // or use that to wrap the entire router diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go new file mode 100644 index 00000000..3c1e8e78 --- /dev/null +++ b/_examples/routing/fallback-handlers/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/kataras/iris" +) + +func main() { + app := iris.New() + + // this works as expected now, + // will handle *all* expect DELETE requests, even if there is no routes + app.Get("/action/{p}", h) + + app.Run(iris.Addr(":8080"), ctx.Method(), ctx.Path(), iris.WithoutServerError(iris.ErrServerClosed)) +} + +func h(ctx iris.Context) { + ctx.Writef("[%s] %s : Parameter = `%s`", ctx.Params().Get("p")) +} + +func fallbackHandler(ctx iris.Context) { + if ctx.Method() == "DELETE" { + ctx.Next() + + return + } + + ctx.Writef("[%s] %s : From fallback handler", ctx.Method(), ctx.Path()) +} diff --git a/context/application.go b/context/application.go index 90738ce7..6ac8bd1f 100644 --- a/context/application.go +++ b/context/application.go @@ -48,4 +48,7 @@ type Application interface { // If a handler is not already registered, // then it creates & registers a new trivial handler on the-fly. FireErrorCode(ctx Context) + + // RouteExists checks if a route exists + RouteExists(method string, path string, ctx Context) bool } diff --git a/context/context.go b/context/context.go index dc3ecf80..1143aa2a 100644 --- a/context/context.go +++ b/context/context.go @@ -905,6 +905,9 @@ type Context interface { // It's for extreme use cases, 99% of the times will never be useful for you. Exec(method string, path string) + // RouteExists checks if a route exists + RouteExists(method string, path string) bool + // Application returns the iris app instance which belongs to this context. // Worth to notice that this function returns an interface // of the Application, which contains methods that are safe @@ -3130,6 +3133,11 @@ func (ctx *context) Exec(method string, path string) { } } +// RouteExists checks if a route exists +func (ctx *context) RouteExists(method string, path string) bool { + return ctx.Application().RouteExists(method, path, ctx) +} + // Application returns the iris app instance which belongs to this context. // Worth to notice that this function returns an interface // of the Application, which contains methods that are safe diff --git a/core/router/api_builder.go b/core/router/api_builder.go index e0cfc4d7..a300760c 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -273,7 +273,7 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P // per-party/children middleware: middleware, doneHandlers: api.doneHandlers, - fallbackStack: api.fallbackStack, + fallbackStack: api.fallbackStack.Fork(), relativePath: fullpath, } } @@ -426,13 +426,19 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...) } -// Fallback appends Handler(s) to the current Party's fallback stack. +// Fallback appends Handler(s) to the current fallback stack. // Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, // if `Context.Next()` method is not called. // Done & DoneGlobal Handlers are not called. func (api *APIBuilder) Fallback(middleware ...context.Handler) { - api.fallbackStack.add(middleware) + api.fallbackStack.Add(middleware) +} + +// FallBackStack returns Fallback stack, this is implementation of interface RoutesProvider +// that is used in Router building by the RequestHandler. +func (api *APIBuilder) GetFallBackStack() *FallbackStack { + return api.fallbackStack } // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, diff --git a/core/router/fallback_stack.go b/core/router/fallback_stack.go index 499e0896..1b8531f8 100644 --- a/core/router/fallback_stack.go +++ b/core/router/fallback_stack.go @@ -2,37 +2,71 @@ package router import ( "net/http" - "sync" "github.com/kataras/iris/context" ) +// FallbackStack is a stack (with LIFO calling order) for fallback handlers +// A fallback handler(s) is(are) called from Fallback stack +// when no route found and before sending NotFound status. +// Therefore Handler(s) in Fallback stack could send another thing than NotFound status, +// if `Context.Next()` method is not called. +// Done & DoneGlobal Handlers are not called. type FallbackStack struct { + parent *FallbackStack handlers context.Handlers - m sync.Mutex } -func (stk *FallbackStack) add(h context.Handlers) { - stk.m.Lock() - defer stk.m.Unlock() +// _size is a terminal recursive method for computing size the stack +func (stk *FallbackStack) _size(i int) int { + res := i + len(stk.handlers) + if stk.parent == nil { + return res + } + + return stk.parent._size(res) +} + +// populate is a recursive method for concatenating handlers to `list` parameter +func (stk *FallbackStack) populate(list context.Handlers) { + n := copy(list, stk.handlers) + + if stk.parent != nil { + stk.parent.populate(list[n:]) + } +} + +// Size gives the size of the full stack hierarchy +func (stk *FallbackStack) Size() int { + return stk._size(0) +} + +// Add appends handlers to the beginning of the stack to have a LIFO calling order +func (stk *FallbackStack) Add(h context.Handlers) { stk.handlers = append(stk.handlers, h...) copy(stk.handlers[len(h):], stk.handlers) copy(stk.handlers, h) } -func (stk *FallbackStack) list() context.Handlers { - res := make(context.Handlers, len(stk.handlers)) +// Fork make a new stack from this stack, and so create a stack child (leaf from a tree of stacks) +func (stk *FallbackStack) Fork() *FallbackStack { + return &FallbackStack{ + parent: stk, + } +} - stk.m.Lock() - defer stk.m.Unlock() - - copy(res, stk.handlers) +// List concatenate all handlers in stack hierarchy +func (stk *FallbackStack) List() context.Handlers { + res := make(context.Handlers, stk.Size()) + stk.populate(res) return res } +// NewFallbackStack create a new Fallback stack with as first entry +// a handler which send NotFound status (the default) func NewFallbackStack() *FallbackStack { return &FallbackStack{ handlers: context.Handlers{ diff --git a/core/router/fallback_stack_test.go b/core/router/fallback_stack_test.go new file mode 100644 index 00000000..1499cd96 --- /dev/null +++ b/core/router/fallback_stack_test.go @@ -0,0 +1,112 @@ +package router_test + +import ( + "testing" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/router" + "github.com/kataras/iris/httptest" +) + +func TestFallbackStackAdd(t *testing.T) { + l := make([]string, 0) + + stk := &router.FallbackStack{} + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS1") + }, + }) + + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS2") + }, + }) + + if stk.Size() != 2 { + t.Fatalf("Bad size (%d != 2)", stk.Size()) + } + + for _, h := range stk.List() { + h(nil) + } + + if (l[0] != "POS2") || (l[1] != "POS1") { + t.Fatal("Bad positions: ", l) + } +} + +func TestFallbackStackFork(t *testing.T) { + l := make([]string, 0) + + stk := &router.FallbackStack{} + + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS1") + }, + }) + + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS2") + }, + }) + + stk = stk.Fork() + + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS3") + }, + }) + + stk.Add(context.Handlers{ + func(context.Context) { + l = append(l, "POS4") + }, + }) + + if stk.Size() != 4 { + t.Fatalf("Bad size (%d != 4)", stk.Size()) + } + + for _, h := range stk.List() { + h(nil) + } + + if (l[0] != "POS4") || (l[1] != "POS3") || (l[2] != "POS2") || (l[3] != "POS1") { + t.Fatal("Bad positions: ", l) + } +} + +func TestFallbackStackCall(t *testing.T) { + // build the api + app := iris.New() + + // setup an existing routes + app.Handle("GET", "/route", func(ctx context.Context) { + ctx.WriteString("ROUTED") + }) + + // setup fallback handler + app.Fallback(func(ctx context.Context) { + if ctx.Method() != "GET" { + ctx.Next() + + return + } + + ctx.WriteString("FALLBACK") + }) + + // run the tests + e := httptest.New(t, app, httptest.Debug(false)) + + e.Request("GET", "/route").Expect().Status(iris.StatusOK).Body().Equal("ROUTED") + e.Request("POST", "/route").Expect().Status(iris.StatusNotFound) + e.Request("POST", "/noroute").Expect().Status(iris.StatusNotFound) + e.Request("GET", "/noroute").Expect().Status(iris.StatusOK).Body().Equal("FALLBACK") +} diff --git a/core/router/handler.go b/core/router/handler.go index bd8aed9a..ce924493 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -22,6 +22,8 @@ type RequestHandler interface { HandleRequest(context.Context) // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error + // RouteExists checks if a route exists + RouteExists(method, path string, ctx context.Context) bool } type tree struct { @@ -84,12 +86,13 @@ type RoutesProvider interface { // api builder GetRoutes() []*Route GetRoute(routeName string) *Route - FallBackStack() *FallbackStack + GetFallBackStack() *FallbackStack } func (h *routerHandler) Build(provider RoutesProvider) error { registeredRoutes := provider.GetRoutes() h.trees = h.trees[0:0] // reset, inneed when rebuilding. + h.fallbackStack = provider.GetFallBackStack() // sort, subdomains goes first. sort.Slice(registeredRoutes, func(i, j int) bool { @@ -245,5 +248,62 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } - ctx.Do(h.fallbackStack.list()) + if h.fallbackStack == nil { + ctx.StatusCode(http.StatusNotFound) + } else { + ctx.Do(h.fallbackStack.List()) + } +} + +// RouteExists checks if a route exists +func (h *routerHandler) RouteExists(method, path string, ctx context.Context) bool { + for i := range h.trees { + t := h.trees[i] + if method != t.Method { + continue + } + + if h.hosts && t.Subdomain != "" { + requestHost := ctx.Host() + if netutil.IsLoopbackSubdomain(requestHost) { + // this fixes a bug when listening on + // 127.0.0.1:8080 for example + // and have a wildcard subdomain and a route registered to root domain. + continue // it's not a subdomain, it's something like 127.0.0.1 probably + } + // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty + if t.Subdomain == SubdomainWildcardIndicator { + // mydomain.com -> invalid + // localhost -> invalid + // sub.mydomain.com -> valid + // sub.localhost -> valid + serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() + if serverHost == requestHost { + continue // it's not a subdomain, it's a full domain (with .com...) + } + + dotIdx := strings.IndexByte(requestHost, '.') + slashIdx := strings.IndexByte(requestHost, '/') + if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { + // if "." was found anywhere but not at the first path segment (host). + } else { + continue + } + // continue to that, any subdomain is valid. + } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. + continue + } + } + + _, handlers := t.Nodes.Find(path, ctx.Params()) + if len(handlers) > 0 { + // found + return true + } + + // not found or method not allowed. + break + } + + return false } diff --git a/core/router/router.go b/core/router/router.go index 9b81d2ea..891813e1 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -147,6 +147,11 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { router.mainHandler(w, r) } +// RouteExists checks if a route exists +func (router *Router) RouteExists(method, path string, ctx context.Context) bool { + return router.requestHandler.RouteExists(method, path, ctx) +} + type wrapper struct { router http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change. wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc) diff --git a/core/router/router_test.go b/core/router/router_test.go new file mode 100644 index 00000000..ba41a714 --- /dev/null +++ b/core/router/router_test.go @@ -0,0 +1,41 @@ +package router_test + +import ( + "testing" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/httptest" +) + +func TestRouteExists(t *testing.T) { + // build the api + app := iris.New() + emptyHandler := func(context.Context) {} + + // setup the tested routes + app.Handle("GET", "/route-exists", emptyHandler) + app.Handle("POST", "/route-with-param/{param}", emptyHandler) + + // check RouteExists + app.Handle("GET", "/route-test", func(ctx context.Context) { + if ctx.RouteExists("GET", "/route-not-exists") { + t.Error("Route with path should not exists") + } + + if ctx.RouteExists("POST", "/route-exists") { + t.Error("Route with method should not exists") + } + + if !ctx.RouteExists("GET", "/route-exists") { + t.Error("Route 1 should exists") + } + + if !ctx.RouteExists("POST", "/route-with-param/a-param") { + t.Error("Route 2 should exists") + } + }) + + // run the tests + httptest.New(t, app, httptest.Debug(false)).Request("GET", "/route-test").Expect().Status(iris.StatusOK) +} From 643358d7cb509a7719343df62021d4558703ede3 Mon Sep 17 00:00:00 2001 From: ZaniaDevelopper Date: Wed, 21 Feb 2018 14:22:17 +0300 Subject: [PATCH 03/23] Modification - Add configurator for Party - Modification for CORS middleware example Former-commit-id: f01c01d4eac41666a92890461851909a6ade018b --- _examples/experimental-handlers/cors/simple/main.go | 4 ++-- core/router/api_builder.go | 8 ++++++++ core/router/party.go | 7 +++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/_examples/experimental-handlers/cors/simple/main.go b/_examples/experimental-handlers/cors/simple/main.go index 611f664b..a4557b20 100644 --- a/_examples/experimental-handlers/cors/simple/main.go +++ b/_examples/experimental-handlers/cors/simple/main.go @@ -13,14 +13,14 @@ import ( func main() { app := iris.New() - crs := cors.New(cors.Options{ + crs := cors.NewPartyMiddleware(cors.Options{ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. AllowedMethods: router.AllMethods[:], AllowCredentials: true, }) v1 := app.Party("/api/v1") - v1.Use(crs) + v1.ConfigureParty(crs) { v1.Get("/home", func(ctx iris.Context) { ctx.WriteString("Hello from /home") diff --git a/core/router/api_builder.go b/core/router/api_builder.go index a300760c..ab3d3f2c 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -114,6 +114,14 @@ func NewAPIBuilder() *APIBuilder { return api } +// ConfigureParty configures this party like `iris.Application#Configure` +// That allows middlewares focused on the Party like CORS middleware +func (api *APIBuilder) ConfigureParty(conf ...PartyConfigurator) { + for _, h := range conf { + h(api) + } +} + // GetRelPath returns the current party's relative path. // i.e: // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". diff --git a/core/router/party.go b/core/router/party.go index a70c6030..8a91e727 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -9,11 +9,18 @@ import ( // Party is here to separate the concept of // api builder and the sub api builder. +// PartyConfigurator is handler for configuring a party (it works with iris.Application) +type PartyConfigurator func(party Party) + // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. // Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun. // // Look the "APIBuilder" for its implementation. type Party interface { + // ConfigureParty configures this party like `iris.Application#Configure` + // That allows middlewares focused on the Party like CORS middleware + ConfigureParty(...PartyConfigurator) + // GetRelPath returns the current party's relative path. // i.e: // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". From 7a29519c5aa6eeb90660088952417da3e983afbd Mon Sep 17 00:00:00 2001 From: ZaniaDevelopper Date: Wed, 21 Feb 2018 14:48:09 +0300 Subject: [PATCH 04/23] Fixes bad copy/paste Former-commit-id: 437b60bc6b37d8a755198fb97809c61b9c18004e --- _examples/routing/fallback-handlers/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go index 3c1e8e78..66308ae4 100644 --- a/_examples/routing/fallback-handlers/main.go +++ b/_examples/routing/fallback-handlers/main.go @@ -11,11 +11,11 @@ func main() { // will handle *all* expect DELETE requests, even if there is no routes app.Get("/action/{p}", h) - app.Run(iris.Addr(":8080"), ctx.Method(), ctx.Path(), iris.WithoutServerError(iris.ErrServerClosed)) + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) } func h(ctx iris.Context) { - ctx.Writef("[%s] %s : Parameter = `%s`", ctx.Params().Get("p")) + ctx.Writef("[%s] %s : Parameter = `%s`", ctx.Method(), ctx.Path(), ctx.Params().Get("p")) } func fallbackHandler(ctx iris.Context) { From 1196680004fefaefa520f9f3429f3d45da8a4fe5 Mon Sep 17 00:00:00 2001 From: ZaniaDevelopper Date: Wed, 21 Feb 2018 15:19:09 +0300 Subject: [PATCH 05/23] Add comment in example Former-commit-id: 2b9d7a569ed4c5d78cd5dd49865df1cf381619fc --- _examples/experimental-handlers/cors/simple/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_examples/experimental-handlers/cors/simple/main.go b/_examples/experimental-handlers/cors/simple/main.go index a4557b20..94593b08 100644 --- a/_examples/experimental-handlers/cors/simple/main.go +++ b/_examples/experimental-handlers/cors/simple/main.go @@ -13,6 +13,8 @@ import ( func main() { app := iris.New() + + // `crs := cors.NewAllowAllPartyMiddleware()`, or: crs := cors.NewPartyMiddleware(cors.Options{ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. AllowedMethods: router.AllMethods[:], From 8517904bcfb7c1868ab0d6e05a4e5d9c90471ec0 Mon Sep 17 00:00:00 2001 From: Alexander Ermolaev Date: Thu, 22 Feb 2018 18:25:56 +0300 Subject: [PATCH 06/23] fix a typo Former-commit-id: 8b8e3eaff43019fc0503708e8bc33a2fdb6f1496 --- context/context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context/context.go b/context/context.go index dc3ecf80..782b12c4 100644 --- a/context/context.go +++ b/context/context.go @@ -3097,7 +3097,7 @@ func (ctx *context) Exec(method string, path string) { // backup the request path information backupPath := ctx.Path() - bakcupMethod := ctx.Method() + backupMethod := ctx.Method() // don't backupValues := ctx.Values().ReadOnly() // [sessions stays] @@ -3120,7 +3120,7 @@ func (ctx *context) Exec(method string, path string) { // set the request back to its previous state req.RequestURI = backupPath req.URL.Path = backupPath - req.Method = bakcupMethod + req.Method = backupMethod // don't fill the values in order to be able to communicate from and to. // // fill the values as they were before From 86bb29ed14021c6e5220b98ab6768f48d8ae7400 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Fri, 23 Feb 2018 02:15:38 +0200 Subject: [PATCH 07/23] remove unnecessary reflection usage on context#UnmarshalBody Former-commit-id: a7dc78027c5647b3b21098808f360a536d8b9438 --- context/context.go | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/context/context.go b/context/context.go index dc3ecf80..60e0702e 100644 --- a/context/context.go +++ b/context/context.go @@ -15,7 +15,6 @@ import ( "os" "path" "path/filepath" - "reflect" "regexp" "strconv" "strings" @@ -53,16 +52,16 @@ type ( Decode(data []byte) error } - // Unmarshaler is the interface implemented by types that can unmarshal any raw data - // TIP INFO: Any v object which implements the BodyDecoder can be override the unmarshaler. + // Unmarshaler is the interface implemented by types that can unmarshal any raw data. + // TIP INFO: Any pointer to a value which implements the BodyDecoder can be override the unmarshaler. Unmarshaler interface { - Unmarshal(data []byte, v interface{}) error + Unmarshal(data []byte, outPtr interface{}) error } // UnmarshalerFunc a shortcut for the Unmarshaler interface // // See 'Unmarshaler' and 'BodyDecoder' for more. - UnmarshalerFunc func(data []byte, v interface{}) error + UnmarshalerFunc func(data []byte, outPtr interface{}) error ) // Unmarshal parses the X-encoded data and stores the result in the value pointed to by v. @@ -596,16 +595,16 @@ type Context interface { // should be called before reading the request body from the client. SetMaxRequestBodySize(limitOverBytes int64) - // UnmarshalBody reads the request's body and binds it to a value or pointer of any type + // UnmarshalBody reads the request's body and binds it to a value or pointer of any type. // Examples of usage: context.ReadJSON, context.ReadXML. - UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error - // ReadJSON reads JSON from request's body and binds it to a value of any json-valid type. - ReadJSON(jsonObject interface{}) error - // ReadXML reads XML from request's body and binds it to a value of any xml-valid type. - ReadXML(xmlObject interface{}) error + UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error + // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. + ReadJSON(jsonObjectPtr interface{}) error + // ReadXML reads XML from request's body and binds it to a pointer of a value of any xml-valid type. + ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data // it supports any kind of struct. - ReadForm(formObject interface{}) error + ReadForm(formObjectPtr interface{}) error // +------------------------------------------------------------+ // | Body (raw) Writers | @@ -2008,7 +2007,7 @@ func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { // UnmarshalBody reads the request's body and binds it to a value or pointer of any type // Examples of usage: context.ReadJSON, context.ReadXML. -func (ctx *context) UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error { +func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error { if ctx.request.Body == nil { return errors.New("unmarshal: empty body") } @@ -2028,18 +2027,19 @@ func (ctx *context) UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error // in this case the v should be a pointer also, // but this is up to the user's custom Decode implementation* // - // See 'BodyDecoder' for more - if decoder, isDecoder := v.(BodyDecoder); isDecoder { + // See 'BodyDecoder' for more. + if decoder, isDecoder := outPtr.(BodyDecoder); isDecoder { return decoder.Decode(rawData) } - // check if v is already a pointer, if yes then pass as it's - if reflect.TypeOf(v).Kind() == reflect.Ptr { - return unmarshaler.Unmarshal(rawData, v) - } - // finally, if the v doesn't contains a self-body decoder and it's not a pointer - // use the custom unmarshaler to bind the body - return unmarshaler.Unmarshal(rawData, &v) + // // check if v is already a pointer, if yes then pass as it's + // if reflect.TypeOf(v).Kind() == reflect.Ptr { + // return unmarshaler.Unmarshal(rawData, v) + // } <- no need for that, ReadJSON is documented enough to receive a pointer, + // we don't need to reduce the performance here by using the reflect.TypeOf method. + + // f the v doesn't contains a self-body decoder use the custom unmarshaler to bind the body. + return unmarshaler.Unmarshal(rawData, outPtr) } func (ctx *context) shouldOptimize() bool { From 6de64d517e7a65cb0c4f4d832ce12e792de0bb55 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Fri, 23 Feb 2018 04:06:05 +0200 Subject: [PATCH 08/23] New: context#NextOr && context#NextOrNotFound and some performance improvements on the awesome https://github.com/kataras/iris/pull/909 pushed a while ago Former-commit-id: 35dd2ab80b69a5bea6f35f58e636bc11229d9921 --- README.md | 2 +- _examples/routing/fallback-handlers/main.go | 9 ++-- context/context.go | 50 ++++++++++++++++++++- core/router/api_builder.go | 6 +-- core/router/fallback_stack.go | 21 ++------- core/router/fallback_stack_test.go | 3 +- core/router/handler.go | 13 ++++-- 7 files changed, 70 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 096c1e6d..797dc194 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ First of all, the most correct way to begin with a web framework is to learn the Iris has a great collection of handlers[[1]](middleware/)[[2]](https://github.com/iris-contrib/middleware) that you can use side by side with your web apps. However you are not limited to them - you are free to use any third-party middleware that is compatible with the [net/http](https://golang.org/pkg/net/http/) package, [_examples/convert-handlers](_examples/convert-handlers) will show you the way. -Iris, unlike others, is 100% compatible with the standards and that's why the majority of the big companies that adapt Go to their workflow, like a very famous US Television Network, trust Iris; it's always up-to-date and it will be aligned with the std `net/http` package which is modernized by the Go Author on each new release of the Go Programming Language forever. +Iris, unlike others, is 100% compatible with the standards and that's why the majority of the big companies that adapt Go to their workflow, like a very famous US Television Network, trust Iris; it's up-to-date and it will be always aligned with the std `net/http` package which is modernized by the Go Authors on each new release of the Go Programming Language. ### Articles diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go index 66308ae4..72a07d14 100644 --- a/_examples/routing/fallback-handlers/main.go +++ b/_examples/routing/fallback-handlers/main.go @@ -1,14 +1,12 @@ package main -import ( - "github.com/kataras/iris" -) +import "github.com/kataras/iris" func main() { app := iris.New() // this works as expected now, - // will handle *all* expect DELETE requests, even if there is no routes + // will handle *all* expect DELETE requests, even if there is no routes. app.Get("/action/{p}", h) app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) @@ -20,8 +18,7 @@ func h(ctx iris.Context) { func fallbackHandler(ctx iris.Context) { if ctx.Method() == "DELETE" { - ctx.Next() - + ctx.NextOrNotFound() return } diff --git a/context/context.go b/context/context.go index d1cc75d6..b5001587 100644 --- a/context/context.go +++ b/context/context.go @@ -309,7 +309,21 @@ type Context interface { // // Note: Custom context should override this method in order to be able to pass its own context.Context implementation. Next() - // NextHandler returns(but it is NOT executes) the next handler from the handlers chain. + // NextOr checks if chain has a next handler, if so then it executes it + // otherwise it sets a new chain assigned to this Context based on the given handler(s) + // and executes its first handler. + // + // Returns true if next handler exists and executed, otherwise false. + // + // Note that if no next handler found and handlers are missing then + // it sends a Status Not Found (404) to the client and it stops the execution. + NextOr(handlers ...Handler) bool + // NextOrNotFound checks if chain has a next handler, if so then it executes it + // otherwise it sends a Status Not Found (404) to the client and stops the execution. + // + // Returns true if next handler exists and executed, otherwise false. + NextOrNotFound() bool + // NextHandler returns (it doesn't execute) the next handler from the handlers chain. // // Use .Skip() to skip this handler if needed to execute the next of this returning handler. NextHandler() Handler @@ -1262,7 +1276,39 @@ func (ctx *context) Next() { // or context.Next(ctx) Next(ctx) } -// NextHandler returns, but it doesn't executes, the next handler from the handlers chain. +// NextOr checks if chain has a next handler, if so then it executes it +// otherwise it sets a new chain assigned to this Context based on the given handler(s) +// and executes its first handler. +// +// Returns true if next handler exists and executed, otherwise false. +// +// Note that if no next handler found and handlers are missing then +// it sends a Status Not Found (404) to the client and it stops the execution. +func (ctx *context) NextOr(handlers ...Handler) bool { + if next := ctx.NextHandler(); next != nil { + next(ctx) + ctx.Skip() // skip this handler from the chain. + return true + } + + if len(handlers) == 0 { + ctx.NotFound() + ctx.StopExecution() + return false + } + + ctx.Do(handlers) + + return false +} + +// NextOrNotFound checks if chain has a next handler, if so then it executes it +// otherwise it sends a Status Not Found (404) to the client and stops the execution. +// +// Returns true if next handler exists and executed, otherwise false. +func (ctx *context) NextOrNotFound() bool { return ctx.NextOr() } + +// NextHandler returns (it doesn't execute) the next handler from the handlers chain. // // Use .Skip() to skip this handler if needed to execute the next of this returning handler. func (ctx *context) NextHandler() Handler { diff --git a/core/router/api_builder.go b/core/router/api_builder.go index ab3d3f2c..c5e0cc65 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -90,7 +90,7 @@ type APIBuilder struct { doneHandlers context.Handlers // global done handlers, order doesn't matter doneGlobalHandlers context.Handlers - // fallback stack, LIFO order + // fallback stack, LIFO order, initialized on first `Fallback`. fallbackStack *FallbackStack // the per-party relativePath string @@ -437,13 +437,13 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { // Fallback appends Handler(s) to the current fallback stack. // Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `Context.Next()` method is not called. +// if `context.NextOrNotFound()` method is not called. // Done & DoneGlobal Handlers are not called. func (api *APIBuilder) Fallback(middleware ...context.Handler) { api.fallbackStack.Add(middleware) } -// FallBackStack returns Fallback stack, this is implementation of interface RoutesProvider +// GetFallBackStack returns Fallback stack, this is implementation of interface RoutesProvider // that is used in Router building by the RequestHandler. func (api *APIBuilder) GetFallBackStack() *FallbackStack { return api.fallbackStack diff --git a/core/router/fallback_stack.go b/core/router/fallback_stack.go index 1b8531f8..9ff89dd6 100644 --- a/core/router/fallback_stack.go +++ b/core/router/fallback_stack.go @@ -1,16 +1,12 @@ package router -import ( - "net/http" - - "github.com/kataras/iris/context" -) +import "github.com/kataras/iris/context" // FallbackStack is a stack (with LIFO calling order) for fallback handlers // A fallback handler(s) is(are) called from Fallback stack // when no route found and before sending NotFound status. // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `Context.Next()` method is not called. +// if `context#NextOrNotFound()` method is not called. // Done & DoneGlobal Handlers are not called. type FallbackStack struct { parent *FallbackStack @@ -65,14 +61,5 @@ func (stk *FallbackStack) List() context.Handlers { return res } -// NewFallbackStack create a new Fallback stack with as first entry -// a handler which send NotFound status (the default) -func NewFallbackStack() *FallbackStack { - return &FallbackStack{ - handlers: context.Handlers{ - func(ctx context.Context) { - ctx.StatusCode(http.StatusNotFound) - }, - }, - } -} +// NewFallbackStack create a new empty Fallback stack. +func NewFallbackStack() *FallbackStack { return &FallbackStack{} } diff --git a/core/router/fallback_stack_test.go b/core/router/fallback_stack_test.go index 1499cd96..da229c89 100644 --- a/core/router/fallback_stack_test.go +++ b/core/router/fallback_stack_test.go @@ -94,8 +94,7 @@ func TestFallbackStackCall(t *testing.T) { // setup fallback handler app.Fallback(func(ctx context.Context) { if ctx.Method() != "GET" { - ctx.Next() - + ctx.NextOrNotFound() // it checks if we have next, otherwise fire 404 not found. return } diff --git a/core/router/handler.go b/core/router/handler.go index ce924493..18501b39 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -38,6 +38,11 @@ type routerHandler struct { trees []*tree hosts bool // true if at least one route contains a Subdomain. fallbackStack *FallbackStack + // on build: true if fallbackStack.Size() > 0, + // reduces the checks because fallbackStack is NEVER nil (api_builder.go always initializes it). + // If re-checked needed (serve-time fallback handler added) + // then a re-build/refresh of the application's router is necessary, as with every handler. + hasFallbackHandlers bool } var _ RequestHandler = &routerHandler{} @@ -93,6 +98,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { registeredRoutes := provider.GetRoutes() h.trees = h.trees[0:0] // reset, inneed when rebuilding. h.fallbackStack = provider.GetFallBackStack() + h.hasFallbackHandlers = h.fallbackStack.Size() > 0 // sort, subdomains goes first. sort.Slice(registeredRoutes, func(i, j int) bool { @@ -248,11 +254,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } - if h.fallbackStack == nil { - ctx.StatusCode(http.StatusNotFound) - } else { + if h.hasFallbackHandlers { ctx.Do(h.fallbackStack.List()) + return } + + ctx.StatusCode(http.StatusNotFound) } // RouteExists checks if a route exists From a1232c9a9a259849fa455867ca509d3422087b3d Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Fri, 23 Feb 2018 04:25:12 +0200 Subject: [PATCH 09/23] _examples: replace DELETE string with iris.MethodDelete, for important changes see: https://github.com/kataras/iris/pull/909 and https://github.com/kataras/iris/commit/6de64d517e7a65cb0c4f4d832ce12e792de0bb55 [formerly 35dd2ab80b69a5bea6f35f58e636bc11229d9921] Former-commit-id: d0f5785aff289b8c140e4f253eb555135e79603d --- _examples/experimental-handlers/cors/simple/main.go | 3 --- _examples/routing/fallback-handlers/main.go | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/_examples/experimental-handlers/cors/simple/main.go b/_examples/experimental-handlers/cors/simple/main.go index 94593b08..c149044a 100644 --- a/_examples/experimental-handlers/cors/simple/main.go +++ b/_examples/experimental-handlers/cors/simple/main.go @@ -5,19 +5,16 @@ package main import ( "github.com/kataras/iris" - "github.com/kataras/iris/core/router" "github.com/iris-contrib/middleware/cors" ) func main() { - app := iris.New() // `crs := cors.NewAllowAllPartyMiddleware()`, or: crs := cors.NewPartyMiddleware(cors.Options{ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. - AllowedMethods: router.AllMethods[:], AllowCredentials: true, }) diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go index 72a07d14..e9e6b2ec 100644 --- a/_examples/routing/fallback-handlers/main.go +++ b/_examples/routing/fallback-handlers/main.go @@ -17,7 +17,7 @@ func h(ctx iris.Context) { } func fallbackHandler(ctx iris.Context) { - if ctx.Method() == "DELETE" { + if ctx.Method() == iris.MethodDelete { ctx.NextOrNotFound() return } From abb10e9cbfe5b28b693e3a138e54be495538ed31 Mon Sep 17 00:00:00 2001 From: ZaniaDevelopper Date: Fri, 23 Feb 2018 09:24:35 +0300 Subject: [PATCH 10/23] Add a missing call for `fallbackHandler` Former-commit-id: a1ce94b39d326657cdbce34276477c34752f0000 --- _examples/routing/fallback-handlers/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go index 66308ae4..aa3c871b 100644 --- a/_examples/routing/fallback-handlers/main.go +++ b/_examples/routing/fallback-handlers/main.go @@ -7,6 +7,9 @@ import ( func main() { app := iris.New() + // add a fallback handler to process requests that would not be declared in the router. + app.Fallback(fallbackHandler) + // this works as expected now, // will handle *all* expect DELETE requests, even if there is no routes app.Get("/action/{p}", h) From a1aa1b66c63f4f4cbc2f65b794725e9bff9500bc Mon Sep 17 00:00:00 2001 From: Alexey McSakoff Date: Sun, 25 Feb 2018 12:28:17 +0400 Subject: [PATCH 11/23] Add wrappers for Pongo's AsValue() and AsSaveValue() Former-commit-id: 642c8709b7566dbe5827759155167ec4d82212e3 --- view/django.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/view/django.go b/view/django.go index 47817b98..d989a1a2 100644 --- a/view/django.go +++ b/view/django.go @@ -44,6 +44,18 @@ type ( TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) ) +// AsValue converts any given value to a view.Value. +// Usually being used within own functions passed to a template +// through a Context or within filter functions. +func AsValue(i interface{}) *Value { + return (*Value)(pongo2.AsValue(i)) +} + +// AsSafeValue works like AsValue, but does not apply the 'escape' filter. +func AsSafeValue(i interface{}) *Value { + return (*Value)(pongo2.AsSafeValue(i)) +} + // GetValue returns the `Value` as *pongo2.Value type. // This method was added by balthild at https://github.com/kataras/iris/pull/765 func (value *Value) GetValue() *pongo2.Value { From b20933e0bb5c1b9dc8231b26a33fb500e178d031 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Sun, 25 Feb 2018 12:14:49 +0200 Subject: [PATCH 12/23] upgrade go-bindata link to the most maintainable one Former-commit-id: 58d5253b3332acef480d36f0cf716f2ad6b1c09e --- _examples/file-server/embedding-files-into-app/main.go | 2 +- .../main.go | 2 +- .../embedded-single-page-application/main.go | 2 +- _examples/view/embedding-templates-into-app/main.go | 2 +- doc.go | 4 ++-- view/README.md | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/_examples/file-server/embedding-files-into-app/main.go b/_examples/file-server/embedding-files-into-app/main.go index 9f5125ae..f2ec4dab 100644 --- a/_examples/file-server/embedding-files-into-app/main.go +++ b/_examples/file-server/embedding-files-into-app/main.go @@ -5,7 +5,7 @@ import ( ) // Follow these steps first: -// $ go get -u github.com/jteeuwen/go-bindata/... +// $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./assets/... // $ go build // $ ./embedding-files-into-app diff --git a/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go b/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go index 57b3f352..14140f92 100644 --- a/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go +++ b/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go @@ -2,7 +2,7 @@ package main import "github.com/kataras/iris" -// $ go get -u github.com/jteeuwen/go-bindata/... +// $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./public/... // $ go build // $ ./embedded-single-page-application-with-other-routes diff --git a/_examples/file-server/single-page-application/embedded-single-page-application/main.go b/_examples/file-server/single-page-application/embedded-single-page-application/main.go index fbf9618c..2f709867 100644 --- a/_examples/file-server/single-page-application/embedded-single-page-application/main.go +++ b/_examples/file-server/single-page-application/embedded-single-page-application/main.go @@ -4,7 +4,7 @@ import ( "github.com/kataras/iris" ) -// $ go get -u github.com/jteeuwen/go-bindata/... +// $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./public/... // $ go build // $ ./embedded-single-page-application diff --git a/_examples/view/embedding-templates-into-app/main.go b/_examples/view/embedding-templates-into-app/main.go index 60e73940..69372f95 100644 --- a/_examples/view/embedding-templates-into-app/main.go +++ b/_examples/view/embedding-templates-into-app/main.go @@ -13,7 +13,7 @@ func main() { return "Greetings " + s + "!" }) - // $ go get -u github.com/jteeuwen/go-bindata/... + // $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./templates/... // $ go build // $ ./embedding-templates-into-app diff --git a/doc.go b/doc.go index 4ae3be38..b9eab9be 100644 --- a/doc.go +++ b/doc.go @@ -1121,7 +1121,7 @@ Example code: -View engine supports bundled(https://github.com/jteeuwen/go-bindata) template files too. +View engine supports bundled(https://github.com/shuLhan/go-bindata) template files too. go-bindata gives you two functions, asset and assetNames, these can be setted to each of the template engines using the `.Binary` func. @@ -1133,7 +1133,7 @@ Example code: func main() { app := iris.New() - // $ go get -u github.com/jteeuwen/go-bindata/... + // $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./templates/... // $ go build // $ ./embedding-templates-into-app diff --git a/view/README.md b/view/README.md index e054a77e..0ce6e74f 100644 --- a/view/README.md +++ b/view/README.md @@ -112,7 +112,7 @@ func hi(ctx iris.Context) { ## Embedded -View engine supports bundled(https://github.com/jteeuwen/go-bindata) template files too. +View engine supports bundled(https://github.com/shuLhan/go-bindata) template files too. `go-bindata` gives you two functions, `Assset` and `AssetNames`, these can be setted to each of the template engines using the `.Binary` function. @@ -125,7 +125,7 @@ import "github.com/kataras/iris" func main() { app := iris.New() - // $ go get -u github.com/jteeuwen/go-bindata/... + // $ go get -u github.com/shuLhan/go-bindata/... // $ go-bindata ./templates/... // $ go build // $ ./embedding-templates-into-app From 5c2edeebecb08c8c477437dc3a3b8135db27a720 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Sun, 25 Feb 2018 12:59:09 +0200 Subject: [PATCH 13/23] add go 1.10 in travis (Tip for other project managers: note the quotes, otherwise travis will act like go1.1 instead of go1.10) Former-commit-id: cf687301a86aab2ae8e8b198f4425234687ef4e3 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fc1c4272..b928da0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ os: - linux - osx go: - - go1.9 + - "go1.9" + - "go1.10" go_import_path: github.com/kataras/iris install: - go get ./... # for iris-contrib/httpexpect, kataras/golog From 1a4803307de61f7a4c2e9a95c59942c759787d86 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Mon, 5 Mar 2018 20:09:30 +0200 Subject: [PATCH 14/23] Add one more example for custom router macro functions, relative to https://github.com/kataras/iris/issues/918 Former-commit-id: 457c1a94dc8d93e614e9da70e1ec2482fe0c5765 --- _examples/routing/dynamic-path/main.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index b7e78c40..5a3f0b57 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -1,6 +1,7 @@ package main import ( + "regexp" "strconv" "github.com/kataras/iris" @@ -140,6 +141,24 @@ func main() { ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. + // Another example using a custom regexp and any custom logic. + latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" + latLonRegex, err := regexp.Compile(latLonExpr) + if err != nil { + panic(err) + } + + app.Macros().String.RegisterFunc("coordinate", func() func(paramName string) (ok bool) { + // MatchString is a type of func(string) bool, so we can return that as it's. + return latLonRegex.MatchString + }) + + app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { + ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) + }) + + // + // http://localhost:8080/game/a-zA-Z/level/0-9 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { From 83c4b7f52d167aed7cce087b0fc78d6adef07b3f Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Thu, 8 Mar 2018 05:21:16 +0200 Subject: [PATCH 15/23] add examples for read using custom decoder per type, read using custom decoder via `iris#UnmarshalerFunc` and to complete it add an example for the `context#ReadXML`. Former-commit-id: 536b1780f12d0b9d9ce9aa976a0f95f18634ec2d --- _examples/README.md | 7 +- .../http_request/read-custom-per-type/main.go | 65 +++++++++++++++++ .../read-custom-per-type/main_test.go | 17 +++++ .../read-custom-via-unmarshaler/main.go | 73 +++++++++++++++++++ .../read-custom-via-unmarshaler/main_test.go | 17 +++++ _examples/http_request/read-json/main.go | 14 ++-- _examples/http_request/read-xml/main.go | 50 +++++++++++++ _examples/http_request/read-xml/main_test.go | 18 +++++ .../http_responsewriter/herotemplate/app.go | 36 ++++----- _examples/tutorial/online-visitors/main.go | 8 +- context/context.go | 29 ++++++++ core/memstore/memstore.go | 2 +- doc.go | 2 +- go19.go | 10 +++ 14 files changed, 314 insertions(+), 34 deletions(-) create mode 100644 _examples/http_request/read-custom-per-type/main.go create mode 100644 _examples/http_request/read-custom-per-type/main_test.go create mode 100644 _examples/http_request/read-custom-via-unmarshaler/main.go create mode 100644 _examples/http_request/read-custom-via-unmarshaler/main_test.go create mode 100644 _examples/http_request/read-xml/main.go create mode 100644 _examples/http_request/read-xml/main_test.go diff --git a/_examples/README.md b/_examples/README.md index 2ba8c680..a53b4998 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -318,8 +318,11 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her ### How to Read from `context.Request() *http.Request` -- [Bind JSON](http_request/read-json/main.go) -- [Bind Form](http_request/read-form/main.go) +- [Read JSON](http_request/read-json/main.go) +- [Read XML](http_request/read-xml/main.go) +- [Read Form](http_request/read-form/main.go) +- [Read Custom per type](http_request/read-custom-per-type/main.go) +- [Read Custom via Unmarshaler](http_request/read-custom-via-unmarshaler/main.go) - [Upload/Read File](http_request/upload-file/main.go) - [Upload multiple files with an easy way](http_request/upload-files/main.go) diff --git a/_examples/http_request/read-custom-per-type/main.go b/_examples/http_request/read-custom-per-type/main.go new file mode 100644 index 00000000..2c339988 --- /dev/null +++ b/_examples/http_request/read-custom-per-type/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "gopkg.in/yaml.v2" + + "github.com/kataras/iris" +) + +func main() { + app := newApp() + + // use Postman or whatever to do a POST request + // (however you are always free to use app.Get and GET http method requests to read body of course) + // to the http://localhost:8080 with RAW BODY: + /* + addr: localhost:8080 + serverName: Iris + */ + // + // The response should be: + // Received: main.config{Addr:"localhost:8080", ServerName:"Iris"} + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) +} + +func newApp() *iris.Application { + app := iris.New() + app.Post("/", handler) + + return app +} + +// simple yaml stuff, read more at https://github.com/go-yaml/yaml +type config struct { + Addr string `yaml:"addr"` + ServerName string `yaml:"serverName"` +} + +// Decode implements the `kataras/iris/context#BodyDecoder` optional interface +// that any go type can implement in order to be self-decoded when reading the request's body. +func (c *config) Decode(body []byte) error { + return yaml.Unmarshal(body, c) +} + +func handler(ctx iris.Context) { + var c config + + // + // Note: + // second parameter is nil because our &c implements the `context#BodyDecoder` + // which has a priority over the context#Unmarshaler (which can be a more global option for reading request's body) + // see the `http_request/read-custom-via-unmarshaler/main.go` example to learn how to use the context#Unmarshaler too. + // + // Note 2: + // If you need to read the body again for any reason + // you should disable the body consumption via `app.Run(..., iris.WithoutBodyConsumptionOnUnmarshal)`. + // + + if err := ctx.UnmarshalBody(&c, nil); err != nil { + ctx.StatusCode(iris.StatusBadRequest) + ctx.WriteString(err.Error()) + return + } + + ctx.Writef("Received: %#+v", c) +} diff --git a/_examples/http_request/read-custom-per-type/main_test.go b/_examples/http_request/read-custom-per-type/main_test.go new file mode 100644 index 00000000..323b08b3 --- /dev/null +++ b/_examples/http_request/read-custom-per-type/main_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" + + "github.com/kataras/iris/httptest" +) + +func TestReadCustomPerType(t *testing.T) { + app := newApp() + e := httptest.New(t, app) + + expectedResponse := `Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}` + + e.POST("/").WithText("addr: localhost:8080\nserverName: Iris").Expect(). + Status(httptest.StatusOK).Body().Equal(expectedResponse) +} diff --git a/_examples/http_request/read-custom-via-unmarshaler/main.go b/_examples/http_request/read-custom-via-unmarshaler/main.go new file mode 100644 index 00000000..4c4bcf55 --- /dev/null +++ b/_examples/http_request/read-custom-via-unmarshaler/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "gopkg.in/yaml.v2" + + "github.com/kataras/iris" +) + +func main() { + app := newApp() + + // use Postman or whatever to do a POST request + // (however you are always free to use app.Get and GET http method requests to read body of course) + // to the http://localhost:8080 with RAW BODY: + /* + addr: localhost:8080 + serverName: Iris + */ + // + // The response should be: + // Received: main.config{Addr:"localhost:8080", ServerName:"Iris"} + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) +} + +func newApp() *iris.Application { + app := iris.New() + app.Post("/", handler) + + return app +} + +// simple yaml stuff, read more at https://github.com/go-yaml/yaml +type config struct { + Addr string `yaml:"addr"` + ServerName string `yaml:"serverName"` +} + +/* +type myBodyDecoder struct{} + +var DefaultBodyDecoder = myBodyDecoder{} + +// Implements the `kataras/iris/context#Unmarshaler` but at our example +// we will use the simplest `context#UnmarshalerFunc` to pass just the yaml.Unmarshal. +// +// Can be used as: ctx.UnmarshalBody(&c, DefaultBodyDecoder) +func (r *myBodyDecoder) Unmarshal(data []byte, outPtr interface{}) error { + return yaml.Unmarshal(data, outPtr) +} +*/ + +func handler(ctx iris.Context) { + var c config + + // + // Note: + // yaml.Unmarshal already implements the `context#Unmarshaler` + // so we can use it directly, like the json.Unmarshal(ctx.ReadJSON), xml.Unmarshal(ctx.ReadXML) + // and every library which follows the best practises and is aligned with the Go standards. + // + // Note 2: + // If you need to read the body again for any reason + // you should disable the body consumption via `app.Run(..., iris.WithoutBodyConsumptionOnUnmarshal)`. + // + + if err := ctx.UnmarshalBody(&c, iris.UnmarshalerFunc(yaml.Unmarshal)); err != nil { + ctx.StatusCode(iris.StatusBadRequest) + ctx.WriteString(err.Error()) + return + } + + ctx.Writef("Received: %#+v", c) +} diff --git a/_examples/http_request/read-custom-via-unmarshaler/main_test.go b/_examples/http_request/read-custom-via-unmarshaler/main_test.go new file mode 100644 index 00000000..a068a65d --- /dev/null +++ b/_examples/http_request/read-custom-via-unmarshaler/main_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" + + "github.com/kataras/iris/httptest" +) + +func TestReadCustomViaUnmarshaler(t *testing.T) { + app := newApp() + e := httptest.New(t, app) + + expectedResponse := `Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}` + + e.POST("/").WithText("addr: localhost:8080\nserverName: Iris").Expect(). + Status(httptest.StatusOK).Body().Equal(expectedResponse) +} diff --git a/_examples/http_request/read-json/main.go b/_examples/http_request/read-json/main.go index fb4ff396..88f84407 100644 --- a/_examples/http_request/read-json/main.go +++ b/_examples/http_request/read-json/main.go @@ -11,16 +11,18 @@ type Company struct { } func MyHandler(ctx iris.Context) { - c := &Company{} - if err := ctx.ReadJSON(c); err != nil { + var c Company + + if err := ctx.ReadJSON(&c); err != nil { ctx.StatusCode(iris.StatusBadRequest) ctx.WriteString(err.Error()) return } - ctx.Writef("Received: %#v\n", c) + ctx.Writef("Received: %#+v\n", c) } +// simple json stuff, read more at https://golang.org/pkg/encoding/json type Person struct { Name string `json:"name"` Age int `json:"age"` @@ -55,9 +57,9 @@ func main() { "Other": "Something here" } */ - // and Content-Type to application/json + // and Content-Type to application/json (optionally but good practise) // // The response should be: - // Received: &main.Company{Name:"iris-Go", City:"New York", Other:"Something here"} - app.Run(iris.Addr(":8080")) + // Received: main.Company{Name:"iris-Go", City:"New York", Other:"Something here"} + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) } diff --git a/_examples/http_request/read-xml/main.go b/_examples/http_request/read-xml/main.go new file mode 100644 index 00000000..5261bcfa --- /dev/null +++ b/_examples/http_request/read-xml/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "encoding/xml" + + "github.com/kataras/iris" +) + +func main() { + app := newApp() + + // use Postman or whatever to do a POST request + // to the http://localhost:8080 with RAW BODY: + /* + + Description of this person, the body of this inner element. + + */ + // and Content-Type to application/xml (optionally but good practise) + // + // The response should be: + // Received: main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Winston Churchill", Age:90, Description:"Description of this person, the body of this inner element."} + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations) +} + +func newApp() *iris.Application { + app := iris.New() + app.Post("/", handler) + + return app +} + +// simple xml stuff, read more at https://golang.org/pkg/encoding/xml +type person struct { + XMLName xml.Name `xml:"person"` // element name + Name string `xml:"name,attr"` // ,attr for attribute. + Age int `xml:"age,attr"` // ,attr attribute. + Description string `xml:"description"` // inner element name, value is its body. +} + +func handler(ctx iris.Context) { + var p person + if err := ctx.ReadXML(&p); err != nil { + ctx.StatusCode(iris.StatusBadRequest) + ctx.WriteString(err.Error()) + return + } + + ctx.Writef("Received: %#+v", p) +} diff --git a/_examples/http_request/read-xml/main_test.go b/_examples/http_request/read-xml/main_test.go new file mode 100644 index 00000000..da4fbd7a --- /dev/null +++ b/_examples/http_request/read-xml/main_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "testing" + + "github.com/kataras/iris/httptest" +) + +func TestReadXML(t *testing.T) { + app := newApp() + e := httptest.New(t, app) + + expectedResponse := `Received: main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Winston Churchill", Age:90, Description:"Description of this person, the body of this inner element."}` + send := `Description of this person, the body of this inner element.` + + e.POST("/").WithText(send).Expect(). + Status(httptest.StatusOK).Body().Equal(expectedResponse) +} diff --git a/_examples/http_responsewriter/herotemplate/app.go b/_examples/http_responsewriter/herotemplate/app.go index 4f1412ac..f948f71d 100644 --- a/_examples/http_responsewriter/herotemplate/app.go +++ b/_examples/http_responsewriter/herotemplate/app.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "log" "github.com/kataras/iris/_examples/http_responsewriter/herotemplate/template" @@ -13,11 +12,15 @@ import ( // $ go run app.go // // Read more at https://github.com/shiyanhui/hero/hero + func main() { app := iris.New() app.Get("/users", func(ctx iris.Context) { + ctx.Gzip(true) + ctx.ContentType("text/html") + var userList = []string{ "Alice", "Bob", @@ -25,30 +28,27 @@ func main() { } // Had better use buffer sync.Pool. - // Hero exports GetBuffer and PutBuffer for this. + // Hero(github.com/shiyanhui/hero/hero) exports GetBuffer and PutBuffer for this. // // buffer := hero.GetBuffer() // defer hero.PutBuffer(buffer) - buffer := new(bytes.Buffer) - template.UserList(userList, buffer) - - if _, err := ctx.Write(buffer.Bytes()); err != nil { - log.Printf("ERR: %s\n", err) - } - }) - - app.Get("/users2", func(ctx iris.Context) { - var userList = []string{ - "Alice", - "Bob", - "Tom", - } + // buffer := new(bytes.Buffer) + // template.UserList(userList, buffer) + // ctx.Write(buffer.Bytes()) // using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool), // iris context implements the io.Writer by its ResponseWriter // which is an enhanced version of the standard http.ResponseWriter - // but still 100% compatible. - template.UserListToWriter(userList, ctx) + // but still 100% compatible, GzipResponseWriter too: + // _, err := template.UserListToWriter(userList, ctx.GzipResponseWriter()) + buffer := new(bytes.Buffer) + template.UserList(userList, buffer) + + _, err := ctx.Write(buffer.Bytes()) + if err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.WriteString(err.Error()) + } }) app.Run(iris.Addr(":8080")) diff --git a/_examples/tutorial/online-visitors/main.go b/_examples/tutorial/online-visitors/main.go index 023bda63..e6f3fa11 100644 --- a/_examples/tutorial/online-visitors/main.go +++ b/_examples/tutorial/online-visitors/main.go @@ -58,15 +58,11 @@ func (v *pageView) increment() { } func (v *pageView) decrement() { - oldCount := v.count - if oldCount > 0 { - atomic.StoreUint64(&v.count, oldCount-1) - } + atomic.AddUint64(&v.count, ^uint64(0)) } func (v *pageView) getCount() uint64 { - val := atomic.LoadUint64(&v.count) - return val + return atomic.LoadUint64(&v.count) } type ( diff --git a/context/context.go b/context/context.go index b5001587..e083306f 100644 --- a/context/context.go +++ b/context/context.go @@ -48,6 +48,8 @@ type ( // // Note: This is totally optionally, the default decoders // for ReadJSON is the encoding/json and for ReadXML is the encoding/xml. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-per-type/main.go BodyDecoder interface { Decode(data []byte) error } @@ -61,6 +63,8 @@ type ( // UnmarshalerFunc a shortcut for the Unmarshaler interface // // See 'Unmarshaler' and 'BodyDecoder' for more. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go UnmarshalerFunc func(data []byte, outPtr interface{}) error ) @@ -563,6 +567,8 @@ type Context interface { // // The default form's memory maximum size is 32MB, it can be changed by the // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. + // + // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-file FormFile(key string) (multipart.File, *multipart.FileHeader, error) // UploadFormFiles uploads any received file(s) from the client // to the system physical location "destDirectory". @@ -587,6 +593,9 @@ type Context interface { // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. // // See `FormFile` to a more controlled to receive a file. + // + // + // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-files UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) // +------------------------------------------------------------+ @@ -611,13 +620,21 @@ type Context interface { // UnmarshalBody reads the request's body and binds it to a value or pointer of any type. // Examples of usage: context.ReadJSON, context.ReadXML. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-json/main.go ReadJSON(jsonObjectPtr interface{}) error // ReadXML reads XML from request's body and binds it to a pointer of a value of any xml-valid type. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data // it supports any kind of struct. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error // +------------------------------------------------------------+ @@ -1932,6 +1949,8 @@ func (ctx *context) PostValues(name string) []string { // // The default form's memory maximum size is 32MB, it can be changed by the // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. +// +// Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-file func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { // we don't have access to see if the request is body stream // and then the ParseMultipartForm can be useless @@ -1965,6 +1984,8 @@ func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. // // See `FormFile` to a more controlled to receive a file. +// +// Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-files func (ctx *context) UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) { err = ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()) if err != nil { @@ -2056,6 +2077,8 @@ func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { // UnmarshalBody reads the request's body and binds it to a value or pointer of any type // Examples of usage: context.ReadJSON, context.ReadXML. +// +// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error { if ctx.request.Body == nil { return errors.New("unmarshal: empty body") @@ -2096,6 +2119,8 @@ func (ctx *context) shouldOptimize() bool { } // ReadJSON reads JSON from request's body and binds it to a value of any json-valid type. +// +// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-json/main.go func (ctx *context) ReadJSON(jsonObject interface{}) error { var unmarshaler = json.Unmarshal if ctx.shouldOptimize() { @@ -2105,6 +2130,8 @@ func (ctx *context) ReadJSON(jsonObject interface{}) error { } // ReadXML reads XML from request's body and binds it to a value of any xml-valid type. +// +// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go func (ctx *context) ReadXML(xmlObject interface{}) error { return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal)) } @@ -2115,6 +2142,8 @@ var ( // ReadForm binds the formObject with the form data // it supports any kind of struct. +// +// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if values == nil { diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 2e161560..0beec0e6 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -180,7 +180,7 @@ func (e Entry) BoolDefault(def bool) (bool, error) { // respects the immutable. func (e Entry) Value() interface{} { if e.immutable { - // take its value, no pointer even if setted with a rreference. + // take its value, no pointer even if setted with a reference. vv := reflect.Indirect(reflect.ValueOf(e.ValueRaw)) // return copy of that slice diff --git a/doc.go b/doc.go index b9eab9be..e4bd5942 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -10.0.0 +10.2.1 Installation diff --git a/go19.go b/go19.go index 8a16665e..adcd8a4f 100644 --- a/go19.go +++ b/go19.go @@ -17,6 +17,16 @@ type ( // Developers send responses to the client's request through a Context. // Developers get request information from the client's request by a Context. Context = context.Context + // UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type + // which implements the `context#Unmarshaler` interface for reading request's body + // via custom decoders, most of them already implement the `context#UnmarshalerFunc` + // like the json.Unmarshal, xml.Unmarshal, yaml.Unmarshal and every library which + // follows the best practises and is aligned with the Go standards. + // + // See 'context#UnmarshalBody` for more. + // + // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go + UnmarshalerFunc = context.UnmarshalerFunc // A Handler responds to an HTTP request. // It writes reply headers and data to the Context.ResponseWriter() and then return. // Returning signals that the request is finished; From 1165b4527a1f1c9fed72b43bbf31e9ee75fe12fd Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Thu, 8 Mar 2018 20:55:58 +0200 Subject: [PATCH 16/23] fix https://github.com/kataras/iris/issues/921 Former-commit-id: f8560514a7b48f83121ddd21d74b4016af4e1b67 --- configuration_test.go | 2 +- core/router/handler.go | 8 ++++++++ sessions/sessiondb/redis/service/service.go | 7 +++---- view/handlebars.go | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/configuration_test.go b/configuration_test.go index f5aed4f7..61915011 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -30,7 +30,7 @@ func TestConfigurationStatic(t *testing.T) { afterNew = *app.config - if app.config.DisableBodyConsumptionOnUnmarshal == false { + if !app.config.DisableBodyConsumptionOnUnmarshal { t.Fatalf("Passing a Configuration field as Option fails, expected DisableBodyConsumptionOnUnmarshal to be true but was false") } diff --git a/core/router/handler.go b/core/router/handler.go index 18501b39..b9b79202 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -172,6 +172,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { r.URL.Path = path url := r.URL.String() + // Fixes https://github.com/kataras/iris/issues/921 + // This is caused for security reasons, imagine a payment shop, + // you can't just permantly redirect a POST request, so just 307 (RFC 7231, 6.4.7). + if method == http.MethodPost || method == http.MethodPut { + ctx.Redirect(url, http.StatusTemporaryRedirect) + return + } + ctx.Redirect(url, http.StatusMovedPermanently) // RFC2616 recommends that a short note "SHOULD" be included in the diff --git a/sessions/sessiondb/redis/service/service.go b/sessions/sessiondb/redis/service/service.go index e48e63de..34d4f8db 100644 --- a/sessions/sessiondb/redis/service/service.go +++ b/sessions/sessiondb/redis/service/service.go @@ -128,10 +128,9 @@ func (r *Service) GetBytes(key string) ([]byte, error) { func (r *Service) Delete(key string) error { c := r.pool.Get() defer c.Close() - if _, err := c.Do("DEL", r.Config.Prefix+key); err != nil { - return err - } - return nil + + _, err := c.Do("DEL", r.Config.Prefix+key) + return err } func dial(network string, addr string, pass string) (redis.Conn, error) { diff --git a/view/handlebars.go b/view/handlebars.go index bfede2be..68b1fcee 100644 --- a/view/handlebars.go +++ b/view/handlebars.go @@ -128,7 +128,7 @@ func (s *HandlebarsEngine) loadDirectory() error { // instead of the html/template engine which works like {{ render "myfile.html"}} and accepts the parent binding, with handlebars we can't do that because of lack of runtime helpers (dublicate error) var templateErr error - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + filepath.Walk(dir, func(path string, info os.FileInfo, _ error) error { if info == nil || info.IsDir() { return nil } From 4993918a121c07921b93860aacbaa6ba73563809 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Sat, 10 Mar 2018 14:22:56 +0200 Subject: [PATCH 17/23] remove the complicated fallback handlers, that didn't work and not approve the coblexity addition of the https://github.com/kataras/iris/pull/919, RouteExists accepts first argument the Context, add new AllowMethods per party and fix cors by https://github.com/iris-contrib/middleware/commit/048e2be034ed172c6754448b8a54a9c55debad46 https://github.com/kataras/iris/issues/922, rel: https://github.com/iris-contrib/middleware/issues/36, https://github.com/iris-contrib/middleware/issues/34, https://github.com/iris-contrib/middleware/issues/32, https://github.com/iris-contrib/middleware/issues/30, https://github.com/kataras/iris/pull/909 Former-commit-id: 5576c44b64014fb00dd79e618b815b5f52b705e4 --- .../experimental-handlers/cors/simple/main.go | 17 +-- _examples/routing/fallback-handlers/main.go | 29 ----- context/application.go | 5 +- context/context.go | 101 ++++++++-------- core/router/api_builder.go | 100 ++++++++-------- core/router/fallback_stack.go | 65 ---------- core/router/fallback_stack_test.go | 111 ------------------ core/router/handler.go | 28 ++--- core/router/party.go | 25 ++-- core/router/router.go | 7 +- mvc/mvc.go | 7 -- 11 files changed, 131 insertions(+), 364 deletions(-) delete mode 100644 _examples/routing/fallback-handlers/main.go delete mode 100644 core/router/fallback_stack.go delete mode 100644 core/router/fallback_stack_test.go diff --git a/_examples/experimental-handlers/cors/simple/main.go b/_examples/experimental-handlers/cors/simple/main.go index c149044a..ea8f4e70 100644 --- a/_examples/experimental-handlers/cors/simple/main.go +++ b/_examples/experimental-handlers/cors/simple/main.go @@ -1,7 +1,6 @@ package main -// $ go get github.com/rs/cors -// $ go run main.go +// go get -u github.com/iris-contrib/middleware/... import ( "github.com/kataras/iris" @@ -12,14 +11,12 @@ import ( func main() { app := iris.New() - // `crs := cors.NewAllowAllPartyMiddleware()`, or: - crs := cors.NewPartyMiddleware(cors.Options{ + crs := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. AllowCredentials: true, }) - v1 := app.Party("/api/v1") - v1.ConfigureParty(crs) + v1 := app.Party("/api/v1", crs).AllowMethods(iris.MethodOptions) // <- important for the preflight. { v1.Get("/home", func(ctx iris.Context) { ctx.WriteString("Hello from /home") @@ -38,13 +35,5 @@ func main() { }) } - // or use that to wrap the entire router - // even before the path and method matching - // this should work better and with all cors' features. - // Use that instead, if suits you. - // app.WrapRouter(cors.WrapNext(cors.Options{ - // AllowedOrigins: []string{"*"}, - // AllowCredentials: true, - // })) app.Run(iris.Addr("localhost:8080")) } diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go deleted file mode 100644 index 6d2b2d2d..00000000 --- a/_examples/routing/fallback-handlers/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import "github.com/kataras/iris" - -func main() { - app := iris.New() - - // add a fallback handler to process requests that would not be declared in the router. - app.Fallback(fallbackHandler) - - // this works as expected now, - // will handle *all* expect DELETE requests, even if there is no routes. - app.Get("/action/{p}", h) - - app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) -} - -func h(ctx iris.Context) { - ctx.Writef("[%s] %s : Parameter = `%s`", ctx.Method(), ctx.Path(), ctx.Params().Get("p")) -} - -func fallbackHandler(ctx iris.Context) { - if ctx.Method() == iris.MethodDelete { - ctx.NextOrNotFound() - return - } - - ctx.Writef("[%s] %s : From fallback handler", ctx.Method(), ctx.Path()) -} diff --git a/context/application.go b/context/application.go index 6ac8bd1f..9a94fbcb 100644 --- a/context/application.go +++ b/context/application.go @@ -49,6 +49,7 @@ type Application interface { // then it creates & registers a new trivial handler on the-fly. FireErrorCode(ctx Context) - // RouteExists checks if a route exists - RouteExists(method string, path string, ctx Context) bool + // RouteExists reports whether a particular route exists + // It will search from the current subdomain of context's host, if not inside the root domain. + RouteExists(ctx Context, method, path string) bool } diff --git a/context/context.go b/context/context.go index e083306f..040258ee 100644 --- a/context/context.go +++ b/context/context.go @@ -910,7 +910,7 @@ type Context interface { // TransactionsSkipped returns true if the transactions skipped or canceled at all. TransactionsSkipped() bool - // Exec calls the framewrok's ServeCtx + // Exec calls the `context/Application#ServeCtx` // based on this context but with a changed method and path // like it was requested by the user, but it is not. // @@ -933,10 +933,11 @@ type Context interface { // Context's Values and the Session are kept in order to be able to communicate via the result route. // // It's for extreme use cases, 99% of the times will never be useful for you. - Exec(method string, path string) + Exec(method, path string) - // RouteExists checks if a route exists - RouteExists(method string, path string) bool + // RouteExists reports whether a particular route exists + // It will search from the current subdomain of context's host, if not inside the root domain. + RouteExists(method, path string) bool // Application returns the iris app instance which belongs to this context. // Worth to notice that this function returns an interface @@ -3164,53 +3165,57 @@ func (ctx *context) TransactionsSkipped() bool { // // It's for extreme use cases, 99% of the times will never be useful for you. func (ctx *context) Exec(method string, path string) { - if path != "" { - if method == "" { - method = "GET" - } - - // backup the handlers - backupHandlers := ctx.Handlers()[0:] - backupPos := ctx.HandlerIndex(-1) - - // backup the request path information - backupPath := ctx.Path() - backupMethod := ctx.Method() - // don't backupValues := ctx.Values().ReadOnly() - - // [sessions stays] - // [values stays] - // reset handlers - ctx.SetHandlers(nil) - - req := ctx.Request() - // set the request to be align with the 'againstRequestPath' - req.RequestURI = path - req.URL.Path = path - req.Method = method - // execute the route from the (internal) context router - // this way we keep the sessions and the values - ctx.Application().ServeHTTPC(ctx) - - // set back the old handlers and the last known index - ctx.SetHandlers(backupHandlers) - ctx.HandlerIndex(backupPos) - // set the request back to its previous state - req.RequestURI = backupPath - req.URL.Path = backupPath - req.Method = backupMethod - - // don't fill the values in order to be able to communicate from and to. - // // fill the values as they were before - // backupValues.Visit(func(key string, value interface{}) { - // ctx.Values().Set(key, value) - // }) + if path == "" { + return } + + if method == "" { + method = "GET" + } + + // backup the handlers + backupHandlers := ctx.Handlers()[0:] + backupPos := ctx.HandlerIndex(-1) + + // backup the request path information + backupPath := ctx.Path() + backupMethod := ctx.Method() + // don't backupValues := ctx.Values().ReadOnly() + + // [values stays] + // reset handlers + ctx.SetHandlers(nil) + + req := ctx.Request() + // set the request to be align with the 'againstRequestPath' + req.RequestURI = path + req.URL.Path = path + req.Method = method + req.Host = req.Host + + // execute the route from the (internal) context router + // this way we keep the sessions and the values + ctx.Application().ServeHTTPC(ctx) + + // set back the old handlers and the last known index + ctx.SetHandlers(backupHandlers) + ctx.HandlerIndex(backupPos) + // set the request back to its previous state + req.RequestURI = backupPath + req.URL.Path = backupPath + req.Method = backupMethod + + // don't fill the values in order to be able to communicate from and to. + // // fill the values as they were before + // backupValues.Visit(func(key string, value interface{}) { + // ctx.Values().Set(key, value) + // }) } -// RouteExists checks if a route exists -func (ctx *context) RouteExists(method string, path string) bool { - return ctx.Application().RouteExists(method, path, ctx) +// RouteExists reports whether a particular route exists +// It will search from the current subdomain of context's host, if not inside the root domain. +func (ctx *context) RouteExists(method, path string) bool { + return ctx.Application().RouteExists(ctx, method, path) } // Application returns the iris app instance which belongs to this context. diff --git a/core/router/api_builder.go b/core/router/api_builder.go index c5e0cc65..739641a1 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -42,6 +42,12 @@ type repository struct { } func (r *repository) register(route *Route) { + for _, r := range r.routes { + if r.String() == route.String() { + return // do not register any duplicates, the sooner the better. + } + } + r.routes = append(r.routes, route) } @@ -90,14 +96,17 @@ type APIBuilder struct { doneHandlers context.Handlers // global done handlers, order doesn't matter doneGlobalHandlers context.Handlers - // fallback stack, LIFO order, initialized on first `Fallback`. - fallbackStack *FallbackStack // the per-party relativePath string + // allowMethods are filled with the `AllowMethods` func. + // They are used to create new routes + // per any party's (and its children) routes registered + // if the method "x" wasn't registered already via the `Handle` (and its extensions like `Get`, `Post`...). + allowMethods []string } -var _ Party = &APIBuilder{} -var _ RoutesProvider = &APIBuilder{} // passed to the default request handler (routerHandler) +var _ Party = (*APIBuilder)(nil) +var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handler (routerHandler) // NewAPIBuilder creates & returns a new builder // which is responsible to build the API and the router handler. @@ -108,20 +117,11 @@ func NewAPIBuilder() *APIBuilder { reporter: errors.NewReporter(), relativePath: "/", routes: new(repository), - fallbackStack: NewFallbackStack(), } return api } -// ConfigureParty configures this party like `iris.Application#Configure` -// That allows middlewares focused on the Party like CORS middleware -func (api *APIBuilder) ConfigureParty(conf ...PartyConfigurator) { - for _, h := range conf { - h(api) - } -} - // GetRelPath returns the current party's relative path. // i.e: // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". @@ -140,6 +140,16 @@ func (api *APIBuilder) GetReporter() *errors.Reporter { return api.reporter } +// AllowMethods will re-register the future routes that will be registered +// via `Handle`, `Get`, `Post`, ... to the given "methods" on that Party and its children "Parties", +// duplicates are not registered. +// +// Call of `AllowMethod` will override any previous allow methods. +func (api *APIBuilder) AllowMethods(methods ...string) Party { + api.allowMethods = methods + return api +} + // Handle registers a route to the server's api. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // @@ -181,23 +191,30 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co // here we separate the subdomain and relative path subdomain, path := splitSubdomainAndPath(fullpath) - r, err := NewRoute(method, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros) - if err != nil { // template path parser errors: - api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path) - return nil + // if allowMethods are empty, then simply register with the passed, main, method. + methods := append(api.allowMethods, method) + + var ( + route *Route // the latest one is this route registered, see methods append. + err error // not used outside of loop scope. + ) + + for _, m := range methods { + route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros) + if err != nil { // template path parser errors: + api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path) + return nil // fail on first error. + } + + // Add UseGlobal & DoneGlobal Handlers + route.use(api.beginGlobalHandlers) + route.done(api.doneGlobalHandlers) + + // global + api.routes.register(route) } - // Add UseGlobal & DoneGlobal Handlers - r.use(api.beginGlobalHandlers) - r.done(api.doneGlobalHandlers) - - // global - api.routes.register(r) - - // per -party, used for done handlers - // api.apiRoutes = append(api.apiRoutes, r) - - return r + return route } // HandleMany works like `Handle` but can receive more than one @@ -270,6 +287,10 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P // append the parent's + child's handlers middleware := joinHandlers(api.middleware, handlers) + // the allow methods per party and its children. + allowMethods := make([]string, len(api.allowMethods)) + copy(allowMethods, api.allowMethods) + return &APIBuilder{ // global/api builder macros: api.macros, @@ -279,10 +300,10 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P doneGlobalHandlers: api.doneGlobalHandlers, reporter: api.reporter, // per-party/children - middleware: middleware, - doneHandlers: api.doneHandlers, - fallbackStack: api.fallbackStack.Fork(), - relativePath: fullpath, + middleware: middleware, + doneHandlers: api.doneHandlers[0:], + relativePath: fullpath, + allowMethods: allowMethods, } } @@ -434,21 +455,6 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...) } -// Fallback appends Handler(s) to the current fallback stack. -// Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. -// Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `context.NextOrNotFound()` method is not called. -// Done & DoneGlobal Handlers are not called. -func (api *APIBuilder) Fallback(middleware ...context.Handler) { - api.fallbackStack.Add(middleware) -} - -// GetFallBackStack returns Fallback stack, this is implementation of interface RoutesProvider -// that is used in Router building by the RequestHandler. -func (api *APIBuilder) GetFallBackStack() *FallbackStack { - return api.fallbackStack -} - // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, // note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. // diff --git a/core/router/fallback_stack.go b/core/router/fallback_stack.go deleted file mode 100644 index 9ff89dd6..00000000 --- a/core/router/fallback_stack.go +++ /dev/null @@ -1,65 +0,0 @@ -package router - -import "github.com/kataras/iris/context" - -// FallbackStack is a stack (with LIFO calling order) for fallback handlers -// A fallback handler(s) is(are) called from Fallback stack -// when no route found and before sending NotFound status. -// Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `context#NextOrNotFound()` method is not called. -// Done & DoneGlobal Handlers are not called. -type FallbackStack struct { - parent *FallbackStack - handlers context.Handlers -} - -// _size is a terminal recursive method for computing size the stack -func (stk *FallbackStack) _size(i int) int { - res := i + len(stk.handlers) - - if stk.parent == nil { - return res - } - - return stk.parent._size(res) -} - -// populate is a recursive method for concatenating handlers to `list` parameter -func (stk *FallbackStack) populate(list context.Handlers) { - n := copy(list, stk.handlers) - - if stk.parent != nil { - stk.parent.populate(list[n:]) - } -} - -// Size gives the size of the full stack hierarchy -func (stk *FallbackStack) Size() int { - return stk._size(0) -} - -// Add appends handlers to the beginning of the stack to have a LIFO calling order -func (stk *FallbackStack) Add(h context.Handlers) { - stk.handlers = append(stk.handlers, h...) - - copy(stk.handlers[len(h):], stk.handlers) - copy(stk.handlers, h) -} - -// Fork make a new stack from this stack, and so create a stack child (leaf from a tree of stacks) -func (stk *FallbackStack) Fork() *FallbackStack { - return &FallbackStack{ - parent: stk, - } -} - -// List concatenate all handlers in stack hierarchy -func (stk *FallbackStack) List() context.Handlers { - res := make(context.Handlers, stk.Size()) - stk.populate(res) - - return res -} - -// NewFallbackStack create a new empty Fallback stack. -func NewFallbackStack() *FallbackStack { return &FallbackStack{} } diff --git a/core/router/fallback_stack_test.go b/core/router/fallback_stack_test.go deleted file mode 100644 index da229c89..00000000 --- a/core/router/fallback_stack_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package router_test - -import ( - "testing" - - "github.com/kataras/iris" - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router" - "github.com/kataras/iris/httptest" -) - -func TestFallbackStackAdd(t *testing.T) { - l := make([]string, 0) - - stk := &router.FallbackStack{} - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS1") - }, - }) - - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS2") - }, - }) - - if stk.Size() != 2 { - t.Fatalf("Bad size (%d != 2)", stk.Size()) - } - - for _, h := range stk.List() { - h(nil) - } - - if (l[0] != "POS2") || (l[1] != "POS1") { - t.Fatal("Bad positions: ", l) - } -} - -func TestFallbackStackFork(t *testing.T) { - l := make([]string, 0) - - stk := &router.FallbackStack{} - - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS1") - }, - }) - - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS2") - }, - }) - - stk = stk.Fork() - - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS3") - }, - }) - - stk.Add(context.Handlers{ - func(context.Context) { - l = append(l, "POS4") - }, - }) - - if stk.Size() != 4 { - t.Fatalf("Bad size (%d != 4)", stk.Size()) - } - - for _, h := range stk.List() { - h(nil) - } - - if (l[0] != "POS4") || (l[1] != "POS3") || (l[2] != "POS2") || (l[3] != "POS1") { - t.Fatal("Bad positions: ", l) - } -} - -func TestFallbackStackCall(t *testing.T) { - // build the api - app := iris.New() - - // setup an existing routes - app.Handle("GET", "/route", func(ctx context.Context) { - ctx.WriteString("ROUTED") - }) - - // setup fallback handler - app.Fallback(func(ctx context.Context) { - if ctx.Method() != "GET" { - ctx.NextOrNotFound() // it checks if we have next, otherwise fire 404 not found. - return - } - - ctx.WriteString("FALLBACK") - }) - - // run the tests - e := httptest.New(t, app, httptest.Debug(false)) - - e.Request("GET", "/route").Expect().Status(iris.StatusOK).Body().Equal("ROUTED") - e.Request("POST", "/route").Expect().Status(iris.StatusNotFound) - e.Request("POST", "/noroute").Expect().Status(iris.StatusNotFound) - e.Request("GET", "/noroute").Expect().Status(iris.StatusOK).Body().Equal("FALLBACK") -} diff --git a/core/router/handler.go b/core/router/handler.go index b9b79202..323e693e 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -22,8 +22,8 @@ type RequestHandler interface { HandleRequest(context.Context) // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error - // RouteExists checks if a route exists - RouteExists(method, path string, ctx context.Context) bool + // RouteExists reports whether a particular route exists. + RouteExists(ctx context.Context, method, path string) bool } type tree struct { @@ -35,14 +35,8 @@ type tree struct { } type routerHandler struct { - trees []*tree - hosts bool // true if at least one route contains a Subdomain. - fallbackStack *FallbackStack - // on build: true if fallbackStack.Size() > 0, - // reduces the checks because fallbackStack is NEVER nil (api_builder.go always initializes it). - // If re-checked needed (serve-time fallback handler added) - // then a re-build/refresh of the application's router is necessary, as with every handler. - hasFallbackHandlers bool + trees []*tree + hosts bool // true if at least one route contains a Subdomain. } var _ RequestHandler = &routerHandler{} @@ -90,15 +84,11 @@ func NewDefaultHandler() RequestHandler { type RoutesProvider interface { // api builder GetRoutes() []*Route GetRoute(routeName string) *Route - - GetFallBackStack() *FallbackStack } func (h *routerHandler) Build(provider RoutesProvider) error { registeredRoutes := provider.GetRoutes() h.trees = h.trees[0:0] // reset, inneed when rebuilding. - h.fallbackStack = provider.GetFallBackStack() - h.hasFallbackHandlers = h.fallbackStack.Size() > 0 // sort, subdomains goes first. sort.Slice(registeredRoutes, func(i, j int) bool { @@ -262,16 +252,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } - if h.hasFallbackHandlers { - ctx.Do(h.fallbackStack.List()) - return - } - ctx.StatusCode(http.StatusNotFound) } -// RouteExists checks if a route exists -func (h *routerHandler) RouteExists(method, path string, ctx context.Context) bool { +// RouteExists reports whether a particular route exists +// It will search from the current subdomain of context's host, if not inside the root domain. +func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { for i := range h.trees { t := h.trees[i] if method != t.Method { diff --git a/core/router/party.go b/core/router/party.go index 8a91e727..c83f508a 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -6,21 +6,11 @@ import ( "github.com/kataras/iris/core/router/macro" ) -// Party is here to separate the concept of -// api builder and the sub api builder. - -// PartyConfigurator is handler for configuring a party (it works with iris.Application) -type PartyConfigurator func(party Party) - // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. // Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun. // // Look the "APIBuilder" for its implementation. type Party interface { - // ConfigureParty configures this party like `iris.Application#Configure` - // That allows middlewares focused on the Party like CORS middleware - ConfigureParty(...PartyConfigurator) - // GetRelPath returns the current party's relative path. // i.e: // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". @@ -67,13 +57,6 @@ type Party interface { // If the current Party is the root, then it registers the middleware to all child Parties' routes too. Use(middleware ...context.Handler) - // Fallback appends Handler(s) to the current Party's fallback stack. - // Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. - // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, - // if `Context.Next()` method is not called. - // Done Handler(s) is(are) not called. - Fallback(middleware ...context.Handler) - // Done appends to the very end, Handler(s) to the current Party's routes and child routes. // The difference from .Use is that this/or these Handler(s) are being always running last. Done(handlers ...context.Handler) @@ -82,6 +65,14 @@ type Party interface { // // Returns this Party. Reset() Party + + // AllowMethods will re-register the future routes that will be registered + // via `Handle`, `Get`, `Post`, ... to the given "methods" on that Party and its children "Parties", + // duplicates are not registered. + // + // Call of `AllowMethod` will override any previous allow methods. + AllowMethods(methods ...string) Party + // Handle registers a route to the server's router. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // diff --git a/core/router/router.go b/core/router/router.go index 891813e1..50526395 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -147,9 +147,10 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { router.mainHandler(w, r) } -// RouteExists checks if a route exists -func (router *Router) RouteExists(method, path string, ctx context.Context) bool { - return router.requestHandler.RouteExists(method, path, ctx) +// RouteExists reports whether a particular route exists +// It will search from the current subdomain of context's host, if not inside the root domain. +func (router *Router) RouteExists(ctx context.Context, method, path string) bool { + return router.requestHandler.RouteExists(ctx, method, path) } type wrapper struct { diff --git a/mvc/mvc.go b/mvc/mvc.go index 35b21cba..f11e02d4 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -176,13 +176,6 @@ func (app *Application) Handle(controller interface{}) *Application { return app } -// Fallback is an alias to `app.Router.Fallback(handlers...)` -// -// See `core/router#Party.Fallback` -func (app *Application) Fallback(handlers ...context.Handler) { - app.Router.Fallback(handlers...) -} - // Clone returns a new mvc Application which has the dependencies // of the current mvc Mpplication's dependencies. // From c746d631d76977ca60c9dfb9ae7d6ff772c9625e Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Sat, 10 Mar 2018 15:13:07 +0200 Subject: [PATCH 18/23] Update to version 10.3.0 | Read HISTORY.md Former-commit-id: 10a0663cdf518dcf35290f18dfdc1eb69ea4a127 --- HISTORY.md | 38 +++++++++++++++++++++++++++++++++++++ HISTORY_GR.md | 4 ++++ HISTORY_ZH.md | 4 ++++ README.md | 4 ++-- README_GR.md | 4 ++-- README_RU.md | 4 ++-- README_ZH.md | 4 ++-- VERSION | 2 +- core/maintenance/version.go | 2 +- doc.go | 2 +- 10 files changed, 57 insertions(+), 11 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c3216d7a..5dc31053 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,6 +17,44 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Sa, 10 March 2018 | v10.3.0 + +- The only one API Change is the [Application/Context/Router#RouteExists](https://godoc.org/github.com/kataras/iris/core/router#Router.RouteExists), it accepts the `Context` as its first argument instead of last now. + +- Fix cors middleware via https://github.com/iris-contrib/middleware/commit/048e2be034ed172c6754448b8a54a9c55debad46, relative issue: https://github.com/kataras/iris/issues/922 (still pending for a verification). + +- Add `Context#NextOr` and `Context#NextOrNotFound` + +```go +// NextOr checks if chain has a next handler, if so then it executes it +// otherwise it sets a new chain assigned to this Context based on the given handler(s) +// and executes its first handler. +// +// Returns true if next handler exists and executed, otherwise false. +// +// Note that if no next handler found and handlers are missing then +// it sends a Status Not Found (404) to the client and it stops the execution. +NextOr(handlers ...Handler) bool +// NextOrNotFound checks if chain has a next handler, if so then it executes it +// otherwise it sends a Status Not Found (404) to the client and stops the execution. +// +// Returns true if next handler exists and executed, otherwise false. +NextOrNotFound() bool +``` + +- Add a new `Party#AllowMethods` which if called before any `Handle, Get, Post...` will clone the routes to that methods as well. + +- Fix trailing slash from POST method request redirection as reported at: https://github.com/kataras/iris/issues/921 via https://github.com/kataras/iris/commit/dc589d9135295b4d080a9a91e942aacbfe5d56c5 + +- Add examples for read using custom decoder per type, read using custom decoder via `iris#UnmarshalerFunc` and to complete it add an example for the `context#ReadXML`, you can find them [here](https://github.com/kataras/iris/tree/master/_examples#how-to-read-from-contextrequest-httprequest)via https://github.com/kataras/iris/commit/78cd8e5f677fe3ff2c863c5bea7d1c161bf4c31e. + +- Add one more example for custom router macro functions, relative to https://github.com/kataras/iris/issues/918, you can find it [there](https://github.com/kataras/iris/blob/master/_examples/routing/dynamic-path/main.go#L144-L158), via https://github.com/kataras/iris/commit/a7690c71927cbf3aa876592fab94f04cada91b72 + +- Add wrappers for `Pongo`'s `AsValue()` and `AsSaveValue()` by @neenar via PR: https://github.com/kataras/iris/pull/913 + +- Remove unnecessary reflection usage on `context#UnmarshalBody` via https://github.com/kataras/iris/commit/4b9e41458b62035ea4933789c0a132c3ef2a90cc + + # Th, 15 February 2018 | v10.2.1 Fix subdomains' `StaticEmbedded` & `StaticWeb` not found errors, as reported by [@speedwheel](https://github.com/speedwheel) via [facebook page's chat](https://facebook.com/iris.framework). diff --git a/HISTORY_GR.md b/HISTORY_GR.md index ca3e398a..538fe749 100644 --- a/HISTORY_GR.md +++ b/HISTORY_GR.md @@ -17,6 +17,10 @@ **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας. +# Sa, 10 March 2018 | v10.3.0 + +This history entry is not translated yet to the Greek language yet, please refer to the original [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030) instead. + # Th, 15 February 2018 | v10.2.1 Διόρθωση το οποίο αφορά 404 not found errors στα αρχεία που σερβίρονται από τα `StaticEmbedded` και `StaticWeb` των υποτομεών(subdomains), όπως αναφέρθηκε πριν λίγο από τον [@speedwheel](https://github.com/speedwheel) μέσω [της σελίδας μας στο facebook](https://facebook.com/iris.framework). diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index dc864ba3..781606a1 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -17,6 +17,10 @@ **如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 +# Sa, 10 March 2018 | v10.3.0 + +This history entry is not translated yet to the Chinese language yet, please refer to the original [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030) instead. + # 2018 2月15号 | v10.2.1 版本更新 修正 子域名 (subdomain) 的 `StaticEmbedded` 和 `StaticWeb` 不存在错误, 由 [@speedwheel](https://github.com/speedwheel) 通过 [facebook page's chat](https://facebook.com/iris.framework) 反馈。 diff --git a/README.md b/README.md index 797dc194..b8a0b157 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.2-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. @@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Support -- [HISTORY](HISTORY.md#th-15-february-2018--v1021) file is your best friend, it contains information about the latest features and changes +- [HISTORY](HISTORY.md#sa-10-march-2018--v1030) file is your best friend, it contains information about the latest features and changes - Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues) - Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com) - Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_GR.md b/README_GR.md index f9277217..c958d9b5 100644 --- a/README_GR.md +++ b/README_GR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.2-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go. @@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο ## Υποστήριξη -- To [HISTORY](HISTORY_GR.md#th-15-february-2018--v1021) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές +- To [HISTORY](HISTORY_GR.md#sa-10-march-2018--v1030) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_RU.md b/README_RU.md index aaa29ad4..0d9b46d8 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.2-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go. @@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ ## Поддержка -- Файл [HISTORY](HISTORY.md#th-15-february-2018--v1021) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях +- Файл [HISTORY](HISTORY.md#sa-10-march-2018--v1030) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ZH.md b/README_ZH.md index cebf49ae..9693820d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.2-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。 @@ -102,7 +102,7 @@ _更新于: [2017年11月21日星期二](_benchmarks/README_UNIX.md)_ ## 支持 -- [更新记录](HISTORY_ZH.md#th-15-february-2018--v1021) 是您最好的朋友,它包含有关最新功能和更改的信息 +- [更新记录](HISTORY_ZH.md#sa-10-march-2018--v1030) 是您最好的朋友,它包含有关最新功能和更改的信息 - 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues) - 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com) - [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/VERSION b/VERSION index b2996f61..0f696b9f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.2.1:https://github.com/kataras/iris/blob/master/HISTORY.md#th-15-february-2018--v1021 \ No newline at end of file +10.3.0:https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030 \ No newline at end of file diff --git a/core/maintenance/version.go b/core/maintenance/version.go index 82648e6e..3057af63 100644 --- a/core/maintenance/version.go +++ b/core/maintenance/version.go @@ -13,7 +13,7 @@ import ( const ( // Version is the string representation of the current local Iris Web Framework version. - Version = "10.2.1" + Version = "10.3.0" ) // CheckForUpdates checks for any available updates diff --git a/doc.go b/doc.go index e4bd5942..7bbdd4a3 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -10.2.1 +10.3.0 Installation From 6c9e3c69ff9d3c033883e262d55c7a92891f6640 Mon Sep 17 00:00:00 2001 From: Alexey McSakoff Date: Sat, 10 Mar 2018 19:31:40 +0400 Subject: [PATCH 19/23] Handle non-WS connections in WS handler with no panic Former-commit-id: f56665ee444bd16258edf6a34606efc132eced5a --- websocket/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/websocket/server.go b/websocket/server.go index a08b3e1a..44120b90 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -147,6 +147,9 @@ func New(cfg Config) *Server { func (s *Server) Handler() context.Handler { return func(ctx context.Context) { c := s.Upgrade(ctx) + if c.Err() != nil { + return + } // NOTE TO ME: fire these first BEFORE startReader and startPinger // in order to set the events and any messages to send // the startPinger will send the OK to the client and only From d07391df7889c0ca7c565baafc071356bac9ca9d Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Mon, 12 Mar 2018 23:36:53 +0200 Subject: [PATCH 20/23] Update the golang.org/x/sys vendor Former-commit-id: 85d90c202236cb32b9a4feb4436ecf0dc713a633 From d61d509bc8a3e2fdf629200b089389f4e2c33f4e Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Mon, 12 Mar 2018 23:43:23 +0200 Subject: [PATCH 21/23] update Gopkg.lock and Gopkg.toml Former-commit-id: 6f00ef74a8b96f0ecb5d1b1587a804f43e2d69c5 --- Gopkg.lock | 10 ++-------- Gopkg.toml | 4 ---- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 4ce68abb..ec66e816 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -107,7 +107,7 @@ branch = "master" name = "github.com/kataras/golog" packages = ["."] - revision = "2ed680e7b1f34147164fa8073373e14fce02ac30" + revision = "dd676348ce75fa471fbbcd1bbbd131d00179756a" [[projects]] branch = "master" @@ -115,12 +115,6 @@ packages = [".","terminal"] revision = "825e39f34365e7db2c9fbc3692c16220e3bd7418" -[[projects]] - branch = "master" - name = "github.com/kataras/survey" - packages = ["."] - revision = "20e139a6d2469769ae88e0a3579ba5df71839ca7" - [[projects]] name = "github.com/klauspost/compress" packages = ["flate","gzip"] @@ -251,7 +245,7 @@ branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "2d6f6f883a06fc0d5f4b14a81e4c28705ea64c15" + revision = "c28acc882ebcbfbe8ce9f0f14b9ac26ee138dd51" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index bae0c6d9..af31eeb5 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -42,10 +42,6 @@ branch = "master" name = "github.com/kataras/golog" -[[constraint]] - branch = "master" - name = "github.com/kataras/survey" - [[constraint]] name = "github.com/klauspost/compress" version = "1.2.1" From 05b1e80e7f9c9b106ce245f728041a975dbaf853 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Tue, 13 Mar 2018 00:02:45 +0200 Subject: [PATCH 22/23] add protobuf on sessions/sessiondb/badger/vendor and columnize at middleware/logger/vendor Former-commit-id: 7fe5ce84ef9e9c5f46269ebde5dd0d8185ee74a6 From 5e5e91e4bbaab6ac55802c357a599d141a44b84c Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Tue, 13 Mar 2018 15:57:52 +0200 Subject: [PATCH 23/23] fix https://github.com/kataras/iris/issues/927 Former-commit-id: 7de62d36b7927d790f5af0c1e64713e0a78d12c5 --- core/router/path.go | 82 +++++++++++++++++++++++++++++++++++----- core/router/path_test.go | 36 +++++++++++++++++- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/core/router/path.go b/core/router/path.go index 51b1716f..333b1ea6 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/kataras/iris/core/netutil" + "github.com/kataras/iris/core/router/macro/interpreter/lexer" ) const ( @@ -73,30 +74,91 @@ func joinPath(path1 string, path2 string) string { // iteratively until no further processing can be done: // // 1. Replace multiple slashes with a single slash. -// 3. Eliminate each inner .. path name element (the parent directory) -// along with the non-.. element that precedes it. -// 4. Eliminate .. elements that begin a rooted path: -// that is, replace "/.." by "/" at the beginning of a path. +// 2. Replace '\' with '/' +// 3. Replace "\\" with '/' +// 4. Ignore anything inside '{' and '}' +// 5. Makes sure that prefixed with '/' +// 6. Remove trailing '/'. // // The returned path ends in a slash only if it is the root "/". func cleanPath(s string) string { + // note that we don't care about the performance here, it's before the server ran. if s == "" || s == "." { return "/" } - // remove suffix "/" + // remove suffix "/". if lidx := len(s) - 1; s[lidx] == '/' { s = s[:lidx] } - // prefix with "/" + // prefix with "/". s = prefix(s, "/") - // remove the os specific dir sep - s = strings.Replace(s, "\\", "/", -1) + // If you're learning go through Iris I will ask you to ignore the + // following part, it's not the recommending way to do that, + // but it's understable to me. + var ( + insideMacro = false + i = -1 + ) - // use std path to clean the path - s = path.Clean(s) + for { + i++ + if len(s) <= i { + break + } + + if s[i] == lexer.Begin { + insideMacro = true + continue + } + + if s[i] == lexer.End { + insideMacro = false + continue + } + + // when inside {} then don't try to clean it. + if !insideMacro { + if s[i] == '/' { + if len(s)-1 >= i+1 && s[i+1] == '/' { // we have "//". + bckp := s + s = bckp[:i] + "/" + // forward two, we ignore the second "/" in the raw. + i = i + 2 + if len(bckp)-1 >= i { + s += bckp[i:] + } + } + // if we have just a single slash then continue. + continue + } + + if s[i] == '\\' { // this will catch "\\" and "\". + bckp := s + s = bckp[:i] + "/" + + if len(bckp)-1 >= i+1 { + s += bckp[i+1:] + i++ + } + + if len(s)-1 > i && s[i] == '\\' { + bckp := s + s = bckp[:i] + if len(bckp)-1 >= i+2 { + s = bckp[:i-1] + bckp[i+1:] + i++ + } + } + + continue + } + + } + + } return s } diff --git a/core/router/path_test.go b/core/router/path_test.go index 28c49b13..33a00626 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -4,6 +4,38 @@ import ( "testing" ) +func TestCleanPath(t *testing.T) { + tests := []struct { + path string + expected string + }{ + {"noslashPrefix", + "/noslashPrefix"}, + {"slashSuffix/", + "/slashSuffix"}, + {"noSlashPrefixAndslashSuffix/", + "/noSlashPrefixAndslashSuffix"}, + // don't do any clean up inside {}, + // fixes #927. + {"/total/{year:string regexp(\\d{4})}", + "/total/{year:string regexp(\\d{4})}"}, + {"/total/{year:string regexp(\\d{4})}/more", + "/total/{year:string regexp(\\d{4})}/more"}, + {"/total/{year:string regexp(\\d{4})}/more/{s:string regexp(\\d{7})}", + "/total/{year:string regexp(\\d{4})}/more/{s:string regexp(\\d{7})}"}, + {"/single_no_params", + "/single_no_params"}, + {"/single/{id:int}", + "/single/{id:int}"}, + } + + for i, tt := range tests { + if expected, got := tt.expected, cleanPath(tt.path); expected != got { + t.Fatalf("[%d] - expected path '%s' but got '%s'", i, expected, got) + } + } +} + func TestSplitPath(t *testing.T) { tests := []struct { path string @@ -50,8 +82,8 @@ func TestSplitSubdomainAndPath(t *testing.T) { }{ {"admin./users/42", "admin.", "/users/42"}, {"//api/users\\42", "", "/api/users/42"}, - {"admin./users/\\42", "admin.", "/users/42"}, - {"*./users/\\42", "*.", "/users/42"}, + {"admin./users//42", "admin.", "/users/42"}, + {"*./users/42/", "*.", "/users/42"}, } for i, tt := range tests {