Introduce version 5.0.1

This commit is contained in:
Gerasimos Maropoulos 2016-10-25 15:58:18 +03:00
parent 9958337e5d
commit 78d145c207
8 changed files with 144 additions and 158 deletions

View File

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

View File

@ -19,7 +19,7 @@
<br/>
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204%20LTS%20-blue.svg?style=flat-square" alt="Releases"></a>
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.0.1%20-blue.svg?style=flat-square" alt="Releases"></a>
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
@ -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

View File

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

View File

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

94
http.go
View File

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

View File

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

22
iris.go
View File

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

View File

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