Update to 7.0.5 | Dynamic and static paths are not in conflict anymore.

Read more at: https://github.com/kataras/iris/blob/master/HISTORY.md


Former-commit-id: b636d25c141ebdd5ad095ae9271433876a96e7ff
This commit is contained in:
kataras 2017-06-11 23:07:50 +03:00
parent 5fa9789c35
commit d031ad55b8
21 changed files with 545 additions and 651 deletions

View File

@ -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)`,

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -9,6 +9,7 @@ import (
/*
Read:
"overview"
"basic"
"dynamic-path"
and "reverse" examples if you want to release Iris' real power.

View File

@ -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)
}

View File

@ -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)

View File

@ -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.

View File

@ -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"
//

View File

@ -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)

View File

@ -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{

View File

@ -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)
}

View File

@ -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 := "<a href=\"" +
html.EscapeString(url) +
"\">Moved Permanently</a>.\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 := "<a href=\"" +
html.EscapeString(urlToRedirect) +
"\">Moved Permanently</a>.\n"
ctx.ResponseWriter().WriteString(note)
}
return
}
}
// not found
// not found or method not allowed.
break
}

View File

@ -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.

View File

@ -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
}
}

View File

@ -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.

View File

@ -31,7 +31,7 @@ func (l *Lexer) readChar() {
l.ch = l.input[l.readPos]
}
l.pos = l.readPos
l.readPos += 1
l.readPos++
}
const (

285
core/router/node/node.go Normal file
View File

@ -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
}

View File

@ -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"
//

8
doc.go
View File

@ -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:
+------------------------+

View File

@ -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 (