conn_handler.go 9.4 KB

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