mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
☕ add a dependency-injection examples folder for the next release and some improvements
Former-commit-id: 040168afb7caf808618f7da5e68ae8eb01cb7170
This commit is contained in:
parent
5fc24812bc
commit
ce2eae9121
|
@ -72,6 +72,11 @@ func main() {
|
|||
|
||||
Your eyes don't lie you. You read well, no `ctx.ReadJSON(&v)` and `ctx.JSON(send)` neither `error` handling are presented. It is a huge relief but don't worry you can still control everything if you ever need, even errors from dependencies. Any error may occur from request-scoped dependencies or your own handler is dispatched through `Party.GetContainer().GetErrorHandler` which defaults to the `hero.DefaultErrorHandler` which sends a `400 Bad Request` response with the error's text as its body contents. If you want to handle `testInput` otherwise then just add a `Party.RegisterDependency(func(ctx iris.Context) testInput {...})` and you are ready to go.
|
||||
|
||||
Other Improvements:
|
||||
|
||||
- `ctx.JSON, JSONP, XML`: if `iris.WithOptimizations` is NOT passed on `app.Run/Listen` then the indentation defaults to `" "` (two spaces) otherwise it is empty or the provided value.
|
||||
- Hero Handlers (and `app.HandleFunc`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.
|
||||
|
||||
New Context Methods:
|
||||
|
||||
- `context.Defer(Handler)` works like `Party.Done` but for the request life-cycle.
|
||||
|
|
|
@ -14,10 +14,17 @@ Internal selected benchmarks between modified features across different versions
|
|||
|
||||
Measures handler factory time.
|
||||
|
||||
```sh
|
||||
$ cd v12.1.x
|
||||
$ go test -run=NONE --bench=. -count=5 --benchmem > di_test.txt
|
||||
$ cd ../vNext
|
||||
$ go test -run=NONE --bench=. -count=5 --benchmem > di_test.txt
|
||||
```
|
||||
|
||||
| Name | Ops | Ns/op | B/op | Allocs/op |
|
||||
|---------|:------|:--------|:--------|----|
|
||||
| vNext | 181726 | 6631 | 1544 | 17 |
|
||||
| v12.1.x | 96001 | 12604 | 976 | 26 |
|
||||
| vNext | 184512 | 6607 | 1544 | 17 |
|
||||
| v12.1.x | 95974 | 12653 | 976 | 26 |
|
||||
|
||||
It accepts a dynamic path parameter and a JSON request. It returns a JSON response. Fires 500000 requests with 125 concurrent connections.
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,8 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
type (
|
||||
testInput struct {
|
||||
|
|
Binary file not shown.
|
@ -4,4 +4,7 @@ go 1.14
|
|||
|
||||
replace github.com/kataras/iris/v12 => C:/mygopath/src/github.com/kataras/iris
|
||||
|
||||
require github.com/kataras/iris/v12 v12.1.8
|
||||
require (
|
||||
github.com/kataras/iris/v12 v12.1.8
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
)
|
||||
|
|
|
@ -8,6 +8,8 @@ It doesn't always contain the "best ways" but it does cover each important featu
|
|||
|
||||
## Running the examples
|
||||
|
||||
[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/bd489282b676e30de158)
|
||||
|
||||
1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl.
|
||||
2. [Install Iris](https://github.com/kataras/iris/wiki/installation)
|
||||
3. Install any external packages that required by the examples
|
||||
|
|
27
_examples/dependency-injection/basic/main.go
Normal file
27
_examples/dependency-injection/basic/main.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
type (
|
||||
testInput struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
testOutput struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
)
|
||||
|
||||
func handler(id int, in testInput) testOutput {
|
||||
return testOutput{
|
||||
ID: id,
|
||||
Name: in.Email,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.HandleFunc(iris.MethodPost, "/{id:int}", handler)
|
||||
app.Listen(":5000", iris.WithOptimizations)
|
||||
}
|
61
_examples/dependency-injection/basic/middleware/main.go
Normal file
61
_examples/dependency-injection/basic/middleware/main.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type (
|
||||
testInput struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
testOutput struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
)
|
||||
|
||||
func handler(id int, in testInput) testOutput {
|
||||
return testOutput{
|
||||
ID: id,
|
||||
Name: in.Email,
|
||||
}
|
||||
}
|
||||
|
||||
var errCustom = errors.New("my_error")
|
||||
|
||||
func middleware(in testInput) (int, error) {
|
||||
if in.Email == "invalid" {
|
||||
// stop the execution and don't continue to "handler"
|
||||
// without firing an error.
|
||||
return iris.StatusAccepted, iris.ErrStopExecution
|
||||
} else if in.Email == "error" {
|
||||
// stop the execution and fire a custom error.
|
||||
return iris.StatusConflict, errCustom
|
||||
}
|
||||
|
||||
return iris.StatusOK, nil
|
||||
}
|
||||
|
||||
func newApp() *iris.Application {
|
||||
app := iris.New()
|
||||
|
||||
// handle the route, respond with
|
||||
// a JSON and 200 status code
|
||||
// or 202 status code and empty body
|
||||
// or a 409 status code and "my_error" body.
|
||||
app.HandleFunc(iris.MethodPost, "/{id:int}", middleware, handler)
|
||||
|
||||
app.Configure(
|
||||
iris.WithOptimizations, /* optional */
|
||||
iris.WithoutBodyConsumptionOnUnmarshal /* required when more than one handler is consuming request payload(testInput) */)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := newApp()
|
||||
app.Listen(":8080")
|
||||
}
|
25
_examples/dependency-injection/basic/middleware/main_test.go
Normal file
25
_examples/dependency-injection/basic/middleware/main_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/v12/httptest"
|
||||
)
|
||||
|
||||
func TestDependencyInjectionBasic_Middleware(t *testing.T) {
|
||||
app := newApp()
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.POST("/42").WithJSON(testInput{Email: "my_email"}).Expect().
|
||||
Status(httptest.StatusOK).
|
||||
JSON().Equal(testOutput{ID: 42, Name: "my_email"})
|
||||
|
||||
// it should stop the execution at the middleware and return the middleware's status code,
|
||||
// because the error is `ErrStopExecution`.
|
||||
e.POST("/42").WithJSON(testInput{Email: "invalid"}).Expect().
|
||||
Status(httptest.StatusAccepted).Body().Empty()
|
||||
|
||||
// it should stop the execution at the middleware and return the error's text.
|
||||
e.POST("/42").WithJSON(testInput{Email: "error"}).Expect().
|
||||
Status(httptest.StatusConflict).Body().Equal("my_error")
|
||||
}
|
|
@ -3200,13 +3200,16 @@ var (
|
|||
|
||||
// WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
|
||||
// Ignores StatusCode, Gzip, StreamingJSON options.
|
||||
func WriteJSON(writer io.Writer, v interface{}, options JSON, enableOptimization ...bool) (int, error) {
|
||||
func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (int, error) {
|
||||
var (
|
||||
result []byte
|
||||
err error
|
||||
optimize = len(enableOptimization) > 0 && enableOptimization[0]
|
||||
result []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if !optimize && options.Indent == "" {
|
||||
options.Indent = " "
|
||||
}
|
||||
|
||||
if indent := options.Indent; indent != "" {
|
||||
marshalIndent := json.MarshalIndent
|
||||
if optimize {
|
||||
|
@ -3291,7 +3294,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
|
|||
var finishCallbackB = []byte(");")
|
||||
|
||||
// WriteJSONP marshals the given interface object and writes the JSON response to the writer.
|
||||
func WriteJSONP(writer io.Writer, v interface{}, options JSONP, enableOptimization ...bool) (int, error) {
|
||||
func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) (int, error) {
|
||||
if callback := options.Callback; callback != "" {
|
||||
n, err := writer.Write([]byte(callback + "("))
|
||||
if err != nil {
|
||||
|
@ -3300,7 +3303,9 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, enableOptimizati
|
|||
defer writer.Write(finishCallbackB)
|
||||
}
|
||||
|
||||
optimize := len(enableOptimization) > 0 && enableOptimization[0]
|
||||
if !optimize && options.Indent == "" {
|
||||
options.Indent = " "
|
||||
}
|
||||
|
||||
if indent := options.Indent; indent != "" {
|
||||
marshalIndent := json.MarshalIndent
|
||||
|
@ -3396,7 +3401,7 @@ func (m xmlMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||
}
|
||||
|
||||
// WriteXML marshals the given interface object and writes the XML response to the writer.
|
||||
func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
|
||||
func WriteXML(writer io.Writer, v interface{}, options XML, optimize bool) (int, error) {
|
||||
if prefix := options.Prefix; prefix != "" {
|
||||
n, err := writer.Write([]byte(prefix))
|
||||
if err != nil {
|
||||
|
@ -3404,6 +3409,10 @@ func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if !optimize && options.Indent == "" {
|
||||
options.Indent = " "
|
||||
}
|
||||
|
||||
if indent := options.Indent; indent != "" {
|
||||
result, err := xml.MarshalIndent(v, "", indent)
|
||||
if err != nil {
|
||||
|
@ -3435,7 +3444,7 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
|
|||
|
||||
ctx.ContentType(ContentXMLHeaderValue)
|
||||
|
||||
n, err := WriteXML(ctx.writer, v, options)
|
||||
n, err := WriteXML(ctx.writer, v, options, ctx.shouldOptimize())
|
||||
if err != nil {
|
||||
ctx.Application().Logger().Debugf("XML: %v", err)
|
||||
ctx.StatusCode(http.StatusInternalServerError)
|
||||
|
|
|
@ -282,12 +282,22 @@ func (api *APIBuilder) RegisterDependency(dependency interface{}) *hero.Dependen
|
|||
// can accept any input arguments that match with the Party's registered Container's `Dependencies` and
|
||||
// any output result; like custom structs <T>, string, []byte, int, error,
|
||||
// a combination of the above, hero.Result(hero.View | hero.Response) and more.
|
||||
//
|
||||
// It's common from a hero handler to not even need to accept a `Context`, for that reason,
|
||||
// the "handlersFn" will call `ctx.Next()` automatically when not called manually.
|
||||
// To stop the execution and not continue to the next "handlersFn"
|
||||
// the end-developer should output an error and return `iris.ErrStopExecution`.
|
||||
func (api *APIBuilder) HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route {
|
||||
handlers := make(context.Handlers, 0, len(handlersFn))
|
||||
for _, h := range handlersFn {
|
||||
handlers = append(handlers, api.container.Handler(h))
|
||||
}
|
||||
|
||||
// On that type of handlers the end-developer does not have to include the Context in the handler,
|
||||
// so the ctx.Next is automatically called unless an `ErrStopExecution` returned (implementation inside hero pkg).
|
||||
o := ExecutionOptions{Force: true}
|
||||
o.apply(&handlers)
|
||||
|
||||
return api.Handle(method, relativePath, handlers...)
|
||||
}
|
||||
|
||||
|
|
|
@ -130,6 +130,11 @@ type Party interface {
|
|||
// can accept any input arguments that match with the Party's registered Container's `Dependencies` and
|
||||
// any output result; like custom structs <T>, string, []byte, int, error,
|
||||
// a combination of the above, hero.Result(hero.View | hero.Response) and more.
|
||||
//
|
||||
// It's common from a hero handler to not even need to accept a `Context`, for that reason,
|
||||
// the "handlersFn" will call `ctx.Next()` automatically when not called manually.
|
||||
// To stop the execution and not continue to the next "handlersFn"
|
||||
// the end-developer should output an error and return `iris.ErrStopExecution`.
|
||||
HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route
|
||||
|
||||
// Handle registers a route to the server's router.
|
||||
|
|
|
@ -225,7 +225,7 @@ func dispatchFuncResult(ctx context.Context, values []reflect.Value) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if statusCode < 400 {
|
||||
if statusCode < 400 && value != ErrStopExecution {
|
||||
statusCode = DefaultErrStatusCode
|
||||
}
|
||||
|
||||
|
@ -286,7 +286,11 @@ func dispatchCommon(ctx context.Context,
|
|||
if contentType == "" {
|
||||
// to respect any ctx.ContentType(...) call
|
||||
// especially if v is not nil.
|
||||
contentType = ctx.GetContentType()
|
||||
if contentType = ctx.GetContentType(); contentType == "" {
|
||||
// if it's still empty set to JSON. (useful for dynamic middlewares that returns an int status code and the next handler dispatches the JSON,
|
||||
// see dependency-injection/basic/middleware example)
|
||||
contentType = context.ContentJSONHeaderValue
|
||||
}
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
|
@ -302,10 +306,13 @@ func dispatchCommon(ctx context.Context,
|
|||
if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
|
||||
_, err = ctx.JSONP(v)
|
||||
} else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
|
||||
_, err = ctx.XML(v, context.XML{Indent: " "})
|
||||
_, err = ctx.XML(v)
|
||||
// no need: context.XML{Indent: " "}), after v12.2,
|
||||
// if not iris.WithOptimizations passed and indent is empty then it sets it to two spaces for JSON, JSONP and XML,
|
||||
// otherwise given indentation.
|
||||
} else {
|
||||
// defaults to json if content type is missing or its application/json.
|
||||
_, err = ctx.JSON(v, context.JSON{Indent: " "})
|
||||
_, err = ctx.JSON(v)
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
|
@ -18,23 +18,6 @@ func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
|
|||
fn(ctx, err)
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultErrStatusCode is the default error status code (400)
|
||||
// when the response contains a non-nil error or a request-scoped binding error occur.
|
||||
DefaultErrStatusCode = 400
|
||||
|
||||
// DefaultErrorHandler is the default error handler which is fired
|
||||
// when a function returns a non-nil error or a request-scoped dependency failed to binded.
|
||||
DefaultErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) {
|
||||
if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) {
|
||||
ctx.StatusCode(DefaultErrStatusCode)
|
||||
}
|
||||
|
||||
ctx.WriteString(err.Error())
|
||||
ctx.StopExecution()
|
||||
})
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSeeOther may be returned from a dependency handler to skip a specific dependency
|
||||
// based on custom logic.
|
||||
|
@ -45,6 +28,26 @@ var (
|
|||
ErrStopExecution = fmt.Errorf("stop execution")
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultErrStatusCode is the default error status code (400)
|
||||
// when the response contains a non-nil error or a request-scoped binding error occur.
|
||||
DefaultErrStatusCode = 400
|
||||
|
||||
// DefaultErrorHandler is the default error handler which is fired
|
||||
// when a function returns a non-nil error or a request-scoped dependency failed to binded.
|
||||
DefaultErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) {
|
||||
if err != ErrStopExecution {
|
||||
if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) {
|
||||
ctx.StatusCode(DefaultErrStatusCode)
|
||||
}
|
||||
|
||||
ctx.WriteString(err.Error())
|
||||
}
|
||||
|
||||
ctx.StopExecution()
|
||||
})
|
||||
)
|
||||
|
||||
func makeHandler(fn interface{}, c *Container) context.Handler {
|
||||
if fn == nil {
|
||||
panic("makeHandler: function is nil")
|
||||
|
@ -77,10 +80,12 @@ func makeHandler(fn interface{}, c *Container) context.Handler {
|
|||
if err != nil {
|
||||
if err == ErrSeeOther {
|
||||
continue
|
||||
} else if err == ErrStopExecution {
|
||||
ctx.StopExecution()
|
||||
return // return without error.
|
||||
}
|
||||
// handled inside ErrorHandler.
|
||||
// else if err == ErrStopExecution {
|
||||
// ctx.StopExecution()
|
||||
// return // return without error.
|
||||
// }
|
||||
|
||||
c.GetErrorHandler(ctx).HandleError(ctx, err)
|
||||
return
|
||||
|
|
|
@ -156,7 +156,7 @@ before begin the implementation of it.
|
|||
b.Register("user_dep", func(db myDB) User{...}).DependsOn("db")
|
||||
b.Handler(func(user User) error{...})
|
||||
b.Handler(func(ctx iris.Context, reuseDB myDB) {...})
|
||||
Why linked over automatically? Because more thna one dependency can implement the same input and
|
||||
Why linked over automatically? Because more than one dependency can implement the same input and
|
||||
end-user does not care about ordering the registered ones.
|
||||
Link with `DependsOn` SHOULD be optional, if exists then limit the available dependencies,
|
||||
`DependsOn` SHOULD accept comma-separated values, e.g. "db, otherdep" and SHOULD also work
|
||||
|
@ -170,6 +170,8 @@ so, in theory, end-developers could achieve same results by hand-code(inside the
|
|||
|
||||
26 Feb 2020. Gerasimos Maropoulos
|
||||
______________________________________________
|
||||
|
||||
29 Feb 2020. It's done.
|
||||
*/
|
||||
|
||||
type testMessage struct {
|
||||
|
|
5
iris.go
5
iris.go
|
@ -18,6 +18,8 @@ import (
|
|||
|
||||
// context for the handlers
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"github.com/kataras/iris/v12/hero"
|
||||
|
||||
// core packages, required to build the application
|
||||
"github.com/kataras/iris/v12/core/errgroup"
|
||||
"github.com/kataras/iris/v12/core/host"
|
||||
|
@ -527,6 +529,9 @@ var (
|
|||
//
|
||||
// A shortcut for the `context#XMLMap`.
|
||||
XMLMap = context.XMLMap
|
||||
// ErrStopExecution if returned from a hero middleware or a request-scope dependency
|
||||
// stops the handler's execution, see _examples/dependency-injection/basic/middleware.
|
||||
ErrStopExecution = hero.ErrStopExecution
|
||||
)
|
||||
|
||||
// Constants for input argument at `router.RouteRegisterRule`.
|
||||
|
|
|
@ -341,9 +341,10 @@ func (c *ControllerActivator) handlerOf(methodName string) context.Handler {
|
|||
return func(ctx context.Context) {
|
||||
ctrl, err := c.injector.Acquire(ctx)
|
||||
if err != nil {
|
||||
if err != hero.ErrStopExecution {
|
||||
c.injector.Container.GetErrorHandler(ctx).HandleError(ctx, err)
|
||||
}
|
||||
// if err != hero.ErrStopExecution {
|
||||
// c.injector.Container.GetErrorHandler(ctx).HandleError(ctx, err)
|
||||
// }
|
||||
c.injector.Container.GetErrorHandler(ctx).HandleError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
34
mvc/param.go
34
mvc/param.go
|
@ -1,34 +0,0 @@
|
|||
package mvc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"github.com/kataras/iris/v12/macro"
|
||||
)
|
||||
|
||||
func getPathParamsForInput(startParamIndex int, params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) {
|
||||
if len(funcIn) == 0 || len(params) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
consumed := make(map[int]struct{})
|
||||
for _, in := range funcIn {
|
||||
for j, param := range params {
|
||||
if _, ok := consumed[j]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
funcDep, ok := context.ParamResolverByTypeAndIndex(in, startParamIndex+param.Index)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
values = append(values, funcDep)
|
||||
consumed[j] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user