Update to Version 8.4.2 | Read HISTORY.md

https://github.com/kataras/iris/blob/master/HISTORY.md#fr-15-september-2017--v842

Former-commit-id: 0ee4cc1d93ef7f26e5d402fdfbe07062aff5b08c
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-09-15 15:05:35 +03:00
parent 1c512619c7
commit 69b5327ecc
14 changed files with 382 additions and 192 deletions

View File

@ -18,6 +18,48 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
# Fr, 15 September 2017 | v8.4.2
## MVC
Support more than one dynamic method function receivers.
```go
package main
import "github.com/kataras/iris"
func main() {
app := iris.New()
app.Controller("/user", new(UserController))
app.Run(iris.Addr("localhost:8080"))
}
type UserController struct { iris.Controller }
// Maps to GET /user
// Request example: http://localhost:8080/user
// as usual.
func (c *UserController) Get() {
c.Text = "hello from /user"
}
// Maps to GET /user/{paramfirst:long}
// Request example: http://localhost:8080/user/42
// as usual.
func (c *UserController) GetBy(userID int64) {
c.Ctx.Writef("hello user with id: %d", userID)
}
// NEW:
// Maps to GET /user/{paramfirst:long}/business/{paramsecond:long}
// Request example: http://localhost:8080/user/42/business/93
func (c *UserController) GetByBusinessBy(userID int64, businessID int64) {
c.Ctx.Writef("fetch a business id: %d that user with id: %d owns, may make your db query faster",
businessID, userID)
}
```
# Th, 07 September 2017 | v8.4.1 # Th, 07 September 2017 | v8.4.1
## Routing ## Routing

View File

@ -38,7 +38,7 @@ Iris may have reached version 8, but we're not stopping there. We have many feat
### 📑 Table of contents ### 📑 Table of contents
* [Installation](#-installation) * [Installation](#-installation)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-07-september-2017--v841) * [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#fr-15-september-2017--v842)
* [Learn](#-learn) * [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening) * [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration) * [Configuration](_examples/#configuration)

View File

@ -1 +1 @@
8.4.1:https://github.com/kataras/iris/blob/master/HISTORY.md#th-07-september-2017--v841 8.4.2:https://github.com/kataras/iris/blob/master/HISTORY.md#fr-15-september-2017--v842

View File

@ -98,6 +98,11 @@ func main() {
// GET: http://any_thing_here.localhost:8080 // GET: http://any_thing_here.localhost:8080
dynamicSubdomainRoutes.Get("/", info) dynamicSubdomainRoutes.Get("/", info)
app.Delete("/something", func(ctx iris.Context) {
name := ctx.URLParam("name")
ctx.Writef(name)
})
// GET: http://localhost:8080/ // GET: http://localhost:8080/
// GET: http://localhost:8080/profile/anyusername // GET: http://localhost:8080/profile/anyusername
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here // GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
@ -108,13 +113,12 @@ func main() {
// POST: http://localhost:8080/users // POST: http://localhost:8080/users
// PUT: http://localhost:8080/users // PUT: http://localhost:8080/users
// DELETE: http://localhost:8080/users/42 // DELETE: http://localhost:8080/users/42
// DELETE: http://localhost:8080/something?name=iris
// GET: http://admin.localhost:8080 // GET: http://admin.localhost:8080
// GET: http://admin.localhost:8080/settings // GET: http://admin.localhost:8080/settings
// GET: http://any_thing_here.localhost:8080 // GET: http://any_thing_here.localhost:8080
if err := app.Run(iris.Addr(":8080")); err != nil { app.Run(iris.Addr(":8080"))
panic(err)
}
} }
func info(ctx iris.Context) { func info(ctx iris.Context) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

5
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
8.4.0 8.4.2
Installation Installation
@ -820,6 +820,8 @@ and it adds its logic to its `BeginRequest`. Source file: https://github.com/kat
Read access to the current route via the `Route` field. Read access to the current route via the `Route` field.
Support for more than one input arguments (map to dynamic request path parameters).
Register one or more relative paths and able to get path parameters, i.e Register one or more relative paths and able to get path parameters, i.e
If `app.Controller("/user", new(user.Controller))` If `app.Controller("/user", new(user.Controller))`
@ -843,6 +845,7 @@ Register one or more relative paths and able to get path parameters, i.e
If `app.Controller("/equality", new(profile.Equality))` If `app.Controller("/equality", new(profile.Equality))`
- `func(*Controller) GetBy(is bool)` - `GET:/equality/{param:boolean}` - `func(*Controller) GetBy(is bool)` - `GET:/equality/{param:boolean}`
- `func(*Controller) GetByOtherBy(is bool, otherID int64)` - `GET:/equality/{paramfirst:boolean}/other/{paramsecond:long}`
Supported types for method functions receivers: int, int64, bool and string. Supported types for method functions receivers: int, int64, bool and string.

View File

@ -32,7 +32,7 @@ import (
const ( const (
// Version is the current version number of the Iris Web Framework. // Version is the current version number of the Iris Web Framework.
Version = "8.4.1" Version = "8.4.2"
) )
// HTTP status codes as registered with IANA. // HTTP status codes as registered with IANA.

View File

@ -141,7 +141,7 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
// the most important, execute the specific function // the most important, execute the specific function
// from the controller that is responsible to handle // from the controller that is responsible to handle
// this request, by method and path. // this request, by method and path.
handleRequest(ctx, c.Method(methodFunc.Index).Interface()) handleRequest(ctx, c.Method(methodFunc.Index))
// if had models, set them after the end-developer's handler. // if had models, set them after the end-developer's handler.
if hasModels { if hasModels {
t.modelController.Handle(ctx, c) t.modelController.Handle(ctx, c)

View File

@ -1,68 +1,62 @@
package methodfunc package methodfunc
import ( import (
"reflect"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
) )
// FuncCaller is responsible to call the controller's function // buildMethodCall builds the method caller.
// which is responsible // We have repeated code here but it's the only way
// for that request for this http method. // to support more than one input arguments without performance cost compared to previous implementation.
type FuncCaller interface { // so it's hard-coded written to check the length of input args and their types.
// MethodCall fires the actual handler. func buildMethodCall(a *ast) func(ctx context.Context, f reflect.Value) {
// The "ctx" is the current context, helps us to get any path parameter's values. // if accepts one or more parameters.
// if a.dynamic {
// The "f" is the controller's function which is responsible // if one function input argument then call the function
// for that request for this http method. // by "casting" (faster).
// That function can accept one parameter. if l := len(a.paramKeys); l == 1 {
// paramType := a.paramTypes[0]
// The default callers (and the only one for now) paramKey := a.paramKeys[0]
// are pre-calculated by the framework.
MethodCall(ctx context.Context, f interface{})
}
type callerFunc func(ctx context.Context, f interface{}) if paramType == paramTypeInt {
return func(ctx context.Context, f reflect.Value) {
func (c callerFunc) MethodCall(ctx context.Context, f interface{}) { v, _ := ctx.Params().GetInt(paramKey)
c(ctx, f) f.Interface().(func(int))(v)
}
func resolveCaller(p pathInfo) callerFunc {
// if it's standard `Get`, `Post` without parameters.
if p.ParamType == "" {
return func(ctx context.Context, f interface{}) {
f.(func())()
} }
} }
// remember, if paramType == paramTypeLong {
// the router already checks for the correct type, return func(ctx context.Context, f reflect.Value) {
// we did pre-calculate everything v, _ := ctx.Params().GetInt64(paramKey)
// and now we will pre-calculate the method caller itself as well. f.Interface().(func(int64))(v)
}
if p.ParamType == paramTypeInt { }
return func(ctx context.Context, f interface{}) {
paramValue, _ := ctx.Params().GetInt(paramName) if paramType == paramTypeBoolean {
f.(func(int))(paramValue) return func(ctx context.Context, f reflect.Value) {
v, _ := ctx.Params().GetBool(paramKey)
f.Interface().(func(bool))(v)
} }
} }
if p.ParamType == paramTypeLong { // string, path...
return func(ctx context.Context, f interface{}) { return func(ctx context.Context, f reflect.Value) {
paramValue, _ := ctx.Params().GetInt64(paramName) f.Interface().(func(string))(ctx.Params().Get(paramKey))
f.(func(int64))(paramValue) }
}
// if func input arguments are more than one then
// use the Call method (slower).
return func(ctx context.Context, f reflect.Value) {
f.Call(a.paramValues(ctx))
} }
} }
if p.ParamType == paramTypeBoolean { // if it's static without any receivers then just call it.
return func(ctx context.Context, f interface{}) { return func(ctx context.Context, f reflect.Value) {
paramValue, _ := ctx.Params().GetBool(paramName) f.Interface().(func())()
f.(func(bool))(paramValue)
}
}
// else it's string or path, both of them are simple strings.
return func(ctx context.Context, f interface{}) {
paramValue := ctx.Params().Get(paramName)
f.(func(string))(paramValue)
} }
} }

View File

@ -0,0 +1,89 @@
package methodfunc
import (
"unicode"
)
const (
tokenBy = "By"
tokenWildcard = "Wildcard" // should be followed by "By",
)
// word lexer, not characters.
type lexer struct {
words []string
cur int
}
func newLexer(s string) *lexer {
l := new(lexer)
l.reset(s)
return l
}
func (l *lexer) reset(trailing string) {
l.cur = -1
var words []string
if trailing != "" {
end := len(trailing)
start := -1
for i, n := 0, end; i < n; i++ {
c := rune(trailing[i])
if unicode.IsUpper(c) {
// it doesn't count the last uppercase
if start != -1 {
end = i
words = append(words, trailing[start:end])
}
start = i
continue
}
end = i + 1
}
if end > 0 && len(trailing) >= end {
words = append(words, trailing[start:end])
}
}
l.words = words
}
func (l *lexer) next() (w string) {
cur := l.cur + 1
if w = l.peek(cur); w != "" {
l.cur++
}
return
}
func (l *lexer) skip() {
if cur := l.cur + 1; cur < len(l.words) {
l.cur = cur
} else {
l.cur = len(l.words) - 1
}
}
func (l *lexer) peek(idx int) string {
if idx < len(l.words) {
return l.words[idx]
}
return ""
}
func (l *lexer) peekNext() (w string) {
return l.peek(l.cur + 1)
}
func (l *lexer) peekPrev() (w string) {
if l.cur > 0 {
cur := l.cur - 1
w = l.words[cur]
}
return w
}

View File

@ -0,0 +1,162 @@
package methodfunc
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/kataras/iris/context"
)
var posWords = map[int]string{
0: "",
1: "first",
2: "second",
3: "third",
4: "forth",
5: "five",
6: "sixth",
7: "seventh",
8: "eighth",
9: "ninth",
}
func genParamKey(argIdx int) string {
return "param" + posWords[argIdx] // paramfirst, paramsecond...
}
const (
paramTypeInt = "int"
paramTypeLong = "long"
paramTypeBoolean = "boolean"
paramTypeString = "string"
paramTypePath = "path"
)
var macroTypes = map[string]string{
"int": paramTypeInt,
"int64": paramTypeLong,
"bool": paramTypeBoolean,
"string": paramTypeString,
// there is "path" param type but it's being captured "on-air"
// "file" param type is not supported by the current implementation, yet
// but if someone ask for it I'll implement it, it's easy.
}
type funcParser struct {
info FuncInfo
lexer *lexer
}
func newFuncParser(info FuncInfo) *funcParser {
return &funcParser{
info: info,
lexer: newLexer(info.Trailing),
}
}
func (p *funcParser) parse() (*ast, error) {
a := new(ast)
funcArgPos := 0
for {
w := p.lexer.next()
if w == "" {
break
}
if w == tokenBy {
typ := p.info.Type
funcArgPos++ // starting with 1 because in typ.NumIn() the first is the struct receiver.
if p.lexer.peekPrev() == tokenBy || typ.NumIn() == 1 { // ByBy, then act this second By like a path
a.relPath += "/" + strings.ToLower(w)
continue
}
if typ.NumIn() <= funcArgPos {
return nil, errors.New("keyword 'By' found but length of input receivers are not match for " +
p.info.Name)
}
var (
paramKey = genParamKey(funcArgPos) // paramfirst, paramsecond...
paramType = paramTypeString // default string
)
// string, int...
goType := typ.In(funcArgPos).Name()
if p.lexer.peekNext() == tokenWildcard {
p.lexer.skip() // skip the Wildcard word.
paramType = paramTypePath
} else if pType, ok := macroTypes[goType]; ok {
// it's not wildcard, so check base on our available macro types.
paramType = pType
} else {
return nil, errors.New("invalid syntax for " + p.info.Name)
}
a.paramKeys = append(a.paramKeys, paramKey)
a.paramTypes = append(a.paramTypes, paramType)
// /{paramfirst:path}, /{paramfirst:long}...
a.relPath += fmt.Sprintf("/{%s:%s}", paramKey, paramType)
a.dynamic = true
continue
}
a.relPath += "/" + strings.ToLower(w)
}
return a, nil
}
type ast struct {
paramKeys []string // paramfirst, paramsecond... [0]
paramTypes []string // string, int, long, path... [0]
relPath string
dynamic bool // when paramKeys (and paramTypes, are equal) > 0
}
// moved to func_caller#buildMethodcall, it's bigger and with repeated code
// than this, below function but it's faster.
// func (a *ast) MethodCall(ctx context.Context, f reflect.Value) {
// if a.dynamic {
// f.Call(a.paramValues(ctx))
// return
// }
//
// f.Interface().(func())()
// }
func (a *ast) paramValues(ctx context.Context) []reflect.Value {
l := len(a.paramKeys)
values := make([]reflect.Value, l, l)
for i := 0; i < l; i++ {
paramKey := a.paramKeys[i]
paramType := a.paramTypes[i]
values[i] = getParamValueFromType(ctx, paramType, paramKey)
}
return values
}
func getParamValueFromType(ctx context.Context, paramType string, paramKey string) reflect.Value {
if paramType == paramTypeInt {
v, _ := ctx.Params().GetInt(paramKey)
return reflect.ValueOf(v)
}
if paramType == paramTypeLong {
v, _ := ctx.Params().GetInt64(paramKey)
return reflect.ValueOf(v)
}
if paramType == paramTypeBoolean {
v, _ := ctx.Params().GetBool(paramKey)
return reflect.ValueOf(v)
}
// string, path...
return reflect.ValueOf(ctx.Params().Get(paramKey))
}

View File

@ -1,121 +0,0 @@
package methodfunc
import (
"bytes"
"fmt"
"strings"
"unicode"
)
const (
by = "By"
wildcard = "Wildcard"
paramName = "param"
)
type pathInfo struct {
GoParamType string
ParamType string
RelPath string
}
const (
paramTypeInt = "int"
paramTypeLong = "long"
paramTypeBoolean = "boolean"
paramTypeString = "string"
paramTypePath = "path"
)
var macroTypes = map[string]string{
"int": paramTypeInt,
"int64": paramTypeLong,
"bool": paramTypeBoolean,
"string": paramTypeString,
// there is "path" param type but it's being captured "on-air"
// "file" param type is not supported by the current implementation, yet
// but if someone ask for it I'll implement it, it's easy.
}
func resolveRelativePath(info FuncInfo) (p pathInfo, ok bool) {
if info.Trailing == "" {
// it's valid
// it's just don't have a relative path,
// therefore p.RelPath will be empty, as we want.
return p, true
}
var (
typ = info.Type
tr = info.Trailing
relPath = resolvePathFromFunc(tr)
goType, paramType string
)
byKeywordIdx := strings.LastIndex(tr, by)
if byKeywordIdx != -1 && typ.NumIn() == 2 { // first is the struct receiver.
funcPath := tr[0:byKeywordIdx] // remove the "By"
goType = typ.In(1).Name()
afterBy := byKeywordIdx + len(by)
if len(tr) > afterBy {
if tr[afterBy:] == wildcard {
paramType = paramTypePath
} else {
// invalid syntax
return p, false
}
} else {
// it's not wildcard, so check base on our available macro types.
if paramType, ok = macroTypes[goType]; !ok {
// ivalid type
return p, false
}
}
// int, int64, bool and string are supported.
// as there is no way to get the parameter name
// we will use the "param" everywhere.
suffix := fmt.Sprintf("/{%s:%s}", paramName, paramType)
relPath = resolvePathFromFunc(funcPath) + suffix
}
// if GetSomething/PostSomething/PutSomething...
// we will not check for "Something" because we could
// occur unexpected behaviors to the existing users
// who using exported functions for controller's internal
// functionalities and not for serving a request path.
return pathInfo{
GoParamType: goType,
ParamType: paramType,
RelPath: relPath,
}, true
}
func resolvePathFromFunc(funcName string) string {
end := len(funcName)
start := -1
buf := &bytes.Buffer{}
for i, n := 0, end; i < n; i++ {
c := rune(funcName[i])
if unicode.IsUpper(c) {
// it doesn't count the last uppercase
if start != -1 {
end = i
s := "/" + strings.ToLower(funcName[start:end])
buf.WriteString(s)
}
start = i
continue
}
end = i + 1
}
if end > 0 && len(funcName) >= end {
buf.WriteString("/" + strings.ToLower(funcName[start:end]))
}
return buf.String()
}

View File

@ -2,12 +2,24 @@ package methodfunc
import ( import (
"reflect" "reflect"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
) )
// MethodFunc the handler function. // MethodFunc the handler function.
type MethodFunc struct { type MethodFunc struct {
FuncInfo FuncInfo
FuncCaller // MethodCall fires the actual handler.
// The "ctx" is the current context, helps us to get any path parameter's values.
//
// The "f" is the controller's function which is responsible
// for that request for this http method.
// That function can accept one parameter.
//
// The default callers (and the only one for now)
// are pre-calculated by the framework.
MethodCall func(ctx context.Context, f reflect.Value)
RelPath string RelPath string
} }
@ -17,15 +29,16 @@ type MethodFunc struct {
func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) { func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) {
infos := fetchInfos(typ) infos := fetchInfos(typ)
for _, info := range infos { for _, info := range infos {
p, ok := resolveRelativePath(info) parser := newFuncParser(info)
if !ok { a, err := parser.parse()
if err != nil {
golog.Errorf("MVC: %s\n", err)
continue continue
} }
caller := resolveCaller(p)
methodFunc := MethodFunc{ methodFunc := MethodFunc{
RelPath: p.RelPath, RelPath: a.relPath,
FuncInfo: info, FuncInfo: info,
FuncCaller: caller, MethodCall: buildMethodCall(a),
} }
methodFuncs = append(methodFuncs, methodFunc) methodFuncs = append(methodFuncs, methodFunc)

View File

@ -429,6 +429,7 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
) )
app := iris.New() app := iris.New()
app.Controller("/user/{username}", new(testCtrl0), app.Controller("/user/{username}", new(testCtrl0),
&testBindType{title: title}) &testBindType{title: title})
@ -455,6 +456,7 @@ func (c *testControllerRelPathFromFunc) GetAdminLogin() {}
func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {} func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {}
func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {}
func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments
func TestControllerRelPathFromFunc(t *testing.T) { func TestControllerRelPathFromFunc(t *testing.T) {
app := iris.New() app := iris.New()
@ -472,6 +474,8 @@ func TestControllerRelPathFromFunc(t *testing.T) {
Body().Equal("GET:/something/false") Body().Equal("GET:/something/false")
e.GET("/something/truee").Expect().Status(httptest.StatusNotFound) e.GET("/something/truee").Expect().Status(httptest.StatusNotFound)
e.GET("/something/falsee").Expect().Status(httptest.StatusNotFound) e.GET("/something/falsee").Expect().Status(httptest.StatusNotFound)
e.GET("/something/true/else/this/42").Expect().Status(httptest.StatusOK).
Body().Equal("GET:/something/true/else/this/42")
e.GET("/login").Expect().Status(httptest.StatusOK). e.GET("/login").Expect().Status(httptest.StatusOK).
Body().Equal("GET:/login") Body().Equal("GET:/login")