mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +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)
|
||||
- [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)
|
||||
- [Request Authentication](authentication/request/main.go) **NEW**
|
||||
|
||||
### File Server
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
- [Basic Authentication](basicauth/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)
|
|
@ -134,3 +134,5 @@ func main() {
|
|||
// You can read more examples and run testable code
|
||||
// at the `iris/crypto`, `iris/crypto/sign`
|
||||
// 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("Claim content:\n")
|
||||
|
||||
ctx.Writef("%s", user.Signature)
|
||||
ctx.Writef("%#+v\n", user.Claims)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -42,5 +42,12 @@ func main() {
|
|||
app.Use(jwtHandler.Serve)
|
||||
|
||||
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"))
|
||||
} // 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/websocket"
|
||||
|
||||
"github.com/kataras/neffos"
|
||||
)
|
||||
|
||||
func main() {
|
||||
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.
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
// render the ./views/index.html.
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.View("index.html")
|
||||
})
|
||||
// render the ./browser/index.html.
|
||||
app.HandleDir("/", "./browser")
|
||||
|
||||
mvc.Configure(app.Party("/websocket"), configureMVC)
|
||||
// Or mvc.New(app.Party(...)).Configure(configureMVC)
|
||||
websocketAPI := app.Party("/websocket")
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
func increment() uint64 {
|
||||
|
@ -47,36 +51,74 @@ func decrement() uint64 {
|
|||
}
|
||||
|
||||
type websocketController struct {
|
||||
// Note that you could use an anonymous field as well, it doesn't matter, binder will find it.
|
||||
//
|
||||
// This is the current websocket connection, each client has its own instance of the *websocketController.
|
||||
Conn websocket.Connection
|
||||
*neffos.NSConn `stateless:"true"`
|
||||
Namespace string
|
||||
Age int
|
||||
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--
|
||||
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)
|
||||
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++
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
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 */ ) {
|
||||
c.Conn.OnLeave(c.onLeave)
|
||||
c.Conn.On("visit", c.update)
|
||||
func (c *websocketController) OnChat(msg neffos.Message) error {
|
||||
ctx := websocket.GetContext(c.Conn)
|
||||
|
||||
// call it after all event callbacks registration.
|
||||
c.Conn.Wait()
|
||||
ctx.Application().Logger().Infof("[IP: %s] [ID: %s] broadcast to other clients the message [%s]",
|
||||
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
|
||||
websocketRoute := app.Get("/echo", websocket.Handler(websocketServer))
|
||||
|
||||
j.Get()
|
||||
if enableJWT {
|
||||
// Register the jwt middleware (on handshake):
|
||||
websocketRoute.Use(j.Serve)
|
||||
|
@ -76,20 +76,25 @@ func main() {
|
|||
//
|
||||
// Check for token through the jwt middleware
|
||||
// on websocket connection or on any event:
|
||||
/*
|
||||
websocketServer.OnConnect = func(c *neffos.Conn) error {
|
||||
ctx := websocket.GetContext(c)
|
||||
if err := j.CheckJWT(ctx); err != nil {
|
||||
// will send the above error on the client
|
||||
// and will not allow it to connect to the websocket server at all.
|
||||
return err
|
||||
}
|
||||
/* websocketServer.OnConnect = func(c *neffos.Conn) error {
|
||||
ctx := websocket.GetContext(c)
|
||||
if err := j.CheckJWT(ctx); err != nil {
|
||||
// will send the above error on the client
|
||||
// and will not allow it to connect to the websocket server at all.
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[%s] connected to the server", c.ID())
|
||||
user := ctx.Values().Get("jwt").(*jwt.Token)
|
||||
// or just: user := j.Get(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
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())
|
||||
|
||||
return nil
|
||||
} */
|
||||
}
|
||||
|
||||
// serves the browser-based websocket client.
|
||||
|
|
|
@ -401,7 +401,7 @@ type Configuration struct {
|
|||
|
||||
// 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
|
||||
// the last slash ("/") instead of send a redirection status.
|
||||
// the trailing slash ("/") instead of send a redirection status.
|
||||
//
|
||||
// Defaults to false.
|
||||
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/json-iterator/go v1.1.6 // vendor removed.
|
||||
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/microcosm-cc/bluemonday v1.0.2
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package di
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// EmptyIn is just an empty slice of 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.
|
||||
}
|
||||
|
||||
// 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 reflect.Type
|
||||
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++ {
|
||||
f := elemTyp.Field(i)
|
||||
if IndirectType(f.Type).Kind() == reflect.Struct &&
|
||||
!structFieldIgnored(f) {
|
||||
!structFieldIgnored(f) && !structFieldStateless(f) {
|
||||
fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...)
|
||||
continue
|
||||
}
|
||||
|
@ -230,7 +239,8 @@ func LookupNonZeroFieldsValues(v reflect.Value, skipUnexported bool) (bindValues
|
|||
|
||||
for _, f := range fields {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,22 @@ const (
|
|||
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 (
|
||||
targetStructField struct {
|
||||
Object *BindObject
|
||||
|
@ -65,6 +81,20 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
|
|||
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)
|
||||
for _, f := range fields {
|
||||
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.
|
||||
b, err := MakeBindObject(val, goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return s // if error stop here.
|
||||
}
|
||||
|
||||
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())
|
||||
s.fields = append(s.fields, &targetStructField{
|
||||
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 static binding length is 0
|
||||
// but an unexported field is set-ed then act that as singleton.
|
||||
|
||||
if allStructFieldsLength > staticBindingsFieldsLength {
|
||||
structFieldsUnexportedNonZero := LookupNonZeroFieldsValues(s.initRef, false)
|
||||
staticBindingsFieldsLength = len(structFieldsUnexportedNonZero)
|
||||
}
|
||||
|
||||
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
|
||||
// println("allStructFieldsLength: ", allStructFieldsLength)
|
||||
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
|
||||
|
||||
// if the number of static values binded is equal to the
|
||||
// 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) {
|
||||
for i, f := range s.fields {
|
||||
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(),
|
||||
elemField.Name, elemField.Type.String())
|
||||
|
||||
format := "\t[%d] %s binding: %#+v for field '%s %s'"
|
||||
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
|
||||
|
|
|
@ -29,7 +29,22 @@ func (bv Values) CloneWithFieldsOf(s interface{}) Values {
|
|||
|
||||
// add the manual filled fields to the dependencies.
|
||||
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...)
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
|
|
|
@ -87,8 +87,11 @@ type ControllerActivator struct {
|
|||
|
||||
errorHandler hero.ErrorHandler
|
||||
|
||||
// initialized on the first `Handle`.
|
||||
// initialized on the first `Handle` or immediately when "servesWebsocket" is true.
|
||||
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,
|
||||
|
@ -145,6 +148,11 @@ func whatReservedMethods(typ reflect.Type) map[string]*router.Route {
|
|||
return routes
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) markAsWebsocket() {
|
||||
c.servesWebsocket = true
|
||||
c.attachInjector()
|
||||
}
|
||||
|
||||
// Dependencies returns the write and read access of the dependencies that are
|
||||
// came from the parent MVC Application, with this you can customize
|
||||
// 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{}
|
||||
|
||||
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 {
|
||||
// Remember:
|
||||
// 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`,
|
||||
// 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.
|
||||
if c.injector == nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
c.attachInjector()
|
||||
|
||||
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
|
||||
|
||||
|
|
50
mvc/mvc.go
50
mvc/mvc.go
|
@ -1,12 +1,14 @@
|
|||
package mvc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/router"
|
||||
"github.com/kataras/iris/hero"
|
||||
"github.com/kataras/iris/hero/di"
|
||||
"github.com/kataras/iris/websocket"
|
||||
|
||||
"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
|
||||
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.
|
||||
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)
|
||||
return app
|
||||
return c
|
||||
}
|
||||
|
||||
// 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.
|
||||
Dial = neffos.Dial
|
||||
|
||||
// IsTryingToReconnect reports whether the returning "err" from the `Server#Upgrade`
|
||||
// is from a client that was trying to reconnect to the websocket server.
|
||||
//
|
||||
// Look the `Conn#WasReconnected` and `Conn#ReconnectTries` too.
|
||||
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,
|
||||
// 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.
|
||||
|
|
|
@ -39,7 +39,8 @@ type (
|
|||
// i.e on `OnNamespaceConnect` it will abort a remote namespace connection.
|
||||
// See examples for more.
|
||||
MessageHandlerFunc = neffos.MessageHandlerFunc
|
||||
|
||||
// ConnHandler is the interface which namespaces and events can be retrieved through.
|
||||
ConnHandler = neffos.ConnHandler
|
||||
// Events completes the `ConnHandler` interface.
|
||||
// It is a map which its key is the event name
|
||||
// and its value the event's callback.
|
||||
|
@ -63,6 +64,11 @@ type (
|
|||
//
|
||||
// See `New` and `Dial`.
|
||||
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.
|
||||
// Emitter's "body" argument is the `Message.Body` field.
|
||||
// Emitter's return non-nil error is the `Message.Err` field.
|
||||
|
|
Loading…
Reference in New Issue
Block a user