diff --git a/HISTORY.md b/HISTORY.md
index 0a24f31f..a4206c94 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,30 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+
+## v4 -> 5.0.1
+
+- **IMPROVE**: [Iris command line tool](https://github.com/kataras/iris/tree/master/iris) introduces a **new** `get` command (replacement for the old `create`)
+
+
+**The get command** downloads, installs and runs a project based on a `prototype`, such as `basic`, `static` and `mongo` .
+
+> These projects are located [online](https://github.com/iris-contrib/examples/tree/master/AIO_examples)
+
+
+```sh
+iris get basic
+```
+
+Downloads the [basic](https://github.com/iris-contrib/examples/tree/master/AIO_examples/basic) sample protoype project to the `$GOPATH/src/github.com/iris-contrib/examples` directory(the iris cmd will open this folder to you, automatically) builds, runs and watch for source code changes (hot-reload)
+
+[![Iris get command preview](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iriscmd.gif)](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/iriscmd.gif)
+
+
+- **CHANGE**: The `Path parameters` are now **immutable**. Now you don't have to copy a `path parameter` before passing to another function which maybe modifies it, this has a side-affect of `context.GetString("key") = context.Param("key")` so you have to be careful to not override a path parameter via other custom (per-context) user value.
+
+
+
## v3 -> v4 long term support
- **NEW**: `iris.StaticEmbedded`/`app := iris.New(); app.StaticEmbedded` - Embed static assets into your executable with [go-bindata](https://github.com/jteeuwen/go-bindata) and serve them.
diff --git a/README.md b/README.md
index 070c9b7b..75905613 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
-
+
@@ -870,14 +870,9 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **V4 LTS**
+Current: **5.0.1**
-A new LTS version is released every 6 months
-
-> LTS stands for Long Term Support
-
-
-Todo for the next release
+Todo
------------
- [ ] Server-side React render, as requested [here](https://github.com/kataras/iris/issues/503)
@@ -913,7 +908,7 @@ under the Apache Version 2 license found in the [LICENSE file](LICENSE).
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-Apache%20Version%202-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
-[Release Widget]: https://img.shields.io/badge/release-V4%20LTS%20-blue.svg?style=flat-square
+[Release Widget]: https://img.shields.io/badge/release-V5.0.1%20-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat%20-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris
diff --git a/context.go b/context.go
index 6af586c1..3f660fa8 100644
--- a/context.go
+++ b/context.go
@@ -2,6 +2,7 @@ package iris
import (
"bufio"
+ "bytes"
"encoding/base64"
"encoding/json"
"encoding/xml"
@@ -93,7 +94,6 @@ type (
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
Context struct {
*fasthttp.RequestCtx
- Params PathParameters
framework *Framework
//keep track all registed middleware (handlers)
Middleware Middleware // exported because is useful for debugging
@@ -145,36 +145,6 @@ func (ctx *Context) GetHandlerName() string {
/* Request */
-// Param returns the string representation of the key's path named parameter's value
-//
-// Return value should be never stored directly, instead store it to a local variable,
-// for example
-// instead of: context.Session().Set("name", ctx.Param("user"))
-// do this: username:= ctx.Param("user");ctx.Session().Set("name", username)
-func (ctx *Context) Param(key string) string {
- return ctx.Params.Get(key)
-}
-
-// ParamInt returns the int representation of the key's path named parameter's value
-//
-// Return value should be never stored directly, instead store it to a local variable,
-// for example
-// instead of: context.Session().Set("age", ctx.Param("age"))
-// do this: age:= ctx.Param("age");ctx.Session().Set("age", age)
-func (ctx *Context) ParamInt(key string) (int, error) {
- return strconv.Atoi(ctx.Param(key))
-}
-
-// ParamInt64 returns the int64 representation of the key's path named parameter's value
-//
-// Return value should be never stored directly, instead store it to a local variable,
-// for example
-// instead of: context.Session().Set("ms", ctx.ParamInt64("ms"))
-// do this: ms:= ctx.ParamInt64("ms");ctx.Session().Set("ms", ms)
-func (ctx *Context) ParamInt64(key string) (int64, error) {
- return strconv.ParseInt(ctx.Param(key), 10, 64)
-}
-
// URLParam returns the get parameter from a request , if any
func (ctx *Context) URLParam(key string) string {
return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
@@ -826,6 +796,14 @@ func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
/* Storage */
+// ValuesLen returns the total length of the user values storage, some of them maybe path parameters
+func (ctx *Context) ValuesLen() (n int) {
+ ctx.VisitUserValues(func([]byte, interface{}) {
+ n++
+ })
+ return
+}
+
// Get returns the user's value from a key
// if doesn't exists returns nil
func (ctx *Context) Get(key string) interface{} {
@@ -852,14 +830,19 @@ func (ctx *Context) GetString(key string) string {
return ""
}
-// GetInt same as Get but returns the value as int
-// if nothing founds returns -1
-func (ctx *Context) GetInt(key string) int {
- if v, ok := ctx.Get(key).(int); ok {
- return v
+var errIntParse = errors.New("Unable to find or parse the integer, found: %#v")
+
+// GetInt same as Get but tries to convert the return value as integer
+// if nothing found or canno be parsed to integer it returns an error
+func (ctx *Context) GetInt(key string) (int, error) {
+ v := ctx.Get(key)
+ if vint, ok := v.(int); ok {
+ return vint, nil
+ } else if vstring, sok := v.(string); sok {
+ return strconv.Atoi(vstring)
}
- return -1
+ return -1, errIntParse.Format(v)
}
// Set sets a value to a key in the values map
@@ -867,6 +850,60 @@ func (ctx *Context) Set(key string, value interface{}) {
ctx.RequestCtx.SetUserValue(key, value)
}
+// ParamsLen tries to return all the stored values which values are string, probably most of them will be the path parameters
+func (ctx *Context) ParamsLen() (n int) {
+ ctx.VisitUserValues(func(kb []byte, vg interface{}) {
+ if _, ok := vg.(string); ok {
+ n++
+ }
+
+ })
+ return
+}
+
+// Param returns the string representation of the key's path named parameter's value
+// same as GetString
+func (ctx *Context) Param(key string) string {
+ return ctx.GetString(key)
+}
+
+// ParamInt returns the int representation of the key's path named parameter's value
+// same as GetInt
+func (ctx *Context) ParamInt(key string) (int, error) {
+ return ctx.GetInt(key)
+}
+
+// ParamInt64 returns the int64 representation of the key's path named parameter's value
+func (ctx *Context) ParamInt64(key string) (int64, error) {
+ return strconv.ParseInt(ctx.Param(key), 10, 64)
+}
+
+// ParamsSentence returns a string implementation of all parameters that this context keeps
+// hasthe form of key1=value1,key2=value2...
+func (ctx *Context) ParamsSentence() string {
+ var buff bytes.Buffer
+ ctx.VisitUserValues(func(kb []byte, vg interface{}) {
+ v, ok := vg.(string)
+ if !ok {
+ return
+ }
+ k := string(kb)
+ buff.WriteString(k)
+ buff.WriteString("=")
+ buff.WriteString(v)
+ // we don't know where that (yet) stops so...
+ buff.WriteString(",")
+
+ })
+ result := buff.String()
+ if len(result) < 2 {
+ return ""
+ }
+
+ return result[0 : len(result)-1]
+
+}
+
// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
//
// Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment.
diff --git a/context_test.go b/context_test.go
index 143df767..5f5b053d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -72,16 +72,24 @@ func TestContextDoNextStop(t *testing.T) {
}
}
+type pathParameter struct {
+ Key string
+ Value string
+}
+type pathParameters []pathParameter
+
// White-box testing *
func TestContextParams(t *testing.T) {
- var context iris.Context
- params := iris.PathParameters{
- iris.PathParameter{Key: "testkey", Value: "testvalue"},
- iris.PathParameter{Key: "testkey2", Value: "testvalue2"},
- iris.PathParameter{Key: "id", Value: "3"},
- iris.PathParameter{Key: "bigint", Value: "548921854390354"},
+ context := &iris.Context{RequestCtx: &fasthttp.RequestCtx{}}
+ params := pathParameters{
+ pathParameter{Key: "testkey", Value: "testvalue"},
+ pathParameter{Key: "testkey2", Value: "testvalue2"},
+ pathParameter{Key: "id", Value: "3"},
+ pathParameter{Key: "bigint", Value: "548921854390354"},
+ }
+ for _, p := range params {
+ context.Set(p.Key, p.Value)
}
- context.Params = params
if v := context.Param(params[0].Key); v != params[0].Value {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[0].Value, context.Param("testkey"))
@@ -90,8 +98,8 @@ func TestContextParams(t *testing.T) {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[1].Value, context.Param("testkey2"))
}
- if len(context.Params) != len(params) {
- t.Fatalf("Expecting to have %d parameters but we got %d", len(params), len(context.Params))
+ if context.ParamsLen() != len(params) {
+ t.Fatalf("Expecting to have %d parameters but we got %d", len(params), context.ParamsLen())
}
if vi, err := context.ParamInt(params[2].Key); err != nil {
@@ -111,7 +119,7 @@ func TestContextParams(t *testing.T) {
iris.ResetDefault()
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
iris.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) {
- paramsStr := ctx.Params.String()
+ paramsStr := ctx.ParamsSentence()
ctx.Write(paramsStr)
})
@@ -349,8 +357,8 @@ func TestContextRedirectTo(t *testing.T) {
iris.Get("/mypath", h)("my-path")
iris.Get("/mypostpath", h)("my-post-path")
iris.Get("mypath/with/params/:param1/:param2", func(ctx *iris.Context) {
- if len(ctx.Params) != 2 {
- t.Fatalf("Strange error, expecting parameters to be two but we got: %d", len(ctx.Params))
+ if l := ctx.ParamsLen(); l != 2 {
+ t.Fatalf("Strange error, expecting parameters to be two but we got: %d", l)
}
ctx.Write(ctx.PathString())
})("my-path-with-params")
@@ -462,7 +470,7 @@ func TestContextFlashMessages(t *testing.T) {
firstKey := "name"
lastKey := "package"
- values := iris.PathParameters{iris.PathParameter{Key: firstKey, Value: "kataras"}, iris.PathParameter{Key: lastKey, Value: "iris"}}
+ values := pathParameters{pathParameter{Key: firstKey, Value: "kataras"}, pathParameter{Key: lastKey, Value: "iris"}}
jsonExpected := map[string]string{firstKey: "kataras", lastKey: "iris"}
// set the flashes, the cookies are filled
iris.Put("/set", func(ctx *iris.Context) {
diff --git a/http.go b/http.go
index af06b5ce..c854c378 100644
--- a/http.go
+++ b/http.go
@@ -317,16 +317,6 @@ const (
)
type (
- // PathParameter is a struct which contains Key and Value, used for named path parameters
- PathParameter struct {
- Key string
- Value string
- }
-
- // PathParameters type for a slice of PathParameter
- // Tt's a slice of PathParameter type, because it's faster than map
- PathParameters []PathParameter
-
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
entryCase uint8
@@ -356,58 +346,6 @@ var (
errMuxEntryWildcardMissingSlash = errors.New("Router: No slash(/) were found before wildcard in the route path: '%s' !")
)
-// Get returns a value from a key inside this Parameters
-// If no parameter with this key given then it returns an empty string
-func (params PathParameters) Get(key string) string {
- for _, p := range params {
- if p.Key == key {
- return p.Value
- }
- }
- return ""
-}
-
-// String returns a string implementation of all parameters that this PathParameters object keeps
-// hasthe form of key1=value1,key2=value2...
-func (params PathParameters) String() string {
- var buff bytes.Buffer
- for i := range params {
- buff.WriteString(params[i].Key)
- buff.WriteString("=")
- buff.WriteString(params[i].Value)
- if i < len(params)-1 {
- buff.WriteString(",")
- }
-
- }
- return buff.String()
-}
-
-// ParseParams receives a string and returns PathParameters (slice of PathParameter)
-// received string must have this form: key1=value1,key2=value2...
-func ParseParams(str string) PathParameters {
- _paramsstr := strings.Split(str, ",")
- if len(_paramsstr) == 0 {
- return nil
- }
-
- params := make(PathParameters, 0) // PathParameters{}
-
- // for i := 0; i < len(_paramsstr); i++ {
- for i := range _paramsstr {
- idxOfEq := strings.IndexRune(_paramsstr[i], '=')
- if idxOfEq == -1 {
- //error
- return nil
- }
-
- key := _paramsstr[i][:idxOfEq]
- val := _paramsstr[i][idxOfEq+1:]
- params = append(params, PathParameter{key, val})
- }
- return params
-}
-
// getParamsLen returns the parameters length from a given path
func getParamsLen(path string) uint8 {
var n uint
@@ -644,8 +582,7 @@ func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middle
}
// get is used by the Router, it finds and returns the correct muxEntry for a path
-func (e *muxEntry) get(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
- params = _params
+func (e *muxEntry) get(path string, ctx *Context) (mustRedirect bool) {
loop:
for {
if len(path) > len(e.part) {
@@ -674,13 +611,7 @@ loop:
end++
}
- if cap(params) < int(e.paramsLen) {
- params = make(PathParameters, 0, e.paramsLen)
- }
- i := len(params)
- params = params[:i+1]
- params[i].Key = e.part[1:]
- params[i].Value = path[:end]
+ ctx.Set(e.part[1:], path[:end])
if end < len(path) {
if len(e.nodes) > 0 {
@@ -692,8 +623,7 @@ loop:
mustRedirect = (len(path) == end+1)
return
}
-
- if middleware = e.middleware; middleware != nil {
+ if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
} else if len(e.nodes) == 1 {
e = e.nodes[0]
@@ -703,15 +633,9 @@ loop:
return
case matchEverything:
- if cap(params) < int(e.paramsLen) {
- params = make(PathParameters, 0, e.paramsLen)
- }
- i := len(params)
- params = params[:i+1]
- params[i].Key = e.part[2:]
- params[i].Value = path
- middleware = e.middleware
+ ctx.Set(e.part[2:], path)
+ ctx.Middleware = e.middleware
return
default:
@@ -719,7 +643,7 @@ loop:
}
}
} else if path == e.part {
- if middleware = e.middleware; middleware != nil {
+ if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
}
@@ -1159,11 +1083,9 @@ func (mux *serveMux) BuildHandler() HandlerFunc {
}
}
- middleware, params, mustRedirect := tree.entry.get(routePath, context.Params) // pass the parameters here for 0 allocation
- if middleware != nil {
+ mustRedirect := tree.entry.get(routePath, context) // pass the parameters here for 0 allocation
+ if context.Middleware != nil {
// ok we found the correct route, serve it and exit entirely from here
- context.Params = params
- context.Middleware = middleware
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
context.Do()
return
diff --git a/http_test.go b/http_test.go
index 5a6dd83b..751f2612 100644
--- a/http_test.go
+++ b/http_test.go
@@ -348,7 +348,7 @@ func TestMuxSimple(t *testing.T) {
iris.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.SetStatusCode(r.Status)
if r.Params != nil && len(r.Params) > 0 {
- ctx.SetBodyString(ctx.Params.String())
+ ctx.SetBodyString(ctx.ParamsSentence())
} else if r.URLParams != nil && len(r.URLParams) > 0 {
if len(r.URLParams) != len(ctx.URLParams()) {
t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams()))
diff --git a/iris.go b/iris.go
index 8de536c9..53082a66 100644
--- a/iris.go
+++ b/iris.go
@@ -64,6 +64,7 @@ import (
"sync"
"time"
+ "bytes"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-serializer"
@@ -76,9 +77,9 @@ import (
const (
// IsLongTermSupport flag is true when the below version number is a long-term-support version
- IsLongTermSupport = true
+ IsLongTermSupport = false
// Version is the current version number of the Iris web framework
- Version = "4"
+ Version = "5.0.1"
banner = ` _____ _
|_ _| (_)
@@ -681,7 +682,6 @@ func ReleaseCtx(ctx *Context) {
// ReleaseCtx puts the Iris' Context back to the pool in order to be re-used
// see .AcquireCtx & .Serve
func (s *Framework) ReleaseCtx(ctx *Context) {
- ctx.Params = ctx.Params[0:0]
ctx.Middleware = nil
ctx.session = nil
s.contextPool.Put(ctx)
@@ -1425,7 +1425,7 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
// or no, I changed my mind, let all be named parameters and let users to decide what info they need,
// using the Context to take more values (post form,url params and so on).-
- paramPrefix := "param"
+ paramPrefix := []byte("param")
for _, methodName := range AllMethods {
methodWithBy := strings.Title(strings.ToLower(methodName)) + "By"
if method, found := typ.MethodByName(methodWithBy); found {
@@ -1439,9 +1439,9 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
for i := 1; i < numInLen; i++ { // from 1 because the first is the 'object'
if registedPath[len(registedPath)-1] == slashByte {
- registedPath += ":" + paramPrefix + strconv.Itoa(i)
+ registedPath += ":" + string(paramPrefix) + strconv.Itoa(i)
} else {
- registedPath += "/:" + paramPrefix + strconv.Itoa(i)
+ registedPath += "/:" + string(paramPrefix) + strconv.Itoa(i)
}
}
@@ -1454,15 +1454,15 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
args := make([]reflect.Value, paramsLen+1, paramsLen+1)
args[0] = newController
- realParamsLen := len(ctx.Params)
j := 1
- for i := 0; i < realParamsLen; i++ { // here we don't looping with the len we are already known by the 'API' because maybe there is a party/or/path witch accepting parameters before, see https://github.com/kataras/iris/issues/293
- if strings.HasPrefix(ctx.Params[i].Key, paramPrefix) {
- args[j] = reflect.ValueOf(ctx.Params[i].Value)
+
+ ctx.VisitUserValues(func(k []byte, v interface{}) {
+ if bytes.HasPrefix(k, paramPrefix) {
+ args[j] = reflect.ValueOf(v.(string))
j++ // the first parameter is the context, other are the path parameters, j++ to be align with (API's registered)paramsLen
}
- }
+ })
methodFunc.Call(args)
})
diff --git a/iris/get.go b/iris/get.go
index 2921d654..00ddfc34 100644
--- a/iris/get.go
+++ b/iris/get.go
@@ -91,7 +91,7 @@ func buildGetCommand() *cli.Cmd {
availabletypes = append(availabletypes, "'"+k+"'")
}
// comma separated of projects' map key
- return cli.Command("get", "gets & runs a simple protoype-based project").
+ return cli.Command("get", "gets & runs a simple prototype-based project").
Flag("type",
"basic",
// we take the os.Args in order to have access both via subcommand and when flag passed