Skip to content

Commit 26005b6

Browse files
committed
Export the ability of custom ConnHandlers and a lot of improvements that allow externals package to interact with neffos structs-to-events feature such as Iris' new v11.2+ fully featured MVC websocket controller
1 parent c2363a4 commit 26005b6

7 files changed

+113
-64
lines changed

client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func Dial(ctx context.Context, dial Dialer, url string, connHandler ConnHandler)
8484
connHandler = Namespaces{}
8585
}
8686

87-
c := newConn(underline, connHandler.getNamespaces())
87+
c := newConn(underline, connHandler.GetNamespaces())
8888
readTimeout, writeTimeout := getTimeouts(connHandler)
8989
c.readTimeout = readTimeout
9090
c.writeTimeout = writeTimeout

conn_handler.go

+53-17
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import (
66
"time"
77
)
88

9-
// ConnHandler is the interface which
10-
// `Events`, `Namespaces` and `WithTimeout` are implement.
11-
// It's exported just to be used on the `Dial`(client) and `New` (server) functions.
9+
// ConnHandler is the interface which namespaces and events can be retrieved through.
10+
// Built-in ConnHandlers are the`Events`, `Namespaces`, `WithTimeout` and `NewStruct`.
11+
// Users of this are the `Dial`(client) and `New` (server) functions.
1212
type ConnHandler interface {
13-
getNamespaces() Namespaces
13+
GetNamespaces() Namespaces
1414
}
1515

1616
var (
@@ -31,7 +31,8 @@ var (
3131
// See `Namespaces`, `New` and `Dial` too.
3232
type Events map[string]MessageHandlerFunc
3333

34-
func (e Events) getNamespaces() Namespaces {
34+
// GetNamespaces returns an empty namespace with the "e" Events.
35+
func (e Events) GetNamespaces() Namespaces {
3536
return Namespaces{"": e}
3637
}
3738

@@ -55,7 +56,8 @@ func (e Events) fireEvent(c *NSConn, msg Message) error {
5556
// See `WithTimeout`, `New` and `Dial` too.
5657
type Namespaces map[string]Events
5758

58-
func (nss Namespaces) getNamespaces() Namespaces { return nss }
59+
// GetNamespaces just returns the "nss" namespaces.
60+
func (nss Namespaces) GetNamespaces() Namespaces { return nss }
5961

6062
// WithTimeout completes the `ConnHandler` interface.
6163
// Can be used to register namespaces and events or just events on an empty namespace
@@ -70,8 +72,10 @@ type WithTimeout struct {
7072
Events Events
7173
}
7274

73-
func (t WithTimeout) getNamespaces() Namespaces {
74-
return JoinConnHandlers(t.Namespaces, t.Events).getNamespaces()
75+
// GetNamespaces returns combined namespaces from "Namespaces" and "Events" fields
76+
// with read and write timeouts from "ReadTimeout" and "WriteTimeout" fields of "t".
77+
func (t WithTimeout) GetNamespaces() Namespaces {
78+
return JoinConnHandlers(t.Namespaces, t.Events).GetNamespaces()
7579
}
7680

7781
func getTimeouts(h ConnHandler) (readTimeout time.Duration, writeTimeout time.Duration) {
@@ -103,6 +107,9 @@ type Struct struct {
103107
eventMatcher EventMatcherFunc
104108
readTimeout, writeTimeout time.Duration
105109

110+
// This field is set when external dependency injection system is used.
111+
injector StructInjector
112+
106113
events Events
107114
}
108115

@@ -157,6 +164,18 @@ func (s *Struct) SetTimeouts(read, write time.Duration) *Struct {
157164
return s
158165
}
159166

167+
// SetInjector sets a custom injector and overrides the neffos default behavior
168+
// on dynamic structs.
169+
// The "fn" should handle to fill static fields and the NSConn.
170+
// This "fn" will only be called when dynamic struct "ptr" is passed
171+
// on the `NewStruct`.
172+
// The caller should return a
173+
// valid type of "ptr" reflect.Value.
174+
func (s *Struct) SetInjector(fn StructInjector) *Struct {
175+
s.injector = fn
176+
return s
177+
}
178+
160179
// NewStruct returns a new Struct value instance type of ConnHandler.
161180
// The "ptr" should be a pointer to a struct.
162181
// This function is used when you want to convert a structure to
@@ -173,7 +192,27 @@ func (s *Struct) SetTimeouts(read, write time.Duration) *Struct {
173192
//
174193
// Note that this method has a tiny performance cost when an event's callback's logic has small footprint.
175194
func NewStruct(ptr interface{}) *Struct {
176-
typ := reflect.TypeOf(ptr) // use for methods with receiver Ptr.
195+
if ptr == nil {
196+
panic("NewStruct: value is nil")
197+
}
198+
199+
if s, ok := ptr.(*Struct); ok { // if it's already a *Struct then just return it.
200+
return s
201+
}
202+
203+
var v reflect.Value // use for methods with receiver Ptr.
204+
if rValue, ok := ptr.(reflect.Value); ok {
205+
v = rValue
206+
} else {
207+
v = reflect.ValueOf(ptr)
208+
}
209+
210+
if !v.IsValid() {
211+
panic("NewStruct: value is not a valid one")
212+
}
213+
214+
typ := v.Type() // use for methods with receiver Ptr.
215+
177216
if typ.Kind() != reflect.Ptr {
178217
panic("NewStruct: value should be a pointer")
179218
}
@@ -186,11 +225,6 @@ func NewStruct(ptr interface{}) *Struct {
186225
panic("NewStruct: value does not points to a struct")
187226
}
188227

189-
v := reflect.ValueOf(ptr) // use for methods with receiver Ptr.
190-
if !v.IsValid() {
191-
panic("NewStruct: value is not a valid one")
192-
}
193-
194228
n := typ.NumMethod()
195229
_, hasNamespaceMethod := typ.MethodByName("Namespace")
196230
if n == 0 || (n == 1 && hasNamespaceMethod) {
@@ -212,11 +246,13 @@ func (s *Struct) Events() Events {
212246
return s.events
213247
}
214248

215-
s.events = makeEventsFromStruct(s.ptr, s.eventMatcher)
249+
s.events = makeEventsFromStruct(s.ptr, s.eventMatcher, s.injector)
216250
return s.events
217251
}
218252

219-
func (s *Struct) getNamespaces() Namespaces { // completes the `ConnHandler` interface.
253+
// GetNamespaces creates and returns Namespaces based on the
254+
// pointer to struct value provided by the "s".
255+
func (s *Struct) GetNamespaces() Namespaces { // completes the `ConnHandler` interface.
220256
if s.namespace == "" {
221257
s.namespace, _ = resolveStructNamespace(s.ptr)
222258
}
@@ -233,7 +269,7 @@ func JoinConnHandlers(connHandlers ...ConnHandler) ConnHandler {
233269
namespaces := Namespaces{}
234270

235271
for _, h := range connHandlers {
236-
nss := h.getNamespaces()
272+
nss := h.GetNamespaces()
237273
if len(nss) > 0 {
238274
for namespace, events := range nss {
239275
if events == nil {

conn_handler_struct_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestConnHandlerStructStatic(t *testing.T) {
2323
v := new(testStructStatic)
2424
v.Err = fmt.Errorf("from static")
2525
s := NewStruct(v)
26-
nss := s.getNamespaces()
26+
nss := s.GetNamespaces()
2727

2828
if expected, got := v.Namespace(), s.namespace; expected != got {
2929
t.Fatalf("expected namespace to be: %s but got: %s", expected, got)
@@ -56,7 +56,7 @@ func TestConnHandlerStructDynamic(t *testing.T) {
5656
StaticFieldErr: fmt.Errorf("a static field which should be set on each new testStructDynamic"),
5757
}
5858
s := NewStruct(v)
59-
nss := s.getNamespaces()
59+
nss := s.GetNamespaces()
6060

6161
nsConn := &NSConn{namespace: s.namespace}
6262
nss[s.namespace][OnNamespaceConnect](nsConn, Message{Namespace: s.namespace})
@@ -84,7 +84,7 @@ func TestConnHandlerStructDynamicEmbedded(t *testing.T) {
8484
v := new(testStructDynamicEmbedded)
8585
s := NewStruct(v)
8686
s.namespace = "default"
87-
nss := s.getNamespaces()
87+
nss := s.GetNamespaces()
8888

8989
nsConn := &NSConn{namespace: s.namespace}
9090
nss[s.namespace][OnNamespaceConnect](nsConn, Message{Namespace: s.namespace})

conn_namespace.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package neffos
22

33
import (
44
"context"
5+
"reflect"
56
"sync"
6-
"sync/atomic"
77
)
88

99
// NSConn describes a connection connected to a specific namespace,
@@ -25,9 +25,9 @@ type NSConn struct {
2525
rooms map[string]*Room
2626
roomsMutex sync.RWMutex
2727

28-
// Value is just a temporarily atomic value.
28+
// value is just a temporarily value.
2929
// Storage across event callbacks for this namespace.
30-
Value atomic.Value
30+
value reflect.Value
3131
}
3232

3333
func newNSConn(c *Conn, namespace string, events Events) *NSConn {

debug.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ func EnableDebug(printer interface{}) {
3232
}
3333

3434
if printer == nil {
35-
printer = log.New(os.Stderr, "| neffos | ", 0)
35+
logger := log.New(os.Stderr, "| neffos | ", 0)
36+
printer = logger
37+
logger.Println("debug mode is set")
3638
}
3739

3840
debugPrinter = printer
39-
debugf("debug mode is set")
4041
}
4142

4243
type (

reflect.go

+49-37
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package neffos
22

33
import (
44
"reflect"
5+
"strings"
56
)
67

78
func indirectType(typ reflect.Type) reflect.Type {
@@ -109,8 +110,8 @@ func resolveStructNamespace(v reflect.Value) (string, bool) {
109110
if ok {
110111
if getNamespace, ok := v.Method(method.Index).Interface().(func() string); ok {
111112
namespace := getNamespace()
112-
debugf("set namespace [%s] from method [%s.%s]", func() dargs {
113-
return dargs{namespace, indirectType(typ).Name(), method.Name}
113+
debugf("Set namespace [\"%s\"] from method [%s.%s]", func() dargs {
114+
return dargs{namespace, nameOf(typ), method.Name}
114115
})
115116

116117
return namespace, true
@@ -123,8 +124,8 @@ func resolveStructNamespace(v reflect.Value) (string, bool) {
123124
if f, ok := typ.FieldByNameFunc(func(s string) bool { return s == "Namespace" }); ok {
124125
if f.Type.Kind() == reflect.String {
125126
namespace := v.Field(f.Index[0]).String()
126-
debugf("set namespace [%s] from field [%s.%s]", func() dargs {
127-
return dargs{namespace, typ.Name(), f.Name}
127+
debugf("Set namespace [\"%s\"] from field [%s.%s]", func() dargs {
128+
return dargs{namespace, nameOf(typ), f.Name}
128129
})
129130
return namespace, true
130131
}
@@ -204,15 +205,27 @@ func makeEventFromMethod(v reflect.Value, method reflect.Method, eventMatcher Ev
204205
// the NSConn exists on the "controller" itself which is set dynamically.
205206
cb = func(c *NSConn, msg Message) error {
206207
// load an existing instance which contains the same "c".
207-
cachePtr := c.Value.Load().(reflect.Value)
208-
return cachePtr.Method(method.Index).Interface().(func(Message) error)(msg)
208+
return c.value.Method(method.Index).Interface().(func(Message) error)(msg)
209209
}
210210
}
211211

212212
return
213213
}
214214

215-
func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc) Events {
215+
// StructInjector is a type which injects a dynamic struct value.
216+
// See `Struct.SetInjector` for more.
217+
type StructInjector func(structType reflect.Type, nsConn *NSConn) (structValue reflect.Value)
218+
219+
func nameOf(structType reflect.Type) string {
220+
structType = indirectType(structType)
221+
222+
typName := structType.Name()
223+
pkgPath := structType.PkgPath()
224+
fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
225+
return fullname
226+
}
227+
228+
func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc, injector StructInjector) Events {
216229
events := make(Events, 0)
217230

218231
typ := v.Type()
@@ -233,60 +246,59 @@ func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc) Events
233246
continue
234247
}
235248

236-
debugf("event [%s] is handled by [%s.%s]", func() dargs {
237-
return dargs{eventName, indirectType(typ).Name(), method.Name}
249+
debugf("Event [\"%s\"] is handled by [%s.%s] method", func() dargs {
250+
return dargs{eventName, nameOf(typ), method.Name}
238251
})
239252

240253
events[eventName] = cb
241254
}
242255

243256
if nsConnFieldIndex != -1 {
244-
// if it's a dynamic.
245257
typ = indirectType(typ)
246258

247-
cb, hasNamespaceConnect := events[OnNamespaceConnect]
248-
staticFields := getNonZeroFields(v)
249-
250-
// debugf("field [%s.%s] will be automatically re-filled with [%T(%s)]", func(fidx int, f reflect.Value) dargs {
251-
// fval := f.Interface()
252-
// return dargs{typ.Name(), typ.Field(fidx).Name, fval, fval}
253-
// }).forEach(staticFields)
254-
255-
debugEach(staticFields, func(idx int, f reflect.Value) {
256-
fval := f.Interface()
257-
fname := typ.Field(idx).Name
258-
if fname == "Namespace" {
259-
return // let's no log this as user field because
260-
// it's optionally used to provide a namespace on NewStruct.getNamespaces().
261-
}
259+
var staticFields map[int]reflect.Value
262260

263-
debugf("field [%s.%s] marked as static on value [%v]", typ.Name(), fname, fval)
264-
})
261+
if injector == nil {
262+
// maybe this should be added no matter what, I have to check
263+
// some things in our company's production server first.
264+
staticFields = getNonZeroFields(v)
265+
266+
debugEach(staticFields, func(idx int, f reflect.Value) {
267+
fval := f.Interface()
268+
fname := typ.Field(idx).Name
269+
if fname == "Namespace" {
270+
// let's no log this as user field because
271+
// it's optionally used to provide a namespace on NewStruct.GetNamespaces().
272+
return
273+
}
274+
275+
debugf("Field [%s.%s] marked as static on value [%v]", nameOf(typ), fname, fval)
276+
})
277+
278+
injector = func(typ reflect.Type, nsConn *NSConn) reflect.Value {
279+
return reflect.New(typ)
280+
}
281+
}
265282

266-
// if debugEnabled() {
267-
// for fidx, f := range staticFields {
268-
// fval := f.Interface()
269-
// debugf(, typ.Name(), typ.Field(fidx).Name, fval, fval)
270-
// }
271-
// }
283+
cb, hasNamespaceConnect := events[OnNamespaceConnect]
272284

273285
events[OnNamespaceConnect] = func(c *NSConn, msg Message) error {
274-
// create a new elem instance.
275-
cachePtr := reflect.New(typ)
286+
cachePtr := injector(typ, c)
276287
cacheElem := cachePtr.Elem()
277288

278289
// set the NSConn dynamic field.
279290
cacheElem.Field(nsConnFieldIndex).Set(reflect.ValueOf(c))
291+
280292
if staticFields != nil {
281-
// set any static fields.
293+
// set any static fields if default injector (see above).
282294
for findex, fvalue := range staticFields {
283295
cacheElem.Field(findex).Set(fvalue)
284296
}
285297
}
286298

287299
// Store it for the rest of the events inside
288300
// this namespace of that specific connection.
289-
c.Value.Store(cachePtr)
301+
c.value = cachePtr
290302

291303
if hasNamespaceConnect {
292304
return cb(c, msg)

server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func New(upgrader Upgrader, connHandler ConnHandler) *Server {
8686

8787
s := &Server{
8888
upgrader: upgrader,
89-
namespaces: connHandler.getNamespaces(),
89+
namespaces: connHandler.GetNamespaces(),
9090
readTimeout: readTimeout,
9191
writeTimeout: writeTimeout,
9292
connections: make(map[*Conn]struct{}),

0 commit comments

Comments
 (0)