-
-
Notifications
You must be signed in to change notification settings - Fork 49
/
conn_handler.go
314 lines (262 loc) · 9.39 KB
/
conn_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package neffos
import (
"reflect"
"strings"
"time"
)
// ConnHandler is the interface which namespaces and events can be retrieved through.
// Built-in ConnHandlers are the`Events`, `Namespaces`, `WithTimeout` and `NewStruct`.
// Users of this are the `Dial`(client) and `New` (server) functions.
type ConnHandler interface {
GetNamespaces() Namespaces
}
var (
_ ConnHandler = (Events)(nil)
_ ConnHandler = (Namespaces)(nil)
_ ConnHandler = WithTimeout{}
_ ConnHandler = (*Struct)(nil)
)
// Events completes the `ConnHandler` interface.
// It is a map which its key is the event name
// and its value the event's callback.
//
// Events type completes the `ConnHandler` itself therefore,
// can be used as standalone value on the `New` and `Dial` functions
// to register events on empty namespace as well.
//
// See `Namespaces`, `New` and `Dial` too.
type Events map[string]MessageHandlerFunc
// GetNamespaces returns an empty namespace with the "e" Events.
func (e Events) GetNamespaces() Namespaces {
return Namespaces{"": e}
}
func (e Events) fireEvent(c *NSConn, msg Message) error {
if h, ok := e[msg.Event]; ok {
return h(c, msg)
}
if h, ok := e[OnAnyEvent]; ok {
return h(c, msg)
}
return nil
}
// On is a shortcut of Events { eventName: msgHandler }.
// It registers a callback "msgHandler" for an event "eventName".
func (e Events) On(eventName string, msgHandler MessageHandlerFunc) {
e[eventName] = msgHandler
}
// Namespaces completes the `ConnHandler` interface.
// Can be used to register one or more namespaces on the `New` and `Dial` functions.
// The key is the namespace literal and the value is the `Events`,
// a map with event names and their callbacks.
//
// See `WithTimeout`, `New` and `Dial` too.
type Namespaces map[string]Events
// GetNamespaces just returns the "nss" namespaces.
func (nss Namespaces) GetNamespaces() Namespaces { return nss }
// On is a shortcut of Namespaces { namespace: Events: { eventName: msgHandler } }.
// It registers a callback "msgHandler" for an event "eventName" of the particular "namespace".
func (nss Namespaces) On(namespace, eventName string, msgHandler MessageHandlerFunc) Events {
if nss[namespace] == nil {
nss[namespace] = make(Events)
}
nss[namespace][eventName] = msgHandler
return nss[namespace]
}
// WithTimeout completes the `ConnHandler` interface.
// Can be used to register namespaces and events or just events on an empty namespace
// with Read and Write timeouts.
//
// See `New` and `Dial`.
type WithTimeout struct {
ReadTimeout time.Duration
WriteTimeout time.Duration
Namespaces Namespaces
Events Events
}
// GetNamespaces returns combined namespaces from "Namespaces" and "Events" fields
// with read and write timeouts from "ReadTimeout" and "WriteTimeout" fields of "t".
func (t WithTimeout) GetNamespaces() Namespaces {
return JoinConnHandlers(t.Namespaces, t.Events).GetNamespaces()
}
func getTimeouts(h ConnHandler) (readTimeout time.Duration, writeTimeout time.Duration) {
if t, ok := h.(WithTimeout); ok {
readTimeout = t.ReadTimeout
writeTimeout = t.WriteTimeout
}
if s, ok := h.(*Struct); ok {
readTimeout = s.readTimeout
writeTimeout = s.writeTimeout
}
return
}
// EventMatcherFunc is a type of which a Struct matches the methods with neffos events.
type EventMatcherFunc = func(methodName string) (string, bool)
// Struct is a ConnHandler. All fields are unexported, use `NewStruct` instead.
// It converts any pointer to a struct value to `neffos.Namespaces` using reflection.
type Struct struct {
ptr reflect.Value
// defaults to empty and tries to get it through `Struct.Namespace() string` method.
namespace string
// defaults to nil, if specified
// then it matches the events based on the result string or false if this method shouldn't register as event.
eventMatcher EventMatcherFunc
readTimeout, writeTimeout time.Duration
// This field is set when external dependency injection system is used.
injector StructInjector
events Events
}
// SetNamespace sets a namespace that this Struct is responsible for,
// Alterinatively create a method on the controller named `Namespace() string`
// to retrieve this namespace at build time.
func (s *Struct) SetNamespace(namespace string) *Struct {
s.namespace = namespace
return s
}
var (
// EventPrefixMatcher matches methods to events based on the "prefix".
EventPrefixMatcher = func(prefix string) EventMatcherFunc {
return func(methodName string) (string, bool) {
if strings.HasPrefix(methodName, prefix) {
return methodName, true
}
return "", false
}
}
// EventTrimPrefixMatcher matches methods based on the "prefixToTrim"
// and events are registered without this prefix.
EventTrimPrefixMatcher = func(prefixToTrim string) EventMatcherFunc {
return func(methodName string) (string, bool) {
if strings.HasPrefix(methodName, prefixToTrim) {
return methodName[len(prefixToTrim):], true
}
return "", false
}
}
)
// SetEventMatcher sets an event method matcher which applies to every
// event except the system events (OnNamespaceConnected, and so on).
func (s *Struct) SetEventMatcher(matcher EventMatcherFunc) *Struct {
s.eventMatcher = matcher
return s
}
// SetTimeouts sets read and write deadlines on the underlying network connection.
// After a read or write have timed out, the websocket connection is closed.
//
// Defaults to 0, no timeout except an `Upgrader` or `Dialer` specifies its own values.
func (s *Struct) SetTimeouts(read, write time.Duration) *Struct {
s.readTimeout = read
s.writeTimeout = write
return s
}
// SetInjector sets a custom injector and overrides the neffos default behavior
// on dynamic structs.
// The "fn" should handle to fill static fields and the NSConn.
// This "fn" will only be called when dynamic struct "ptr" is passed
// on the `NewStruct`.
// The caller should return a
// valid type of "ptr" reflect.Value.
func (s *Struct) SetInjector(fn StructInjector) *Struct {
s.injector = fn
return s
}
// NewStruct returns a new Struct value instance type of ConnHandler.
// The "ptr" should be a pointer to a struct.
// This function is used when you want to convert a structure to
// neffos.ConnHandler based on the struct's methods.
// The methods if "ptr" structure value
// can be func(msg neffos.Message) error if the structure contains a *neffos.NSConn field,
// otherwise they should be like any event callback: func(nsConn *neffos.NSConn, msg neffos.Message) error.
// If contains a field of type *neffos.NSConn then on each new connection to the namespace a new controller is created
// and static fields(if any) are set on runtime with the NSConn itself.
// If it's a static controller (does not contain a NSConn field)
// then it just registers its functions as regular events without performance cost.
//
// Users of this method is `New` and `Dial`.
//
// Note that this method has a tiny performance cost when an event's callback's logic has small footprint.
func NewStruct(ptr interface{}) *Struct {
if ptr == nil {
panic("NewStruct: value is nil")
}
if s, ok := ptr.(*Struct); ok { // if it's already a *Struct then just return it.
return s
}
var v reflect.Value // use for methods with receiver Ptr.
if rValue, ok := ptr.(reflect.Value); ok {
v = rValue
} else {
v = reflect.ValueOf(ptr)
}
if !v.IsValid() {
panic("NewStruct: value is not a valid one")
}
typ := v.Type() // use for methods with receiver Ptr.
if typ.Kind() != reflect.Ptr {
panic("NewStruct: value should be a pointer")
}
if typ.ConvertibleTo(nsConnType) {
panic("NewStruct: conversion for type" + typ.String() + " NSConn is not allowed.")
}
if indirectType(typ).Kind() != reflect.Struct {
panic("NewStruct: value does not points to a struct")
}
n := typ.NumMethod()
_, hasNamespaceMethod := typ.MethodByName("Namespace")
if n == 0 || (n == 1 && hasNamespaceMethod) {
panic("NewStruct: value does not contain any exported methods")
}
return &Struct{
ptr: v,
}
}
// Events builds and returns the Events.
// Callers of this method is users that want to add Structs to different namespaces
// in the same application.
// When a single namespace is used then this call is unnecessary,
// the `Struct` is already a fully featured `ConnHandler` by itself.
func (s *Struct) Events() Events {
if s.events != nil {
return s.events
}
s.events = makeEventsFromStruct(s.ptr, s.eventMatcher, s.injector)
return s.events
}
// GetNamespaces creates and returns Namespaces based on the
// pointer to struct value provided by the "s".
func (s *Struct) GetNamespaces() Namespaces { // completes the `ConnHandler` interface.
if s.namespace == "" {
s.namespace, _ = resolveStructNamespace(s.ptr)
}
return Namespaces{
s.namespace: s.Events(),
}
}
// JoinConnHandlers combines two or more "connHandlers"
// and returns a result of a single `ConnHandler` that
// can be passed on the `New` and `Dial` functions.
func JoinConnHandlers(connHandlers ...ConnHandler) ConnHandler {
namespaces := Namespaces{}
for _, h := range connHandlers {
nss := h.GetNamespaces()
if len(nss) > 0 {
for namespace, events := range nss {
if events == nil {
continue
}
clonedEvents := make(Events, len(events))
for evt, cb := range events {
clonedEvents[evt] = cb
}
if curEvents, exists := namespaces[namespace]; exists {
// fill missing events.
for evt, cb := range clonedEvents {
curEvents[evt] = cb
}
} else {
namespaces[namespace] = clonedEvents
}
}
}
}
return namespaces
}