mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
MVC improvements: add HandleWebsocket that now registers events automatically based on the struct's methods(!) and fix a bug when more than one value of the same type is registered to a static field of a controller
Former-commit-id: e369d1426ac1a6b58314930a18362670317da3c1
This commit is contained in:
parent
85666da682
commit
450f20902d
|
@ -363,9 +363,9 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
|
||||||
|
|
||||||
- [Basic Authentication](authentication/basicauth/main.go)
|
- [Basic Authentication](authentication/basicauth/main.go)
|
||||||
- [OAUth2](authentication/oauth2/main.go)
|
- [OAUth2](authentication/oauth2/main.go)
|
||||||
- [JWT](experimental-handlers/jwt/main.go)
|
- [Request Auth](request/main.go) **NEW**
|
||||||
|
- [Request Auth(JWT)](experimental-handlers/jwt/main.go)
|
||||||
- [Sessions](#sessions)
|
- [Sessions](#sessions)
|
||||||
- [Request Authentication](authentication/request/main.go) **NEW**
|
|
||||||
|
|
||||||
### File Server
|
### File Server
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
- [Basic Authentication](basicauth/main.go)
|
- [Basic Authentication](basicauth/main.go)
|
||||||
- [OAUth2](oauth2/main.go)
|
- [OAUth2](oauth2/main.go)
|
||||||
- [JWT](https://github.com/kataras/iris/blob/master/_examples/experimental-handlers/jwt/main.go)
|
- [Request Auth](request/main.go)
|
||||||
|
- [Request Auth(JWT)](https://github.com/kataras/iris/blob/master/_examples/experimental-handlers/jwt/main.go)
|
||||||
- [Sessions](https://github.com/kataras/iris/tree/master/_examples/#sessions)
|
- [Sessions](https://github.com/kataras/iris/tree/master/_examples/#sessions)
|
|
@ -134,3 +134,5 @@ func main() {
|
||||||
// You can read more examples and run testable code
|
// You can read more examples and run testable code
|
||||||
// at the `iris/crypto`, `iris/crypto/sign`
|
// at the `iris/crypto`, `iris/crypto/sign`
|
||||||
// and `iris/crypto/gcm` packages themselves.
|
// and `iris/crypto/gcm` packages themselves.
|
||||||
|
//
|
||||||
|
// See experimental-handlers/jwt example to use JWT for request authentication instead.
|
||||||
|
|
|
@ -23,7 +23,7 @@ func myHandler(ctx iris.Context) {
|
||||||
ctx.Writef("This is an authenticated request\n")
|
ctx.Writef("This is an authenticated request\n")
|
||||||
ctx.Writef("Claim content:\n")
|
ctx.Writef("Claim content:\n")
|
||||||
|
|
||||||
ctx.Writef("%s", user.Signature)
|
ctx.Writef("%#+v\n", user.Claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -42,5 +42,12 @@ func main() {
|
||||||
app.Use(jwtHandler.Serve)
|
app.Use(jwtHandler.Serve)
|
||||||
|
|
||||||
app.Get("/ping", myHandler)
|
app.Get("/ping", myHandler)
|
||||||
|
|
||||||
|
// Example request:
|
||||||
|
// curl -X GET -H\
|
||||||
|
// "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjozMjEzMjF9.8waEX7-vPKACa-Soi1pQvW3Rl8QY-SUFcHKTLZI4mvU"\
|
||||||
|
// http://localhost:3001/ping
|
||||||
|
//
|
||||||
|
//Read more at: https://jwt.io/
|
||||||
app.Run(iris.Addr("localhost:3001"))
|
app.Run(iris.Addr("localhost:3001"))
|
||||||
} // don't forget to look ../jwt_test.go to see how to set your own custom claims
|
}
|
||||||
|
|
106
_examples/mvc/websocket/browser/index.html
Normal file
106
_examples/mvc/websocket/browser/index.html
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Online visitors MVC example</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, "San Francisco", "Helvetica Neue", "Noto", "Roboto", "Calibri Light", sans-serif;
|
||||||
|
color: #212121;
|
||||||
|
font-size: 1.0em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 750px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#online_visitors {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<span id="online_visitors">1 online visitor</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- the message's input -->
|
||||||
|
<input id="input" type="text" />
|
||||||
|
|
||||||
|
<!-- when clicked then a websocket event will be sent to the server, at this example we registered the 'chat' -->
|
||||||
|
<button id="sendBtn" disabled>Send</button>
|
||||||
|
|
||||||
|
<!-- the messages will be shown here -->
|
||||||
|
<pre id="output"></pre>
|
||||||
|
<!-- import the iris client-side library for browser from a CDN or locally.
|
||||||
|
However, `neffos.(min.)js` is a NPM package too so alternatively,
|
||||||
|
you can use it as dependency on your package.json and all nodejs-npm tooling become available:
|
||||||
|
see the "browserify" example for more-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/neffos.js@latest/dist/neffos.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
const wsURL = "ws://localhost:8080/websocket"
|
||||||
|
var outputTxt = document.getElementById("output");
|
||||||
|
function addMessage(msg) {
|
||||||
|
outputTxt.innerHTML += msg + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runExample() {
|
||||||
|
try {
|
||||||
|
const conn = await neffos.dial(wsURL, {
|
||||||
|
default: { // "default" namespace.
|
||||||
|
_OnNamespaceConnected: function (nsConn, msg) {
|
||||||
|
if (nsConn.conn.wasReconnected()) {
|
||||||
|
addMessage("re-connected after " + nsConn.conn.reconnectTries.toString() + " trie(s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputTxt = document.getElementById("input");
|
||||||
|
let sendBtn = document.getElementById("sendBtn");
|
||||||
|
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
sendBtn.onclick = function () {
|
||||||
|
const input = inputTxt.value;
|
||||||
|
inputTxt.value = "";
|
||||||
|
nsConn.emit("OnChat", input);
|
||||||
|
addMessage("Me: " + input);
|
||||||
|
};
|
||||||
|
|
||||||
|
addMessage("connected to namespace: " + msg.Namespace);
|
||||||
|
},
|
||||||
|
_OnNamespaceDisconnect: function (nsConn, msg) {
|
||||||
|
addMessage("disconnected from namespace: " + msg.Namespace);
|
||||||
|
},
|
||||||
|
OnChat: function (nsConn, msg) { // "OnChat" event.
|
||||||
|
console.log(msg);
|
||||||
|
|
||||||
|
addMessage(msg.Body);
|
||||||
|
},
|
||||||
|
OnVisit: function (nsConn, msg) {
|
||||||
|
const newCount = Number(msg.Body); // or parseInt.
|
||||||
|
console.log("visit websocket event with newCount of: ", newCount);
|
||||||
|
|
||||||
|
var text = "1 online visitor";
|
||||||
|
if (newCount > 1) {
|
||||||
|
text = newCount + " online visitors";
|
||||||
|
}
|
||||||
|
document.getElementById("online_visitors").innerHTML = text;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
conn.connect("default");
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runExample();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -1,41 +1,45 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/mvc"
|
"github.com/kataras/iris/mvc"
|
||||||
"github.com/kataras/iris/websocket"
|
"github.com/kataras/iris/websocket"
|
||||||
|
|
||||||
|
"github.com/kataras/neffos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
app.Logger().SetLevel("debug")
|
||||||
|
|
||||||
|
// optionally enable debug messages to the neffos real-time framework
|
||||||
|
// and print them through the iris' logger.
|
||||||
|
neffos.EnableDebug(app.Logger())
|
||||||
|
|
||||||
// load templates.
|
// load templates.
|
||||||
app.RegisterView(iris.HTML("./views", ".html"))
|
app.RegisterView(iris.HTML("./views", ".html"))
|
||||||
|
|
||||||
// render the ./views/index.html.
|
// render the ./browser/index.html.
|
||||||
app.Get("/", func(ctx iris.Context) {
|
app.HandleDir("/", "./browser")
|
||||||
ctx.View("index.html")
|
|
||||||
})
|
|
||||||
|
|
||||||
mvc.Configure(app.Party("/websocket"), configureMVC)
|
websocketAPI := app.Party("/websocket")
|
||||||
// Or mvc.New(app.Party(...)).Configure(configureMVC)
|
|
||||||
|
|
||||||
|
m := mvc.New(websocketAPI)
|
||||||
|
m.Register(
|
||||||
|
&prefixedLogger{prefix: "DEV"},
|
||||||
|
)
|
||||||
|
m.HandleWebsocket(&websocketController{Namespace: "default", Age: 42, Otherstring: "other string"})
|
||||||
|
|
||||||
|
websocketServer := neffos.New(websocket.DefaultGorillaUpgrader, m)
|
||||||
|
|
||||||
|
websocketAPI.Get("/", websocket.Handler(websocketServer))
|
||||||
// http://localhost:8080
|
// http://localhost:8080
|
||||||
app.Run(iris.Addr(":8080"))
|
app.Run(iris.Addr(":8080"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureMVC(m *mvc.Application) {
|
|
||||||
ws := websocket.New(websocket.Config{})
|
|
||||||
// http://localhost:8080/websocket/iris-ws.js
|
|
||||||
m.Router.Any("/iris-ws.js", websocket.ClientHandler())
|
|
||||||
|
|
||||||
// This will bind the result of ws.Upgrade which is a websocket.Connection
|
|
||||||
// to the controller(s) served by the `m.Handle`.
|
|
||||||
m.Register(ws.Upgrade)
|
|
||||||
m.Handle(new(websocketController))
|
|
||||||
}
|
|
||||||
|
|
||||||
var visits uint64
|
var visits uint64
|
||||||
|
|
||||||
func increment() uint64 {
|
func increment() uint64 {
|
||||||
|
@ -47,36 +51,74 @@ func decrement() uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type websocketController struct {
|
type websocketController struct {
|
||||||
// Note that you could use an anonymous field as well, it doesn't matter, binder will find it.
|
*neffos.NSConn `stateless:"true"`
|
||||||
//
|
Namespace string
|
||||||
// This is the current websocket connection, each client has its own instance of the *websocketController.
|
Age int
|
||||||
Conn websocket.Connection
|
Otherstring string
|
||||||
|
|
||||||
|
Logger LoggerService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *websocketController) onLeave(roomName string) {
|
// or
|
||||||
|
// func (c *websocketController) Namespace() string {
|
||||||
|
// return "default"
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (c *websocketController) OnNamespaceDisconnect(msg neffos.Message) error {
|
||||||
|
c.Logger.Log("Disconnected")
|
||||||
// visits--
|
// visits--
|
||||||
newCount := decrement()
|
newCount := decrement()
|
||||||
// This will call the "visit" event on all clients, except the current one,
|
// This will call the "OnVisit" event on all clients, except the current one,
|
||||||
// (it can't because it's left but for any case use this type of design)
|
// (it can't because it's left but for any case use this type of design)
|
||||||
c.Conn.To(websocket.Broadcast).Emit("visit", newCount)
|
c.Conn.Server().Broadcast(nil, neffos.Message{
|
||||||
|
Namespace: msg.Namespace,
|
||||||
|
Event: "OnVisit",
|
||||||
|
Body: []byte(fmt.Sprintf("%d", newCount)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *websocketController) update() {
|
func (c *websocketController) OnNamespaceConnected(msg neffos.Message) error {
|
||||||
|
// println("Broadcast prefix is: " + c.BroadcastPrefix)
|
||||||
|
c.Logger.Log("Connected")
|
||||||
|
|
||||||
// visits++
|
// visits++
|
||||||
newCount := increment()
|
newCount := increment()
|
||||||
|
|
||||||
// This will call the "visit" event on all clients, including the current
|
// This will call the "OnVisit" event on all clients, including the current
|
||||||
// with the 'newCount' variable.
|
// with the 'newCount' variable.
|
||||||
//
|
//
|
||||||
// There are many ways that u can do it and faster, for example u can just send a new visitor
|
// There are many ways that u can do it and faster, for example u can just send a new visitor
|
||||||
// and client can increment itself, but here we are just "showcasing" the websocket controller.
|
// and client can increment itself, but here we are just "showcasing" the websocket controller.
|
||||||
c.Conn.To(websocket.All).Emit("visit", newCount)
|
c.Conn.Server().Broadcast(c, neffos.Message{
|
||||||
|
Namespace: msg.Namespace,
|
||||||
|
Event: "OnVisit",
|
||||||
|
Body: []byte(fmt.Sprintf("%d", newCount)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *websocketController) Get( /* websocket.Connection could be lived here as well, it doesn't matter */ ) {
|
func (c *websocketController) OnChat(msg neffos.Message) error {
|
||||||
c.Conn.OnLeave(c.onLeave)
|
ctx := websocket.GetContext(c.Conn)
|
||||||
c.Conn.On("visit", c.update)
|
|
||||||
|
|
||||||
// call it after all event callbacks registration.
|
ctx.Application().Logger().Infof("[IP: %s] [ID: %s] broadcast to other clients the message [%s]",
|
||||||
c.Conn.Wait()
|
ctx.RemoteAddr(), c, string(msg.Body))
|
||||||
|
|
||||||
|
c.Conn.Server().Broadcast(c, msg)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerService interface {
|
||||||
|
Log(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type prefixedLogger struct {
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *prefixedLogger) Log(msg string) {
|
||||||
|
fmt.Printf("%s: %s\n", s.prefix, msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Online visitors MVC example</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, "San Francisco", "Helvetica Neue", "Noto", "Roboto", "Calibri Light", sans-serif;
|
|
||||||
color: #212121;
|
|
||||||
font-size: 1.0em;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 750px;
|
|
||||||
margin: auto;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#online_visitors {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<span id="online_visitors">1 online visitor</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/websocket/iris-ws.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
(function () {
|
|
||||||
var socket = new Ws("ws://localhost:8080/websocket");
|
|
||||||
|
|
||||||
socket.OnConnect(function(){
|
|
||||||
// update the rest of connected clients, including "myself" when "my" connection is 100% ready.
|
|
||||||
socket.Emit("visit");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
socket.On("visit", function (newCount) {
|
|
||||||
console.log("visit websocket event with newCount of: ", newCount);
|
|
||||||
|
|
||||||
var text = "1 online visitor";
|
|
||||||
if (newCount > 1) {
|
|
||||||
text = newCount + " online visitors";
|
|
||||||
}
|
|
||||||
document.getElementById("online_visitors").innerHTML = text;
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.OnDisconnect(function () {
|
|
||||||
document.getElementById("online_visitors").innerHTML = "you've been disconnected";
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -68,7 +68,7 @@ func main() {
|
||||||
|
|
||||||
// serves the endpoint of ws://localhost:8080/echo
|
// serves the endpoint of ws://localhost:8080/echo
|
||||||
websocketRoute := app.Get("/echo", websocket.Handler(websocketServer))
|
websocketRoute := app.Get("/echo", websocket.Handler(websocketServer))
|
||||||
|
j.Get()
|
||||||
if enableJWT {
|
if enableJWT {
|
||||||
// Register the jwt middleware (on handshake):
|
// Register the jwt middleware (on handshake):
|
||||||
websocketRoute.Use(j.Serve)
|
websocketRoute.Use(j.Serve)
|
||||||
|
@ -76,8 +76,7 @@ func main() {
|
||||||
//
|
//
|
||||||
// Check for token through the jwt middleware
|
// Check for token through the jwt middleware
|
||||||
// on websocket connection or on any event:
|
// on websocket connection or on any event:
|
||||||
/*
|
/* websocketServer.OnConnect = func(c *neffos.Conn) error {
|
||||||
websocketServer.OnConnect = func(c *neffos.Conn) error {
|
|
||||||
ctx := websocket.GetContext(c)
|
ctx := websocket.GetContext(c)
|
||||||
if err := j.CheckJWT(ctx); err != nil {
|
if err := j.CheckJWT(ctx); err != nil {
|
||||||
// will send the above error on the client
|
// will send the above error on the client
|
||||||
|
@ -85,11 +84,17 @@ func main() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user := ctx.Values().Get("jwt").(*jwt.Token)
|
||||||
|
// or just: user := j.Get(ctx)
|
||||||
|
|
||||||
|
log.Printf("This is an authenticated request\n")
|
||||||
|
log.Printf("Claim content:")
|
||||||
|
log.Printf("%#+v\n", user.Claims)
|
||||||
|
|
||||||
log.Printf("[%s] connected to the server", c.ID())
|
log.Printf("[%s] connected to the server", c.ID())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
} */
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serves the browser-based websocket client.
|
// serves the browser-based websocket client.
|
||||||
|
|
|
@ -401,7 +401,7 @@ type Configuration struct {
|
||||||
|
|
||||||
// DisablePathCorrectionRedirection works whenever configuration.DisablePathCorrection is set to false
|
// DisablePathCorrectionRedirection works whenever configuration.DisablePathCorrection is set to false
|
||||||
// and if DisablePathCorrectionRedirection set to true then it will fire the handler of the matching route without
|
// and if DisablePathCorrectionRedirection set to true then it will fire the handler of the matching route without
|
||||||
// the last slash ("/") instead of send a redirection status.
|
// the trailing slash ("/") instead of send a redirection status.
|
||||||
//
|
//
|
||||||
// Defaults to false.
|
// Defaults to false.
|
||||||
DisablePathCorrectionRedirection bool `json:"disablePathCorrectionRedirection,omitempty" yaml:"DisablePathCorrectionRedirection" toml:"DisablePathCorrectionRedirection"`
|
DisablePathCorrectionRedirection bool `json:"disablePathCorrectionRedirection,omitempty" yaml:"DisablePathCorrectionRedirection" toml:"DisablePathCorrectionRedirection"`
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -15,7 +15,7 @@ require (
|
||||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible
|
github.com/iris-contrib/go.uuid v2.0.0+incompatible
|
||||||
github.com/json-iterator/go v1.1.6 // vendor removed.
|
github.com/json-iterator/go v1.1.6 // vendor removed.
|
||||||
github.com/kataras/golog v0.0.0-20180321173939-03be10146386
|
github.com/kataras/golog v0.0.0-20180321173939-03be10146386
|
||||||
github.com/kataras/neffos v0.0.3
|
github.com/kataras/neffos v0.0.5
|
||||||
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
|
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2
|
github.com/microcosm-cc/bluemonday v1.0.2
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible
|
github.com/ryanuber/columnize v2.1.0+incompatible
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package di
|
package di
|
||||||
|
|
||||||
import "reflect"
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
// EmptyIn is just an empty slice of reflect.Value.
|
// EmptyIn is just an empty slice of reflect.Value.
|
||||||
var EmptyIn = []reflect.Value{}
|
var EmptyIn = []reflect.Value{}
|
||||||
|
@ -164,6 +166,13 @@ func structFieldIgnored(f reflect.StructField) bool {
|
||||||
return s == "true" // if has an ignore tag then ignore it.
|
return s == "true" // if has an ignore tag then ignore it.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for controller's fields only. Explicit set a stateless to a field
|
||||||
|
// in order to make the controller a Stateless one even if no other dynamic dependencies exist.
|
||||||
|
func structFieldStateless(f reflect.StructField) bool {
|
||||||
|
s := f.Tag.Get("stateless")
|
||||||
|
return s == "true"
|
||||||
|
}
|
||||||
|
|
||||||
type field struct {
|
type field struct {
|
||||||
Type reflect.Type
|
Type reflect.Type
|
||||||
Name string // the actual name.
|
Name string // the actual name.
|
||||||
|
@ -190,7 +199,7 @@ func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int)
|
||||||
for i, n := 0, elemTyp.NumField(); i < n; i++ {
|
for i, n := 0, elemTyp.NumField(); i < n; i++ {
|
||||||
f := elemTyp.Field(i)
|
f := elemTyp.Field(i)
|
||||||
if IndirectType(f.Type).Kind() == reflect.Struct &&
|
if IndirectType(f.Type).Kind() == reflect.Struct &&
|
||||||
!structFieldIgnored(f) {
|
!structFieldIgnored(f) && !structFieldStateless(f) {
|
||||||
fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...)
|
fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -230,7 +239,8 @@ func LookupNonZeroFieldsValues(v reflect.Value, skipUnexported bool) (bindValues
|
||||||
|
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/
|
if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/
|
||||||
!IsZero(fieldVal) {
|
goodVal(fieldVal) && !IsZero(fieldVal) {
|
||||||
|
// fmt.Printf("[%d][field index = %d] append to bindValues: %s = %s\n", i, f.Index[0], f.Name, fieldVal.String())
|
||||||
bindValues = append(bindValues, fieldVal)
|
bindValues = append(bindValues, fieldVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,22 @@ const (
|
||||||
Singleton
|
Singleton
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// read-only on runtime.
|
||||||
|
var scopeNames = map[Scope]string{
|
||||||
|
Stateless: "Stateless",
|
||||||
|
Singleton: "Singleton",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return "Stateless" for 0 or "Singleton" for 1.
|
||||||
|
func (scope Scope) String() string {
|
||||||
|
name, ok := scopeNames[scope]
|
||||||
|
if !ok {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
targetStructField struct {
|
targetStructField struct {
|
||||||
Object *BindObject
|
Object *BindObject
|
||||||
|
@ -65,6 +81,20 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
|
||||||
elemType: IndirectType(v.Type()),
|
elemType: IndirectType(v.Type()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optionally check and keep good values only here,
|
||||||
|
// but not required because they are already checked by users of this function.
|
||||||
|
//
|
||||||
|
// for i, v := range values {
|
||||||
|
// if !goodVal(v) || IsZero(v) {
|
||||||
|
// if last := len(values) - 1; last > i {
|
||||||
|
// values = append(values[:i], values[i+1:]...)
|
||||||
|
// } else {
|
||||||
|
// values = values[0:last]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
visited := make(map[int]struct{}, 0) // add a visited to not add twice a single value (09-Jul-2019).
|
||||||
fields := lookupFields(s.elemType, true, nil)
|
fields := lookupFields(s.elemType, true, nil)
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if hijack != nil {
|
if hijack != nil {
|
||||||
|
@ -78,15 +108,19 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range values {
|
for idx, val := range values {
|
||||||
|
if _, alreadySet := visited[idx]; alreadySet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// the binded values to the struct's fields.
|
// the binded values to the struct's fields.
|
||||||
b, err := MakeBindObject(val, goodFunc)
|
b, err := MakeBindObject(val, goodFunc)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s // if error stop here.
|
return s // if error stop here.
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.IsAssignable(f.Type) {
|
if b.IsAssignable(f.Type) {
|
||||||
|
visited[idx] = struct{}{}
|
||||||
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
|
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
|
||||||
s.fields = append(s.fields, &targetStructField{
|
s.fields = append(s.fields, &targetStructField{
|
||||||
FieldIndex: f.Index,
|
FieldIndex: f.Index,
|
||||||
|
@ -126,13 +160,14 @@ func (s *StructInjector) setState() {
|
||||||
// if so then set the temp staticBindingsFieldsLength to that number, so for example:
|
// if so then set the temp staticBindingsFieldsLength to that number, so for example:
|
||||||
// if static binding length is 0
|
// if static binding length is 0
|
||||||
// but an unexported field is set-ed then act that as singleton.
|
// but an unexported field is set-ed then act that as singleton.
|
||||||
|
|
||||||
if allStructFieldsLength > staticBindingsFieldsLength {
|
if allStructFieldsLength > staticBindingsFieldsLength {
|
||||||
structFieldsUnexportedNonZero := LookupNonZeroFieldsValues(s.initRef, false)
|
structFieldsUnexportedNonZero := LookupNonZeroFieldsValues(s.initRef, false)
|
||||||
staticBindingsFieldsLength = len(structFieldsUnexportedNonZero)
|
staticBindingsFieldsLength = len(structFieldsUnexportedNonZero)
|
||||||
}
|
}
|
||||||
|
|
||||||
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
|
|
||||||
// println("allStructFieldsLength: ", allStructFieldsLength)
|
// println("allStructFieldsLength: ", allStructFieldsLength)
|
||||||
|
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
|
||||||
|
|
||||||
// if the number of static values binded is equal to the
|
// if the number of static values binded is equal to the
|
||||||
// total struct's fields(including unexported fields this time) then set as singleton.
|
// total struct's fields(including unexported fields this time) then set as singleton.
|
||||||
|
@ -169,9 +204,23 @@ func (s *StructInjector) fillStruct() {
|
||||||
func (s *StructInjector) String() (trace string) {
|
func (s *StructInjector) String() (trace string) {
|
||||||
for i, f := range s.fields {
|
for i, f := range s.fields {
|
||||||
elemField := s.elemType.FieldByIndex(f.FieldIndex)
|
elemField := s.elemType.FieldByIndex(f.FieldIndex)
|
||||||
trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n",
|
|
||||||
i+1, bindTypeString(f.Object.BindType), f.Object.Type.String(),
|
format := "\t[%d] %s binding: %#+v for field '%s %s'"
|
||||||
elemField.Name, elemField.Type.String())
|
if len(s.fields) > i+1 {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.Object.Value.IsValid() {
|
||||||
|
continue // probably a Context.
|
||||||
|
}
|
||||||
|
|
||||||
|
valuePresent := f.Object.Value.Interface()
|
||||||
|
|
||||||
|
if f.Object.BindType == Dynamic {
|
||||||
|
valuePresent = f.Object.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
trace += fmt.Sprintf(format, i+1, bindTypeString(f.Object.BindType), valuePresent, elemField.Name, elemField.Type.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -29,7 +29,22 @@ func (bv Values) CloneWithFieldsOf(s interface{}) Values {
|
||||||
|
|
||||||
// add the manual filled fields to the dependencies.
|
// add the manual filled fields to the dependencies.
|
||||||
filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true)
|
filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true)
|
||||||
|
|
||||||
|
for i, filled := range filledFieldValues {
|
||||||
|
for _, v := range values {
|
||||||
|
// do NOT keep duplicate equal values (09-Jul-2019).
|
||||||
|
if reflect.DeepEqual(v, filled) {
|
||||||
|
if last := len(filledFieldValues) - 1; last > i {
|
||||||
|
filledFieldValues = append(filledFieldValues[:i], filledFieldValues[i+1:]...)
|
||||||
|
} else {
|
||||||
|
filledFieldValues = filledFieldValues[0:last]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
values = append(values, filledFieldValues...)
|
values = append(values, filledFieldValues...)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,11 @@ type ControllerActivator struct {
|
||||||
|
|
||||||
errorHandler hero.ErrorHandler
|
errorHandler hero.ErrorHandler
|
||||||
|
|
||||||
// initialized on the first `Handle`.
|
// initialized on the first `Handle` or immediately when "servesWebsocket" is true.
|
||||||
injector *di.StructInjector
|
injector *di.StructInjector
|
||||||
|
|
||||||
|
// true if this controller listens and serves to websocket events.
|
||||||
|
servesWebsocket bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameOf returns the package name + the struct type's name,
|
// NameOf returns the package name + the struct type's name,
|
||||||
|
@ -145,6 +148,11 @@ func whatReservedMethods(typ reflect.Type) map[string]*router.Route {
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ControllerActivator) markAsWebsocket() {
|
||||||
|
c.servesWebsocket = true
|
||||||
|
c.attachInjector()
|
||||||
|
}
|
||||||
|
|
||||||
// Dependencies returns the write and read access of the dependencies that are
|
// Dependencies returns the write and read access of the dependencies that are
|
||||||
// came from the parent MVC Application, with this you can customize
|
// came from the parent MVC Application, with this you can customize
|
||||||
// the dependencies per controller, used at the `BeforeActivation`.
|
// the dependencies per controller, used at the `BeforeActivation`.
|
||||||
|
@ -325,6 +333,21 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
|
||||||
|
|
||||||
var emptyIn = []reflect.Value{}
|
var emptyIn = []reflect.Value{}
|
||||||
|
|
||||||
|
func (c *ControllerActivator) attachInjector() {
|
||||||
|
if c.injector == nil {
|
||||||
|
c.injector = di.Struct(c.Value, c.dependencies...)
|
||||||
|
if !c.servesWebsocket {
|
||||||
|
golog.Debugf("MVC Controller [%s] [Scope=%s]", c.fullName, c.injector.Scope)
|
||||||
|
} else {
|
||||||
|
golog.Debugf("MVC Websocket Controller [%s]", c.fullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.injector.Has {
|
||||||
|
golog.Debugf("Dependencies:\n%s", c.injector.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []reflect.Value) context.Handler {
|
func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []reflect.Value) context.Handler {
|
||||||
// Remember:
|
// Remember:
|
||||||
// The `Handle->handlerOf` can be called from `BeforeActivation` event
|
// The `Handle->handlerOf` can be called from `BeforeActivation` event
|
||||||
|
@ -333,12 +356,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
|
||||||
// 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.
|
||||||
if c.injector == nil {
|
c.attachInjector()
|
||||||
c.injector = di.Struct(c.Value, c.dependencies...)
|
|
||||||
if c.injector.Has {
|
|
||||||
golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
|
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
|
||||||
|
|
||||||
|
|
50
mvc/mvc.go
50
mvc/mvc.go
|
@ -1,12 +1,14 @@
|
||||||
package mvc
|
package mvc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/core/router"
|
"github.com/kataras/iris/core/router"
|
||||||
"github.com/kataras/iris/hero"
|
"github.com/kataras/iris/hero"
|
||||||
"github.com/kataras/iris/hero/di"
|
"github.com/kataras/iris/hero/di"
|
||||||
|
"github.com/kataras/iris/websocket"
|
||||||
|
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
)
|
)
|
||||||
|
@ -172,6 +174,52 @@ 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 {
|
||||||
|
app.handle(controller)
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleWebsocket handles a websocket specific controller.
|
||||||
|
// Its exported methods are the events.
|
||||||
|
// If a "Namespace" field or method exists then namespace is set, otherwise empty namespace.
|
||||||
|
// Note that a websocket controller is registered and ran under a specific connection connected to a namespace
|
||||||
|
// and it cannot send HTTP responses on that state.
|
||||||
|
// However all static and dynamic dependency injection features are working, as expected, like any regular MVC Controller.
|
||||||
|
func (app *Application) HandleWebsocket(controller interface{}) {
|
||||||
|
c := app.handle(controller)
|
||||||
|
c.markAsWebsocket()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ websocket.ConnHandler = (*Application)(nil)
|
||||||
|
|
||||||
|
// GetNamespaces completes the websocket ConnHandler interface.
|
||||||
|
// It returns a collection of namespace and events that
|
||||||
|
// were registered through `HandleWebsocket` controllers.
|
||||||
|
func (app *Application) GetNamespaces() websocket.Namespaces {
|
||||||
|
makeInjector := func(injector *di.StructInjector) websocket.StructInjector {
|
||||||
|
return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
|
||||||
|
v := injector.Acquire()
|
||||||
|
if injector.CanInject {
|
||||||
|
injector.InjectElem(v.Elem(), reflect.ValueOf(websocket.GetContext(nsConn.Conn)))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var websocketControllers []websocket.ConnHandler
|
||||||
|
|
||||||
|
for _, c := range app.Controllers {
|
||||||
|
if c.servesWebsocket {
|
||||||
|
wsInjector := makeInjector(c.injector)
|
||||||
|
s := websocket.NewStruct(c.Value).SetInjector(wsInjector)
|
||||||
|
websocketControllers = append(websocketControllers, s)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return websocket.JoinConnHandlers(websocketControllers...).GetNamespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) handle(controller interface{}) *ControllerActivator {
|
||||||
// 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, app.ErrorHandler)
|
c := newControllerActivator(app.Router, controller, app.Dependencies, app.ErrorHandler)
|
||||||
|
|
||||||
|
@ -193,7 +241,7 @@ func (app *Application) Handle(controller interface{}) *Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Controllers = append(app.Controllers, c)
|
app.Controllers = append(app.Controllers, c)
|
||||||
return app
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
|
// HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package mvc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kataras/iris/context"
|
|
||||||
"github.com/kataras/iris/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultSessionManager = sessions.New(sessions.Config{})
|
|
||||||
|
|
||||||
// SessionController is a simple `Controller` implementation
|
|
||||||
// which requires a binded session manager in order to give
|
|
||||||
// direct access to the current client's session via its `Session` field.
|
|
||||||
//
|
|
||||||
// SessionController is deprecated please use the new dependency injection's methods instead,
|
|
||||||
// i.e `mvcApp.Register(sessions.New(sessions.Config{}).Start)`.
|
|
||||||
// It's more controlled by you,
|
|
||||||
// also *sessions.Session type can now `Destroy` itself without the need of the manager, embrace it.
|
|
||||||
type SessionController struct {
|
|
||||||
Manager *sessions.Sessions
|
|
||||||
Session *sessions.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeforeActivation called, once per application lifecycle NOT request,
|
|
||||||
// every single time the dev registers a specific SessionController-based controller.
|
|
||||||
// It makes sure that its "Manager" field is filled
|
|
||||||
// even if the caller didn't provide any sessions manager via the MVC's Application's `Handle` function.
|
|
||||||
func (s *SessionController) BeforeActivation(b BeforeActivation) {
|
|
||||||
if didntBindManually := b.Dependencies().AddOnce(defaultSessionManager); didntBindManually {
|
|
||||||
b.Router().GetReporter().Add(
|
|
||||||
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
|
||||||
therefore this controller is using the default sessions manager instead.
|
|
||||||
Please refer to the documentation to learn how you can provide the session manager`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeginRequest initializes the current user's Session.
|
|
||||||
func (s *SessionController) BeginRequest(ctx context.Context) {
|
|
||||||
if s.Manager == nil {
|
|
||||||
ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
|
|
||||||
because the SessionController should predict this on its activation state and use a default one automatically`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Session = s.Manager.Start(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndRequest is here to complete the `BaseController`.
|
|
||||||
func (s *SessionController) EndRequest(ctx context.Context) {}
|
|
|
@ -53,13 +53,15 @@ var (
|
||||||
//
|
//
|
||||||
// See examples for more.
|
// See examples for more.
|
||||||
Dial = neffos.Dial
|
Dial = neffos.Dial
|
||||||
|
|
||||||
// IsTryingToReconnect reports whether the returning "err" from the `Server#Upgrade`
|
// IsTryingToReconnect reports whether the returning "err" from the `Server#Upgrade`
|
||||||
// is from a client that was trying to reconnect to the websocket server.
|
// is from a client that was trying to reconnect to the websocket server.
|
||||||
//
|
//
|
||||||
// Look the `Conn#WasReconnected` and `Conn#ReconnectTries` too.
|
// Look the `Conn#WasReconnected` and `Conn#ReconnectTries` too.
|
||||||
IsTryingToReconnect = neffos.IsTryingToReconnect
|
IsTryingToReconnect = neffos.IsTryingToReconnect
|
||||||
|
// NewStruct returns the `Struct` Conn Handler based on ptr value.
|
||||||
|
NewStruct = neffos.NewStruct
|
||||||
|
// JoinConnHandlers combines two or more ConnHandlers as one.
|
||||||
|
JoinConnHandlers = neffos.JoinConnHandlers
|
||||||
// OnNamespaceConnect is the event name which its callback is fired right before namespace connect,
|
// OnNamespaceConnect is the event name which its callback is fired right before namespace connect,
|
||||||
// if non-nil error then the remote connection's `Conn.Connect` will fail and send that error text.
|
// if non-nil error then the remote connection's `Conn.Connect` will fail and send that error text.
|
||||||
// Connection is not ready to emit data to the namespace.
|
// Connection is not ready to emit data to the namespace.
|
||||||
|
|
|
@ -39,7 +39,8 @@ type (
|
||||||
// i.e on `OnNamespaceConnect` it will abort a remote namespace connection.
|
// i.e on `OnNamespaceConnect` it will abort a remote namespace connection.
|
||||||
// See examples for more.
|
// See examples for more.
|
||||||
MessageHandlerFunc = neffos.MessageHandlerFunc
|
MessageHandlerFunc = neffos.MessageHandlerFunc
|
||||||
|
// ConnHandler is the interface which namespaces and events can be retrieved through.
|
||||||
|
ConnHandler = neffos.ConnHandler
|
||||||
// Events completes the `ConnHandler` interface.
|
// Events completes the `ConnHandler` interface.
|
||||||
// It is a map which its key is the event name
|
// It is a map which its key is the event name
|
||||||
// and its value the event's callback.
|
// and its value the event's callback.
|
||||||
|
@ -63,6 +64,11 @@ type (
|
||||||
//
|
//
|
||||||
// See `New` and `Dial`.
|
// See `New` and `Dial`.
|
||||||
WithTimeout = neffos.WithTimeout
|
WithTimeout = neffos.WithTimeout
|
||||||
|
// Struct completes the `ConnHandler` interface.
|
||||||
|
// It uses a structure to register a specific namespace and its events.
|
||||||
|
Struct = neffos.Struct
|
||||||
|
// StructInjector can be used to customize the value creation that can is used on serving events.
|
||||||
|
StructInjector = neffos.StructInjector
|
||||||
// The Message is the structure which describes the incoming and outcoming data.
|
// The Message is the structure which describes the incoming and outcoming data.
|
||||||
// Emitter's "body" argument is the `Message.Body` field.
|
// Emitter's "body" argument is the `Message.Body` field.
|
||||||
// Emitter's return non-nil error is the `Message.Err` field.
|
// Emitter's return non-nil error is the `Message.Err` field.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user