mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
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:
parent
5fa9789c35
commit
d031ad55b8
14
HISTORY.md
14
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)`,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
/*
|
||||
Read:
|
||||
"overview"
|
||||
"basic"
|
||||
"dynamic-path"
|
||||
and "reverse" examples if you want to release Iris' real power.
|
||||
|
|
134
_examples/beginner/routing/overview/main.go
Normal file
134
_examples/beginner/routing/overview/main.go
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
285
core/router/node/node.go
Normal 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
|
||||
}
|
|
@ -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
8
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:
|
||||
|
||||
+------------------------+
|
||||
|
|
Loading…
Reference in New Issue
Block a user