mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:26:26 +01:00
add my new trie data structure implementation written from scratch and specifically designed for HTTP (and Iris) - see https://github.com/kataras/muxie for the net/http version of it
Former-commit-id: 4eed1585f29b57418b61f6de058f5d6db4bb98bf
This commit is contained in:
parent
29a4354e1d
commit
3002736086
46
HISTORY.md
46
HISTORY.md
|
@ -22,29 +22,41 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
|||
## Breaking changes
|
||||
|
||||
- Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker`
|
||||
- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:number` if possible (although it will stay for backwards-compatibility)
|
||||
- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:int` if possible (although it will stay for backwards-compatibility)
|
||||
|
||||
## Routing
|
||||
|
||||
- `:number` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers and without digits limitation
|
||||
- `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers
|
||||
- Add `:int8` parameter type and `ctx.Params().GetInt8`
|
||||
- Add `:int16` parameter type and `ctx.Params().GetInt16`
|
||||
- Add `:int32` parameter type and `ctx.Params().GetInt32`
|
||||
- Add `:int64` parameter type and `ctx.Params().GetInt64`
|
||||
- Add `:uint` parameter type and `ctx.Params().GetUint`
|
||||
- Add `:uint8` parameter type and `ctx.Params().GetUint8`
|
||||
- Add `:uint16` parameter type and `ctx.Params().GetUint16`
|
||||
- Add `:uint32` parameter type and `ctx.Params().GetUint32`
|
||||
- Add `:uint64` parameter type and `ctx.Params().GetUint64`
|
||||
- `:bool` parameter type as an alias for `:boolean`
|
||||
- Add alias `:bool` for the `:boolean` parameter type
|
||||
|
||||
Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules.
|
||||
|
||||
| Param Type | Go Type | Validation |
|
||||
| -----------|---------|------------|
|
||||
| `:string` | string | anything |
|
||||
| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits |
|
||||
| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 |
|
||||
| `:uint8` | uint8 | 0 to 255 |
|
||||
| `:uint64` | uint64 | 0 to 18446744073709551615 |
|
||||
| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" |
|
||||
| `:alphabetical` | string | lowercase or uppercase letters |
|
||||
| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames |
|
||||
| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path |
|
||||
| Param Type | Go Type | Validation | Retrieve Helper |
|
||||
| -----------------|------|-------------|------|
|
||||
| `:string` | string | anything (single path segment) | `Params().Get` |
|
||||
| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` |
|
||||
| `:int8` | int8 | -128 to 127 | `Params().GetInt8` |
|
||||
| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` |
|
||||
| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` |
|
||||
| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` |
|
||||
| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` |
|
||||
| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` |
|
||||
| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` |
|
||||
| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` |
|
||||
| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` |
|
||||
| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` |
|
||||
| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` |
|
||||
| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` |
|
||||
| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` |
|
||||
|
||||
**Usage**:
|
||||
|
||||
|
@ -61,9 +73,9 @@ app.Get("/users/{id:uint64}", func(ctx iris.Context){
|
|||
| `prefix`(prefix string) | :string |
|
||||
| `suffix`(suffix string) | :string |
|
||||
| `contains`(s string) | :string |
|
||||
| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 |
|
||||
| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 |
|
||||
| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 |
|
||||
| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 |
|
||||
| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 |
|
||||
| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 |
|
||||
|
||||
**Usage**:
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ func main() {
|
|||
|
||||
| Param Type | Go Type | Validation | Retrieve Helper |
|
||||
| -----------------|------|-------------|------|
|
||||
| `:string` | string | anything | `Params().Get` |
|
||||
| `:string` | string | anything (single path segment) | `Params().Get` |
|
||||
| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` |
|
||||
| `:int8` | int8 | -128 to 127 | `Params().GetInt8` |
|
||||
| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` |
|
||||
|
|
|
@ -104,7 +104,7 @@ Structuring depends on your own needs. We can't tell you how to design your own
|
|||
|
||||
### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context
|
||||
|
||||
* `app.Get("{userid:number min(1)}", myHandler)`
|
||||
* `app.Get("{userid:int min(1)}", myHandler)`
|
||||
* `app.Post("{asset:path}", myHandler)`
|
||||
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
|
||||
|
||||
|
@ -128,10 +128,10 @@ app.Get("/profile/me", userHandler)
|
|||
|
||||
// Matches all GET requests prefixed with /users/
|
||||
// and followed by a number which should be equal or bigger than 1
|
||||
app.Get("/user/{userid:number min(1)}", getUserHandler)
|
||||
app.Get("/user/{userid:int min(1)}", getUserHandler)
|
||||
// Matches all requests DELETE prefixed with /users/
|
||||
// and following by a number which should be equal or bigger than 1
|
||||
app.Delete("/user/{userid:number min(1)}", deleteUserHandler)
|
||||
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
|
||||
|
||||
// Matches all GET requests except "/", "/about", anything starts with "/assets/" etc...
|
||||
// because it does not conflict with the rest of the routes.
|
||||
|
@ -149,7 +149,6 @@ Navigate through examples for a better understanding.
|
|||
- [Write your own custom parameter types](routing/macros/main.go) **NEW**
|
||||
- [Reverse routing](routing/reverse/main.go)
|
||||
- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW**
|
||||
- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW**
|
||||
- [Custom Wrapper](routing/custom-wrapper/main.go)
|
||||
- Custom Context
|
||||
* [method overriding](routing/custom-context/method-overriding/main.go)
|
||||
|
|
|
@ -63,7 +63,7 @@ Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件
|
|||
|
||||
### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文
|
||||
|
||||
* `app.Get("{userid:number min(1)}", myHandler)`
|
||||
* `app.Get("{userid:int min(1)}", myHandler)`
|
||||
* `app.Post("{asset:path}", myHandler)`
|
||||
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
|
||||
|
||||
|
@ -87,10 +87,10 @@ app.Get("/profile/me", userHandler)
|
|||
|
||||
// 匹配所有前缀为 /users/ 的 GET 请求
|
||||
// 参数为数字,且 >= 1
|
||||
app.Get("/user/{userid:number min(1)}", getUserHandler)
|
||||
app.Get("/user/{userid:int min(1)}", getUserHandler)
|
||||
// 匹配所有前缀为 /users/ 的 DELETE 请求
|
||||
// 参数为数字,且 >= 1
|
||||
app.Delete("/user/{userid:number min(1)}", deleteUserHandler)
|
||||
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
|
||||
|
||||
// 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头
|
||||
// 因为它不会与其他路线冲突。
|
||||
|
@ -108,7 +108,6 @@ app.Get("{root:path}", rootWildcardHandler)
|
|||
- [Write your own custom parameter types](routing/macros/main.go) **NEW**
|
||||
- [反向路由](routing/reverse/main.go)
|
||||
- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW**
|
||||
- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW**
|
||||
- [自定义包装](routing/custom-wrapper/main.go)
|
||||
- 自定义上下文
|
||||
* [方法重写](routing/custom-context/method-overriding/main.go)
|
||||
|
|
|
@ -68,7 +68,7 @@ func main() {
|
|||
usersRoutes := app.Party("/users", logThisMiddleware)
|
||||
{
|
||||
// Method GET: http://localhost:8080/users/42
|
||||
usersRoutes.Get("/{id:number min(1)}", getUserByID)
|
||||
usersRoutes.Get("/{id:int min(1)}", getUserByID)
|
||||
// Method POST: http://localhost:8080/users/create
|
||||
usersRoutes.Post("/create", createUser)
|
||||
}
|
||||
|
|
|
@ -152,26 +152,62 @@ Standard macro types for route path parameters
|
|||
| {param:string} |
|
||||
+------------------------+
|
||||
string type
|
||||
anything
|
||||
anything (single path segmnent)
|
||||
|
||||
+-------------------------------+
|
||||
| {param:number} or {param:int} |
|
||||
| {param:int} |
|
||||
+-------------------------------+
|
||||
int type
|
||||
both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
|
||||
-9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch
|
||||
|
||||
+-------------------------------+
|
||||
| {param:long} or {param:int64} |
|
||||
+-------------------------------+
|
||||
+------------------------+
|
||||
| {param:int8} |
|
||||
+------------------------+
|
||||
int8 type
|
||||
-128 to 127
|
||||
|
||||
+------------------------+
|
||||
| {param:int16} |
|
||||
+------------------------+
|
||||
int16 type
|
||||
-32768 to 32767
|
||||
|
||||
+------------------------+
|
||||
| {param:int32} |
|
||||
+------------------------+
|
||||
int32 type
|
||||
-2147483648 to 2147483647
|
||||
|
||||
+------------------------+
|
||||
| {param:int64} |
|
||||
+------------------------+
|
||||
int64 type
|
||||
-9223372036854775808 to 9223372036854775807
|
||||
|
||||
+------------------------+
|
||||
| {param:uint} |
|
||||
+------------------------+
|
||||
uint type
|
||||
0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32)
|
||||
|
||||
+------------------------+
|
||||
| {param:uint8} |
|
||||
+------------------------+
|
||||
uint8 type
|
||||
0 to 255
|
||||
|
||||
+------------------------+
|
||||
| {param:uint16} |
|
||||
+------------------------+
|
||||
uint16 type
|
||||
0 to 65535
|
||||
|
||||
+------------------------+
|
||||
| {param:uint32} |
|
||||
+------------------------+
|
||||
uint32 type
|
||||
0 to 4294967295
|
||||
|
||||
+------------------------+
|
||||
| {param:uint64} |
|
||||
+------------------------+
|
||||
|
@ -206,8 +242,8 @@ no spaces ! or other character
|
|||
| {param:path} |
|
||||
+------------------------+
|
||||
path type
|
||||
anything, should be the last part, more than one path segment,
|
||||
i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3"
|
||||
anything, should be the last part, can be more than one path segment,
|
||||
i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3"
|
||||
```
|
||||
|
||||
If type is missing then parameter's type is defaulted to string, so
|
||||
|
@ -221,21 +257,24 @@ you are able to register your own too!.
|
|||
Register a named path parameter function
|
||||
|
||||
```go
|
||||
app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool {
|
||||
app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool {
|
||||
// [...]
|
||||
return true
|
||||
// -> true means valid, false means invalid fire 404 or if "else 500" is appended to the macro syntax then internal server error.
|
||||
return func(paramValue int) bool {
|
||||
// -> true means valid, false means invalid fire 404
|
||||
// or if "else 500" is appended to the macro syntax then internal server error.
|
||||
return true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue string) bool`.
|
||||
At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue <T>) bool`.
|
||||
|
||||
```go
|
||||
{param:string equal(iris)}
|
||||
```
|
||||
The "iris" will be the argument here:
|
||||
```go
|
||||
app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool {
|
||||
app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool {
|
||||
return func(paramValue string){ return argument == paramValue }
|
||||
})
|
||||
```
|
||||
|
@ -249,20 +288,16 @@ app.Get("/username/{name}", func(ctx iris.Context) {
|
|||
ctx.Writef("Hello %s", ctx.Params().Get("name"))
|
||||
}) // type is missing = {name:string}
|
||||
|
||||
// Let's register our first macro attached to number macro type.
|
||||
// Let's register our first macro attached to int macro type.
|
||||
// "min" = the function
|
||||
// "minValue" = the argument of the function
|
||||
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
|
||||
// a user requests a path which contains the :number macro type with the min(...) macro parameter function.
|
||||
app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool {
|
||||
// func(<T>) bool = the macro's path parameter evaluator, this executes in serve time when
|
||||
// a user requests a path which contains the :int macro type with the min(...) macro parameter function.
|
||||
app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool {
|
||||
// do anything before serve here [...]
|
||||
// at this case we don't need to do anything
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= minValue
|
||||
return func(paramValue int) bool {
|
||||
return paramValue >= minValue
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -789,6 +824,32 @@ type Context interface {
|
|||
// IsStopped checks and returns true if the current position of the Context is 255,
|
||||
// means that the StopExecution() was called.
|
||||
IsStopped() bool
|
||||
// OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
|
||||
// when the underlying connection has gone away.
|
||||
//
|
||||
// This mechanism can be used to cancel long operations on the server
|
||||
// if the client has disconnected before the response is ready.
|
||||
//
|
||||
// It depends on the `http#CloseNotify`.
|
||||
// CloseNotify may wait to notify until Request.Body has been
|
||||
// fully read.
|
||||
//
|
||||
// After the main Handler has returned, there is no guarantee
|
||||
// that the channel receives a value.
|
||||
//
|
||||
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
|
||||
// The "cb" will not fire for sure if the output value is false.
|
||||
//
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
//
|
||||
// Look the `ResponseWriter#CloseNotifier` for more.
|
||||
OnConnectionClose(fnGoroutine func()) bool
|
||||
// OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
|
||||
// and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
//
|
||||
// Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
|
||||
OnClose(cb func())
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Current "user/request" storage |
|
||||
|
@ -868,8 +929,12 @@ type Context interface {
|
|||
//
|
||||
// Keep note that this checks the "User-Agent" request header.
|
||||
IsMobile() bool
|
||||
// GetReferrer extracts and returns the information from the "Referer" header as specified
|
||||
// in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// or by the URL query parameter "referer".
|
||||
GetReferrer() Referrer
|
||||
// +------------------------------------------------------------+
|
||||
// | Response Headers helpers |
|
||||
// | Headers helpers |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
// Header adds a header to the response writer.
|
||||
|
@ -880,16 +945,18 @@ type Context interface {
|
|||
// GetContentType returns the response writer's header value of "Content-Type"
|
||||
// which may, setted before with the 'ContentType'.
|
||||
GetContentType() string
|
||||
// GetContentType returns the request's header value of "Content-Type".
|
||||
GetContentTypeRequested() string
|
||||
|
||||
// GetContentLength returns the request's header value of "Content-Length".
|
||||
// Returns 0 if header was unable to be found or its value was not a valid number.
|
||||
GetContentLength() int64
|
||||
|
||||
// StatusCode sets the status code header to the response.
|
||||
// Look .GetStatusCode too.
|
||||
// Look .`GetStatusCode` too.
|
||||
StatusCode(statusCode int)
|
||||
// GetStatusCode returns the current status code of the response.
|
||||
// Look StatusCode too.
|
||||
// Look `StatusCode` too.
|
||||
GetStatusCode() int
|
||||
|
||||
// Redirect sends a redirect response to the client
|
||||
|
@ -924,6 +991,9 @@ type Context interface {
|
|||
// URLParamIntDefault returns the url query parameter as int value from a request,
|
||||
// if not found or parse failed then "def" is returned.
|
||||
URLParamIntDefault(name string, def int) int
|
||||
// URLParamInt32Default returns the url query parameter as int32 value from a request,
|
||||
// if not found or parse failed then "def" is returned.
|
||||
URLParamInt32Default(name string, def int32) int32
|
||||
// URLParamInt64 returns the url query parameter as int64 value from a request,
|
||||
// returns -1 and an error if parse failed.
|
||||
URLParamInt64(name string) (int64, error)
|
||||
|
@ -1071,6 +1141,10 @@ type Context interface {
|
|||
// Examples of usage: context.ReadJSON, context.ReadXML.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go
|
||||
//
|
||||
// UnmarshalBody does not check about gzipped data.
|
||||
// Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb
|
||||
// However you are still free to read the `ctx.Request().Body io.Reader` manually.
|
||||
UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error
|
||||
// ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type.
|
||||
//
|
||||
|
@ -1081,7 +1155,8 @@ type Context interface {
|
|||
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
|
||||
ReadXML(xmlObjectPtr interface{}) error
|
||||
// ReadForm binds the formObject with the form data
|
||||
// it supports any kind of struct.
|
||||
// it supports any kind of type, including custom structs.
|
||||
// It will return nothing if request data are empty.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go
|
||||
ReadForm(formObjectPtr interface{}) error
|
||||
|
|
|
@ -103,7 +103,7 @@ func main() {
|
|||
ctx.Writef("All users")
|
||||
})
|
||||
// http://v1.localhost:8080/api/users/42
|
||||
usersAPI.Get("/{userid:number}", func(ctx iris.Context) {
|
||||
usersAPI.Get("/{userid:int}", func(ctx iris.Context) {
|
||||
ctx.Writef("user with id: %s", ctx.Params().Get("userid"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ func (r *customRouter) HandleRequest(ctx context.Context) {
|
|||
parts := strings.Split(path, "/")[1:]
|
||||
staticPath := "/" + parts[0]
|
||||
for _, route := range r.provider.GetRoutes() {
|
||||
if strings.HasPrefix(route.Path, staticPath) {
|
||||
if strings.HasPrefix(route.Path, staticPath) && route.Method == ctx.Method() {
|
||||
paramParts := parts[1:]
|
||||
for _, paramValue := range paramParts {
|
||||
for _, p := range route.Tmpl().Params {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/// TODO: showcase the `app.Downgrade` feature tomorrow if not already existing elsewhere.
|
||||
package main
|
||||
|
||||
func main() {
|
||||
panic("TODO")
|
||||
}
|
|
@ -35,7 +35,7 @@ func main() {
|
|||
// anything
|
||||
//
|
||||
// +-------------------------------+
|
||||
// | {param:int} or {param:number} |
|
||||
// | {param:int} or {param:int} |
|
||||
// +-------------------------------+
|
||||
// int type
|
||||
// both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
|
||||
|
@ -209,7 +209,7 @@ func main() {
|
|||
|
||||
// http://localhost:8080/game/a-zA-Z/level/42
|
||||
// remember, alphabetical is lowercase or uppercase letters only.
|
||||
app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) {
|
||||
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) {
|
||||
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build go1.11
|
||||
// +build js
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -16,6 +16,13 @@ type RequestParams struct {
|
|||
memstore.Store
|
||||
}
|
||||
|
||||
// Set inserts a value to the key-value storage.
|
||||
//
|
||||
// See `SetImmutable` and `Get` too.
|
||||
func (r *RequestParams) Set(key, value string) {
|
||||
r.Store.Set(key, value)
|
||||
}
|
||||
|
||||
// GetEntryAt will return the parameter's internal store's `Entry` based on the index.
|
||||
// If not found it will return an emptry `Entry`.
|
||||
func (r *RequestParams) GetEntryAt(index int) memstore.Entry {
|
||||
|
|
|
@ -56,7 +56,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string {
|
|||
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
|
||||
b.WriteString("/{name:string}/") // sugar.
|
||||
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
|
||||
b.WriteString("/{age:number}/end")
|
||||
b.WriteString("/{age:int}/end")
|
||||
paths[i] = b.String()
|
||||
|
||||
b.Reset()
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
"github.com/kataras/iris/core/router/node"
|
||||
)
|
||||
|
||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||
|
@ -25,25 +24,17 @@ type RequestHandler interface {
|
|||
RouteExists(ctx context.Context, method, path string) bool
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
Method string
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
Subdomain string
|
||||
Nodes *node.Nodes
|
||||
}
|
||||
|
||||
type routerHandler struct {
|
||||
trees []*tree
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
|
||||
func (h *routerHandler) getTree(method, subdomain string) *tree {
|
||||
func (h *routerHandler) getTree(method, subdomain string) *trie {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if t.Method == method && t.Subdomain == subdomain {
|
||||
if t.method == method && t.subdomain == subdomain {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +54,14 @@ func (h *routerHandler) addRoute(r *Route) error {
|
|||
t := h.getTree(method, subdomain)
|
||||
|
||||
if t == nil {
|
||||
n := node.Nodes{}
|
||||
n := newTrieNode()
|
||||
// first time we register a route to this method with this subdomain
|
||||
t = &tree{Method: method, Subdomain: subdomain, Nodes: &n}
|
||||
t = &trie{method: method, subdomain: subdomain, root: n}
|
||||
h.trees = append(h.trees, t)
|
||||
}
|
||||
return t.Nodes.Add(routeName, path, handlers)
|
||||
|
||||
t.insert(path, routeName, handlers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
|
@ -188,11 +181,11 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if method != t.Method {
|
||||
if method != t.method {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
if h.hosts && t.subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
|
@ -201,7 +194,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.Subdomain == SubdomainWildcardIndicator {
|
||||
if t.subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
|
@ -219,14 +212,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
continue
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
|
||||
} else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
|
||||
continue
|
||||
}
|
||||
}
|
||||
routeName, handlers := t.Nodes.Find(path, ctx.Params())
|
||||
if len(handlers) > 0 {
|
||||
ctx.SetCurrentRouteName(routeName)
|
||||
ctx.Do(handlers)
|
||||
n := t.search(path, ctx.Params())
|
||||
if n != nil {
|
||||
ctx.SetCurrentRouteName(n.RouteName)
|
||||
ctx.Do(n.Handlers)
|
||||
// found
|
||||
return
|
||||
}
|
||||
|
@ -237,15 +230,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
// a bit slower than previous implementation but @kataras let me to apply this change
|
||||
// because it's more reliable.
|
||||
//
|
||||
// if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not
|
||||
// run, therefore performance kept as before.
|
||||
if t.Nodes.Exists(path) {
|
||||
if h.subdomainAndPathAndMethodExists(ctx, t, "", path) {
|
||||
// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
|
||||
ctx.Header("Allow", t.Method)
|
||||
ctx.Header("Allow", t.method)
|
||||
ctx.StatusCode(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
@ -255,55 +245,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
ctx.StatusCode(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool {
|
||||
if method != "" && method != t.method {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.hosts && t.subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
return false // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
if serverHost == requestHost {
|
||||
return false // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
n := t.search(path, ctx.Params())
|
||||
return n != nil
|
||||
}
|
||||
|
||||
// RouteExists reports whether a particular route exists
|
||||
// It will search from the current subdomain of context's host, if not inside the root domain.
|
||||
func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if method != t.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.Subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
if serverHost == requestHost {
|
||||
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, handlers := t.Nodes.Find(path, ctx.Params())
|
||||
if len(handlers) > 0 {
|
||||
// found
|
||||
if h.subdomainAndPathAndMethodExists(ctx, t, method, path) {
|
||||
return true
|
||||
}
|
||||
|
||||
// not found or method not allowed.
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
@ -1,448 +0,0 @@
|
|||
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
|
||||
routeName string
|
||||
wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed
|
||||
paramNames []string // only-names
|
||||
childrenNodes Nodes
|
||||
handlers context.Handlers
|
||||
root bool
|
||||
rootWildcard bool // if it's a wildcard {path} type on root, it should allow everything but it is not conflicts with
|
||||
// any other static or dynamic or wildcard paths if exists on other nodes.
|
||||
}
|
||||
|
||||
// ErrDublicate returnned from `Add` when two or more routes have the same registered path.
|
||||
var ErrDublicate = errors.New("two or more routes have the same registered path")
|
||||
|
||||
/// TODO: clean up needed until v8.5
|
||||
|
||||
// Add adds a node to the tree, returns an ErrDublicate error on failure.
|
||||
func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error {
|
||||
// println("[Add] adding path: " + path)
|
||||
// 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
|
||||
}
|
||||
|
||||
var p []int
|
||||
for i := 0; i < len(path); i++ {
|
||||
idx := strings.IndexByte(path[i:], ':')
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
p = append(p, idx+i)
|
||||
i = idx + i
|
||||
}
|
||||
|
||||
for _, idx := range p {
|
||||
// print("-2 nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// print("-1 nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if nidx := idx + 1; len(path) > nidx {
|
||||
if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// print("nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if err := nodes.add(routeName, path, params, handlers, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prioritize by static path remember, they were already sorted by subdomains too.
|
||||
nodes.prioritize()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) {
|
||||
// println("[add] route name: " + routeName)
|
||||
// println("[add] adding path: " + path)
|
||||
|
||||
// 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 /
|
||||
|
||||
// set the wildcard param name to the root and its children.
|
||||
wildcardIdx := strings.IndexByte(path, '*')
|
||||
wildcardParamName := ""
|
||||
if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
|
||||
|
||||
// if path[len(path)-1] == '/' {
|
||||
// if root wildcard, then add it as it's and return
|
||||
rootWildcard := path == "/"
|
||||
if rootWildcard {
|
||||
path += "/" // if root wildcard, then do it like "//" instead of simple "/"
|
||||
}
|
||||
|
||||
n := &node{
|
||||
rootWildcard: rootWildcard,
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
*nodes = append(*nodes, n)
|
||||
// println("1. nodes.Add path: " + path)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
loop:
|
||||
for _, n := range *nodes {
|
||||
if n.rootWildcard {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(n.paramNames) == 0 && n.wildcardParamName != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
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],
|
||||
childrenNodes: Nodes{
|
||||
{
|
||||
s: n.s[i:],
|
||||
routeName: n.routeName,
|
||||
wildcardParamName: n.wildcardParamName, // wildcardParamName
|
||||
paramNames: n.paramNames,
|
||||
childrenNodes: n.childrenNodes,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
{
|
||||
s: path[i:],
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
},
|
||||
},
|
||||
root: n.root,
|
||||
}
|
||||
|
||||
// println("2. change n and return " + n.s[:i] + " and " + path[i:])
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) < len(n.s) {
|
||||
// println("3. change n and return | n.s[:len(path)] = " + n.s[:len(path)-1] + " and child: " + n.s[len(path)-1:])
|
||||
|
||||
*n = node{
|
||||
s: n.s[:len(path)],
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
childrenNodes: Nodes{
|
||||
{
|
||||
s: n.s[len(path):],
|
||||
routeName: n.routeName,
|
||||
wildcardParamName: n.wildcardParamName, // wildcardParamName
|
||||
paramNames: n.paramNames,
|
||||
childrenNodes: n.childrenNodes,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
},
|
||||
handlers: handlers,
|
||||
root: n.root,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) > len(n.s) {
|
||||
if n.wildcardParamName != "" {
|
||||
n := &node{
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
// println("3.5. nodes.Add path: " + n.s)
|
||||
*nodes = append(*nodes, n)
|
||||
return
|
||||
}
|
||||
|
||||
pathToAdd := path[len(n.s):]
|
||||
// println("4. nodes.Add route name: " + routeName)
|
||||
// println("4. nodes.Add path: " + pathToAdd)
|
||||
err = n.childrenNodes.add(routeName, pathToAdd, 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
|
||||
n.routeName = routeName
|
||||
return
|
||||
}
|
||||
|
||||
// START
|
||||
// Author's note:
|
||||
// 27 Oct 2017; fixes s|i|l+static+p
|
||||
// without breaking the current tests.
|
||||
if wildcardIdx > 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/"
|
||||
}
|
||||
// END
|
||||
|
||||
n := &node{
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
*nodes = append(*nodes, n)
|
||||
|
||||
// println("5. node add on path: " + path + " n.s: " + n.s + " wildcard param: " + n.wildcardParamName)
|
||||
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) (string, context.Handlers) {
|
||||
n, paramValues := nodes.findChild(path, nil)
|
||||
if n != nil {
|
||||
// map the params,
|
||||
// n.params are the param names
|
||||
if len(paramValues) > 0 {
|
||||
// println("-----------")
|
||||
// print("param values returned len: ")
|
||||
// println(len(paramValues))
|
||||
// println("first value is: " + paramValues[0])
|
||||
// print("n.paramNames len: ")
|
||||
// println(len(n.paramNames))
|
||||
for i, name := range n.paramNames {
|
||||
// println("setting param name: " + name + " = " + paramValues[i])
|
||||
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) {
|
||||
// println("len(paramValues) > len(n.paramNames)")
|
||||
lastWildcardVal := paramValues[len(paramValues)-1]
|
||||
// println("setting wildcard param name: " + n.wildcardParamName + " = " + lastWildcardVal)
|
||||
params.Set(n.wildcardParamName, lastWildcardVal)
|
||||
}
|
||||
}
|
||||
|
||||
return n.routeName, n.handlers
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Exists returns true if a node with that "path" exists,
|
||||
// otherise false.
|
||||
//
|
||||
// We don't care about parameters here.
|
||||
func (nodes Nodes) Exists(path string) bool {
|
||||
n, _ := nodes.findChild(path, nil)
|
||||
return n != nil && len(n.handlers) > 0
|
||||
}
|
||||
|
||||
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.childrenNodes.findChild(path[paramEnd:], append(params, path[:paramEnd]))
|
||||
}
|
||||
|
||||
// println("n.s: " + n.s)
|
||||
// print("n.childrenNodes len: ")
|
||||
// println(len(n.childrenNodes))
|
||||
// print("n.root: ")
|
||||
// println(n.root)
|
||||
|
||||
// by runtime check of:,
|
||||
// if n.s == "//" && n.root && n.wildcardParamName != "" {
|
||||
// but this will slow down, so we have a static field on the node itself:
|
||||
if n.rootWildcard {
|
||||
// println("return from n.rootWildcard")
|
||||
// single root wildcard
|
||||
if len(path) < 2 {
|
||||
// do not remove that, it seems useless but it's not,
|
||||
// we had an error while production, this fixes that.
|
||||
path = "/" + path
|
||||
}
|
||||
return n, append(params, path[1:])
|
||||
}
|
||||
|
||||
// second conditional may be unnecessary
|
||||
// because of the n.rootWildcard before, but do it.
|
||||
if n.wildcardParamName != "" && len(path) > 2 {
|
||||
// println("n has wildcard n.s: " + n.s + " on path: " + path)
|
||||
// n.s = static/, path = static
|
||||
|
||||
// println(n.s + " vs path: " + path)
|
||||
|
||||
// we could have /other/ as n.s so
|
||||
// we must do this check, remember:
|
||||
// now wildcards live on their own nodes
|
||||
if len(path) == len(n.s)-1 {
|
||||
// then it's like:
|
||||
// path = /other2
|
||||
// ns = /other2/
|
||||
if path == n.s[0:len(n.s)-1] {
|
||||
return n, params
|
||||
}
|
||||
}
|
||||
|
||||
// othwerwise path = /other2/dsadas
|
||||
// ns= /other2/
|
||||
if strings.HasPrefix(path, n.s) {
|
||||
if len(path) > len(n.s)+1 {
|
||||
return n, append(params, path[len(n.s):]) // without slash
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, n.s) {
|
||||
// fmt.Printf("---here root: %v, n.s: "+n.s+" and path: "+path+" is dynamic: %v , wildcardParamName: %s, children len: %v \n", n.root, n.isDynamic(), n.wildcardParamName, len(n.childrenNodes))
|
||||
// println(path + " n.s: " + n.s + " continue...")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(path) == len(n.s) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return n, params
|
||||
}
|
||||
|
||||
child, childParamNames := n.childrenNodes.findChild(path[len(n.s):], params)
|
||||
|
||||
// print("childParamNames len: ")
|
||||
// println(len(childParamNames))
|
||||
|
||||
// if len(childParamNames) > 0 {
|
||||
// println("childParamsNames[0] = " + childParamNames[0])
|
||||
// }
|
||||
|
||||
if child == nil || len(child.handlers) == 0 {
|
||||
if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.childrenNodes) > 0)) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// println("if child == nil.... | n.s = " + n.s)
|
||||
// print("n.paramNames len: ")
|
||||
// println(n.paramNames)
|
||||
// print("n.wildcardParamName is: ")
|
||||
// println(n.wildcardParamName)
|
||||
// print("return n, append(params, path[len(n.s) | params: ")
|
||||
// println(path[len(n.s):])
|
||||
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.childrenNodes {
|
||||
i++
|
||||
i += n.childLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *node) isDynamic() bool {
|
||||
return n.s == ":" || n.wildcardParamName != "" || n.rootWildcard
|
||||
}
|
||||
|
||||
// prioritize sets the static paths first.
|
||||
func (nodes Nodes) prioritize() {
|
||||
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.childrenNodes.prioritize()
|
||||
}
|
||||
}
|
|
@ -12,14 +12,6 @@ import (
|
|||
"github.com/kataras/iris/macro/interpreter/lexer"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
|
||||
ParamStart = ":"
|
||||
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
|
||||
// path parameter.
|
||||
WildcardParamStart = "*"
|
||||
)
|
||||
|
||||
// Param receives a parameter name prefixed with the ParamStart symbol.
|
||||
func Param(name string) string {
|
||||
return prefix(name, ParamStart)
|
||||
|
@ -35,10 +27,8 @@ func WildcardParam(name string) string {
|
|||
|
||||
func convertMacroTmplToNodePath(tmpl macro.Template) string {
|
||||
routePath := tmpl.Src
|
||||
if len(tmpl.Params) > 0 {
|
||||
if routePath[len(routePath)-1] == '/' {
|
||||
routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's.
|
||||
}
|
||||
if len(routePath) > 1 && routePath[len(routePath)-1] == '/' {
|
||||
routePath = routePath[0 : len(routePath)-1] // remove any last "/"
|
||||
}
|
||||
|
||||
// if it has started with {} and it's valid
|
||||
|
|
|
@ -53,8 +53,8 @@ func TestSplitPath(t *testing.T) {
|
|||
[]string{"/user", "/admin"}},
|
||||
{"/single_no_params",
|
||||
[]string{"/single_no_params"}},
|
||||
{"/single/{id:number}",
|
||||
[]string{"/single/{id:number}"}},
|
||||
{"/single/{id:int}",
|
||||
[]string{"/single/{id:int}"}},
|
||||
}
|
||||
|
||||
equalSlice := func(s1 []string, s2 []string) bool {
|
||||
|
|
|
@ -122,20 +122,25 @@ func TestRouterWildcardRootMany(t *testing.T) {
|
|||
|
||||
func TestRouterWildcardRootManyAndRootStatic(t *testing.T) {
|
||||
var tt = []testRoute{
|
||||
// all routes will be handlded by "h" because we added wildcard to root,
|
||||
// routes that may return 404 will be handled by the below route ("h" handler) because we added wildcard to root,
|
||||
// this feature is very important and can remove noumerous of previous hacks on our apps.
|
||||
//
|
||||
// Static paths and parameters have priority over wildcard, all three types can be registered in the same path prefix.
|
||||
//
|
||||
// Remember, all of those routes are registered don't be tricked by the visual appearance of the below test blocks.
|
||||
{"GET", "/{p:path}", h, []testRouteRequest{
|
||||
{"GET", "", "/other2almost/some", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/static/{p:path}", h, []testRouteRequest{
|
||||
{"GET", "", "/static", iris.StatusOK, same_as_request_path},
|
||||
{"GET", "", "/static", iris.StatusOK, same_as_request_path}, // HERE<- IF NOT FOUND THEN BACKWARDS TO WILDCARD IF THERE IS ONE, HMM.
|
||||
{"GET", "", "/static/something/here", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/", h, []testRouteRequest{
|
||||
{"GET", "", "/", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/other/{paramother:path}", h2, []testRouteRequest{
|
||||
{"GET", "", "/other", iris.StatusForbidden, same_as_request_path},
|
||||
// OK and not h2 because of the root wildcard.
|
||||
{"GET", "", "/other", iris.StatusOK, same_as_request_path},
|
||||
{"GET", "", "/other/wildcard", iris.StatusForbidden, same_as_request_path},
|
||||
{"GET", "", "/other/wildcard/here", iris.StatusForbidden, same_as_request_path},
|
||||
}},
|
||||
|
@ -145,6 +150,7 @@ func TestRouterWildcardRootManyAndRootStatic(t *testing.T) {
|
|||
}},
|
||||
{"GET", "/other2/static", h3, []testRouteRequest{
|
||||
{"GET", "", "/other2/static", iris.StatusOK, prefix_static_path_following_by_request_path},
|
||||
// h2(Forbiddenn) instead of h3 OK because it will be handled by the /other2/{paramothersecond:path}'s handler which gives 403.
|
||||
{"GET", "", "/other2/staticed", iris.StatusForbidden, same_as_request_path},
|
||||
}},
|
||||
}
|
||||
|
@ -165,6 +171,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) {
|
|||
// run the tests
|
||||
for _, tt := range tests {
|
||||
for _, req := range tt.requests {
|
||||
// t.Logf("req: %s:%s\n", tt.method, tt.path)
|
||||
method := req.method
|
||||
if method == "" {
|
||||
method = tt.method
|
||||
|
|
267
core/router/trie.go
Normal file
267
core/router/trie.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
|
||||
ParamStart = ":"
|
||||
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
|
||||
// path parameter.
|
||||
WildcardParamStart = "*"
|
||||
)
|
||||
|
||||
// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018
|
||||
type trieNode struct {
|
||||
parent *trieNode
|
||||
|
||||
children map[string]*trieNode
|
||||
hasDynamicChild bool // does one of the children contains a parameter or wildcard?
|
||||
childNamedParameter bool // is the child a named parameter (single segmnet)
|
||||
childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ?
|
||||
paramKeys []string // the param keys without : or *.
|
||||
end bool // it is a complete node, here we stop and we can say that the node is valid.
|
||||
key string // if end == true then key is filled with the original value of the insertion's key.
|
||||
// if key != "" && its parent has childWildcardParameter == true,
|
||||
// we need it to track the static part for the closest-wildcard's parameter storage.
|
||||
staticKey string
|
||||
|
||||
// insert data.
|
||||
Handlers context.Handlers
|
||||
RouteName string
|
||||
}
|
||||
|
||||
func newTrieNode() *trieNode {
|
||||
n := new(trieNode)
|
||||
return n
|
||||
}
|
||||
|
||||
func (tn *trieNode) hasChild(s string) bool {
|
||||
return tn.getChild(s) != nil
|
||||
}
|
||||
|
||||
func (tn *trieNode) getChild(s string) *trieNode {
|
||||
if tn.children == nil {
|
||||
tn.children = make(map[string]*trieNode)
|
||||
}
|
||||
|
||||
return tn.children[s]
|
||||
}
|
||||
|
||||
func (tn *trieNode) addChild(s string, n *trieNode) {
|
||||
if tn.children == nil {
|
||||
tn.children = make(map[string]*trieNode)
|
||||
}
|
||||
|
||||
if _, exists := tn.children[s]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
n.parent = tn
|
||||
tn.children[s] = n
|
||||
}
|
||||
|
||||
func (tn *trieNode) findClosestParentWildcardNode() *trieNode {
|
||||
tn = tn.parent
|
||||
for tn != nil {
|
||||
if tn.childWildcardParameter {
|
||||
return tn.getChild(WildcardParamStart)
|
||||
}
|
||||
|
||||
tn = tn.parent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tn *trieNode) String() string {
|
||||
return tn.key
|
||||
}
|
||||
|
||||
type trie struct {
|
||||
root *trieNode
|
||||
|
||||
// if true then it will handle any path if not other parent wildcard exists,
|
||||
// so even 404 (on http services) is up to it, see trie#insert.
|
||||
hasRootWildcard bool
|
||||
|
||||
method string
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
subdomain string
|
||||
}
|
||||
|
||||
func newTrie() *trie {
|
||||
return &trie{
|
||||
root: newTrieNode(),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
pathSep = "/"
|
||||
pathSepB = '/'
|
||||
)
|
||||
|
||||
func slowPathSplit(path string) []string {
|
||||
if path == "/" {
|
||||
return []string{"/"}
|
||||
}
|
||||
|
||||
return strings.Split(path, pathSep)[1:]
|
||||
}
|
||||
|
||||
func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
||||
input := slowPathSplit(path)
|
||||
|
||||
n := tr.root
|
||||
var paramKeys []string
|
||||
|
||||
for _, s := range input {
|
||||
c := s[0]
|
||||
|
||||
if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard {
|
||||
n.hasDynamicChild = true
|
||||
paramKeys = append(paramKeys, s[1:]) // without : or *.
|
||||
|
||||
// if node has already a wildcard, don't force a value, check for true only.
|
||||
if isParam {
|
||||
n.childNamedParameter = true
|
||||
s = ParamStart
|
||||
}
|
||||
|
||||
if isWildcard {
|
||||
n.childWildcardParameter = true
|
||||
s = WildcardParamStart
|
||||
if tr.root == n {
|
||||
tr.hasRootWildcard = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !n.hasChild(s) {
|
||||
child := newTrieNode()
|
||||
n.addChild(s, child)
|
||||
}
|
||||
|
||||
n = n.getChild(s)
|
||||
}
|
||||
|
||||
n.RouteName = routeName
|
||||
n.Handlers = handlers
|
||||
n.paramKeys = paramKeys
|
||||
n.key = path
|
||||
n.end = true
|
||||
|
||||
i := strings.Index(path, ParamStart)
|
||||
if i == -1 {
|
||||
i = strings.Index(path, WildcardParamStart)
|
||||
}
|
||||
if i == -1 {
|
||||
i = len(n.key)
|
||||
}
|
||||
|
||||
n.staticKey = path[:i]
|
||||
}
|
||||
|
||||
func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
|
||||
end := len(q)
|
||||
n := tr.root
|
||||
if end == 1 && q[0] == pathSepB {
|
||||
return n.getChild(pathSep)
|
||||
}
|
||||
|
||||
start := 1
|
||||
i := 1
|
||||
var paramValues []string
|
||||
|
||||
for {
|
||||
if i == end || q[i] == pathSepB {
|
||||
if child := n.getChild(q[start:i]); child != nil {
|
||||
n = child
|
||||
} else if n.childNamedParameter {
|
||||
n = n.getChild(ParamStart)
|
||||
if ln := len(paramValues); cap(paramValues) > ln {
|
||||
paramValues = paramValues[:ln+1]
|
||||
paramValues[ln] = q[start:i]
|
||||
} else {
|
||||
paramValues = append(paramValues, q[start:i])
|
||||
}
|
||||
} else if n.childWildcardParameter {
|
||||
n = n.getChild(WildcardParamStart)
|
||||
if ln := len(paramValues); cap(paramValues) > ln {
|
||||
paramValues = paramValues[:ln+1]
|
||||
paramValues[ln] = q[start:]
|
||||
} else {
|
||||
paramValues = append(paramValues, q[start:])
|
||||
}
|
||||
break
|
||||
} else {
|
||||
n = n.findClosestParentWildcardNode()
|
||||
if n != nil {
|
||||
// means that it has :param/static and *wildcard, we go trhough the :param
|
||||
// but the next path segment is not the /static, so go back to *wildcard
|
||||
// instead of not found.
|
||||
//
|
||||
// Fixes:
|
||||
// /hello/*p
|
||||
// /hello/:p1/static/:p2
|
||||
// req: http://localhost:8080/hello/dsadsa/static/dsadsa => found
|
||||
// req: http://localhost:8080/hello/dsadsa => but not found!
|
||||
// and
|
||||
// /second/wild/*p
|
||||
// /second/wild/static/otherstatic/
|
||||
// req: /second/wild/static/otherstatic/random => but not found!
|
||||
params.Set(n.paramKeys[0], q[len(n.staticKey):])
|
||||
return n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if i == end {
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if n == nil || !n.end {
|
||||
if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above).
|
||||
if n = n.findClosestParentWildcardNode(); n != nil {
|
||||
params.Set(n.paramKeys[0], q[len(n.staticKey):])
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
if tr.hasRootWildcard {
|
||||
// that's the case for root wildcard, tests are passing
|
||||
// even without it but stick with it for reference.
|
||||
// Note ote that something like:
|
||||
// Routes: /other2/*myparam and /other2/static
|
||||
// Reqs: /other2/staticed will be handled
|
||||
// the /other2/*myparam and not the root wildcard, which is what we want.
|
||||
//
|
||||
n = tr.root.getChild(WildcardParamStart)
|
||||
params.Set(n.paramKeys[0], q[1:])
|
||||
return n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, paramValue := range paramValues {
|
||||
if len(n.paramKeys) > i {
|
||||
params.Set(n.paramKeys[i], paramValue)
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
84
doc.go
84
doc.go
|
@ -491,7 +491,7 @@ Example code:
|
|||
// http://myhost.com/users/42/profile
|
||||
users.Get("/{id:uint64}/profile", userProfileHandler)
|
||||
// http://myhost.com/users/messages/1
|
||||
users.Get("/inbox/{id:number}", userMessageHandler)
|
||||
users.Get("/inbox/{id:int}", userMessageHandler)
|
||||
|
||||
|
||||
Custom HTTP Errors
|
||||
|
@ -553,7 +553,7 @@ Example code:
|
|||
|
||||
if err != nil {
|
||||
ctx.Writef("error while trying to parse userid parameter," +
|
||||
"this will never happen if :number is being used because if it's not integer it will fire Not Found automatically.")
|
||||
"this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.")
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -709,26 +709,62 @@ Standard macro types for parameters:
|
|||
| {param:string} |
|
||||
+------------------------+
|
||||
string type
|
||||
anything
|
||||
anything (single path segmnent)
|
||||
|
||||
+-------------------------------+
|
||||
| {param:number} or {param:int} |
|
||||
| {param:int} |
|
||||
+-------------------------------+
|
||||
int type
|
||||
both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
|
||||
-9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch
|
||||
|
||||
+-------------------------------+
|
||||
| {param:long} or {param:int64} |
|
||||
+-------------------------------+
|
||||
+------------------------+
|
||||
| {param:int8} |
|
||||
+------------------------+
|
||||
int8 type
|
||||
-128 to 127
|
||||
|
||||
+------------------------+
|
||||
| {param:int16} |
|
||||
+------------------------+
|
||||
int16 type
|
||||
-32768 to 32767
|
||||
|
||||
+------------------------+
|
||||
| {param:int32} |
|
||||
+------------------------+
|
||||
int32 type
|
||||
-2147483648 to 2147483647
|
||||
|
||||
+------------------------+
|
||||
| {param:int64} |
|
||||
+------------------------+
|
||||
int64 type
|
||||
-9223372036854775808 to 9223372036854775807
|
||||
|
||||
+------------------------+
|
||||
| {param:uint} |
|
||||
+------------------------+
|
||||
uint type
|
||||
0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32)
|
||||
|
||||
+------------------------+
|
||||
| {param:uint8} |
|
||||
+------------------------+
|
||||
uint8 type
|
||||
0 to 255
|
||||
|
||||
+------------------------+
|
||||
| {param:uint16} |
|
||||
+------------------------+
|
||||
uint16 type
|
||||
0 to 65535
|
||||
|
||||
+------------------------+
|
||||
| {param:uint32} |
|
||||
+------------------------+
|
||||
uint32 type
|
||||
0 to 4294967295
|
||||
|
||||
+------------------------+
|
||||
| {param:uint64} |
|
||||
+------------------------+
|
||||
|
@ -763,8 +799,8 @@ Standard macro types for parameters:
|
|||
| {param:path} |
|
||||
+------------------------+
|
||||
path type
|
||||
anything, should be the last part, more than one path segment,
|
||||
i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3"
|
||||
anything, should be the last part, can be more than one path segment,
|
||||
i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3"
|
||||
|
||||
if type is missing then parameter's type is defaulted to string, so
|
||||
{param} == {param:string}.
|
||||
|
@ -773,7 +809,7 @@ If a function not found on that type then the "string"'s types functions are bei
|
|||
i.e:
|
||||
|
||||
|
||||
{param:number min(3)}
|
||||
{param:int min(3)}
|
||||
|
||||
|
||||
Besides the fact that iris provides the basic types and some default "macro funcs"
|
||||
|
@ -782,16 +818,18 @@ you are able to register your own too!.
|
|||
Register a named path parameter function:
|
||||
|
||||
|
||||
app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool {
|
||||
[...]
|
||||
return true/false -> true means valid.
|
||||
app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool {
|
||||
return func(paramValue int) bool {
|
||||
[...]
|
||||
return true/false -> true means valid.
|
||||
}
|
||||
})
|
||||
|
||||
at the func(argument ...) you can have any standard type, it will be validated before the server starts
|
||||
so don't care about performance here, the only thing it runs at serve time is the returning func(paramValue string) bool.
|
||||
|
||||
{param:string equal(iris)} , "iris" will be the argument here:
|
||||
app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool {
|
||||
app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool {
|
||||
return func(paramValue string){ return argument == paramValue }
|
||||
})
|
||||
|
||||
|
@ -804,20 +842,16 @@ Example Code:
|
|||
ctx.Writef("Hello %s", ctx.Params().Get("name"))
|
||||
}) // type is missing = {name:string}
|
||||
|
||||
// Let's register our first macro attached to number macro type.
|
||||
// Let's register our first macro attached to int macro type.
|
||||
// "min" = the function
|
||||
// "minValue" = the argument of the function
|
||||
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
|
||||
// a user requests a path which contains the number macro type with the min(...) macro parameter function.
|
||||
app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool {
|
||||
// func(<T>) bool = the macro's path parameter evaluator, this executes in serve time when
|
||||
// a user requests a path which contains the int macro type with the min(...) macro parameter function.
|
||||
app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool {
|
||||
// do anything before serve here [...]
|
||||
// at this case we don't need to do anything
|
||||
return func(paramValue string) bool {
|
||||
n, err := strconv.Atoi(paramValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return n >= minValue
|
||||
return func(paramValue int) bool {
|
||||
return paramValue >= minValue
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ func MakeHandler(tmpl macro.Template) context.Handler {
|
|||
continue // allow.
|
||||
}
|
||||
|
||||
if !p.Eval(ctx.Params().Get(p.Name), ctx.Params()) {
|
||||
if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) {
|
||||
ctx.StatusCode(p.ErrCode)
|
||||
ctx.StopExecution()
|
||||
return
|
||||
|
|
|
@ -7,27 +7,27 @@ import (
|
|||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := `{id:number min(1) max(5) else 404}`
|
||||
input := `{id:int min(1) max(5) else 404}`
|
||||
|
||||
tests := []struct {
|
||||
expectedType token.Type
|
||||
expectedLiteral string
|
||||
}{
|
||||
{token.LBRACE, "{"}, // 0
|
||||
{token.IDENT, "id"}, // 1
|
||||
{token.COLON, ":"}, // 2
|
||||
{token.IDENT, "number"}, // 3
|
||||
{token.IDENT, "min"}, // 4
|
||||
{token.LPAREN, "("}, // 5
|
||||
{token.INT, "1"}, // 6
|
||||
{token.RPAREN, ")"}, // 7
|
||||
{token.IDENT, "max"}, // 8
|
||||
{token.LPAREN, "("}, // 9
|
||||
{token.INT, "5"}, // 10
|
||||
{token.RPAREN, ")"}, // 11
|
||||
{token.ELSE, "else"}, // 12
|
||||
{token.INT, "404"}, // 13
|
||||
{token.RBRACE, "}"}, // 14
|
||||
{token.LBRACE, "{"}, // 0
|
||||
{token.IDENT, "id"}, // 1
|
||||
{token.COLON, ":"}, // 2
|
||||
{token.IDENT, "int"}, // 3
|
||||
{token.IDENT, "min"}, // 4
|
||||
{token.LPAREN, "("}, // 5
|
||||
{token.INT, "1"}, // 6
|
||||
{token.RPAREN, ")"}, // 7
|
||||
{token.IDENT, "max"}, // 8
|
||||
{token.LPAREN, "("}, // 9
|
||||
{token.INT, "5"}, // 10
|
||||
{token.RPAREN, ")"}, // 11
|
||||
{token.ELSE, "else"}, // 12
|
||||
{token.INT, "404"}, // 13
|
||||
{token.RBRACE, "}"}, // 14
|
||||
}
|
||||
|
||||
l := New(input)
|
||||
|
|
|
@ -95,7 +95,7 @@ func TestParseParam(t *testing.T) {
|
|||
}{
|
||||
{true,
|
||||
ast.ParamStatement{
|
||||
Src: "{id:number min(1) max(5) else 404}",
|
||||
Src: "{id:int min(1) max(5) else 404}",
|
||||
Name: "id",
|
||||
Type: mustLookupParamType("number"),
|
||||
Funcs: []ast.ParamFunc{
|
||||
|
@ -111,6 +111,7 @@ func TestParseParam(t *testing.T) {
|
|||
|
||||
{true,
|
||||
ast.ParamStatement{
|
||||
// test alias of int.
|
||||
Src: "{id:number range(1,5)}",
|
||||
Name: "id",
|
||||
Type: mustLookupParamType("number"),
|
||||
|
@ -163,7 +164,7 @@ func TestParseParam(t *testing.T) {
|
|||
}}, // 6
|
||||
{true,
|
||||
ast.ParamStatement{
|
||||
Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN)
|
||||
Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN)
|
||||
Name: "id",
|
||||
Type: mustLookupParamType("number"),
|
||||
Funcs: []ast.ParamFunc{
|
||||
|
@ -236,9 +237,9 @@ func TestParse(t *testing.T) {
|
|||
valid bool
|
||||
expectedStatements []ast.ParamStatement
|
||||
}{
|
||||
{"/api/users/{id:number min(1) max(5) else 404}", true,
|
||||
{"/api/users/{id:int min(1) max(5) else 404}", true,
|
||||
[]ast.ParamStatement{{
|
||||
Src: "{id:number min(1) max(5) else 404}",
|
||||
Src: "{id:int min(1) max(5) else 404}",
|
||||
Name: "id",
|
||||
Type: paramTypeNumber,
|
||||
Funcs: []ast.ParamFunc{
|
||||
|
|
|
@ -14,7 +14,7 @@ type Token struct {
|
|||
// /about/{fullname:alphabetical}
|
||||
// /profile/{anySpecialName:string}
|
||||
// {id:uint64 range(1,5) else 404}
|
||||
// /admin/{id:number eq(1) else 402}
|
||||
// /admin/{id:int eq(1) else 402}
|
||||
// /file/{filepath:file else 405}
|
||||
const (
|
||||
EOF = iota // 0
|
||||
|
|
Loading…
Reference in New Issue
Block a user