orbit

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2019 License: MIT Imports: 18 Imported by: 0

README

GoDoc coverage license

Orbit provides a powerful backend to interlink remote applications with each other.
It replaces connectionless RPC solutions with a multiplexed session-based solution that includes convenient features (such as raw streams, signals and more ...).

Orbit generally does not expect you to use a strict client-server architecture. Both peers can call functions and trigger signals at the other peer. Therefore, it is possible to implement both client-server and peer-to-peer applications.

Features
  • Session-based
  • Multiplexed connections with multiple channel streams and Keep-Alive (using yamux)
  • Plugable custom codecs for encoding and decoding (defaulting to MessagePack using msgpack)
  • Use raw tcp streams to implement your own protocols, and/or
    • Use the control package for RPC-like approaches
    • Use efficient signals for event-based approaches
  • Provide an authentication hook to easily authenticate your peers.
  • Configure everything to your needs, such as:
    • your preferred logger
    • allowed message sizes
    • timeouts
    • ...
  • Easy setup (check out the sample)
Control - RPC

The control package provides an implementation for Remote Procedure Calls. This allows a peer to define functions that other connected peers can then call.

Setup

First, you need to setup a control on each peer. It is best to use either the provided Init or InitMany functions to do this.

ctrl, _, err := orbitSession.Init(&orbit.Init{
    Control: orbit.InitControl{
        Funcs: map[string]control.Func{
            api.Action1: handleAction1,
        },
    },
})
if err != nil {
    return err
}

ctrl.Ready()

To use a custom initialization, check out the source code of the InitMany function to get a grasp of what needs to be done.

Synchronous Call

To make a synchronous call, simply trigger a call on peer1 to peer2 and then wait for the incoming response.

type Action1Args struct {
    ID int
}

type Action1Ret struct {
    SomeData string
}

func Action1() (data string, err error) {
    // Call
    ctx, err := s.ctrl.Call("Action1", &Action1Args{
        ID: 28
    })
    if err != nil {
        return
    }
    
    // Response
    var response api.Action1Ret
    err = ctx.Decode(&response)
    if err != nil {
        return
    }
    
    data = response.SomeData
    return 
}

peer2 might handle this call like this:

func handleAction1(ctx *control.Context) (v interface{}, err error) {
    var args Action1Args
    err = ctx.Decode(&args)
    if err != nil {
        return
    }
    
    // handle the request ...
    
    v = &Action1Ret{
        SomeData: someData,
    }
    return
}

Note, that the handleAction1 func of peer2 must have been added to the control of peer2 for the correct key. Check out the Control Setup to see how to do this

Asynchronous Call

An asynchronous call is very similar to its synchronous counterpart.

type Action2Args struct {
    ID int
}

type Action2Ret struct {
    SomeData string
}

// Call Async
func Action2() error {
    callback := func(data interface{}, err error) {
        if err != nil {
            log.Fatal(err)
        }
        
        // Response
        var response Action2Ret
        err = ctx.Decode(&response)
        if err != nil {
            return
        }
        
        // handle data...
        println(response.SomeData)
    }
    
    return s.ctrl.CallAsync(
        "Action2", 
        &Action2Args{
            ID: 28,
        }, 
        callback,
    )
}

Inside the callback, you receive the response (or an error) and can handle it the same way as with the synchronous call.

Signaler - Events

The signaler package provides an implementation for sending events to remote peers. Under the hood, it uses the control package's CallOneWay function tFirst, you need to setup a control on each peer. It is best to use either the provided Init or InitMany functions to do this.o make calls without expecting a response.

The signaler package adds a lot of convenient stuff, such as allowing peers to set filters on their events, or unregister from an event completely.

The code in the following sections is taken from the sample.

Setup

First, you need to setup a signaler on each peer. It is best to use either the provided Init or InitMany functions to do this.

We start with the peer that emits the signal, the sender:

// Initialize the signaler and declare, which signals
// can be triggered on it.
_, sig, err := orbitSesion.Init(&orbit.Init{
    Signaler: orbit.InitSignaler{
        Signals: []orbit.InitSignal{
            {
                ID: "TimeBomb",
            },
        },
    },
})
if err != nil {
    return
}

// Start the signaler.
sig.Ready()

First, we initialize an orbit session using Init and we register a signaler on it that can emit the "TimeBomb" signal.
If this succeeds, we start the signaler by calling its Ready() method, which starts the listen routines of the signaler.

Now let us move on to the peer that receives the signal, the receiver:

// Initialize the signaler and declare, which signals
// can be triggered on it.
_, sig, err := orbitSesion.Init(&orbit.Init{
    Signaler: orbit.InitSignaler{
        Signals: []orbit.InitSignal{},
    },
})
if err != nil {
    return
}

// Register handlers for events from the remote peer
_ = sig.OnSignalFunc("TimeBomb", onEventTimeBomb)

// Start the signaler.
sig.Ready()

Again, we need to initialize the signaler for this peer as well, however, we do not register any signals on it, since we only want to receive signals from the remote peer right now.
Afterwards, we register a handler func for the "TimeBomb" signal, the onEventTimeBomb function.
In the end, we start the signaler.

Here is the implementation of the onEventTimeBomb handler func:

func onEventTimeBomb(ctx *signaler.Context) {
	var args api.TimeBombData
	err := ctx.Decode(&args)
	if err != nil {
		log.Printf("onEventTimeBomb error: %v", err)
		return
	}

	// Do something with the signal data...
}

It is identical to the control handler funcs, only that we do not return something to the caller, as signals are unidirectional.

Now, if we want to finally trigger our signal on the sender, we can do it like this:

// Trigger the event.
args := &api.TimeBombData{
    Countdown: 5,
}

err = sig.TriggerSignal("TimeBomb", &args)
if err != nil {
    log.Printf("triggerSignal TimeBomb: %v", err)
    return
}

We call the TriggerSignal method on our signaler we defined at the beginning of this section. This sends the given arguments over the wire to our receiver, where the onEventTimeBomb handler func will be triggered.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidVersion defines the error if the version of both peers do not match
	// during the version exchange.
	ErrInvalidVersion = errors.New("invalid version")

	// ErrOpenTimeout defines the error if the opening of a stream timeouts.
	ErrOpenTimeout = errors.New("open timeout")

	// ErrClosed defines the error if a stream is unexpectedly closed.
	ErrClosed = errors.New("closed")
)

Functions

This section is empty.

Types

type AcceptStreamFunc

type AcceptStreamFunc func(net.Conn) error

The AcceptStreamFunc type describes the function that is called whenever a new connection is requested on a peer. It must then handle the new connection, if it could be set up correctly.

type AuthFunc

type AuthFunc func(net.Conn) (value interface{}, err error)

The AuthFunc type describes the function that is used during the authentication phase of the session initialization. It may use the given connection to perform some kind of data exchange between the client and the server. It can return some arbitrary data that will be saved to the session. It must return a non nil error, if the authentication did fail.

type Config

type Config struct {
	// Codec used to encode and decode orbit messages.
	// Defaults to msgpack.
	Codec codec.Codec

	// KeepAliveInterval is how often to perform the keep alive.
	// Default to 30 seconds.
	KeepAliveInterval time.Duration

	// Logger is used to pass in the logger to be used.
	// Uses a default logger to os.Stderr.
	Logger *log.Logger

	// AuthFunc authenticates the session connection if defined.
	// It gets called right after the version byte has been exchanged
	// between client and server. Therefore, not much resources are wasted
	// in case the authentication fails.
	AuthFunc AuthFunc
}

type Init

type Init struct {
	AcceptStreams InitAcceptStreams
	Control       InitControl
	Signaler      InitSignaler
}

The Init type is used during the initialization of the orbit session and contains the definition to accept streams and define exactly one control and one signaler.

type InitAcceptStreams

type InitAcceptStreams map[string]AcceptStreamFunc

The InitAcceptStreams type is a map of AcceptStreamFunc, where the key is the id of the stream and the value the func that is used to accept it.

type InitControl

type InitControl struct {
	Funcs  control.Funcs
	Config *control.Config
}

The InitControl type is used to initialize one control. It contains the functions that the remote peer can call and a config.

type InitControls

type InitControls map[string]InitControl

The InitControls type is a map, where the key is the id of a control and the value the associated InitControl.

type InitMany

type InitMany struct {
	AcceptStreams InitAcceptStreams
	Controls      InitControls
	Signalers     InitSignalers
}

The Init type is used during the initialization of the orbit session and contains the definition to accept streams and define many controls and many signalers.

type InitSignal

type InitSignal struct {
	ID     string
	Filter signaler.FilterFunc
}

The InitSignal type is used to initialize one signal. It contains the id of the signal and a filter for it.

type InitSignaler

type InitSignaler struct {
	Signals []InitSignal
	Config  *control.Config
}

The InitSignaler type is used to initialize one signaler. It contains the signals that can be triggered and a config.

type InitSignalers

type InitSignalers map[string]InitSignaler

The InitSignalers type is a map, where the key is the id of a signaler and the value the associated InitSignaler.

type Server

type Server struct {
	closer.Closer
	// contains filtered or unexported fields
}

Server implements a simple orbit server. It listens with serverWorkers many routines for incoming connections.

func NewServer

func NewServer(ln net.Listener, config *ServerConfig) *Server

NewServer creates a new orbit server. A listener is required the server will use to listen for incoming connections. A config can be provided, where every property of it that has not been set will be initialized with a default value. That makes it possible to overwrite only the interesting properties for the caller.

func NewServerWithCloser

func NewServerWithCloser(ln net.Listener, config *ServerConfig, cl closer.Closer) *Server

NewServerWithCloser creates a new orbit server just like NewServer() does, but you can provide your own closer for it.

func (*Server) Listen

func (l *Server) Listen() error

Listen listens for new socket connections, which it passes to the new connection channel that is read by the server workers. This method is blocking.

func (*Server) NewSessionChan

func (l *Server) NewSessionChan() <-chan *Session

NewSessionChan returns the channel for new incoming sessions.

func (*Server) Session

func (l *Server) Session(id string) (s *Session)

Session obtains a session by its ID. Returns nil if not found.

func (*Server) Sessions

func (l *Server) Sessions() []*Session

Sessions returns a list of all currently connected sessions.

type ServerConfig

type ServerConfig struct {
	// Embed the standard config that both clients and servers share.
	*Config

	// The number of goroutines that handle incoming connections on the server.
	NewConnNumberWorkers int
	// The size of the channel on which new connections are passed to the
	// server workers.
	// Should not be less than NewConnNumberWorkers.
	NewConnChanSize int
	// The size of the channel on which new server sessions are passed into,
	// so that a user of this package can read them from it.
	// Should not be less than NewConnNumberWorkers.
	NewSessionChanSize int
}

type Session

type Session struct {
	closer.Closer

	// Value is a custom value which can be set. In case the config contains
	// a valid AuthFunc, the Value will be set to the return value of it.
	Value interface{}
	// contains filtered or unexported fields
}

The Session type describes a orbit session that is used on both the client and server side, so in general for peers. It contains its underlying connection to the remote peer and may accept new incoming connections by defining AcceptStreamFuncs.

func ClientSession

func ClientSession(conn net.Conn, config *Config) (s *Session, err error)

ClientSession is used to initialize a new client-side session. A config can be provided, where every property of it that has not been set will be initialized with a default value. That makes it possible to overwrite only the interesting properties for the caller. When the connection to the server has been established, one byte containing the version of the client is sent to the server. In case the versions do not match, the server will close the connection. As part of the session setup, the auth func of the config is called, if it has been defined.

func ClientSessionWithCloser

func ClientSessionWithCloser(conn net.Conn, config *Config, cl closer.Closer) (s *Session, err error)

ClientSessionWithCloser initializes a new client-side session, just like ClientSession() does, but allows to hand in an own closer.

func ServerSession

func ServerSession(conn net.Conn, config *Config) (s *Session, err error)

ServerSession is used to initialize a new server-side session. A config can be provided, where every property of it that has not been set will be initialized with a default value. That makes it possible to overwrite only the interesting properties for the caller. When the connection has been established, the server waits for the API version byte of the client. If the versions do not match, the server immediately closes the connection. As part of the session setup, the auth func of the config is called, if it has been defined.

func ServerSessionWithCloser

func ServerSessionWithCloser(conn net.Conn, config *Config, cl closer.Closer) (s *Session, err error)

ServerSessionWithCloser initializes a new server-side session, just as ServerSession() does, but allows to hand in an own closer.

func (*Session) AcceptStream

func (s *Session) AcceptStream(channel string, f AcceptStreamFunc)

AcceptStream registers the given accept handler for the specific channel.

func (*Session) ID

func (s *Session) ID() string

ID returns the session ID. This must be set manually.

func (*Session) Init

func (s *Session) Init(opts *Init) (
	control *control.Control,
	signaler *signaler.Signaler,
	err error,
)

Init initializes the session by using InitMany(), but only defining one control and one signaler. If no more than one control/signaler are needed, this is the more convenient method to call. Ready() must be called manually for the control and signaler afterwards.

func (*Session) InitMany

func (s *Session) InitMany(opts *InitMany) (
	controls map[string]*control.Control,
	signalers map[string]*signaler.Signaler,
	err error,
)

InitMany initializes this session. Pass nil to just start accepting streams. Ready() must be called manually for all controls and signaler afterwards.

func (*Session) IsClient

func (s *Session) IsClient() bool

IsClient returns whether this session is a client connection.

func (*Session) IsServer

func (s *Session) IsServer() bool

IsServer returns whether this session is a server connection.

func (*Session) LocalAddr

func (s *Session) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Session) OpenStream

func (s *Session) OpenStream(channel string) (stream net.Conn, err error)

OpenStream performs the same task as OpenStreamTimeout, but uses the default write timeout openStreamWriteTimeout.

func (*Session) OpenStreamTimeout

func (s *Session) OpenStreamTimeout(channel string, timeout time.Duration) (stream net.Conn, err error)

OpenStreamTimeout opens a new stream with the given channel ID. Expires after the timeout and returns ErrOpenTimeout.

func (*Session) RemoteAddr

func (s *Session) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*Session) SetID

func (s *Session) SetID(id string)

SetID sets the session ID.

Directories

Path Synopsis
Package codec contains sub-packages with different codecs that can be used to encode/decode any entity to/from a byte stream.
Package codec contains sub-packages with different codecs that can be used to encode/decode any entity to/from a byte stream.
json
Package json offers an implementation of the codec.Codec interface for the json data format.
Package json offers an implementation of the codec.Codec interface for the json data format.
msgpack
Package msgpack offers an implementation of the codec.Codec interface for the msgpack data format.
Package msgpack offers an implementation of the codec.Codec interface for the msgpack data format.
Package control provides an implementation of a simple network protocol that offers a RPC-like request/response mechanism between two peers.
Package control provides an implementation of a simple network protocol that offers a RPC-like request/response mechanism between two peers.
internal
api
Package api contains types that are internally used to send data via the control and signaler pkg.
Package api contains types that are internally used to send data via the control and signaler pkg.
bytes
Package bytes offers convenience functions to convert bytes to and from unsigned integers, respecting a defined byte-order.
Package bytes offers convenience functions to convert bytes to and from unsigned integers, respecting a defined byte-order.
flusher
Package flusher provides convenience methods to flush a net.Conn.
Package flusher provides convenience methods to flush a net.Conn.
utils
Package utils is the common sin of every Go programmer, including functions that seem to be usable everywhere, but do not share the same functionality.
Package utils is the common sin of every Go programmer, including functions that seem to be usable everywhere, but do not share the same functionality.
log
Package packet provides convenience methods to read/write packets to a net.Conn.
Package packet provides convenience methods to read/write packets to a net.Conn.
sample
api
client command
server command
* ORBIT - Interlink Remote Applications * * The MIT License (MIT) * * Copyright (c) 2018 Roland Singer <roland.singer[at]desertbit.com> * Copyright (c) 2018 Sebastian Borchers <sebastian[at]desertbit.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software.
* ORBIT - Interlink Remote Applications * * The MIT License (MIT) * * Copyright (c) 2018 Roland Singer <roland.singer[at]desertbit.com> * Copyright (c) 2018 Sebastian Borchers <sebastian[at]desertbit.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software.

Jump to

Keyboard shortcuts

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