implement mvc HandleError as requested at #1244

Former-commit-id: 58a69f9cffe67c3aa1bab5d9425c5df65e2367ed
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-04-16 18:01:48 +03:00
parent df3a68255c
commit 0d4d2bd3fa
13 changed files with 145 additions and 363 deletions

View File

@ -6,7 +6,7 @@ Click [here](HISTORY.md#su-18-november-2018--v1110) to read about the versioning
<a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a> <a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a>
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.1-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) [![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.1-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkataras%2Firis.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkataras%2Firis?ref=badge_shield)
Iris is a fast, simple yet fully featured and very efficient web framework for Go. Iris is a fast, simple yet fully featured and very efficient web framework for Go.

View File

@ -0,0 +1,44 @@
package main
import (
"fmt"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
mvcApp := mvc.New(app)
// To all controllers, it can optionally be overridden per-controller
// if the controller contains the `HandleError(ctx iris.Context, err error)` function.
//
mvcApp.HandleError(func(ctx iris.Context, err error) {
ctx.HTML(fmt.Sprintf("<b>%s</b>", err.Error()))
})
//
mvcApp.Handle(new(myController))
// http://localhost:8080
app.Run(iris.Addr(":8080"))
}
func basicMVC(app *mvc.Application) {
// GET: http://localhost:8080
app.Handle(new(myController))
}
type myController struct {
}
// overriddes the mvcApp.HandleError function.
// func (c *myController) HandleError(ctx iris.Context, err error) {
// ctx.HTML(fmt.Sprintf("<i>%s</i>", err.Error()))
// }
func (c *myController) Get() error {
return fmt.Errorf("error here")
}

View File

@ -51,7 +51,7 @@ func handleConnection(c websocket.Connection) {
// Read events from browser // Read events from browser
c.On("chat", func(msg string) { c.On("chat", func(msg string) {
// Print the message to the console, c.Context() is the iris's http context. // Print the message to the console, c.Context() is the iris's http context.
fmt.Printf("%s sent: %s\n", c.Context().RemoteAddr(), msg) fmt.Printf("[%s <%s>] %s\n", c.ID(), c.Context().RemoteAddr(), msg)
// Write message back to the client message owner with: // Write message back to the client message owner with:
// c.Emit("chat", msg) // c.Emit("chat", msg)
// Write message to all except this client with: // Write message to all except this client with:

View File

@ -1,198 +0,0 @@
package main
import (
"bufio"
"context"
"log"
"math/rand"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/kataras/iris/websocket"
)
var (
url = "ws://localhost:8080"
f *os.File
)
const totalClients = 16000 // max depends on the OS.
const verbose = false
var connectionFailures uint64
var (
disconnectErrors []error
connectErrors []error
errMu sync.Mutex
)
func collectError(op string, err error) {
errMu.Lock()
defer errMu.Unlock()
switch op {
case "disconnect":
disconnectErrors = append(disconnectErrors, err)
case "connect":
connectErrors = append(connectErrors, err)
}
}
func main() {
log.Println("-- Running...")
var err error
f, err = os.Open("./test.data")
if err != nil {
panic(err)
}
defer f.Close()
start := time.Now()
wg := new(sync.WaitGroup)
for i := 0; i < totalClients/4; i++ {
wg.Add(1)
go connect(wg, 5*time.Second)
}
for i := 0; i < totalClients/4; i++ {
wg.Add(1)
waitTime := time.Duration(rand.Intn(5)) * time.Millisecond
time.Sleep(waitTime)
go connect(wg, 14*time.Second+waitTime)
}
for i := 0; i < totalClients/4; i++ {
wg.Add(1)
waitTime := time.Duration(rand.Intn(10)) * time.Millisecond
time.Sleep(waitTime)
go connect(wg, 9*time.Second+waitTime)
}
for i := 0; i < totalClients/4; i++ {
wg.Add(1)
waitTime := time.Duration(rand.Intn(5)) * time.Millisecond
time.Sleep(waitTime)
go connect(wg, 7*time.Second+waitTime)
}
wg.Wait()
log.Printf("execution time [%s]", time.Since(start))
log.Println()
if connectionFailures > 0 {
log.Printf("Finished with %d/%d connection failures.", connectionFailures, totalClients)
}
if n := len(connectErrors); n > 0 {
log.Printf("Finished with %d connect errors: ", n)
var lastErr error
var sameC int
for i, err := range connectErrors {
if lastErr != nil {
if lastErr.Error() == err.Error() {
sameC++
continue
} else {
_, ok := lastErr.(*net.OpError)
if ok {
if _, ok = err.(*net.OpError); ok {
sameC++
continue
}
}
}
}
if sameC > 0 {
log.Printf("and %d more like this...\n", sameC)
sameC = 0
continue
}
log.Printf("[%d] - %v\n", i+1, err)
lastErr = err
}
}
if n := len(disconnectErrors); n > 0 {
log.Printf("Finished with %d disconnect errors\n", n)
for i, err := range disconnectErrors {
if err == websocket.ErrAlreadyDisconnected && i > 0 {
continue
}
log.Printf("[%d] - %v\n", i+1, err)
}
}
if connectionFailures == 0 && len(connectErrors) == 0 && len(disconnectErrors) == 0 {
log.Println("ALL OK.")
}
log.Println("-- Finished.")
}
func connect(wg *sync.WaitGroup, alive time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), alive)
defer cancel()
c, err := websocket.Dial(ctx, url, websocket.ConnectionConfig{})
if err != nil {
atomic.AddUint64(&connectionFailures, 1)
collectError("connect", err)
wg.Done()
return err
}
c.OnError(func(err error) {
log.Printf("error: %v", err)
})
disconnected := false
c.OnDisconnect(func() {
if verbose {
log.Printf("I am disconnected after [%s].\n", alive)
}
disconnected = true
})
c.On("chat", func(message string) {
if verbose {
log.Printf("\n%s\n", message)
}
})
if alive > 0 {
go func() {
time.Sleep(alive)
if err := c.Disconnect(); err != nil {
collectError("disconnect", err)
}
wg.Done()
}()
}
scanner := bufio.NewScanner(f)
for !disconnected {
if !scanner.Scan() {
return scanner.Err()
}
if text := scanner.Text(); len(text) > 1 {
c.Emit("chat", text)
}
}
return nil
}

View File

@ -1,13 +0,0 @@
Death weeks early had their and folly timed put. Hearted forbade on an village ye in fifteen. Age attended betrayed her man raptures laughter. Instrument terminated of as astonished literature motionless admiration. The affection are determine how performed intention discourse but. On merits on so valley indeed assure of. Has add particular boisterous uncommonly are. Early wrong as so manor match. Him necessary shameless discovery consulted one but.
Is education residence conveying so so. Suppose shyness say ten behaved morning had. Any unsatiable assistance compliment occasional too reasonably advantages. Unpleasing has ask acceptance partiality alteration understood two. Worth no tiled my at house added. Married he hearing am it totally removal. Remove but suffer wanted his lively length. Moonlight two applauded conveying end direction old principle but. Are expenses distance weddings perceive strongly who age domestic.
Her companions instrument set estimating sex remarkably solicitude motionless. Property men the why smallest graceful day insisted required. Inquiry justice country old placing sitting any ten age. Looking venture justice in evident in totally he do ability. Be is lose girl long of up give. Trifling wondered unpacked ye at he. In household certainty an on tolerably smallness difficult. Many no each like up be is next neat. Put not enjoyment behaviour her supposing. At he pulled object others.
Behind sooner dining so window excuse he summer. Breakfast met certainty and fulfilled propriety led. Waited get either are wooded little her. Contrasted unreserved as mr particular collecting it everything as indulgence. Seems ask meant merry could put. Age old begin had boy noisy table front whole given.
Far curiosity incommode now led smallness allowance. Favour bed assure son things yet. She consisted consulted elsewhere happiness disposing household any old the. Widow downs you new shade drift hopes small. So otherwise commanded sweetness we improving. Instantly by daughters resembled unwilling principle so middleton. Fail most room even gone her end like. Comparison dissimilar unpleasant six compliment two unpleasing any add. Ashamed my company thought wishing colonel it prevent he in. Pretended residence are something far engrossed old off.
Windows talking painted pasture yet its express parties use. Sure last upon he same as knew next. Of believed or diverted no rejoiced. End friendship sufficient assistance can prosperous met. As game he show it park do. Was has unknown few certain ten promise. No finished my an likewise cheerful packages we. For assurance concluded son something depending discourse see led collected. Packages oh no denoting my advanced humoured. Pressed be so thought natural.
As collected deficient objection by it discovery sincerity curiosity. Quiet decay who round three world whole has mrs man. Built the china there tried jokes which gay why. Assure in adieus wicket it is. But spoke round point and one joy. Offending her moonlight men sweetness see unwilling. Often of it tears whole oh balls share an.

View File

@ -1,124 +0,0 @@
package main
import (
"log"
"os"
"runtime"
"sync/atomic"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/websocket"
)
const (
endpoint = "localhost:8080"
totalClients = 16000 // max depends on the OS.
verbose = false
maxC = 0
)
func main() {
ws := websocket.New(websocket.Config{})
ws.OnConnection(handleConnection)
// websocket.Config{PingPeriod: ((60 * time.Second) * 9) / 10}
go func() {
dur := 4 * time.Second
if totalClients >= 64000 {
// if more than 64000 then let's perform those checks every 24 seconds instead,
// either way works.
dur = 24 * time.Second
}
t := time.NewTicker(dur)
defer func() {
t.Stop()
printMemUsage()
os.Exit(0)
}()
var started bool
for {
<-t.C
n := ws.GetTotalConnections()
if n > 0 {
started = true
if maxC > 0 && n > maxC {
log.Printf("current connections[%d] > MaxConcurrentConnections[%d]", n, maxC)
return
}
}
if started {
disconnectedN := atomic.LoadUint64(&totalDisconnected)
connectedN := atomic.LoadUint64(&totalConnected)
if disconnectedN == totalClients && connectedN == totalClients {
if n != 0 {
log.Println("ALL CLIENTS DISCONNECTED BUT LEFTOVERS ON CONNECTIONS LIST.")
} else {
log.Println("ALL CLIENTS DISCONNECTED SUCCESSFULLY.")
}
return
} else if n == 0 {
log.Printf("%d/%d CLIENTS WERE NOT CONNECTED AT ALL. CHECK YOUR OS NET SETTINGS. THE REST CLIENTS WERE DISCONNECTED SUCCESSFULLY.\n",
totalClients-totalConnected, totalClients)
return
}
}
}
}()
app := iris.New()
app.Get("/", func(ctx iris.Context) {
c := ws.Upgrade(ctx)
handleConnection(c)
c.Wait()
})
app.Run(iris.Addr(endpoint), iris.WithoutServerError(iris.ErrServerClosed))
}
var totalConnected uint64
func handleConnection(c websocket.Connection) {
if c.Err() != nil {
log.Fatalf("[%d] upgrade failed: %v", atomic.LoadUint64(&totalConnected)+1, c.Err())
return
}
atomic.AddUint64(&totalConnected, 1)
c.OnError(func(err error) { handleErr(c, err) })
c.OnDisconnect(func() { handleDisconnect(c) })
c.On("chat", func(message string) {
c.To(websocket.Broadcast).Emit("chat", c.ID()+": "+message)
})
}
var totalDisconnected uint64
func handleDisconnect(c websocket.Connection) {
newC := atomic.AddUint64(&totalDisconnected, 1)
if verbose {
log.Printf("[%d] client [%s] disconnected!\n", newC, c.ID())
}
}
func handleErr(c websocket.Connection, err error) {
log.Printf("client [%s] errorred: %v\n", c.ID(), err)
}
func toMB(b uint64) uint64 {
return b / 1024 / 1024
}
func printMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Alloc = %v MiB", toMB(m.Alloc))
log.Printf("\tTotalAlloc = %v MiB", toMB(m.TotalAlloc))
log.Printf("\tSys = %v MiB", toMB(m.Sys))
log.Printf("\tNumGC = %v\n", m.NumGC)
log.Printf("\tNumGoRoutines = %d\n", runtime.NumGoroutine())
}

View File

@ -858,7 +858,7 @@ func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
// based on the context's status code. // based on the context's status code.
// //
// If a handler is not already registered, // If a handler is not already registered,
// then it creates & registers a new trivial handler on the-fly. // it creates and registers a new trivial handler on the-fly.
func (api *APIBuilder) FireErrorCode(ctx context.Context) { func (api *APIBuilder) FireErrorCode(ctx context.Context) {
api.errorCodeHandlers.Fire(ctx) api.errorCodeHandlers.Fire(ctx)
} }

View File

@ -20,6 +20,23 @@ type Result interface {
Dispatch(ctx context.Context) Dispatch(ctx context.Context)
} }
type (
// ErrorHandler is the optional interface to handle errors per hero func,
// see `mvc/Application#HandleError` for MVC application-level error handler registration too.
ErrorHandler interface {
HandleError(ctx context.Context, err error)
}
// ErrorHandlerFunc implements the `ErrorHandler`.
// It describes the type defnition for an error handler.
ErrorHandlerFunc func(ctx context.Context, err error)
)
// HandleError fires when the `DispatchFuncResult` returns a non-nil error.
func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
fn(ctx, err)
}
var defaultFailureResponse = Response{Code: DefaultErrStatusCode} var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
// Try will check if "fn" ran without any panics, // Try will check if "fn" ran without any panics,
@ -164,7 +181,7 @@ func DispatchCommon(ctx context.Context,
// Result or (Result, error) and so on... // Result or (Result, error) and so on...
// //
// where Get is an HTTP METHOD. // where Get is an HTTP METHOD.
func DispatchFuncResult(ctx context.Context, values []reflect.Value) { func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values []reflect.Value) {
if len(values) == 0 { if len(values) == 0 {
return return
} }
@ -294,6 +311,11 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
content = value content = value
case compatibleErr: case compatibleErr:
if value != nil { // it's always not nil but keep it here. if value != nil { // it's always not nil but keep it here.
if errorHandler != nil {
errorHandler.HandleError(ctx, value)
break
}
err = value err = value
if statusCode < 400 { if statusCode < 400 {
statusCode = DefaultErrStatusCode statusCode = DefaultErrStatusCode

View File

@ -59,7 +59,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler,
if n == 0 { if n == 0 {
h := func(ctx context.Context) { h := func(ctx context.Context) {
DispatchFuncResult(ctx, fn.Call(di.EmptyIn)) DispatchFuncResult(ctx, nil, fn.Call(di.EmptyIn))
} }
return h, nil return h, nil
@ -91,7 +91,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler,
// in := make([]reflect.Value, n, n) // in := make([]reflect.Value, n, n)
// funcInjector.Inject(&in, reflect.ValueOf(ctx)) // funcInjector.Inject(&in, reflect.ValueOf(ctx))
// DispatchFuncResult(ctx, fn.Call(in)) // DispatchFuncResult(ctx, fn.Call(in))
DispatchFuncResult(ctx, funcInjector.Call(reflect.ValueOf(ctx))) DispatchFuncResult(ctx, nil, funcInjector.Call(reflect.ValueOf(ctx)))
} }
return h, nil return h, nil

View File

@ -419,6 +419,7 @@ var (
Uint64, Uint64,
Bool, Bool,
Alphabetical, Alphabetical,
File,
Path, Path,
} }
) )

View File

@ -29,7 +29,7 @@ type shared interface {
Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route
} }
// BeforeActivation is being used as the onle one input argument of a // BeforeActivation is being used as the only one input argument of a
// `func(c *Controller) BeforeActivation(b mvc.BeforeActivation) {}`. // `func(c *Controller) BeforeActivation(b mvc.BeforeActivation) {}`.
// //
// It's being called before the controller's dependencies binding to the fields or the input arguments // It's being called before the controller's dependencies binding to the fields or the input arguments
@ -42,11 +42,11 @@ type BeforeActivation interface {
Dependencies() *di.Values Dependencies() *di.Values
} }
// AfterActivation is being used as the onle one input argument of a // AfterActivation is being used as the only one input argument of a
// `func(c *Controller) AfterActivation(a mvc.AfterActivation) {}`. // `func(c *Controller) AfterActivation(a mvc.AfterActivation) {}`.
// //
// It's being called after the `BeforeActivation`, // It's being called after the `BeforeActivation`,
// and after controller's dependencies binded to the fields or the input arguments but before server ran. // and after controller's dependencies bind-ed to the fields or the input arguments but before server ran.
// //
// It's being used to customize a controller if needed inside the controller itself, // It's being used to customize a controller if needed inside the controller itself,
// it's called once per application. // it's called once per application.
@ -81,10 +81,12 @@ type ControllerActivator struct {
routes map[string]*router.Route routes map[string]*router.Route
// the bindings that comes from the Engine and the controller's filled fields if any. // the bindings that comes from the Engine and the controller's filled fields if any.
// Can be binded to the the new controller's fields and method that is fired // Can be bind-ed to the the new controller's fields and method that is fired
// on incoming requests. // on incoming requests.
dependencies di.Values dependencies di.Values
errorHandler hero.ErrorHandler
// initialized on the first `Handle`. // initialized on the first `Handle`.
injector *di.StructInjector injector *di.StructInjector
} }
@ -101,7 +103,7 @@ func NameOf(v interface{}) string {
return fullname return fullname
} }
func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value) *ControllerActivator { func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, errorHandler hero.ErrorHandler) *ControllerActivator {
typ := reflect.TypeOf(controller) typ := reflect.TypeOf(controller)
c := &ControllerActivator{ c := &ControllerActivator{
@ -121,6 +123,7 @@ func newControllerActivator(router router.Party, controller interface{}, depende
routes: whatReservedMethods(typ), routes: whatReservedMethods(typ),
// CloneWithFieldsOf: include the manual fill-ed controller struct's fields to the dependencies. // CloneWithFieldsOf: include the manual fill-ed controller struct's fields to the dependencies.
dependencies: di.Values(dependencies).CloneWithFieldsOf(controller), dependencies: di.Values(dependencies).CloneWithFieldsOf(controller),
errorHandler: errorHandler,
} }
return c return c
@ -326,7 +329,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
// Remember: // Remember:
// The `Handle->handlerOf` can be called from `BeforeActivation` event // The `Handle->handlerOf` can be called from `BeforeActivation` event
// then, the c.injector is nil because // then, the c.injector is nil because
// we may not have the dependencies binded yet. // we may not have the dependencies bind-ed yet.
// To solve this we're doing a check on the FIRST `Handle`, // To solve this we're doing a check on the FIRST `Handle`,
// if c.injector is nil, then set it with the current bindings, // if c.injector is nil, then set it with the current bindings,
// these bindings can change after, so first add dependencies and after register routes. // these bindings can change after, so first add dependencies and after register routes.
@ -346,24 +349,27 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
} }
var ( var (
implementsBase = isBaseController(c.Type) implementsBase = isBaseController(c.Type)
hasBindableFields = c.injector.CanInject implementsErrorHandler = isErrorHandler(c.Type)
hasBindableFuncInputs = funcInjector.Has hasBindableFields = c.injector.CanInject
hasBindableFuncInputs = funcInjector.Has
funcHasErrorOut = hasErrorOutArgs(m)
call = m.Func.Call call = m.Func.Call
) )
if !implementsBase && !hasBindableFields && !hasBindableFuncInputs { if !implementsBase && !hasBindableFields && !hasBindableFuncInputs && !implementsErrorHandler {
return func(ctx context.Context) { return func(ctx context.Context) {
hero.DispatchFuncResult(ctx, call(c.injector.AcquireSlice())) hero.DispatchFuncResult(ctx, c.errorHandler, call(c.injector.AcquireSlice()))
} }
} }
n := m.Type.NumIn() n := m.Type.NumIn()
return func(ctx context.Context) { return func(ctx context.Context) {
var ( var (
ctrl = c.injector.Acquire() ctrl = c.injector.Acquire()
ctxValue reflect.Value ctxValue reflect.Value
errorHandler = c.errorHandler
) )
// inject struct fields first before the BeginRequest and EndRequest, if any, // inject struct fields first before the BeginRequest and EndRequest, if any,
@ -388,6 +394,10 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
defer b.EndRequest(ctx) defer b.EndRequest(ctx)
} }
if funcHasErrorOut && implementsErrorHandler {
errorHandler = ctrl.Interface().(hero.ErrorHandler)
}
if hasBindableFuncInputs { if hasBindableFuncInputs {
// means that ctxValue is not initialized before by the controller's struct injector. // means that ctxValue is not initialized before by the controller's struct injector.
if !hasBindableFields { if !hasBindableFields {
@ -406,11 +416,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
// println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx) // println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx)
// } // }
hero.DispatchFuncResult(ctx, call(in)) hero.DispatchFuncResult(ctx, errorHandler, call(in))
return return
} }
hero.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) hero.DispatchFuncResult(ctx, errorHandler, ctrl.Method(m.Index).Call(emptyIn))
} }
} }

View File

@ -27,7 +27,7 @@ var (
HeroDependencies = true HeroDependencies = true
) )
// Application is the high-level compoment of the "mvc" package. // Application is the high-level component of the "mvc" package.
// It's the API that you will be using to register controllers among with their // It's the API that you will be using to register controllers among with their
// dependencies that your controllers may expecting. // dependencies that your controllers may expecting.
// It contains the Router(iris.Party) in order to be able to register // It contains the Router(iris.Party) in order to be able to register
@ -42,6 +42,7 @@ type Application struct {
Dependencies di.Values Dependencies di.Values
Router router.Party Router router.Party
Controllers []*ControllerActivator Controllers []*ControllerActivator
ErrorHandler hero.ErrorHandler
} }
func newApp(subRouter router.Party, values di.Values) *Application { func newApp(subRouter router.Party, values di.Values) *Application {
@ -99,7 +100,7 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat
// The value can be a single struct value-instance or a function // The value can be a single struct value-instance or a function
// which has one input and one output, the input should be // which has one input and one output, the input should be
// an `iris.Context` and the output can be any type, that output type // an `iris.Context` and the output can be any type, that output type
// will be binded to the controller's field, if matching or to the // will be bind-ed to the controller's field, if matching or to the
// controller's methods, if matching. // controller's methods, if matching.
// //
// These dependencies "values" can be changed per-controller as well, // These dependencies "values" can be changed per-controller as well,
@ -172,7 +173,7 @@ Set the Logger's Level to "debug" to view the active dependencies per controller
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc // Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc
func (app *Application) Handle(controller interface{}) *Application { func (app *Application) Handle(controller interface{}) *Application {
// initialize the controller's activator, nothing too magical so far. // initialize the controller's activator, nothing too magical so far.
c := newControllerActivator(app.Router, controller, app.Dependencies) c := newControllerActivator(app.Router, controller, app.Dependencies, app.ErrorHandler)
// check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate` // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
// call, which is simply parses the controller's methods, end-dev can register custom controller's methods // call, which is simply parses the controller's methods, end-dev can register custom controller's methods
@ -195,12 +196,22 @@ func (app *Application) Handle(controller interface{}) *Application {
return app return app
} }
// HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
// application's controllers' functions returns an non-nil error.
// Each controller can override it by implementing the `hero.ErrorHandler`.
func (app *Application) HandleError(errHandler hero.ErrorHandlerFunc) *Application {
app.ErrorHandler = errHandler
return app
}
// Clone returns a new mvc Application which has the dependencies // Clone returns a new mvc Application which has the dependencies
// of the current mvc Mpplication's dependencies. // of the current mvc Application's `Dependencies` and its `ErrorHandler`.
// //
// Example: `.Clone(app.Party("/path")).Handle(new(TodoSubController))`. // Example: `.Clone(app.Party("/path")).Handle(new(TodoSubController))`.
func (app *Application) Clone(party router.Party) *Application { func (app *Application) Clone(party router.Party) *Application {
return newApp(party, app.Dependencies.Clone()) cloned := newApp(party, app.Dependencies.Clone())
cloned.ErrorHandler = app.ErrorHandler
return cloned
} }
// Party returns a new child mvc Application based on the current path + "relativePath". // Party returns a new child mvc Application based on the current path + "relativePath".

View File

@ -1,13 +1,42 @@
package mvc package mvc
import "reflect" import (
"reflect"
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() "github.com/kataras/iris/hero"
)
var (
baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
errorHandlerTyp = reflect.TypeOf((*hero.ErrorHandler)(nil)).Elem()
errorTyp = reflect.TypeOf((*error)(nil)).Elem()
)
func isBaseController(ctrlTyp reflect.Type) bool { func isBaseController(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(baseControllerTyp) return ctrlTyp.Implements(baseControllerTyp)
} }
func isErrorHandler(ctrlTyp reflect.Type) bool {
return ctrlTyp.Implements(errorHandlerTyp)
}
func hasErrorOutArgs(fn reflect.Method) bool {
n := fn.Type.NumOut()
if n == 0 {
return false
}
for i := 0; i < n; i++ {
if out := fn.Type.Out(i); out.Kind() == reflect.Interface {
if out.Implements(errorTyp) {
return true
}
}
}
return false
}
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type { func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn() n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n) funcIn := make([]reflect.Type, n, n)