neffos

package module
v0.0.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 10, 2024 License: MIT Imports: 18 Imported by: 32

README

neffos chat example

build status report card view examples chat frontend pkg

About neffos

Neffos is a cross-platform real-time framework with expressive, elegant API written in Go. Neffos takes the pain out of development by easing common tasks used in real-time backend and frontend applications such as:

  • Scale-out using redis or nats*
  • Adaptive request upgradation and server dialing
  • Acknowledgements
  • Namespaces
  • Rooms
  • Broadcast
  • Event-Driven architecture
  • Request-Response architecture
  • Error Awareness
  • Asynchronous Broadcast
  • Timeouts
  • Encoding
  • Reconnection
  • Modern neffos API client for Browsers, Nodejs* and Go

Learning neffos

Quick View

Server

import (
    // [...]
    "github.com/kataras/neffos"
    "github.com/kataras/neffos/gorilla"
)

func runServer() {
    events := make(neffos.Namespaces)
    events.On("/v1", "workday", func(ns *neffos.NSConn, msg neffos.Message) error {
        date := string(msg.Body)

        t, err := time.Parse("01-02-2006", date)
        if err != nil {
            if n := ns.Conn.Increment("tries"); n >= 3 && n%3 == 0 {
                // Return custom error text to the client.
                return fmt.Errorf("Why not try this one? 06-24-2019")
            } else if n >= 6 && n%2 == 0 {
                // Fire the "notify" client event.
                ns.Emit("notify", []byte("What are you doing?"))
            }
            // Return the parse error back to the client.
            return err
        }

        weekday := t.Weekday()

        if weekday == time.Saturday || weekday == time.Sunday {
            return neffos.Reply([]byte("day off"))
        }

        // Reply back to the client.
        responseText := fmt.Sprintf("it's %s, do your job.", weekday)
        return neffos.Reply([]byte(responseText))
    })

    websocketServer := neffos.New(gorilla.DefaultUpgrader, events)

    // Fire the "/v1:notify" event to all clients after server's 1 minute.
    time.AfterFunc(1*time.Minute, func() {
        websocketServer.Broadcast(nil, neffos.Message{
            Namespace: "/v1",
            Event:     "notify",
            Body:      []byte("server is up and running for 1 minute"),
        })
    })

    router := http.NewServeMux()
    router.Handle("/", websocketServer)

    log.Println("Serving websockets on localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Go Client

func runClient() {
    ctx := context.TODO()
    events := make(neffos.Namespaces)
    events.On("/v1", "notify", func(c *neffos.NSConn, msg neffos.Message) error {
        log.Printf("Server says: %s\n", string(msg.Body))
        return nil
    })

    // Connect to the server.
    client, err := neffos.Dial(ctx,
        gorilla.DefaultDialer,
        "ws://localhost:8080",
        events)
    if err != nil {
        panic(err)
    }

    // Connect to a namespace.
    c, err := client.Connect(ctx, "/v1")
    if err != nil {
        panic(err)
    }

    fmt.Println("Please specify a date of format: mm-dd-yyyy")

    for {
        fmt.Print(">> ")
        var date string
        fmt.Scanf("%s", &date)

        // Send to the server and wait reply to this message.
        response, err := c.Ask(ctx, "workday", []byte(date))
        if err != nil {
            if neffos.IsCloseError(err) {
                // Check if the error is a close signal,
                // or make use of the `<- client.NotifyClose`
                // read-only channel instead.
                break
            }

            // >> 13-29-2019
            // error received: parsing time "13-29-2019": month out of range
            fmt.Printf("error received: %v\n", err)
            continue
        }

        // >> 06-29-2019
        // it's a day off!
        //
        // >> 06-24-2019
        // it's Monday, do your job.
        fmt.Println(string(response.Body))
    }
}

Javascript Client

Navigate to: https://github.com/kataras/neffos.js

Neffos contains extensive and thorough wiki making it easy to get started with the framework.

For a more detailed technical documentation you can head over to our godocs. And for executable code you can always visit the _examples repository's subdirectory.

Do you like to read while traveling?

You can download a PDF version of the E-Book today and be participated in the development of neffos.

Contributing

We'd love to see your contribution to the neffos real-time framework! For more information about contributing to the neffos project please check the CONTRIBUTING.md file.

  • neffos-contrib github organisation for more programming languages support, please invite yourself.

Security Vulnerabilities

If you discover a security vulnerability within neffos, please send an e-mail to neffos-go@outlook.com. All security vulnerabilities will be promptly addressed.

License

The word "neffos" has a greek origin and it is translated to "cloud" in English dictionary.

This project is licensed under the MIT license.

Documentation

Index

Examples

Constants

View Source
const (
	TextMessage = iota + 1
	BinaryMessage
)

See `MessageType` definition for details.

View Source
const URLParamAsHeaderPrefix = "X-Websocket-Header-"

URLParamAsHeaderPrefix is the prefix that server parses the url parameters as request headers. The client's `URLParamAsHeaderPrefix` must match. Note that this is mostly useful for javascript browser-side clients, nodejs and go client support custom headers by default. No action required from end-developer, exported only for chance to a custom parsing.

Variables

View Source
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
		}
	}
)
View Source
var (
	// 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.
	OnNamespaceConnect = "_OnNamespaceConnect"
	// OnNamespaceConnected is the event name which its callback is fired after namespace successfully connected.
	// Connection is ready to emit data back to the namespace.
	OnNamespaceConnected = "_OnNamespaceConnected"
	// OnNamespaceDisconnect is the event name which its callback is fired when
	// remote namespace disconnection or local namespace disconnection is happening.
	// For server-side connections the reply matters, so if error returned then the client-side cannot disconnect yet,
	// for client-side the return value does not matter.
	OnNamespaceDisconnect = "_OnNamespaceDisconnect" // if allowed to connect then it's allowed to disconnect as well.
	// OnRoomJoin is the event name which its callback is fired right before room join.
	OnRoomJoin = "_OnRoomJoin" // able to check if allowed to join.
	// OnRoomJoined is the event name which its callback is fired after the connection has successfully joined to a room.
	OnRoomJoined = "_OnRoomJoined" // able to broadcast messages to room.
	// OnRoomLeave is the event name which its callback is fired right before room leave.
	OnRoomLeave = "_OnRoomLeave" // able to broadcast bye-bye messages to room.
	// OnRoomLeft is the event name which its callback is fired after the connection has successfully left from a room.
	OnRoomLeft = "_OnRoomLeft" // if allowed to join to a room, then its allowed to leave from it.
	// OnAnyEvent is the event name which its callback is fired when incoming message's event is not declared to the ConnHandler(`Events` or `Namespaces`).
	OnAnyEvent = "_OnAnyEvent" // when event no match.
	// OnNativeMessage is fired on incoming native/raw websocket messages.
	// If this event defined then an incoming message can pass the check (it's an invalid message format)
	// with just the Message's Body filled, the Event is "OnNativeMessage" and IsNative always true.
	// This event should be defined under an empty namespace in order this to work.
	OnNativeMessage = "_OnNativeMessage"
)
View Source
var (
	// DefaultMarshaler is a global, package-level alternative for `MessageObjectMarshaler`.
	// It's used when the `Marshal.v` parameter is not a `MessageObjectMarshaler`.
	DefaultMarshaler = json.Marshal
	// DefaultUnmarshaler is a global, package-level alternative for `MessageObjectMarshaler`.
	// It's used when the `Message.Unmarshal.outPtr` parameter is not a `MessageObjectUnmarshaler`.
	DefaultUnmarshaler = json.Unmarshal
)
View Source
var (
	// ErrBadNamespace may return from a `Conn#Connect` method when the remote side does not declare the given namespace.
	ErrBadNamespace = errors.New("bad namespace")
	// ErrBadRoom may return from a `Room#Leave` method when trying to leave from a not joined room.
	ErrBadRoom = errors.New("bad room")
	// ErrWrite may return from any connection's method when the underline connection is closed (unexpectedly).
	ErrWrite = errors.New("write closed")
)
View Source
var ErrInvalidPayload = errors.New("invalid payload")

ErrInvalidPayload can be returned by the internal `handleMessage`. In the future it may be exposed by an error listener.

Functions

func DebugEach added in v0.0.6

func DebugEach(mapOrSlice interface{}, onDebugVisitor interface{})

DebugEach prints debug messages for each of "mapOrSlice" elements to the printer defined on `EnableDebug`. Runs only on debug mode. Usage:

DebugEach(staticFields, func(idx int, f reflect.Value) {
	fval := f.Interface()
	Debugf("field [%s.%s] will be automatically re-filled with [%T(%s)]", typ.Name(), typ.Field(idx).Name, fval, fval)
})

func Debugf added in v0.0.6

func Debugf(format string, args ...interface{})

Debugf prints debug messages to the printer defined on `EnableDebug`. Runs only on debug mode.

func EnableDebug added in v0.0.4

func EnableDebug(printer interface{})

EnableDebug enables debug and optionally sets a custom printer to print out debug messages. The "printer" can be any compatible printer such as the standard `log.Logger` or a custom one like the `kataras/golog`.

A "printer" is compatible when it contains AT LEAST ONE of the following methods: Debugf(string, ...interface{}) or Logf(string, ...interface{}) or Printf(string, ...interface{})

If EnableDebug is called but the "printer" value is nil then neffos will print debug messages through a new log.Logger prefixed with "| neffos |".

Note that neffos, currently, uses debug mode only on the build state of the events. Therefore enabling the debugger has zero performance cost on up-and-running servers and clients.

There is no way to disable the debug mode on serve-time.

func Exclude

func Exclude(connID string) fmt.Stringer

Exclude can be passed on `Server#Broadcast` when caller does not have access to the `Conn`, `NSConn` or a `Room` value but has access to a string variable which is a connection's ID instead.

Example Code: nsConn.Conn.Server().Broadcast(

	neffos.Exclude("connection_id_here"),
 neffos.Message{Namespace: "default", Room: "roomName or empty", Event: "chat", Body: [...]})

func IsCloseError

func IsCloseError(err error) bool

IsCloseError reports whether the "err" is a "closed by the remote host" network connection error.

func IsDisconnectError

func IsDisconnectError(err error) bool

IsDisconnectError reports whether the "err" is a timeout or a closed connection error.

func IsSystemEvent

func IsSystemEvent(event string) bool

IsSystemEvent reports whether the "event" is a system event, OnNamespaceConnect, OnNamespaceConnected, OnNamespaceDisconnect, OnRoomJoin, OnRoomJoined, OnRoomLeave and OnRoomLeft.

func IsTimeoutError

func IsTimeoutError(err error) bool

IsTimeoutError reports whether the "err" is caused by a defined timeout.

func IsTryingToReconnect added in v0.0.2

func IsTryingToReconnect(err error) (ok bool)

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.

func Marshal

func Marshal(v interface{}) []byte

Marshal marshals the "v" value and returns a Message's Body. If the "v" value is `MessageObjectMarshaler` then it returns the result of its `Marshal` method, otherwise the DefaultMarshaler will be used instead. Errors are pushed to the result, use the object's Marshal method to catch those when necessary.

func RegisterKnownError added in v0.0.2

func RegisterKnownError(err error)

RegisterKnownError registers an error that it's "known" to both server and client sides. This simply adds an error to a list which, if its static text matches an incoming error text then its value is set to the `Message.Error` field on the events callbacks.

For dynamic text error, there is a special case which if the error "err" contains a `ResolveError(errorText string) bool` method then, it is used to report whether this "err" is match to the incoming error text.

func Reply

func Reply(body []byte) error

Reply is a special type of custom error which sends a message back to the other side with the exact same incoming Message's Namespace (and Room if specified) except its body which would be the given "body".

Types

type Client

type Client struct {

	// ID comes from server, local changes are not reflected,
	// use the `Server#IDGenerator` if you want to set a custom logic for ID set.
	ID string

	// NotifyClose can be optionally registered to notify about the client's disconnect.
	// This callback is for the entire client side connection,
	// the channel is notified after namespace disconnected and any room left events.
	// Don't confuse it with the `OnNamespaceDisconnect` event.
	// Usage:
	// <- client.NotifyClose // blocks until local `Close` or remote close of connection.
	NotifyClose <-chan struct{}
	// contains filtered or unexported fields
}

Client is the neffos client. Contains the neffos client-side connection and the ID came from server on acknowledgement process of the `Dial` function. Use its `Connect` to connect to a namespace or `WaitServerConnect` to wait for server to force-connect this client to a namespace.

func Dial

func Dial(ctx context.Context, dial Dialer, url string, connHandler ConnHandler) (*Client, error)

Dial establishes a new neffos client connection. Context "ctx" is used for handshake timeout. Dialer "dial" can be either `gobwas.Dialer/DefaultDialer` or `gorilla.Dialer/DefaultDialer`, custom dialers can be used as well when complete the `Socket` and `Dialer` interfaces for valid client. URL "url" is the endpoint of the neffos server, i.e "ws://localhost:8080/echo". The last parameter, and the most important one is the "connHandler", it can be filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the server-side as well.

See examples for more.

func (*Client) Close

func (c *Client) Close()

Close method terminates the client-side connection. Forces the client to disconnect from all connected namespaces and leave from all joined rooms, server gets notified.

func (*Client) Connect

func (c *Client) Connect(ctx context.Context, namespace string) (*NSConn, error)

Connect method returns a new connected to the specific "namespace" `NSConn` value. The "namespace" should be declared in the `connHandler` of both server and client sides. Returns error if server-side's `OnNamespaceConnect` event callback returns an error.

See `Conn#Connect` for more details.

func (*Client) WaitServerConnect

func (c *Client) WaitServerConnect(ctx context.Context, namespace string) (*NSConn, error)

WaitServerConnect method blocks until server manually calls the connection's `Connect` on the `Server#OnConnected` event.

See `Conn#WaitConnect` for more details.

type CloseError

type CloseError struct {
	Code int
	// contains filtered or unexported fields
}

CloseError can be used to send and close a remote connection in the event callback's return statement.

func (CloseError) Error

func (err CloseError) Error() string

type Conn

type Conn struct {

	// ReconnectTries, if > 0 then this connection is a result of a client-side reconnection,
	// see `WasReconnected() bool`.
	ReconnectTries int
	// contains filtered or unexported fields
}

Conn contains the websocket connection and the neffos communication functionality. Its `Connection` will return a new `NSConn` instance. Each connection can connect to one or more declared namespaces. Each `NSConn` can join to multiple rooms.

func (*Conn) Ask

func (c *Conn) Ask(ctx context.Context, msg Message) (Message, error)

Ask method sends a message to the remote side and blocks until a response or an error received from the specific `Message.Event`.

func (*Conn) Close

func (c *Conn) Close()

Close method will force-disconnect from all connected namespaces and force-leave from all joined rooms and finally will terminate the underline websocket connection. After this method call the `Conn` is not usable anymore, a new `Dial` call is required.

func (*Conn) Connect

func (c *Conn) Connect(ctx context.Context, namespace string) (*NSConn, error)

Connect method returns a new connected to the specific "namespace" `NSConn` value. The "namespace" should be declared in the `connHandler` of both server and client sides. If this is a client-side connection then the server-side namespace's `OnNamespaceConnect` event callback MUST return null in order to allow this client-side connection to connect, otherwise a non-nil error is returned instead.

func (*Conn) Decrement added in v0.0.8

func (c *Conn) Decrement(key string) int

Decrement works like `Set` method. It's just a helper for decrementing integer values. If value does exist, and it's an integer then it decrements it by 1, otherwise the value is overridden to value -1. If value does not exist, then it assumes the default value is 0 and it decrements it by one, the result will be -1.

Calling it twice for example it will set the value to -2, even if doesn't exist before.

Returns the decremented value.

func (*Conn) DeserializeMessage added in v0.0.6

func (c *Conn) DeserializeMessage(msgTyp MessageType, payload []byte) Message

DeserializeMessage returns a Message from the "payload".

func (*Conn) DisconnectAll

func (c *Conn) DisconnectAll(ctx context.Context) error

DisconnectAll method disconnects from all namespaces, `OnNamespaceDisconnect` even will be fired and its `Message.IsLocal` will be true. The remote side gets notified.

func (*Conn) Get added in v0.0.7

func (c *Conn) Get(key string) interface{}

Get retruns a value based on the given "key"

func (*Conn) HandlePayload added in v0.0.6

func (c *Conn) HandlePayload(msgTyp MessageType, payload []byte) error

HandlePayload fires manually a local event based on the "payload".

func (*Conn) ID

func (c *Conn) ID() string

ID method returns the unique identifier of the connection. If this is a server-side connection then this value is the generated one by the `Server#IDGenerator`. If this is a client-side connection then this value is filled on the acknowledgment process which is done on the `Client#Dial`.

func (*Conn) Increment added in v0.0.8

func (c *Conn) Increment(key string) int

Increment works like `Set` method. It's just a helper for incrementing integer values. If value does exist, and it's an integer then it increments it by 1, otherwise the value is overridden to value 1. If value does not exist, then it assumes the default value is 0 and it increments it by one, the result will be 1.

Returns the incremented value.

func (*Conn) Is added in v0.0.6

func (c *Conn) Is(connID string) bool

Is reports whether the "connID" is part of this server's connections and their IDs are equal.

func (*Conn) IsClient

func (c *Conn) IsClient() bool

IsClient method reports whether this connections is a client-side connetion.

func (*Conn) IsClosed

func (c *Conn) IsClosed() bool

IsClosed method reports whether this connection is remotely or manually terminated.

func (*Conn) Namespace

func (c *Conn) Namespace(namespace string) *NSConn

Namespace method returns an already-connected `NSConn` value based on the given "namespace".

func (*Conn) Server

func (c *Conn) Server() *Server

Server method returns the backend server, it returns null on client-side connections.

func (*Conn) Set added in v0.0.7

func (c *Conn) Set(key string, value interface{})

Set sets a value to this connection's store.

func (*Conn) Socket

func (c *Conn) Socket() Socket

Socket method returns the underline socket implementation.

func (*Conn) String

func (c *Conn) String() string

String method simply returns the ID(). Useful for fmt usage and to a connection to be passed on `Server#Broadcast` method to exclude itself from the broadcasted message's receivers.

func (*Conn) WaitConnect

func (c *Conn) WaitConnect(ctx context.Context, namespace string) (ns *NSConn, err error)

WaitConnect method can be used instead of the `Connect` if the other side force-calls `Connect` to this connection and this side wants to "waits" for that signal.

Nil context means try without timeout, wait until it connects to the specific namespace. Note that, this function will not return an `ErrBadNamespace` if namespace does not exist in the server-side or it's not defined in the client-side, it waits until deadline (if any, or loop forever, so a context with deadline is highly recommended).

func (*Conn) WasReconnected added in v0.0.2

func (c *Conn) WasReconnected() bool

WasReconnected reports whether the current connection is a result of a client-side reconnection. To get the numbers of total retries see the `ReconnectTries` field.

func (*Conn) Write

func (c *Conn) Write(msg Message) bool

Write method sends a message to the remote side, reports whether the connection is still available or when this message is not allowed to be sent to the remote side.

type ConnHandler

type ConnHandler interface {
	GetNamespaces() Namespaces
}

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.

func JoinConnHandlers

func JoinConnHandlers(connHandlers ...ConnHandler) ConnHandler

JoinConnHandlers combines two or more "connHandlers" and returns a result of a single `ConnHandler` that can be passed on the `New` and `Dial` functions.

type Dialer

type Dialer func(ctx context.Context, url string) (Socket, error)

Dialer is the definition type of a dialer, gorilla or gobwas or custom. It is the second parameter of the `Dial` function.

type EventMatcherFunc added in v0.0.3

type EventMatcherFunc = func(methodName string) (string, bool)

EventMatcherFunc is a type of which a Struct matches the methods with neffos events.

type Events

type Events map[string]MessageHandlerFunc

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.

func (Events) GetNamespaces added in v0.0.5

func (e Events) GetNamespaces() Namespaces

GetNamespaces returns an empty namespace with the "e" Events.

func (Events) On added in v0.0.8

func (e Events) On(eventName string, msgHandler MessageHandlerFunc)

On is a shortcut of Events { eventName: msgHandler }. It registers a callback "msgHandler" for an event "eventName".

Example
events := make(Events)
events.On(OnNamespaceConnected, nil)
events.On("chat", nil)

fmt.Println(len(events)) // we can't loop them and expect the same order ofc.
Output:

2

type IDGenerator

type IDGenerator func(w http.ResponseWriter, r *http.Request) string

IDGenerator is the type of function that it is used to generate unique identifiers for new connections.

See `Server.IDGenerator`.

var DefaultIDGenerator IDGenerator = func(http.ResponseWriter, *http.Request) string {
	id, err := uuid.NewV4()
	if err != nil {
		return strconv.FormatInt(time.Now().Unix(), 10)
	}
	return id.String()
}

DefaultIDGenerator returns a universal unique identifier for a new connection. It's the default `IDGenerator` for `Server`.

type Message

type Message struct {

	// The Namespace that this message sent to/received from.
	Namespace string
	// The Room that this message sent to/received from.
	Room string
	// The Event that this message sent to/received from.
	Event string
	// The actual body of the incoming/outcoming data.
	Body []byte
	// The Err contains any message's error, if any.
	// Note that server-side and client-side connections can return an error instead of a message from each event callbacks,
	// except the clients's force Disconnect which its local event doesn't matter when disconnected manually.
	Err error

	// When sent by the same connection of the current running server instance.
	// This field is serialized/deserialized but it's clean on sending or receiving from a client
	// and it's only used on StackExchange feature.
	// It's serialized as the first parameter, instead of wait signal, if incoming starts with 0x.
	FromExplicit string // the exact Conn's pointer in this server instance.
	// Reports whether this message is coming from a stackexchange.
	// This field is not exposed and it's not serialized at all, ~local-use only~.
	//
	// The "wait" field can determinate if this message is coming from a stackexchange using its second char,
	// This value set based on "wait" on deserialization when coming from remote side.
	// Only server-side can actually set it.
	FromStackExchange bool

	// To is the connection ID of the receiver, used only when `Server#Broadcast` is called, indeed when we only need to send a message to a single connection.
	// The Namespace, Room are still respected at all.
	//
	// However, sending messages to a group of connections is done by the `Room` field for groups inside a namespace or just `Namespace` field as usual.
	// This field is not filled on sending/receiving.
	To string

	// True when event came from local (i.e client if running client) on force disconnection,
	// i.e OnNamespaceDisconnect and OnRoomLeave when closing a conn.
	// This field is not filled on sending/receiving.
	// Err does not matter and never sent to the other side.
	IsForced bool
	// True when asking the other side and fire the respond's event (which matches the sent for connect/disconnect/join/leave),
	// i.e if a client (or server) onnection want to connect
	// to a namespace or join to a room.
	// Should be used rarely, state can be checked by `Conn#IsClient() bool`.
	// This field is not filled on sending/receiving.
	IsLocal bool

	// True when user define it for writing, only its body is written as raw native websocket message, namespace, event and all other fields are empty.
	// The receiver should accept it on the `OnNativeMessage` event.
	// This field is not filled on sending/receiving.
	IsNative bool

	// if server or client should write using Binary message or if the incoming message was readen as binary.
	SetBinary bool
	// contains filtered or unexported fields
}

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. If native message sent then the `Message.Body` is filled with the body and when incoming native message then the `Message.Event` is the `OnNativeMessage`, native messages are allowed only when an empty namespace("") and its `OnNativeMessage` callback are present.

The the raw data received/sent structured following this order: <wait()>; <namespace>; <room>; <event>; <isError(0-1)>; <isNoOp(0-1)>; <body||error_message>

Internal `serializeMessage` and exported `DeserializeMessage` functions do the job on `Conn#Write`, `NSConn#Emit` and `Room#Emit` calls.

func DeserializeMessage added in v0.0.7

func DeserializeMessage(msgTyp MessageType, b []byte, allowNativeMessages, shouldHandleOnlyNativeMessages bool) Message

DeserializeMessage accepts a serialized message []byte and returns a neffos Message. When allowNativeMessages only Body is filled and check about message format is skipped.

func (*Message) ClearWait added in v0.0.7

func (m *Message) ClearWait() bool

ClearWait clears the wait token, rarely used.

func (*Message) IsWait added in v0.0.7

func (m *Message) IsWait(isClientConn bool) bool

IsWait reports whether this message waits for a response back.

func (Message) Serialize added in v0.0.6

func (m Message) Serialize() []byte

Serialize returns this message's transport format.

func (*Message) Unmarshal

func (m *Message) Unmarshal(outPtr interface{}) error

Unmarshal unmarshals this Message's body to the "outPtr". The "outPtr" must be a pointer to a value that can customize its decoded value by implementing the `MessageObjectUnmarshaler`, otherwise the `DefaultUnmarshaler` will be used instead.

type MessageHandlerFunc

type MessageHandlerFunc func(*NSConn, Message) error

MessageHandlerFunc is the definition type of the events' callback. Its error can be written to the other side on specific events, i.e on `OnNamespaceConnect` it will abort a remote namespace connection. See examples for more.

type MessageObjectMarshaler

type MessageObjectMarshaler interface {
	Marshal() ([]byte, error)
}

MessageObjectMarshaler is an optional interface that "objects" can implement to customize their byte representation, see `Object` package-level function.

type MessageObjectUnmarshaler

type MessageObjectUnmarshaler interface {
	Unmarshal(body []byte) error
}

MessageObjectUnmarshaler is an optional interface that "objects" can implement to customize their structure, see `Message.Object` method.

type MessageType added in v0.0.11

type MessageType uint8

MessageType is a type for readen and to-send data, helpful to set `msg.SetBinary` to the rest of the clients through a Broadcast, as SetBinary is not part of the deserialization.

type NSConn

type NSConn struct {
	Conn *Conn
	// contains filtered or unexported fields
}

NSConn describes a connection connected to a specific namespace, it emits with the `Message.Namespace` filled and it can join to multiple rooms. A single `Conn` can be connected to one or more namespaces, each connected namespace is described by this structure.

func (*NSConn) Ask

func (ns *NSConn) Ask(ctx context.Context, event string, body []byte) (Message, error)

Ask method writes a message to the remote side and blocks until a response or an error received.

func (*NSConn) Disconnect

func (ns *NSConn) Disconnect(ctx context.Context) error

Disconnect method sends a disconnect signal to the remote side and fires the local `OnNamespaceDisconnect` event.

func (*NSConn) Emit

func (ns *NSConn) Emit(event string, body []byte) bool

Emit method sends a message to the remote side with its `Message.Namespace` filled to this specific namespace.

func (*NSConn) EmitBinary added in v0.0.12

func (ns *NSConn) EmitBinary(event string, body []byte) bool

EmitBinary acts like `Emit` but it sets the `Message.SetBinary` to true and sends the data as binary, the receiver's Message in javascript-side is Uint8Array.

func (*NSConn) JoinRoom

func (ns *NSConn) JoinRoom(ctx context.Context, roomName string) (*Room, error)

JoinRoom method can be used to join a connection to a specific room, rooms are dynamic. Returns the joined `Room`.

func (*NSConn) LeaveAll

func (ns *NSConn) LeaveAll(ctx context.Context) error

LeaveAll method sends a remote and local leave room signal `OnRoomLeave` to and for all rooms and fires the `OnRoomLeft` event if succeed.

func (*NSConn) Room

func (ns *NSConn) Room(roomName string) *Room

Room method returns a joined `Room`.

func (*NSConn) Rooms

func (ns *NSConn) Rooms() []*Room

Rooms returns a slice copy of the joined rooms.

func (*NSConn) String

func (ns *NSConn) String() string

String method simply returns the Conn's ID(). Useful method to this connected to a namespace connection to be passed on `Server#Broadcast` method to exclude itself from the broadcasted message's receivers.

type Namespaces

type Namespaces map[string]Events

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.

func (Namespaces) GetNamespaces added in v0.0.5

func (nss Namespaces) GetNamespaces() Namespaces

GetNamespaces just returns the "nss" namespaces.

func (Namespaces) On added in v0.0.8

func (nss Namespaces) On(namespace, eventName string, msgHandler MessageHandlerFunc) Events

On is a shortcut of Namespaces { namespace: Events: { eventName: msgHandler } }. It registers a callback "msgHandler" for an event "eventName" of the particular "namespace".

Example
nss := make(Namespaces)
nss.On("default", OnNamespaceConnected, nil).
	On("chat", nil) // registers on "default"

nss.On("other", "chat", nil)
nss.On("other", "event", nil)

fmt.Println(len(nss))
Output:

2

type Room

type Room struct {
	NSConn *NSConn

	Name string
}

Room describes a connected connection to a room, emits messages with the `Message.Room` filled to the specific room and `Message.Namespace` to the underline `NSConn`'s namespace.

func (*Room) Emit

func (r *Room) Emit(event string, body []byte) bool

Emit method sends a message to the remote side with its `Message.Room` filled to this specific room and `Message.Namespace` to the underline `NSConn`'s namespace.

func (*Room) Leave

func (r *Room) Leave(ctx context.Context) error

Leave method sends a remote and local leave room signal `OnRoomLeave` to this specific room and fires the `OnRoomLeft` event if succeed.

func (*Room) String

func (r *Room) String() string

String method simply returns the Conn's ID(). To get the room's name simply use the `Room.Name` struct field instead. Useful method to this room to be passed on `Server#Broadcast` method to exclude itself from the broadcasted message's receivers.

type Server

type Server struct {
	IDGenerator   IDGenerator
	StackExchange StackExchange

	// If `StackExchange` is set then this field is ignored.
	//
	// It overrides the default behavior(when no StackExchange is not used)
	// which publishes a message independently.
	// In short the default behavior doesn't wait for a message to be published to all clients
	// before any next broadcast call.
	//
	// Therefore, if set to true,
	// each broadcast call will publish its own message(s) by order.
	SyncBroadcaster bool
	// FireDisconnectAlways will allow firing the `OnDisconnect` server's
	// event even if the connection wasimmediately closed from the `OnConnect` server's event
	// through `Close()` or non-nil error.
	// See https://github.com/kataras/neffos/issues/41
	//
	// Defaults to false.
	FireDisconnectAlways bool

	// OnUpgradeError can be optionally registered to catch upgrade errors.
	OnUpgradeError func(err error)
	// OnConnect can be optionally registered to be notified for any new neffos client connection,
	// it can be used to force-connect a client to a specific namespace(s) or to send data immediately or
	// even to cancel a client connection and dissalow its connection when its return error value is not nil.
	// Don't confuse it with the `OnNamespaceConnect`, this callback is for the entire client side connection.
	OnConnect func(c *Conn) error
	// OnDisconnect can be optionally registered to notify about a connection's disconnect.
	// Don't confuse it with the `OnNamespaceDisconnect`, this callback is for the entire client side connection.
	OnDisconnect func(c *Conn)
	// contains filtered or unexported fields
}

Server is the neffos server. Keeps the `IDGenerator` which can be customized, by default it's the `DefaultIDGenerator` which generates connections unique identifiers using the uuid/v4.

Callers can optionally register callbacks for connection, disconnection and errored. Its most important methods are `ServeHTTP` which is used to register the server on a specific endpoint and `Broadcast` and `Close`. Use the `New` function to create a new server, server starts automatically, no further action is required.

func New

func New(upgrader Upgrader, connHandler ConnHandler) *Server

New constructs and returns a new neffos server. Listens to incoming connections automatically, no further action is required from the caller. The second parameter is the "connHandler", it can be filled as `Namespaces`, `Events` or `WithTimeout`, same namespaces and events can be used on the client-side as well, Use the `Conn#IsClient` on any event callback to determinate if it's a client-side connection or a server-side one.

See examples for more.

func (*Server) Ask added in v0.0.7

func (s *Server) Ask(ctx context.Context, msg Message) (Message, error)

Ask is like `Broadcast` but it blocks until a response from a specific connection if "msg.To" is filled otherwise from the first connection which will reply to this "msg".

Accepts a context for deadline as its first input argument. The second argument is the request message which should be sent to a specific namespace:event like the `Conn.Ask`.

func (*Server) Broadcast

func (s *Server) Broadcast(exceptSender fmt.Stringer, msgs ...Message)

Broadcast method is fast and does not block any new incoming connection by-default, it can be used as frequently as needed. Use the "msg"'s Namespace, or/and Event or/and Room to broadcast to a specific type of connection collectives.

If first "exceptSender" parameter is not nil then the message "msg" will be broadcasted to all connected clients except the given connection's ID, any value that completes the `fmt.Stringer` interface is valid. Keep note that `Conn`, `NSConn`, `Room` and `Exclude(connID) global function` are valid values.

Example Code: nsConn.Conn.Server().Broadcast(

	nsConn OR nil,
 neffos.Message{Namespace: "default", Room: "roomName or empty", Event: "chat", Body: [...]})

Note that it if `StackExchange` is nil then its default behavior doesn't wait for a publish to complete to all clients before any next broadcast call. To change that behavior set the `Server.SyncBroadcaster` to true before server start.

func (*Server) Close

func (s *Server) Close()

Close terminates the server and all of its connections, client connections are getting notified.

func (*Server) Do

func (s *Server) Do(fn func(*Conn), async bool)

Do loops through all connected connections and fires the "fn", with this method callers can do whatever they want on a connection outside of a event's callback, but make sure that these operations are not taking long time to complete because it delays the new incoming connections. If "async" is true then this method does not block the flow of the program.

func (*Server) GetConnections

func (s *Server) GetConnections() map[string]*Conn

GetConnections can be used as an alternative way to retrieve all connected connections to the server on a specific time point. Do not use this function frequently, it is not designed to be fast or cheap, use it for debugging or logging every 'x' time.

Not thread safe.

func (*Server) GetConnectionsByNamespace

func (s *Server) GetConnectionsByNamespace(namespace string) map[string]*NSConn

GetConnectionsByNamespace can be used as an alternative way to retrieve all connected connections to a specific "namespace" on a specific time point. Do not use this function frequently, it is not designed to be fast or cheap, use it for debugging or logging every 'x' time. Users should work with the event's callbacks alone, the usability is enough for all type of operations. See `Do` too.

Not thread safe.

func (*Server) GetTotalConnections

func (s *Server) GetTotalConnections() uint64

GetTotalConnections returns the total amount of the connected connections to the server, it's fast and can be used as frequently as needed.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP completes the `http.Handler` interface, it should be passed on a http server's router to serve this neffos server on a specific endpoint.

func (*Server) Upgrade

func (s *Server) Upgrade(
	w http.ResponseWriter,
	r *http.Request,
	socketWrapper func(Socket) Socket,
	customIDGen IDGenerator,
) (*Conn, error)

Upgrade handles the connection, same as `ServeHTTP` but it can accept a socket wrapper and a "customIDGen" that overrides the server's IDGenerator and it does return the connection or any errors.

func (*Server) UseStackExchange added in v0.0.7

func (s *Server) UseStackExchange(exc StackExchange) error

UseStackExchange can be used to add one or more StackExchange to the server. Returns a non-nil error when "exc" completes the `StackExchangeInitializer` interface and its `Init` failed.

Read more at the `StackExchange` type's docs.

type Socket

type Socket interface {
	// NetConn returns the underline net connection.
	NetConn() net.Conn
	// Request returns the http request value.
	Request() *http.Request
	// ReadData reads binary or text messages from the remote connection.
	ReadData(timeout time.Duration) (body []byte, typ MessageType, err error)
	// WriteBinary sends a binary message to the remote connection.
	WriteBinary(body []byte, timeout time.Duration) error
	// WriteText sends a text message to the remote connection.
	WriteText(body []byte, timeout time.Duration) error
}

Socket is the interface that an underline protocol implementation should implement.

type StackExchange added in v0.0.6

type StackExchange interface {
	// OnConnect should prepare the connection's subscriber.
	// It's called automatically after the neffos server's OnConnect (if any)
	// on incoming client connections.
	OnConnect(c *Conn) error
	// OnDisconnect should close the connection's subscriber that
	// created on the `OnConnect` method.
	// It's called automatically when a connection goes offline,
	// manually by server or client or by network failure.
	OnDisconnect(c *Conn)

	// Publish should publish messages through a stackexchange.
	// It's called automatically on neffos broadcasting.
	Publish(msgs []Message) bool
	// Subscribe should subscribe to a specific namespace,
	// it's called automatically on neffos namespace connected.
	Subscribe(c *Conn, namespace string)
	// Unsubscribe should unsubscribe from a specific namespace,
	// it's called automatically on neffos namespace disconnect.
	Unsubscribe(c *Conn, namespace string) // should close the subscriber.
	// Ask should be able to perform a server Ask to a specific client or to all clients
	// It blocks until response from a specific client if msg.To is filled,
	// otherwise will return on the first responder's reply.
	Ask(ctx context.Context, msg Message, token string) (Message, error)
	// NotifyAsk should notify and unblock a subscribed connection for this
	// specific message, "token" is the neffos wait signal for this message.
	NotifyAsk(msg Message, token string) error
}

StackExchange is an optional interface that can be used to change the way neffos sends messages to its clients, i.e communication between multiple neffos servers.

See the "kataras/neffos/stackexchange" subpackage for more details. Real-World example and usage documentation can be found at: "kataras/neffos/_examples/redis".

type StackExchangeInitializer added in v0.0.7

type StackExchangeInitializer interface {
	// Init should initialize a stackexchange, it's optional.
	Init(Namespaces) error
}

StackExchangeInitializer is an optional interface for a `StackExchange`. It contains a single `Init` method which accepts the registered server namespaces and returns error. It does not called on manual `Server.StackExchange` field set, use the `Server.UseStackExchange` to make sure that this implementation is respected.

type Struct added in v0.0.3

type Struct struct {
	// contains filtered or unexported fields
}

Struct is a ConnHandler. All fields are unexported, use `NewStruct` instead. It converts any pointer to a struct value to `neffos.Namespaces` using reflection.

func NewStruct added in v0.0.3

func NewStruct(ptr interface{}) *Struct

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 (*Struct) Events added in v0.0.4

func (s *Struct) Events() Events

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 (*Struct) GetNamespaces added in v0.0.5

func (s *Struct) GetNamespaces() Namespaces

GetNamespaces creates and returns Namespaces based on the pointer to struct value provided by the "s".

func (*Struct) SetEventMatcher added in v0.0.3

func (s *Struct) SetEventMatcher(matcher EventMatcherFunc) *Struct

SetEventMatcher sets an event method matcher which applies to every event except the system events (OnNamespaceConnected, and so on).

func (*Struct) SetInjector added in v0.0.5

func (s *Struct) SetInjector(fn StructInjector) *Struct

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 (*Struct) SetNamespace added in v0.0.3

func (s *Struct) SetNamespace(namespace string) *Struct

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 (*Struct) SetTimeouts added in v0.0.4

func (s *Struct) SetTimeouts(read, write time.Duration) *Struct

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.

type StructInjector added in v0.0.5

type StructInjector func(structType reflect.Type, nsConn *NSConn) (structValue reflect.Value)

StructInjector is a type which injects a dynamic struct value. See `Struct.SetInjector` for more.

type Upgrader

type Upgrader func(w http.ResponseWriter, r *http.Request) (Socket, error)

Upgrader is the definition type of a protocol upgrader, gorilla or gobwas or custom. It is the first parameter of the `New` function which constructs a neffos server.

type WithTimeout

type WithTimeout struct {
	ReadTimeout  time.Duration
	WriteTimeout time.Duration

	Namespaces Namespaces
	Events     Events
}

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

func (WithTimeout) GetNamespaces added in v0.0.5

func (t WithTimeout) GetNamespaces() Namespaces

GetNamespaces returns combined namespaces from "Namespaces" and "Events" fields with read and write timeouts from "ReadTimeout" and "WriteTimeout" fields of "t".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL