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