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`.
# 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
## 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
* [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)
* [HTTP Listening](_examples/#http-listening)
* [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
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/profile/anyusername
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
@ -108,13 +113,12 @@ func main() {
// POST: http://localhost:8080/users
// PUT: http://localhost:8080/users
// DELETE: http://localhost:8080/users/42
// DELETE: http://localhost:8080/something?name=iris
// GET: http://admin.localhost:8080
// GET: http://admin.localhost:8080/settings
// GET: http://any_thing_here.localhost:8080
if err := app.Run(iris.Addr(":8080")); err != nil {
panic(err)
}
app.Run(iris.Addr(":8080"))
}
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
8.4.0
8.4.2
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.
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
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))`
- `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.

View File

@ -32,7 +32,7 @@ import (
const (
// 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.

View File

@ -141,7 +141,7 @@ func (t TController) HandlerOf(methodFunc methodfunc.MethodFunc) context.Handler
// the most important, execute the specific function
// from the controller that is responsible to handle
// 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 hasModels {
t.modelController.Handle(ctx, c)

View File

@ -1,68 +1,62 @@
package methodfunc
import (
"reflect"
"github.com/kataras/iris/context"
)
// FuncCaller is responsible to call the controller's function
// which is responsible
// for that request for this http method.
type FuncCaller interface {
// 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(ctx context.Context, f interface{})
}
// buildMethodCall builds the method caller.
// We have repeated code here but it's the only way
// to support more than one input arguments without performance cost compared to previous implementation.
// so it's hard-coded written to check the length of input args and their types.
func buildMethodCall(a *ast) func(ctx context.Context, f reflect.Value) {
// if accepts one or more parameters.
if a.dynamic {
// if one function input argument then call the function
// by "casting" (faster).
if l := len(a.paramKeys); l == 1 {
paramType := a.paramTypes[0]
paramKey := a.paramKeys[0]
type callerFunc func(ctx context.Context, f interface{})
if paramType == paramTypeInt {
return func(ctx context.Context, f reflect.Value) {
v, _ := ctx.Params().GetInt(paramKey)
f.Interface().(func(int))(v)
}
}
func (c callerFunc) MethodCall(ctx context.Context, f interface{}) {
c(ctx, f)
}
if paramType == paramTypeLong {
return func(ctx context.Context, f reflect.Value) {
v, _ := ctx.Params().GetInt64(paramKey)
f.Interface().(func(int64))(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())()
}
if paramType == paramTypeBoolean {
return func(ctx context.Context, f reflect.Value) {
v, _ := ctx.Params().GetBool(paramKey)
f.Interface().(func(bool))(v)
}
}
// string, path...
return func(ctx context.Context, f reflect.Value) {
f.Interface().(func(string))(ctx.Params().Get(paramKey))
}
}
// 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))
}
}
// remember,
// the router already checks for the correct type,
// we did pre-calculate everything
// and now we will pre-calculate the method caller itself as well.
if p.ParamType == paramTypeInt {
return func(ctx context.Context, f interface{}) {
paramValue, _ := ctx.Params().GetInt(paramName)
f.(func(int))(paramValue)
}
}
if p.ParamType == paramTypeLong {
return func(ctx context.Context, f interface{}) {
paramValue, _ := ctx.Params().GetInt64(paramName)
f.(func(int64))(paramValue)
}
}
if p.ParamType == paramTypeBoolean {
return func(ctx context.Context, f interface{}) {
paramValue, _ := ctx.Params().GetBool(paramName)
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)
// if it's static without any receivers then just call it.
return func(ctx context.Context, f reflect.Value) {
f.Interface().(func())()
}
}

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,13 +2,25 @@ package methodfunc
import (
"reflect"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
)
// MethodFunc the handler function.
type MethodFunc struct {
FuncInfo
FuncCaller
RelPath string
// 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
}
// Resolve returns all the method funcs
@ -17,15 +29,16 @@ type MethodFunc struct {
func Resolve(typ reflect.Type) (methodFuncs []MethodFunc) {
infos := fetchInfos(typ)
for _, info := range infos {
p, ok := resolveRelativePath(info)
if !ok {
parser := newFuncParser(info)
a, err := parser.parse()
if err != nil {
golog.Errorf("MVC: %s\n", err)
continue
}
caller := resolveCaller(p)
methodFunc := MethodFunc{
RelPath: p.RelPath,
RelPath: a.relPath,
FuncInfo: info,
FuncCaller: caller,
MethodCall: buildMethodCall(a),
}
methodFuncs = append(methodFuncs, methodFunc)

View File

@ -429,6 +429,7 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
)
app := iris.New()
app.Controller("/user/{username}", new(testCtrl0),
&testBindType{title: title})
@ -453,8 +454,9 @@ func (c *testControllerRelPathFromFunc) PostLogin() {}
func (c *testControllerRelPathFromFunc) GetAdminLogin() {}
func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {}
func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {}
func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {}
func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {}
func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments
func TestControllerRelPathFromFunc(t *testing.T) {
app := iris.New()
@ -472,6 +474,8 @@ func TestControllerRelPathFromFunc(t *testing.T) {
Body().Equal("GET:/something/false")
e.GET("/something/truee").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).
Body().Equal("GET:/login")