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:
Gerasimos (Makis) Maropoulos 2019-07-09 12:16:19 +03:00
parent 85666da682
commit 450f20902d
18 changed files with 383 additions and 183 deletions

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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
}

View 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>

View File

@ -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)
}

View File

@ -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>

View File

@ -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.

View File

@ -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
View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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) {}

View File

@ -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.

View File

@ -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.