diff --git a/HISTORY.md b/HISTORY.md
index ce65a429..df24ee28 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -29,6 +29,17 @@ Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.co
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
+# Su, 11 June 2017 | v7.0.5
+
+Iris now supports static paths and dynamic paths for the same path prefix with zero performance cost:
+
+`app.Get("/profile/{id:int}", handler)` and `app.Get("/profile/create", createHandler)` are not in conflict anymore.
+
+
+The the rest of the special Iris' routing features, including static & wildcard subdomains are still work like a charm.
+
+> This was one of the most popular community's feature requests. Click [here](https://github.com/kataras/iris/blob/master/_examples/beginner/routing/overview/main.go) to see a trivial example.
+
# Sa, 10 June 2017 | v7.0.4
- Simplify and add a test for the [basicauth middleware](https://github.com/kataras/iris/tree/master/middleware/basicauth), no need to be
@@ -102,7 +113,8 @@ General
- `app.Adapt(iris.RenderPolicy(...))` -> removed and replaced with the ability to replace the whole context with a custom one or override some methods of it, see below.
Routing
-- Remove of multiple routers, now we have the fresh Iris router which is based on top of the julien's [httprouter](https://github.com/julienschmidt/httprouter)
+- Remove of multiple routers, now we have the fresh Iris router which is based on top of the julien's [httprouter](https://github.com/julienschmidt/httprouter).
+ > Update 11 June 2017: As of 7.0.5 this is changed, read [here](https://github.com/kataras/iris/blob/master/HISTORY.md#su-11-june-2017--v705).
- Subdomains routing algorithm has been improved.
- Iris router is using a custom interpreter with parser and path evaluator to achieve the best expressiveness, with zero performance loss, you ever seen so far, i.e:
- `app.Get("/", "/users/{userid:int min(1)}", handler)`,
diff --git a/README.md b/README.md
index 567f1857..77628eb7 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ A fast, cross-platform and efficient web framework with robust set of well-desig
[![Report card](https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris)
[![Support forum](https://img.shields.io/badge/support-page-ec2eb4.svg?style=flat-square)](http://support.iris-go.com)
[![Examples](https://img.shields.io/badge/howto-examples-3362c2.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples#table-of-contents)
-[![Godocs](https://img.shields.io/badge/7.0.4-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris)
+[![Godocs](https://img.shields.io/badge/7.0.5-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris)
[![Chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris)
[![Buy me a cup of coffee](https://img.shields.io/badge/support-%20open--source-F4A460.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9InJnYigyMjAsMjIwLDIyMCkiIGQ9Ik04ODYuNiwzMDUuM2MtNDUuNywyMDMuMS0xODcsMzEwLjMtNDA5LjYsMzEwLjNoLTc0LjFsLTUxLjUsMzI2LjloLTYybC0zLjIsMjEuMWMtMi4xLDE0LDguNiwyNi40LDIyLjYsMjYuNGgxNTguNWMxOC44LDAsMzQuNy0xMy42LDM3LjctMzIuMmwxLjUtOGwyOS45LTE4OS4zbDEuOS0xMC4zYzIuOS0xOC42LDE4LjktMzIuMiwzNy43LTMyLjJoMjMuNWMxNTMuNSwwLDI3My43LTYyLjQsMzA4LjktMjQyLjdDOTIxLjYsNDA2LjgsOTE2LjcsMzQ4LjYsODg2LjYsMzA1LjN6Ii8%2BPHBhdGggZmlsbD0icmdiKDIyMCwyMjAsMjIwKSIgZD0iTTc5MS45LDgzLjlDNzQ2LjUsMzIuMiw2NjQuNCwxMCw1NTkuNSwxMEgyNTVjLTIxLjQsMC0zOS44LDE1LjUtNDMuMSwzNi44TDg1LDg1MWMtMi41LDE1LjksOS44LDMwLjIsMjUuOCwzMC4ySDI5OWw0Ny4zLTI5OS42bC0xLjUsOS40YzMuMi0yMS4zLDIxLjQtMzYuOCw0Mi45LTM2LjhINDc3YzE3NS41LDAsMzEzLTcxLjIsMzUzLjItMjc3LjVjMS4yLTYuMSwyLjMtMTIuMSwzLjEtMTcuOEM4NDUuMSwxODIuOCw4MzMuMiwxMzAuOCw3OTEuOSw4My45TDc5MS45LDgzLjl6Ii8%2BPC9zdmc%2B)](https://github.com/kataras/iris#buy-me-a-cup-of-coffee)
@@ -394,7 +394,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports
Version
------------
-Current: **7.0.4**
+Current: **7.0.5**
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".
@@ -423,7 +423,3 @@ Note that some third-party packages that you use with Iris may requires
different license agreements.
[Chat]: https://kataras.rocket.chat/channel/iris
-
-
-
-
diff --git a/_examples/README.md b/_examples/README.md
index 9faef83d..2293b1e6 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -21,7 +21,7 @@ It doesn't contains "best ways" neither explains all its features. It's just a s
* [Import from YAML file](beginner/configuration/from-yaml-file/main.go)
* [Import from TOML file](beginner/configuration/from-toml-file/main.go)
* [Routing](beginner/routing)
- * [Overview](beginner/routing/main.go)
+ * [Overview](beginner/routing/overview/main.go)
* [Basic](beginner/routing/basic/main.go)
* [Dynamic Path](beginner/routing/dynamic-path/main.go)
* [Reverse routing](beginner/routing/reverse/main.go)
diff --git a/_examples/beginner/routing/dynamic-path/main.go b/_examples/beginner/routing/dynamic-path/main.go
index 29e747c6..43d44e0e 100644
--- a/_examples/beginner/routing/dynamic-path/main.go
+++ b/_examples/beginner/routing/dynamic-path/main.go
@@ -20,21 +20,15 @@ func main() {
// Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary.
//
// Iris has the easiest and the most powerful routing process you have ever meet.
- // If you're used to use the "httprouter"
- // then you don't have to change a thing of a route's path.
//
// At the same time,
// Iris has its own interpeter(yes like a programming language)
// for route's path syntax and their dynamic path parameters parsing and evaluation,
// I am calling them "macros" for shortcut.
- // In the following examples we will see only the second option, which has exactly the same speed
- // compared to "httprouter".
// How? It calculates its needs and if not any special regexp needed then it just
- // registers the route with the underline httprouter's path syntax,
+ // registers the route with the low-level underline path syntax,
// otherwise it pre-compiles the regexp and adds the necessary middleware(s).
//
- // Note: the Iris' router follows the "httprouter"'s rules for routes confliction.
- //
// Standard macro types for parameters:
// +------------------------+
// | {param:string} |
@@ -142,6 +136,10 @@ func main() {
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
})
+ app.Get("/lowercase/static", func(ctx context.Context) {
+ ctx.Writef("static and dynamic paths are not conflicted anymore!")
+ })
+
// let's use a trivial custom regexp that validates a single path parameter
// which its value is only lowercase letters.
@@ -160,12 +158,14 @@ func main() {
app.Get("/myfiles/{directory:path}", func(ctx context.Context) {
ctx.Writef("path type accepts any number of path segments, path after /myfiles/ is: %s", ctx.Params().Get("directory"))
}) // for wildcard path (any number of path segments) without validation you can use:
- // /myfiles/*directory
+ // /myfiles/*
// "{param}"'s performance is exactly the same of ":param"'s.
- // alternatives -> ":param" for single path parameter and "*paramPath" for wildcard path parameter
- // acquire them by ctx.Params().Get as always.
+ // alternatives -> ":param" for single path parameter and "*" for wildcard path parameter.
+ // Note these:
+ // if "/mypath/*" then the parameter name is "*".
+ // if "/mypath/{myparam:path}" then the parameter has two names, one is the "*" and the other is the user-defined "myparam".
// WARNING:
// A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed.
diff --git a/_examples/beginner/routing/main.go b/_examples/beginner/routing/main.go
index ab03f248..c160a35e 100644
--- a/_examples/beginner/routing/main.go
+++ b/_examples/beginner/routing/main.go
@@ -9,6 +9,7 @@ import (
/*
Read:
+"overview"
"basic"
"dynamic-path"
and "reverse" examples if you want to release Iris' real power.
diff --git a/_examples/beginner/routing/overview/main.go b/_examples/beginner/routing/overview/main.go
new file mode 100644
index 00000000..dab5980c
--- /dev/null
+++ b/_examples/beginner/routing/overview/main.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/context"
+)
+
+func main() {
+ app := iris.New()
+
+ // GET: http://localhost:8080
+ app.Get("/", info)
+ // GET: http://localhost:8080/profile/kataras
+ app.Get("/profile/{username:string}", info)
+ // GET: http://localhost:8080/profile/backups/any/number/of/paths/here
+ app.Get("/profile/backups/{filepath:path}", info)
+
+ // Favicon
+
+ // GET: http://localhost:8080/favicon.ico
+ app.Favicon("./public/images/favicon.ico")
+
+ // Static assets
+
+ // GET: http://localhost:8080/assets/css/bootstrap.min.css
+ // maps to ./public/assets/css/bootstrap.min.css file at system location.
+ // GET: http://localhost:8080/assets/js/react.min.js
+ // maps to ./public/assets/js/react.min.js file at system location.
+ app.StaticWeb("/assets", "./public/assets")
+
+ /* OR
+
+ // GET: http://localhost:8080/js/react.min.js
+ // maps to ./public/assets/js/react.min.js file at system location.
+ app.StaticWeb("/js", "./public/assets/js")
+
+ // GET: http://localhost:8080/css/bootstrap.min.css
+ // maps to ./public/assets/css/bootstrap.min.css file at system location.
+ app.StaticWeb("/css", "./public/assets/css")
+
+ */
+
+ // Grouping
+
+ usersRoutes := app.Party("/users")
+ // GET: http://localhost:8080/users/help
+ usersRoutes.Get("/help", func(ctx context.Context) {
+ ctx.Writef("GET / -- fetch all users\n")
+ ctx.Writef("GET /$ID -- fetch a user by id\n")
+ ctx.Writef("POST / -- create new user\n")
+ ctx.Writef("PUT /$ID -- update an existing user\n")
+ ctx.Writef("DELETE /$ID -- delete an existing user\n")
+ })
+
+ // GET: http://localhost:8080/users
+ usersRoutes.Get("/", func(ctx context.Context) {
+ ctx.Writef("get all users")
+ })
+
+ // GET: http://localhost:8080/users/42
+ // **/users/42 and /users/help works after Iris version 7.0.5**
+ usersRoutes.Get("/{id:int}", func(ctx context.Context) {
+ id, _ := ctx.Params().GetInt("id")
+ ctx.Writef("get user by id: %d", id)
+ })
+
+ // POST: http://localhost:8080/users
+ usersRoutes.Post("/", func(ctx context.Context) {
+ username, password := ctx.PostValue("username"), ctx.PostValue("password")
+ ctx.Writef("create user for username= %s and password= %s", username, password)
+ })
+
+ // PUT: http://localhost:8080/users
+ usersRoutes.Put("/{id:int}", func(ctx context.Context) {
+ id, _ := ctx.Params().GetInt("id") // or .Get to get its string represatantion.
+ username := ctx.PostValue("username")
+ ctx.Writef("update user for id= %d and new username= %s", id, username)
+ })
+
+ // DELETE: http://localhost:8080/users/42
+ usersRoutes.Delete("/{id:int}", func(ctx context.Context) {
+ id, _ := ctx.Params().GetInt("id")
+ ctx.Writef("delete user by id: %d", id)
+ })
+
+ // Subdomains, depends on the host, you have to edit the hosts or nginx/caddy's configuration if you use them.
+ //
+ // See more subdomains examples at _examples/intermediate/subdomains folder.
+ adminRoutes := app.Party("admin.")
+
+ // GET: http://admin.localhost:8080
+ adminRoutes.Get("/", info)
+ // GET: http://admin.localhost:8080/settings
+ adminRoutes.Get("/settings", info)
+
+ // Wildcard/dynamic subdomain
+ dynamicSubdomainRoutes := app.Party("*.")
+
+ // GET: http://any_thing_here.localhost:8080
+ dynamicSubdomainRoutes.Get("/", info)
+
+ // GET: http://localhost:8080/
+ // GET: http://localhost:8080/profile/kataras
+ // GET: http://localhost:8080/profile/backups/any/number/of/paths/here
+
+ // GET: http://localhost:8080/users/help
+ // GET: http://localhost:8080/users
+ // GET: http://localhost:8080/users/42
+ // POST: http://localhost:8080/users
+ // PUT: http://localhost:8080/users
+ // DELETE: http://localhost:8080/users/42
+
+ // GET: http://admin.localhost:8080
+ // GET: http://admin.localhost:8080/settings
+ // GET: http://any_thing_here.localhost:8080
+ app.Run(iris.Addr(":8080"))
+}
+
+func info(ctx context.Context) {
+ method := ctx.Method() // the http method requested a server's resource.
+ subdomain := ctx.Subdomain() // the subdomain, if any.
+
+ // the request path (without scheme and host).
+ path := ctx.Path()
+ // how to get all parameters, if we don't know
+ // the names:
+ paramsLen := ctx.Params().Len()
+
+ ctx.Params().Visit(func(name string, value string) {
+ ctx.Writef("%s = %s\n", name, value)
+ })
+ ctx.Writef("\nInfo\n\n")
+ ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s\nParameters length: %d", method, subdomain, path, paramsLen)
+}
diff --git a/_examples/intermediate/sessions/database/main.go b/_examples/intermediate/sessions/database/main.go
index 9b797b1b..2ec4d7b8 100644
--- a/_examples/intermediate/sessions/database/main.go
+++ b/_examples/intermediate/sessions/database/main.go
@@ -30,9 +30,6 @@ func main() {
// the rest of the code stays the same.
app := iris.New()
- // enable all (error) logs
- // select the httprouter as the servemux
-
// Attach the session manager we just created
app.AttachSessionManager(mySessions)
diff --git a/_examples/intermediate/sessions/securecookie/main.go b/_examples/intermediate/sessions/securecookie/main.go
index 38822633..83ff3d1f 100644
--- a/_examples/intermediate/sessions/securecookie/main.go
+++ b/_examples/intermediate/sessions/securecookie/main.go
@@ -15,8 +15,6 @@ import (
func main() {
app := iris.New()
- // enable all (error) logs
- // select the httprouter as the servemux
cookieName := "mycustomsessionid"
// AES only supports key sizes of 16, 24 or 32 bytes.
diff --git a/_examples/intermediate/sessions/standalone/main.go b/_examples/intermediate/sessions/standalone/main.go
index f58bc781..726cc91e 100644
--- a/_examples/intermediate/sessions/standalone/main.go
+++ b/_examples/intermediate/sessions/standalone/main.go
@@ -14,9 +14,6 @@ type businessModel struct {
func main() {
app := iris.New()
- // enable all (error) logs
- // select the httprouter as the servemux
-
mySessions := sessions.New(sessions.Config{
// Cookie string, the session's client cookie name, for example: "mysessionid"
//
diff --git a/_examples/intermediate/websockets/custom-go-client/main.go b/_examples/intermediate/websockets/custom-go-client/main.go
index 03ed3c52..84625247 100644
--- a/_examples/intermediate/websockets/custom-go-client/main.go
+++ b/_examples/intermediate/websockets/custom-go-client/main.go
@@ -134,8 +134,6 @@ func OnConnect(c websocket.Connection) {
// ServerLoop listen and serve websocket requests
func ServerLoop() {
app := iris.New()
- // enable all (error) logs
- // select the httprouter as the servemux
ws := websocket.New(websocket.Config{Endpoint: "/socket"})
ws.Attach(app)
diff --git a/_examples/intermediate/websockets/native-messages/main.go b/_examples/intermediate/websockets/native-messages/main.go
index bded9657..4110c116 100644
--- a/_examples/intermediate/websockets/native-messages/main.go
+++ b/_examples/intermediate/websockets/native-messages/main.go
@@ -25,8 +25,7 @@ type clientPage struct {
func main() {
app := iris.New()
- // enable all (error) logs
- // select the httprouter as the servemux
+
app.AttachView(view.HTML("./templates", ".html")) // select the html engine to serve templates
ws := websocket.New(websocket.Config{
diff --git a/context/context.go b/context/context.go
index b30b48b0..4ed159d3 100644
--- a/context/context.go
+++ b/context/context.go
@@ -89,8 +89,10 @@ func (r *RequestParams) Set(key, value string) {
// Visit accepts a visitor which will be filled
// by the key-value params.
-func (r *RequestParams) Visit(visitor func(key string, value interface{})) {
- r.store.Visit(visitor)
+func (r *RequestParams) Visit(visitor func(key string, value string)) {
+ r.store.Visit(func(k string, v interface{}) {
+ visitor(k, v.(string)) // always string here.
+ })
}
// Get returns a path parameter's value based on its route's dynamic path key.
@@ -132,6 +134,11 @@ func (r RequestParams) GetIntUnslashed(key string) (int, error) {
return -1, memstore.ErrIntParse.Format(v)
}
+// Len returns the full length of the parameters.
+func (r RequestParams) Len() int {
+ return r.store.Len()
+}
+
// Context is the midle-man server's "object" for the clients.
//
// A New context is being acquired from a sync.Pool on each connection.
@@ -172,6 +179,14 @@ type Context interface {
// Request returns the original *http.Request, as expected.
Request() *http.Request
+ // Do calls the SetHandlers(handlers)
+ // and executes the first handler,
+ // handlers should not be empty.
+ //
+ // It's used by the router, developers may use that
+ // to replace and execute handlers immediately.
+ Do(Handlers)
+
// AddHandler can add handler(s)
// to the current request in serve-time,
// these handlers are not persistenced to the router.
@@ -817,6 +832,17 @@ func (ctx *context) Request() *http.Request {
return ctx.request
}
+// Do calls the SetHandlers(handlers)
+// and executes the first handler,
+// handlers should not be empty.
+//
+// It's used by the router, developers may use that
+// to replace and execute handlers immediately.
+func (ctx *context) Do(handlers Handlers) {
+ ctx.handlers = handlers
+ ctx.handlers[0](ctx)
+}
+
// AddHandler can add handler(s)
// to the current request in serve-time,
// these handlers are not persistenced to the router.
@@ -1230,20 +1256,23 @@ func (ctx *context) Redirect(urlToRedirect string, statusHeader ...int) {
httpStatus = statusHeader[0]
}
- // we don't know the Method of the url to redirect,
- // sure we can find it by reverse routing as we already implemented
- // but it will take too much time for a simple redirect, it doesn't worth it.
- // So we are checking the CURRENT Method for GET, HEAD, CONNECT and TRACE.
- // the
- // Fixes: http: //support.iris-go.com/d/21-wrong-warning-message-while-redirecting
- shouldCheckForCycle := urlToRedirect == ctx.Path() && ctx.Method() == http.MethodGet
- // from POST to GET on the same path will give a warning message but developers don't use the iris.DevLogger
- // for production, so I assume it's OK to let it logs it
- // (it can solve issues when developer redirects to the same handler over and over again)
- // Note: it doesn't stops the redirect, the developer gets what he/she expected.
- if shouldCheckForCycle {
- ctx.Application().Log("warning: redirect from: '%s' to: '%s',\ncurrent method: '%s'", ctx.Path(), urlToRedirect, ctx.Method())
- }
+ // comment these because in some cases the the ctx.Request().URL.Path is already updated
+ // to the new one, so it shows a wrong warning message.
+ //
+ // // we don't know the Method of the url to redirect,
+ // // sure we can find it by reverse routing as we already implemented
+ // // but it will take too much time for a simple redirect, it doesn't worth it.
+ // // So we are checking the CURRENT Method for GET, HEAD, CONNECT and TRACE.
+ // // the
+ // // Fixes: http: //support.iris-go.com/d/21-wrong-warning-message-while-redirecting
+ // shouldCheckForCycle := urlToRedirect == ctx.Path() && ctx.Method() == http.MethodGet
+ // // from POST to GET on the same path will give a warning message but developers don't use the iris.DevLogger
+ // // for production, so I assume it's OK to let it logs it
+ // // (it can solve issues when developer redirects to the same handler over and over again)
+ // // Note: it doesn't stops the redirect, the developer gets what he/she expected.
+ // if shouldCheckForCycle {
+ // ctx.Application().Log("warning: redirect from: '%s' to: '%s',\ncurrent method: '%s'", ctx.Path(), urlToRedirect, ctx.Method())
+ // }
http.Redirect(ctx.writer, ctx.request, urlToRedirect, httpStatus)
}
diff --git a/core/router/handler.go b/core/router/handler.go
index a40ca02b..60144009 100644
--- a/core/router/handler.go
+++ b/core/router/handler.go
@@ -5,15 +5,15 @@
package router
import (
+ "fmt"
"html"
"net/http"
"sort"
"strings"
- "sync/atomic"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/nettools"
- "github.com/kataras/iris/core/router/httprouter"
+ "github.com/kataras/iris/core/router/node"
)
// RequestHandler the middle man between acquiring a context and releasing it.
@@ -31,13 +31,12 @@ type tree struct {
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
Subdomain string
- Entry *httprouter.Node
+ Nodes *node.Nodes
}
type routerHandler struct {
trees []*tree
- vhost atomic.Value // is a string setted at the first it founds a subdomain, we need that here in order to reduce the resolveVHost calls
- hosts bool // true if at least one route contains a Subdomain.
+ hosts bool // true if at least one route contains a Subdomain.
}
var _ RequestHandler = &routerHandler{}
@@ -54,20 +53,20 @@ func (h *routerHandler) getTree(method, subdomain string) *tree {
}
func (h *routerHandler) addRoute(method, subdomain, path string, handlers context.Handlers) error {
- // get or create a tree and add the route
+ if len(path) == 0 || path[0] != '/' {
+ return fmt.Errorf("router: path %q must begin with %q", path, "/")
+ }
+
t := h.getTree(method, subdomain)
if t == nil {
- //first time we register a route to this method with this domain
- t = &tree{Method: method, Subdomain: subdomain, Entry: new(httprouter.Node)}
+ n := make(node.Nodes, 0)
+ // first time we register a route to this method with this subdomain
+ t = &tree{Method: method, Subdomain: subdomain, Nodes: &n}
h.trees = append(h.trees, t)
}
- if err := t.Entry.AddRoute(path, handlers); err != nil {
- return err
- }
-
- return nil
+ return t.Nodes.Add(path, handlers)
}
// NewDefaultHandler returns the handler which is responsible
@@ -98,11 +97,41 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
return err
}
}
+
return nil
}
func (h *routerHandler) HandleRequest(ctx context.Context) {
method := ctx.Method()
+ path := ctx.Path()
+
+ if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() {
+
+ if len(path) > 1 && path[len(path)-1] == '/' {
+ // Remove trailing slash and client-permant rule for redirection,
+ // if confgiuration allows that and path has an extra slash.
+
+ // update the new path and redirect.
+ r := ctx.Request()
+ path = path[:len(path)-1]
+ r.URL.Path = path
+ url := r.URL.String()
+
+ ctx.Redirect(url, http.StatusMovedPermanently)
+
+ // RFC2616 recommends that a short note "SHOULD" be included in the
+ // response because older user agents may not understand 301/307.
+ // Shouldn't send the response for POST or HEAD; that leaves GET.
+ if method == http.MethodGet {
+ note := "Moved Permanently.\n"
+
+ ctx.ResponseWriter().WriteString(note)
+ }
+ return
+ }
+ }
for i := range h.trees {
t := h.trees[i]
@@ -110,39 +139,6 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
continue
}
- // Changed my mind for subdomains, there are unnecessary steps here
- // most servers don't need these and on other servers may force the server to send a 404 not found
- // on a valid subdomain, by commenting my previous implementation we allow any request host to be discovarable for subdomains.
- // if h.hosts && t.Subdomain != "" {
-
- // if h.vhost.Load() == nil {
- // h.vhost.Store(nettools.ResolveVHost(ctx.Application().ConfigurationReadOnly().GetAddr()))
- // }
-
- // host := h.vhost.Load().(string)
- // requestHost := ctx.Host()
-
- // if requestHost != host {
- // // we have a subdomain
- // if strings.Contains(t.Subdomain, DynamicSubdomainIndicator) {
- // } else {
- // // if subdomain+host is not the request host
- // // and
- // // if request host didn't matched the server's host
- // // check if reached the server
- // // with a local address, this case is the developer him/herself,
- // // if both of them failed then continue and ignore this tree.
- // if t.Subdomain+host != requestHost && !nettools.IsLoopbackHost(requestHost) {
- // // go to the next tree, we have a subdomain but it is not the correct
- // continue
- // }
- // }
- // } else {
- // //("it's subdomain but the request is not the same as the vhost)
- // continue
- // }
- // }
- // new, simpler and without the need of known the real host:
if h.hosts && t.Subdomain != "" {
requestHost := ctx.Host()
if nettools.IsLoopbackSubdomain(requestHost) {
@@ -175,62 +171,13 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
}
}
- handlers, mustRedirect := t.Entry.ResolveRoute(ctx)
+ handlers := t.Nodes.Find(path, ctx.Params())
if len(handlers) > 0 {
- ctx.SetHandlers(handlers)
- ctx.Handlers()[0](ctx)
- // to remove the .Next(maybe not a good idea), reduces the performance a bit:
- // ctx.Handlers()[0](ctx) // execute the first, as soon as possible
- // // execute the chain of handlers, carefully
- // current := ctx.HandlerIndex(-1)
- // for {
- // if ctx.IsStopped() || current >= n {
- // break
- // }
-
- // ctx.HandlerIndex(current)
- // ctx.Handlers()[current](ctx)
- // current++
- // if i := ctx.HandlerIndex(-1); i > current { // navigate to previous handler is not allowed
- // current = i
- // }
- // }
-
+ ctx.Do(handlers)
+ // found
return
- } else if mustRedirect && !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() { // && ctx.Method() == MethodConnect {
- urlToRedirect := ctx.Path()
- pathLen := len(urlToRedirect)
-
- if pathLen > 1 {
- if urlToRedirect[pathLen-1] == '/' {
- urlToRedirect = urlToRedirect[:pathLen-1] // remove the last /
- } else {
- // it has path prefix, it doesn't ends with / and it hasn't be found, then just append the slash
- urlToRedirect = urlToRedirect + "/"
- }
-
- statusForRedirect := http.StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
- if t.Method == http.MethodPost ||
- t.Method == http.MethodPut ||
- t.Method == http.MethodDelete {
- statusForRedirect = http.StatusTemporaryRedirect // To maintain POST data
- }
-
- ctx.Redirect(urlToRedirect, statusForRedirect)
- // RFC2616 recommends that a short note "SHOULD" be included in the
- // response because older user agents may not understand 301/307.
- // Shouldn't send the response for POST or HEAD; that leaves GET.
- if t.Method == http.MethodGet {
- note := "Moved Permanently.\n"
-
- ctx.ResponseWriter().WriteString(note)
- }
- return
- }
}
- // not found
+ // not found or method not allowed.
break
}
diff --git a/core/router/httprouter/LICENSE b/core/router/httprouter/LICENSE
deleted file mode 100644
index fc203d5a..00000000
--- a/core/router/httprouter/LICENSE
+++ /dev/null
@@ -1,56 +0,0 @@
-Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Gerasimos Maropoulos nor the name of his
-username, kataras, may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-Third-Parties:
-
-Copyright (c) 2013 Julien Schmidt. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * The names of the contributors may not be used to endorse or promote
- products derived from this software without specific prior written
- permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
diff --git a/core/router/httprouter/node.go b/core/router/httprouter/node.go
deleted file mode 100644
index 2bd72840..00000000
--- a/core/router/httprouter/node.go
+++ /dev/null
@@ -1,439 +0,0 @@
-// Copyright 2013 Julien Schmidt. All rights reserved.
-// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
-// Use of the below source code is governed by the BSD 3-Clause license.
-
-package httprouter
-
-import (
- "strings"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/errors"
-)
-
-func min(a, b int) int {
- if a <= b {
- return a
- }
- return b
-}
-
-func countParams(path string) uint8 {
- var n uint
- for i := 0; i < len(path); i++ {
- if path[i] != ':' && path[i] != '*' {
- continue
- }
- n++
- }
- if n >= 255 {
- return 255
- }
- return uint8(n)
-}
-
-type nodeType uint8
-
-const (
- static nodeType = iota // default
- root
- param
- catchAll
-)
-
-// Node is the default request handler's tree's entry type.
-// Examples of its algorithm can be found via googling or via youtube
-// search term: trie, tree sort, data structures: tree, reversed tree, sort tree etc...
-type Node struct {
- path string
- wildChild bool
- nType nodeType
- maxParams uint8
- indices string
- children []*Node
- handle context.Handlers
- priority uint32
-}
-
-// increments priority of the given child and reorders if necessary
-func (n *Node) incrementChildPrio(pos int) int {
- n.children[pos].priority++
- prio := n.children[pos].priority
-
- // adjust position (move to front)
- newPos := pos
- for newPos > 0 && n.children[newPos-1].priority < prio {
- // swap Node positions
- n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
-
- newPos--
- }
-
- // build new index char string
- if newPos != pos {
- n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
- n.indices[pos:pos+1] + // the index char we move
- n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
- }
-
- return newPos
-}
-
-// AddRoute adds a route with the given handler to the path.
-func (n *Node) AddRoute(path string, handle context.Handlers) error {
- fullPath := path
- n.priority++
- numParams := countParams(path)
-
- // non-empty tree
- if len(n.path) > 0 || len(n.children) > 0 {
- walk:
- for {
- // Update maxParams of the current Node
- if numParams > n.maxParams {
- n.maxParams = numParams
- }
-
- // Find the longest common prefix.
- // This also implies that the common prefix contains no ':' or '*'
- // since the existing key can't contain those chars.
- i := 0
- max := min(len(path), len(n.path))
- for i < max && path[i] == n.path[i] {
- i++
- }
-
- // Split edge
- if i < len(n.path) {
- child := Node{
- path: n.path[i:],
- wildChild: n.wildChild,
- nType: static,
- indices: n.indices,
- children: n.children,
- handle: n.handle,
- priority: n.priority - 1,
- }
-
- // Update maxParams (max of all children)
- for i := range child.children {
- if child.children[i].maxParams > child.maxParams {
- child.maxParams = child.children[i].maxParams
- }
- }
-
- n.children = []*Node{&child}
- // []byte for proper unicode char conversion, see #65
- n.indices = string([]byte{n.path[i]})
- n.path = path[:i]
- n.handle = nil
- n.wildChild = false
- }
-
- // Make new Node a child of this Node
- if i < len(path) {
- path = path[i:]
-
- if n.wildChild {
- n = n.children[0]
- n.priority++
-
- // Update maxParams of the child Node
- if numParams > n.maxParams {
- n.maxParams = numParams
- }
- numParams--
-
- // Check if the wildcard matches
- if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
- // Check for longer wildcard, e.g. :name and :names
- (len(n.path) >= len(path) || path[len(n.path)] == '/') {
- continue walk
- } else {
- // Wildcard conflict
- pathSeg := strings.SplitN(path, "/", 2)[0]
- prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
- return errors.New("'" + pathSeg +
- "' in new path '" + fullPath +
- "' conflicts with existing wildcard '" + n.path +
- "' in existing prefix '" + prefix +
- "'")
- }
- }
-
- c := path[0]
-
- // slash after param
- if n.nType == param && c == '/' && len(n.children) == 1 {
- n = n.children[0]
- n.priority++
- continue walk
- }
-
- // Check if a child with the next path byte exists
- for i := 0; i < len(n.indices); i++ {
- if c == n.indices[i] {
- i = n.incrementChildPrio(i)
- n = n.children[i]
- continue walk
- }
- }
-
- // Otherwise insert it
- if c != ':' && c != '*' {
- // []byte for proper unicode char conversion, see #65
- n.indices += string([]byte{c})
- child := &Node{
- maxParams: numParams,
- }
- n.children = append(n.children, child)
- n.incrementChildPrio(len(n.indices) - 1)
- n = child
- }
- return n.insertChild(numParams, path, fullPath, handle)
-
- } else if i == len(path) { // Make Node a (in-path) leaf
- if n.handle != nil {
- return errors.New("a handle is already registered for path '" + fullPath + "'")
- }
- n.handle = handle
- }
- return nil
- }
- } else { // Empty tree
- n.insertChild(numParams, path, fullPath, handle)
- n.nType = root
- }
- return nil
-}
-
-func (n *Node) insertChild(numParams uint8, path, fullPath string, handle context.Handlers) error {
- var offset int // already handled bytes of the path
-
- // find prefix until first wildcard (beginning with ':'' or '*'')
- for i, max := 0, len(path); numParams > 0; i++ {
- c := path[i]
- if c != ':' && c != '*' {
- continue
- }
-
- // find wildcard end (either '/' or path end)
- end := i + 1
- for end < max && path[end] != '/' {
- switch path[end] {
- // the wildcard name must not contain ':' and '*'
- case ':', '*':
- return errors.New("only one wildcard per path segment is allowed, has: '" +
- path[i:] + "' in path '" + fullPath + "'")
- default:
- end++
- }
- }
-
- // check if this Node existing children which would be
- // unreachable if we insert the wildcard here
- if len(n.children) > 0 {
- return errors.New("wildcard route '" + path[i:end] +
- "' conflicts with existing children in path '" + fullPath + "'")
- }
-
- // check if the wildcard has a name
- if end-i < 2 {
- return errors.New("wildcards must be named with a non-empty name in path '" + fullPath + "'")
- }
-
- if c == ':' { // param
- // split path at the beginning of the wildcard
- if i > 0 {
- n.path = path[offset:i]
- offset = i
- }
-
- child := &Node{
- nType: param,
- maxParams: numParams,
- }
- n.children = []*Node{child}
- n.wildChild = true
- n = child
- n.priority++
- numParams--
-
- // if the path doesn't end with the wildcard, then there
- // will be another non-wildcard subpath starting with '/'
- if end < max {
- n.path = path[offset:end]
- offset = end
-
- child := &Node{
- maxParams: numParams,
- priority: 1,
- }
- n.children = []*Node{child}
- n = child
- }
-
- } else { // catchAll
- if end != max || numParams > 1 {
- return errors.New("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
- }
-
- if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
- return errors.New("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
- }
-
- // currently fixed width 1 for '/'
- i--
- if path[i] != '/' {
- return errors.New("no / before catch-all in path '" + fullPath + "'")
- }
-
- n.path = path[offset:i]
-
- // first Node: catchAll Node with empty path
- child := &Node{
- wildChild: true,
- nType: catchAll,
- maxParams: 1,
- }
- n.children = []*Node{child}
- n.indices = string(path[i])
- n = child
- n.priority++
-
- // second Node: Node holding the variable
- child = &Node{
- path: path[i:],
- nType: catchAll,
- maxParams: 1,
- handle: handle,
- priority: 1,
- }
- n.children = []*Node{child}
-
- return nil
- }
- }
-
- // insert remaining path part and handle to the leaf
- n.path = path[offset:]
- n.handle = handle
- return nil
-}
-
-// ResolveRoute sets the handlers registered to a given path which is acquiring by ctx.Path().
-// The values of
-// wildcards are saved to the context's Values.
-// If no handle can be found, a TSR (trailing slash redirect) recommendation is
-// made if a handle exists with an extra (without the) trailing slash for the
-// given context.
-//
-// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
-func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, tsr bool) { //(p context.RequestParams, tsr bool) {
- path := ctx.Request().URL.Path
- handlers = ctx.Handlers()
-walk: // outer loop for walking the tree
- for {
- if len(path) > len(n.path) {
- if path[:len(n.path)] == n.path {
- path = path[len(n.path):]
- // If this Node does not have a wildcard (param or catchAll)
- // child, we can just look up the next child Node and continue
- // to walk down the tree
- if !n.wildChild {
- c := path[0]
- for i := 0; i < len(n.indices); i++ {
- if c == n.indices[i] {
- n = n.children[i]
- continue walk
- }
- }
-
- // Nothing found.
- // We can recommend to redirect to the same URL without a
- // trailing slash if a leaf exists for that path.
- tsr = (path == "/" && n.handle != nil)
- return
-
- }
-
- // handle wildcard child
- n = n.children[0]
- switch n.nType {
- case param:
- // find param end (either '/' or path end)
- end := 0
- for end < len(path) && path[end] != '/' {
- end++
- }
-
- // save param value
- ctx.Params().Set(n.path[1:], path[:end])
- // we need to go deeper!
- if end < len(path) {
- if len(n.children) > 0 {
- path = path[end:]
- n = n.children[0]
- continue walk
- }
-
- // ... but we can't
- tsr = (len(path) == end+1)
- return
- }
-
- if handlers = n.handle; handlers != nil {
- return
- } else if len(n.children) == 1 {
- // No handle found. Check if a handle for this path + a
- // trailing slash exists for TSR recommendation
- n = n.children[0]
- tsr = (n.path == "/" && n.handle != nil)
- }
-
- return
-
- case catchAll:
- ctx.Params().Set(n.path[2:], path)
- handlers = n.handle
- return
-
- default:
- // invalid Node type here
- return
- }
- }
- } else if path == n.path {
- // We should have reached the Node containing the handle.
- // Check if this Node has a handle registered.
- if handlers = n.handle; handlers != nil {
- return
- }
-
- if path == "/" && n.wildChild && n.nType != root {
- tsr = true
- return
- }
-
- // No handle found. Check if a handle for this path + a
- // trailing slash exists for trailing slash recommendation
- for i := 0; i < len(n.indices); i++ {
- if n.indices[i] == '/' {
- n = n.children[i]
- tsr = (len(n.path) == 1 && n.handle != nil) ||
- (n.nType == catchAll && n.children[0].handle != nil)
- return
- }
- }
-
- return
- }
-
- // Nothing found. We can recommend to redirect to the same URL with an
- // extra trailing slash if a leaf exists for that path
- tsr = (path == "/") ||
- (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
- path == n.path[:len(n.path)-1] && n.handle != nil)
- return
- }
-}
diff --git a/core/router/macro.go b/core/router/macro.go
index 7da71be2..3a088651 100644
--- a/core/router/macro.go
+++ b/core/router/macro.go
@@ -189,8 +189,9 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
for i, p := range tmpl.Params {
if p.Type == ast.ParamTypePath {
if i != len(tmpl.Params)-1 {
- return "", errors.New("parameter type \"ParamTypePath\" is allowed to exists to the very last of a path")
+ return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path")
}
+
routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1)
} else {
routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1)
@@ -202,6 +203,7 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware
func convertTmplToHandler(tmpl *macro.Template) context.Handler {
+
needMacroHandler := false
// check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params.
diff --git a/core/router/macro/interpreter/lexer/lexer.go b/core/router/macro/interpreter/lexer/lexer.go
index f6083699..ce313498 100644
--- a/core/router/macro/interpreter/lexer/lexer.go
+++ b/core/router/macro/interpreter/lexer/lexer.go
@@ -31,7 +31,7 @@ func (l *Lexer) readChar() {
l.ch = l.input[l.readPos]
}
l.pos = l.readPos
- l.readPos += 1
+ l.readPos++
}
const (
diff --git a/core/router/node/node.go b/core/router/node/node.go
new file mode 100644
index 00000000..980d9f97
--- /dev/null
+++ b/core/router/node/node.go
@@ -0,0 +1,285 @@
+package node
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/errors"
+)
+
+// Nodes a conversion type for []*node.
+type Nodes []*node
+
+type node struct {
+ s string
+ wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed
+ paramNames []string // only-names
+ children Nodes
+ handlers context.Handlers
+ root bool
+}
+
+// ErrDublicate returned from MakeChild when more than one routes have the same registered path.
+var ErrDublicate = errors.New("more than one routes have the same registered path")
+
+// Add adds a node to the tree, returns an ErrDublicate error on failure.
+func (nodes *Nodes) Add(path string, handlers context.Handlers) error {
+ // resolve params and if that node should be added as root
+ var params []string
+ var paramStart, paramEnd int
+ for {
+ paramStart = strings.IndexByte(path[paramEnd:], ':')
+ if paramStart == -1 {
+ break
+ }
+ paramStart += paramEnd
+ paramStart++
+ paramEnd = strings.IndexByte(path[paramStart:], '/')
+
+ if paramEnd == -1 {
+ params = append(params, path[paramStart:])
+ path = path[:paramStart]
+ break
+ }
+ paramEnd += paramStart
+ params = append(params, path[paramStart:paramEnd])
+ path = path[:paramStart] + path[paramEnd:]
+ paramEnd -= paramEnd - paramStart
+ }
+
+ for _, idx := range paramsPos(path) {
+
+ if err := nodes.add(path[:idx], nil, nil, true); err != nil { // take the static path to its own node
+ return err
+ }
+ // create a second, empty, dynamic parameter node without the last slash
+ if nidx := idx + 1; len(path) < nidx {
+ if err := nodes.add(path[:nidx], nil, nil, true); err != nil {
+ return err
+ }
+ }
+ }
+
+ // last, create the node filled by the full path, parameters and its handlers
+ if err := nodes.add(path, params, handlers, true); err != nil {
+ return err
+ }
+
+ // sort by static path, remember, they were already sorted by subdomains too.
+ nodes.Sort()
+ return nil
+}
+
+func (nodes *Nodes) add(path string, paramNames []string, handlers context.Handlers, root bool) (err error) {
+ // wraia etsi doulevei ara
+ // na to kanw na exei to node to diko tou wildcard parameter name
+ // kai sto telos na pernei auto, me vasi to *paramname
+ // alla edw mesa 9a ginete register vasi tou last /
+loop:
+ for _, n := range *nodes {
+
+ minlen := len(n.s)
+ if len(path) < minlen {
+ minlen = len(path)
+ }
+
+ for i := 0; i < minlen; i++ {
+ if n.s[i] == path[i] {
+ continue
+ }
+ if i == 0 {
+ continue loop
+ }
+
+ *n = node{
+ s: n.s[:i],
+ children: Nodes{
+ {
+ s: n.s[i:],
+ wildcardParamName: n.wildcardParamName,
+ paramNames: n.paramNames,
+ children: n.children,
+ handlers: n.handlers,
+ },
+ {
+ s: path[i:],
+ wildcardParamName: n.wildcardParamName,
+ paramNames: paramNames,
+ handlers: handlers,
+ },
+ },
+ root: n.root,
+ }
+ return
+ }
+
+ if len(path) < len(n.s) {
+ *n = node{
+ s: n.s[:len(path)],
+ wildcardParamName: n.wildcardParamName,
+ paramNames: paramNames,
+ children: Nodes{
+ {
+ s: n.s[len(path):],
+ wildcardParamName: n.wildcardParamName,
+ paramNames: n.paramNames,
+ children: n.children,
+ handlers: n.handlers,
+ },
+ },
+ handlers: handlers,
+ root: n.root,
+ }
+
+ return
+ }
+
+ if len(path) > len(n.s) {
+ err = n.children.add(path[len(n.s):], paramNames, handlers, false)
+ return err
+ }
+
+ if len(handlers) == 0 { // missing handlers
+ return nil
+ }
+ if len(n.handlers) > 0 { // n.handlers already setted
+ return ErrDublicate
+ }
+ n.paramNames = paramNames
+ n.handlers = handlers
+
+ return
+ }
+
+ // set the wildcard param name to the root.
+
+ wildcardIdx := strings.IndexByte(path, '*')
+ wildcardParamName := ""
+ if wildcardIdx > 0 {
+ wildcardParamName = path[wildcardIdx+1:]
+ path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
+ }
+
+ n := &node{
+ s: path,
+ wildcardParamName: wildcardParamName,
+ paramNames: paramNames,
+ handlers: handlers,
+ root: root,
+ }
+
+ *nodes = append(*nodes, n)
+ return
+}
+
+// Find resolves the path, fills its params
+// and returns the registered to the resolved node's handlers.
+func (nodes Nodes) Find(path string, params *context.RequestParams) context.Handlers {
+ n, paramValues := nodes.findChild(path, nil)
+ if n != nil {
+ // map the params,
+ // n.params are the param names
+ if len(paramValues) > 0 {
+ for i, name := range n.paramNames {
+ params.Set(name, paramValues[i])
+ }
+ // last is the wildcard,
+ // if paramValues are exceed from the registered param names.
+ // Note that n.wildcardParamName can be not empty but that doesn't meaning
+ // that it contains a wildcard path, so the check is required.
+ if len(paramValues) > len(n.paramNames) {
+ lastWildcardVal := paramValues[len(paramValues)-1]
+ params.Set(n.wildcardParamName, lastWildcardVal)
+ }
+ }
+ return n.handlers
+ }
+
+ return nil
+}
+
+func (nodes Nodes) findChild(path string, params []string) (*node, []string) {
+ for _, n := range nodes {
+ if n.s == ":" {
+ paramEnd := strings.IndexByte(path, '/')
+ if paramEnd == -1 {
+ if len(n.handlers) == 0 {
+ return nil, nil
+ }
+ return n, append(params, path)
+ }
+ return n.children.findChild(path[paramEnd:], append(params, path[:paramEnd]))
+ }
+
+ if !strings.HasPrefix(path, n.s) {
+ continue
+ }
+
+ if len(path) == len(n.s) { // Node matched until the end of path.
+ if len(n.handlers) == 0 {
+ return nil, nil
+ }
+ return n, params
+ }
+
+ child, childParamNames := n.children.findChild(path[len(n.s):], params)
+
+ if child == nil || len(child.handlers) == 0 {
+ // is wildcard and it is not root neither has children
+ if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.children) > 0)) {
+ if len(n.handlers) == 0 {
+ return nil, nil
+ }
+ return n, append(params, path[len(n.s):])
+ }
+ continue
+ }
+
+ return child, childParamNames
+ }
+ return nil, nil
+}
+
+// childLen returns all the children's and their children's length.
+func (n *node) childLen() (i int) {
+ for _, n := range n.children {
+ i++
+ i += n.childLen()
+ }
+ return
+}
+
+func (n *node) isDynamic() bool {
+ return n.s == ":"
+}
+
+// Sort sets the static paths first.
+func (nodes Nodes) Sort() {
+
+ sort.Slice(nodes, func(i, j int) bool {
+ if nodes[i].isDynamic() {
+ return false
+ }
+ if nodes[j].isDynamic() {
+ return true
+ }
+ return nodes[i].childLen() > nodes[j].childLen()
+ })
+
+ for _, n := range nodes {
+ n.children.Sort()
+ }
+}
+
+func paramsPos(s string) (pos []int) {
+ for i := 0; i < len(s); i++ {
+ p := strings.IndexByte(s[i:], ':')
+ if p == -1 {
+ break
+ }
+ pos = append(pos, p+i)
+ i = p + i
+ }
+ return
+}
diff --git a/core/router/route.go b/core/router/route.go
index 5e80acab..d33430e4 100644
--- a/core/router/route.go
+++ b/core/router/route.go
@@ -79,7 +79,7 @@ func (r Route) IsOnline() bool {
return r.Method != MethodNone
}
-// formats the parsed to the underline httprouter's path syntax.
+// formats the parsed to the underline path syntax.
// path = "/api/users/:id"
// return "/api/users/%v"
//
diff --git a/doc.go b/doc.go
index c7ac140c..518838f6 100644
--- a/doc.go
+++ b/doc.go
@@ -453,21 +453,15 @@ where context comes from github.com/kataras/iris/context.
Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary.
Iris has the easiest and the most powerful routing process you have ever meet.
-If you're used to use the "httprouter"
-then you don't have to change a thing of a route's path.
At the same time,
Iris has its own interpeter(yes like a programming language)
for route's path syntax and their dynamic path parameters parsing and evaluation,
I am calling them "macros" for shortcut.
-In the following examples we will see only the second option, which has exactly the same speed
-compared to "httprouter".
How? It calculates its needs and if not any special regexp needed then it just
-registers the route with the underline httprouter's path syntax,
+registers the route with the low-level path syntax,
otherwise it pre-compiles the regexp and adds the necessary middleware(s).
-Note: the Iris's router follows the "httprouter"'s rules for routes confliction.
-
Standard macro types for parameters:
+------------------------+
diff --git a/iris.go b/iris.go
index d51ca541..1179c305 100644
--- a/iris.go
+++ b/iris.go
@@ -41,7 +41,7 @@ const (
// Version is the current version number of the Iris Web framework.
//
// Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
- Version = "7.0.4"
+ Version = "7.0.5"
)
const (