frisbee

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: May 18, 2022 License: Apache-2.0 Imports: 18 Imported by: 0

README

Frisbee

Tests Benchmarks Go Report Card go-doc

This is the Go library for Frisbee, a bring-your-own protocol messaging framework designed for performance and stability.

FRPC is a lightweight, fast, and secure RPC framework for Go that uses Frisbee under the hood. This repository houses both projects, with FRPC being contained in the protoc-gen-frpc folder.

This library requires Go1.16 or later.

Important note about releases and stability

This repository generally follows Semantic Versioning. However, this library is currently in Beta and is still considered experimental. Breaking changes of the library will not trigger a new major release. The same is true for selected other new features explicitly marked as EXPERIMENTAL in CHANGELOG.md.

Usage and Documentation

Usage instructions and documentation for Frisbee is available at https://frpc.io/concepts/frisbee. The Frisbee framework also has great documentation coverage using GoDoc.

FRPC

The FRPC Generator is still in very early Alpha. While it is functional and being used within other products we're building at Loophole Labs, the proto3 spec has a myriad of edge-cases that make it difficult to guarantee validity of generated RPC frameworks without extensive real-world use.

That being said, as the library matures and usage of FRPC grows we'll be able to increase our testing coverage and fix any edge case bugs. One of the major benefits to the RPC framework is that reading the generated code is extremely straight forward, making it easy to debug potential issues down the line.

Usage and Documentation

Usage instructions and documentations for FRPC are available at https://frpc.io/.

Unsupported Features

The Frisbee RPC Generator currently does not support the following features, though they are actively being worked on:

  • OneOf Message Types
  • Streaming Messages between the client and server

Example Proto3 files can be found here.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/loopholelabs/frisbee. For more contribution information check out the contribution guide.

License

The Frisbee project is available as open source under the terms of the Apache License, Version 2.0.

Code of Conduct

Everyone interacting in the Frisbee project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the CNCF Code of Conduct.

Project Managed By:

Documentation

Overview

Package frisbee is the core package for using the frisbee messaging framework. The frisbee framework is a messaging framework designed around the aspect of "bring your own protocol", and can be used by simply defining your packet types and their accompanying logic.

This package provides methods for defining packet types and logic, as well as functionality for implementing frisbee servers and clients. Useful features like automatic heartbeats and automatic reconnections are provided as well.

In depth documentation and examples can be found at https://loopholelabs.io/docs/frisbee

An Echo Example

As a starting point, a very basic echo server:

	package main

	import (
		"github.com/loopholelabs/frisbee"
     "github.com/loopholelabs/frisbee/pkg/packet"
		"github.com/rs/zerolog/log"
		"os"
		"os/signal"
	)

	const PING = uint16(10)
	const PONG = uint16(11)

	func handlePing(_ *frisbee.Async, incoming *packet.Packet) (outgoing *packet.Packet, action frisbee.Action) {
		if incoming.Metadata.ContentLength > 0 {
			log.Printf("Server Received Metadata: %s\n", incoming.Content)
         incoming.Metadata.Operation = PONG
			outgoing = incoming
		}

		return
	}

	func main() {
		handlerTable := make(frisbee.ServerRouter)
		handlerTable[PING] = handlePing
		exit := make(chan os.Signal)
		signal.Notify(exit, os.Interrupt)

		s := frisbee.NewServer(":8192", handlerTable, 0)
		err := s.Start()
		if err != nil {
			panic(err)
		}

		<-exit
		err = s.Shutdown()
		if err != nil {
			panic(err)
		}
	}

And an accompanying echo client:

package main

import (
	"fmt"
	"github.com/loopholelabs/frisbee"
	"github.com/loopholelabs/frisbee/pkg/packet"
	"github.com/rs/zerolog/log"
	"os"
	"os/signal"
	"time"
)

const PING = uint16(10)
const PONG = uint16(11)

func handlePong(incoming *packet.Packet) (outgoing *packet.Packet, action frisbee.Action) {
	if incoming.Metadata.ContentLength > 0 {
		log.Printf("Client Received Metadata: %s\n", incoming.Content)
	}
	return
}

func main() {
	handlerTable := make(frisbee.ClientRouter)
	handlerTable[PONG] = handlePong
	exit := make(chan os.Signal)
	signal.Notify(exit, os.Interrupt)

	c, err := frisbee.NewClient("127.0.0.1:8192", handlerTable)
	if err != nil {
		panic(err)
	}
	err = c.ConnectAsync()
	if err != nil {
		panic(err)
	}

	go func() {
		i := 0
		p := packet.Get()
		p.Metadata.Operation = PING
		for {
			p.Write([]byte(fmt.Sprintf("ECHO MESSAGE: %d", i)))
			p.Metadata.ContentLength = uint32(len(p.Content))
			err := c.WritePacket(p)
			if err != nil {
				panic(err)
			}
			i++
			time.Sleep(time.Second)
		}
	}()

	<-exit
	err = c.Close()
	if err != nil {
		panic(err)
	}
}

(Examples taken from https://github.com/loopholelabs/frisbee-examples/)

This example is a simple echo client/server, where the client will repeatedly send packets to the server, and the server will echo them back. Its purpose is to describe the flow of packets from Frisbee Client to Server, as well as give an example of how a Frisbee application must be implemented.

Index

Examples

Constants

View Source
const (
	// NONE is used to do nothing (default)
	NONE = Action(iota)

	// UPDATE is used to trigger an UpdateContext call on the Server or Client
	UPDATE

	// CLOSE is used to close the frisbee connection
	CLOSE
)

These are various frisbee actions, used to modify the state of the client or server from a Handler function:

View Source
const (
	// HEARTBEAT is used to send heartbeats from the client to the server (and measure round trip time)
	HEARTBEAT = uint16(iota)

	// PING is used to check if a client is still alive
	PING

	// PONG is used to respond to a PING packets
	PONG

	RESERVED3
	RESERVED4
	RESERVED5
	RESERVED6
	RESERVED7
	RESERVED8
	RESERVED9
)

These are internal reserved packet types, and are the reason you cannot use 0-9 in Handler functions:

View Source
const DefaultBufferSize = 1 << 16

DefaultBufferSize is the size of the default buffer

Variables

View Source
var (
	InvalidContentLength     = errors.New("invalid content length")
	ConnectionClosed         = errors.New("connection closed")
	ConnectionNotInitialized = errors.New("connection not initialized")
	InvalidBufferLength      = errors.New("invalid buffer length")
	InvalidHandlerTable      = errors.New("invalid handlePacket table configuration, a reserved value may have been used")
)

These are various frisbee errors that can be returned by the client or server:

View Source
var (
	// HEARTBEATPacket is a pre-allocated Frisbee Packet for HEARTBEAT Packets
	HEARTBEATPacket = &packet.Packet{
		Metadata: &metadata.Metadata{
			Operation: HEARTBEAT,
		},
		Content: content.New(),
	}

	// PINGPacket is a pre-allocated Frisbee Packet for PING Packets
	PINGPacket = &packet.Packet{
		Metadata: &metadata.Metadata{
			Operation: PING,
		},
		Content: content.New(),
	}

	// PONGPacket is a pre-allocated Frisbee Packet for PONG Packets
	PONGPacket = &packet.Packet{
		Metadata: &metadata.Metadata{
			Operation: PONG,
		},
		Content: content.New(),
	}
)
View Source
var (
	BaseContextNil = errors.New("BaseContext cannot be nil")
	OnClosedNil    = errors.New("OnClosed cannot be nil")
	PreWriteNil    = errors.New("PreWrite cannot be nil")
)
View Source
var DefaultLogger = zerolog.New(ioutil.Discard)

DefaultLogger is the default logger used within frisbee

View Source
var (
	NotTLSConnectionError = errors.New("connection is not of type *tls.Conn")
)

Functions

This section is empty.

Types

type Action

type Action int

Action is an ENUM used to modify the state of the client or server from a Handler function

NONE: used to do nothing (default)
CLOSE: close the frisbee connection
SHUTDOWN: shutdown the frisbee client or server

type Async

type Async struct {
	sync.Mutex
	// contains filtered or unexported fields
}

Async is the underlying asynchronous frisbee connection which has extremely efficient read and write logic and can handle the specific frisbee requirements. This is not meant to be used on its own, and instead is meant to be used by frisbee client and server implementations

func ConnectAsync

func ConnectAsync(addr string, keepAlive time.Duration, logger *zerolog.Logger, TLSConfig *tls.Config) (*Async, error)

ConnectAsync creates a new TCP connection (using net.Dial) and wraps it in a frisbee connection

func NewAsync

func NewAsync(c net.Conn, logger *zerolog.Logger) (conn *Async)

NewAsync takes an existing net.Conn object and wraps it in a frisbee connection

func (*Async) Close

func (c *Async) Close() error

Close closes the frisbee connection gracefully

func (*Async) CloseChannel added in v0.1.6

func (c *Async) CloseChannel() <-chan struct{}

CloseChannel returns a channel that can be listened to for a close event on a frisbee connection

func (*Async) Closed added in v0.1.6

func (c *Async) Closed() bool

Closed returns whether the frisbee.Async connection is closed

func (*Async) ConnectionState

func (c *Async) ConnectionState() (tls.ConnectionState, error)

ConnectionState returns the tls.ConnectionState of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Async) Context added in v0.2.4

func (c *Async) Context() (ctx context.Context)

Context returns the saved context within the connection

func (*Async) Error

func (c *Async) Error() error

Error returns the error that caused the frisbee.Async connection to close

func (*Async) Flush

func (c *Async) Flush() error

Flush allows for synchronous messaging by flushing the write buffer and instantly sending packets

func (*Async) Handshake added in v0.2.1

func (c *Async) Handshake() error

Handshake performs the tls.Handshake() of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Async) HandshakeContext added in v0.2.1

func (c *Async) HandshakeContext(ctx context.Context) error

HandshakeContext performs the tls.HandshakeContext() of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Async) LocalAddr

func (c *Async) LocalAddr() net.Addr

LocalAddr returns the local address of the underlying net.Conn

func (*Async) Logger

func (c *Async) Logger() *zerolog.Logger

Logger returns the underlying logger of the frisbee connection

func (*Async) Raw

func (c *Async) Raw() net.Conn

Raw shuts off all of frisbee's underlying functionality and converts the frisbee connection into a normal TCP connection (net.Conn)

func (*Async) ReadPacket added in v0.2.0

func (c *Async) ReadPacket() (*packet.Packet, error)

ReadPacket is a blocking function that will wait until a Frisbee packet is available and then return it (and its content). In the event that the connection is closed, ReadPacket will return an error.

func (*Async) RemoteAddr

func (c *Async) RemoteAddr() net.Addr

RemoteAddr returns the remote address of the underlying net.Conn

func (*Async) SetContext added in v0.2.4

func (c *Async) SetContext(ctx context.Context)

SetContext allows users to save a context within a connection

func (*Async) SetDeadline

func (c *Async) SetDeadline(t time.Time) error

SetDeadline sets the read and write deadline on the underlying net.Conn

func (*Async) SetReadDeadline

func (c *Async) SetReadDeadline(t time.Time) error

SetReadDeadline sets the read deadline on the underlying net.Conn

func (*Async) SetWriteDeadline

func (c *Async) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the write deadline on the underlying net.Conn

func (*Async) WriteBufferSize

func (c *Async) WriteBufferSize() int

WriteBufferSize returns the size of the underlying write buffer (used for internal packet handling and for heartbeat logic)

func (*Async) WritePacket added in v0.2.0

func (c *Async) WritePacket(p *packet.Packet) error

WritePacket takes a packet.Packet and queues it up to send asynchronously.

If packet.Metadata.ContentLength == 0, then the content array must be nil. Otherwise, it is required that packet.Metadata.ContentLength == len(content).

type Client

type Client struct {

	// PacketContext is used to define packet-specific contexts based on the incoming packet
	// and is run whenever a new packet arrives
	PacketContext func(context.Context, *packet.Packet) context.Context

	// UpdateContext is used to update a handler-specific context whenever the returned
	// Action from a handler is UPDATE
	UpdateContext func(context.Context, *Async) context.Context
	// contains filtered or unexported fields
}

Client connects to a frisbee Server and can send and receive frisbee packets

func NewClient

func NewClient(handlerTable HandlerTable, ctx context.Context, opts ...Option) (*Client, error)

NewClient returns an uninitialized frisbee Client with the registered ClientRouter. The ConnectAsync method must then be called to dial the server and initialize the connection.

Example
package main

import (
	"context"
	"github.com/loopholelabs/frisbee"
	"github.com/loopholelabs/frisbee/pkg/packet"
	"github.com/rs/zerolog"
	"os"
)

func main() {
	handlerTable := make(frisbee.HandlerTable)

	handlerTable[10] = func(ctx context.Context, incoming *packet.Packet) (outgoing *packet.Packet, action frisbee.Action) {
		return
	}

	logger := zerolog.New(os.Stdout)

	_, _ = frisbee.NewClient(handlerTable, context.Background(), frisbee.WithLogger(&logger))
}
Output:

func (*Client) Close

func (c *Client) Close() error

Close closes the frisbee client and kills all the goroutines

func (*Client) CloseChannel added in v0.1.6

func (c *Client) CloseChannel() <-chan struct{}

CloseChannel returns a channel that can be listened to see if this client has been closed

func (*Client) Closed

func (c *Client) Closed() bool

Closed checks whether this client has been closed

func (*Client) Connect

func (c *Client) Connect(addr string) error

Connect actually connects to the given frisbee server, and starts the reactor goroutines to receive and handle incoming packets. If this function is called, FromConn should not be called.

func (*Client) Error

func (c *Client) Error() error

Error checks whether this client has an error

func (*Client) Flush added in v0.1.6

func (c *Client) Flush() error

Flush flushes any queued frisbee Packets from the client to the server

func (*Client) FromConn added in v0.4.0

func (c *Client) FromConn(conn net.Conn) error

FromConn takes a pre-existing connection to a Frisbee server and starts the reactor goroutines to receive and handle incoming packets. If this function is called, Connect should not be called.

func (*Client) Logger

func (c *Client) Logger() *zerolog.Logger

Logger returns the client's logger (useful for ClientRouter functions)

func (*Client) Raw

func (c *Client) Raw() (net.Conn, error)

Raw converts the frisbee client into a normal net.Conn object, and returns it. This is especially useful in proxying and streaming scenarios.

func (*Client) WritePacket added in v0.2.0

func (c *Client) WritePacket(p *packet.Packet) error

WritePacket sends a frisbee packet.Packet from the client to the server

type Conn

type Conn interface {
	Close() error
	LocalAddr() net.Addr
	RemoteAddr() net.Addr
	ConnectionState() (tls.ConnectionState, error)
	Handshake() error
	HandshakeContext(context.Context) error
	SetDeadline(time.Time) error
	SetReadDeadline(time.Time) error
	SetWriteDeadline(time.Time) error
	WritePacket(*packet.Packet) error
	ReadPacket() (*packet.Packet, error)
	SetContext(context.Context)
	Context() context.Context
	Logger() *zerolog.Logger
	Error() error
	Raw() net.Conn
}

type Handler added in v0.2.0

type Handler func(ctx context.Context, incoming *packet.Packet) (outgoing *packet.Packet, action Action)

Handler is the handler function called by frisbee for incoming packets of data, depending on the packet's Metadata.Operation field

type HandlerTable added in v0.2.0

type HandlerTable map[uint16]Handler

HandlerTable is the lookup table for Frisbee handler functions - based on the Metadata.Operation field of a packet, Frisbee will look up the correct handler for that packet.

type Option

type Option func(opts *Options)

Option is used to generate frisbee client and server options internally

func WithHeartbeat

func WithHeartbeat(heartbeat time.Duration) Option

WithHeartbeat sets the minimum time between heartbeat packets. By default, packets are only sent if no packets have been sent since the last heartbeat packet - to change this behaviour you can disable heartbeats (by passing in -1), and implementing your own logic.

func WithKeepAlive

func WithKeepAlive(keepAlive time.Duration) Option

WithKeepAlive allows users to define TCP keepalive options for the frisbee client or server (use -1 to disable)

func WithLogger

func WithLogger(logger *zerolog.Logger) Option

WithLogger sets the logger for the frisbee client or server

func WithOptions

func WithOptions(options Options) Option

WithOptions allows users to pass in an Options struct to configure a frisbee client or server

func WithTLS

func WithTLS(tlsConfig *tls.Config) Option

WithTLS sets the TLS configuration for Frisbee. By default no TLS configuration is used, and Frisbee will use unencrypted TCP connections. If the Frisbee Server is using TLS, then you must pass in a TLS config (even an empty one `&tls.Config{}`) for the Frisbee Client.

type Options

type Options struct {
	KeepAlive time.Duration
	Heartbeat time.Duration
	Logger    *zerolog.Logger
	TLSConfig *tls.Config
}

Options is used to provide the frisbee client and server with configuration options.

Default Values:

options := Options {
	KeepAlive: time.Minute * 3,
	Logger: &DefaultLogger,
	Heartbeat: time.Second * 5,
}

type Server

type Server struct {

	// ConnContext is used to define a connection-specific context based on the incoming connection
	// and is run whenever a new connection is opened
	ConnContext func(context.Context, *Async) context.Context

	// PacketContext is used to define a handler-specific contexts based on the incoming packet
	// and is run whenever a new packet arrives
	PacketContext func(context.Context, *packet.Packet) context.Context

	// UpdateContext is used to update a handler-specific context whenever the returned
	// Action from a handler is UPDATE
	UpdateContext func(context.Context, *Async) context.Context
	// contains filtered or unexported fields
}

Server accepts connections from frisbee Clients and can send and receive frisbee Packets

func NewServer

func NewServer(handlerTable HandlerTable, opts ...Option) (*Server, error)

NewServer returns an uninitialized frisbee Server with the registered HandlerTable. The Start method must then be called to start the server and listen for connections.

Example
package main

import (
	"context"
	"github.com/loopholelabs/frisbee"
	"github.com/loopholelabs/frisbee/pkg/packet"
	"github.com/rs/zerolog"
	"os"
)

func main() {
	handlerTable := make(frisbee.HandlerTable)

	handlerTable[10] = func(ctx context.Context, incoming *packet.Packet) (outgoing *packet.Packet, action frisbee.Action) {
		return
	}

	logger := zerolog.New(os.Stdout)

	_, _ = frisbee.NewServer(handlerTable, frisbee.WithLogger(&logger))
}
Output:

func (*Server) Logger

func (s *Server) Logger() *zerolog.Logger

Logger returns the server's logger (useful for ServerRouter functions)

func (*Server) ServeConn added in v0.4.0

func (s *Server) ServeConn(newConn net.Conn)

ServeConn takes a TCP net.Conn and serves it using the Server

func (*Server) SetBaseContext added in v0.4.0

func (s *Server) SetBaseContext(f func() context.Context) error

SetBaseContext sets the baseContext function for the server. If f is nil, it returns an error.

func (*Server) SetOnClosed added in v0.4.0

func (s *Server) SetOnClosed(f func(*Async, error)) error

SetOnClosed sets the onClosed function for the server. If f is nil, it returns an error.

func (*Server) SetPreWrite added in v0.4.0

func (s *Server) SetPreWrite(f func()) error

SetPreWrite sets the preWrite function for the server. If f is nil, it returns an error.

func (*Server) Shutdown

func (s *Server) Shutdown() error

Shutdown shuts down the frisbee server and kills all the goroutines and active connections

func (*Server) Start

func (s *Server) Start(addr string) error

Start will start the frisbee server and its reactor goroutines to receive and handle incoming connections. If the baseContext, ConnContext, onClosed, OnShutdown, or preWrite functions have not been defined, it will use the default functions for these.

type Sync

type Sync struct {
	sync.Mutex
	// contains filtered or unexported fields
}

Sync is the underlying synchronous frisbee connection which has extremely efficient read and write logic and can handle the specific frisbee requirements. This is not meant to be used on its own, and instead is meant to be used by frisbee client and server implementations

func ConnectSync

func ConnectSync(addr string, keepAlive time.Duration, logger *zerolog.Logger, TLSConfig *tls.Config) (*Sync, error)

ConnectSync creates a new TCP connection (using net.Dial) and wraps it in a frisbee connection

func NewSync

func NewSync(c net.Conn, logger *zerolog.Logger) (conn *Sync)

NewSync takes an existing net.Conn object and wraps it in a frisbee connection

func (*Sync) Close

func (c *Sync) Close() error

Close closes the frisbee connection gracefully

func (*Sync) ConnectionState

func (c *Sync) ConnectionState() (tls.ConnectionState, error)

ConnectionState returns the tls.ConnectionState of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Sync) Context added in v0.2.4

func (c *Sync) Context() (ctx context.Context)

Context returns the saved context within the connection

func (*Sync) Error

func (c *Sync) Error() error

Error returns the error that caused the frisbee.Sync to close or go into a paused state

func (*Sync) Handshake added in v0.2.1

func (c *Sync) Handshake() error

Handshake performs the tls.Handshake() of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Sync) HandshakeContext added in v0.2.1

func (c *Sync) HandshakeContext(ctx context.Context) error

HandshakeContext performs the tls.HandshakeContext() of a *tls.Conn if the connection is not *tls.Conn then the NotTLSConnectionError is returned

func (*Sync) LocalAddr

func (c *Sync) LocalAddr() net.Addr

LocalAddr returns the local address of the underlying net.Conn

func (*Sync) Logger

func (c *Sync) Logger() *zerolog.Logger

Logger returns the underlying logger of the frisbee connection

func (*Sync) Raw

func (c *Sync) Raw() net.Conn

Raw shuts off all of frisbee's underlying functionality and converts the frisbee connection into a normal TCP connection (net.Conn)

func (*Sync) ReadPacket added in v0.2.0

func (c *Sync) ReadPacket() (*packet.Packet, error)

ReadPacket is a blocking function that will wait until a frisbee packet is available and then return it (and its content). In the event that the connection is closed, ReadPacket will return an error.

func (*Sync) RemoteAddr

func (c *Sync) RemoteAddr() net.Addr

RemoteAddr returns the remote address of the underlying net.Conn

func (*Sync) SetContext added in v0.2.4

func (c *Sync) SetContext(ctx context.Context)

SetContext allows users to save a context within a connection

func (*Sync) SetDeadline

func (c *Sync) SetDeadline(t time.Time) error

SetDeadline sets the read and write deadline on the underlying net.Conn

func (*Sync) SetReadDeadline

func (c *Sync) SetReadDeadline(t time.Time) error

SetReadDeadline sets the read deadline on the underlying net.Conn

func (*Sync) SetWriteDeadline

func (c *Sync) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the write deadline on the underlying net.Conn

func (*Sync) WritePacket added in v0.2.0

func (c *Sync) WritePacket(p *packet.Packet) error

WritePacket takes a packet.Packet and sends it synchronously.

If packet.Metadata.ContentLength == 0, then the content array must be nil. Otherwise, it is required that packet.Metadata.ContentLength == len(content).

Directories

Path Synopsis
internal
pkg

Jump to

Keyboard shortcuts

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