juggler

package module
v0.0.0-...-bb33fe3 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2025 License: BSD-3-Clause Imports: 15 Imported by: 0

README

juggler - websocket-based, redis-backed RPC and pub-sub GoDoc build status

Juggler implements highly decoupled, asynchronous RPC and pub-sub over websocket connections using redis as broker. It refers both to a websocket subprotocol and the implementation of a juggler server. The repository also contains implementations of the callee, broker and client roles.

This is still experimental. Use at your own risk. Not battle-tested in production environment. API may change. Javascript (and other languages) client not implemented yet.

Architecture

In a nutshell, the architecture looks like this:

     +------------+
     |            |-+
     | (a) Client | |
     |            | |
     +------------+ |
       +------------+
           ^
           |
           v
     +------------+
     |            |
     | (b) Server |
     |            |
     +------------+
           ^
           |
           v
     +-----------+                +------------+
     |           |                |            |-+
     | (c) Redis | <------------> | (d) Callee | |
     | (pub-sub) |                |    (RPC)   | |
     |           |                |            | |
     +-----------+                +------------+ |
                                    +------------+
  • (a) Client is a juggler client.

    • It can be any kind of client - a web browser, a mobile application, a message queue worker process, anything that can make websocket connections.
    • It only communicates with the juggler server.
    • It can make RPC requests (CALL), subscribe to (SUB) and unsubscribe from (UNSB) pub-sub channels, and publish events (PUB).
  • (b) Server is the juggler server.

    • It listens for websocket connections and accepts clients that support the juggler subprotocol.
    • It acknowledges (ACK or NACK in case of failure) RPC and pub-sub client requests, and sends RPC results (RES) and pub-sub events (EVNT) to the clients.
    • It uses a broker.CallerBroker to make RPC calls and a broker.PubSubBroker to handle pub-sub subscriptions and events via redis.
    • For scalability and high availability, multiple servers can be used behind a websocket load balancer, e.g. using Caddy.
  • (c) Redis is the broker.

    • For RPC requests, the call payload is stored in a list with a corresponding key holding its time-to-live (TTL) before the request expires. Callees listen for calls on those lists, execute the corresponding function, and store the result payload in another list identified by the client connection.
    • For pub-sub, the native pub-sub support of redis is used.
    • For scalability and high availability, redis cluster is supported, and a different redis server (or cluster) can be used for RPC and for pub-sub.
  • (d) Callee exposes RPC functions via a URI.

    • It listens for call requests, executes the corresponding function, and stores the result payload via a broker.CalleeBroker interface, which is responsible for the communication with redis.
    • It can be added and removed completely independently of the running application.
    • It only depends on redis, not on any other part of the system.

Goals

The project was initially inspired by the Web Application Messaging Protocol (WAMP). This is an application protocol that aims to work on a variety of transports (including websockets) with a focus on supporting embedded devices / the "internet of things", and any peer can be a caller and a callee. The specification is still a work-in-progress and as such, the protocol is a bit of a moving target, but it is a very interesting and ambitious project that you should check out if you're interested in something like this.

While trying to implement a WAMP router, I started thinking about a simpler, less ambitious implementation that would support what I feel are the most typically useful features using a battle-tested, scalable backend as broker - redis - instead of having a new piece of software manage the state (the router in the WAMP specification). Out-of-the-box, redis supports horizontal scaling and failover via the redis cluster, and it natively supports pub-sub. It also offers all the required primitives to handle RPC.

The goals of the juggler protocol and implementation are, in no specific order:

  • Simplicity - the "protocol" is really just a pre-defined set of JSON-encoded messages exchanged over websockets: "CALL", "SUB", "UNSB" and "PUB" for clients, "ACK, "NACK", "RES" and "EVNT" for servers.
  • Minimalism - it offers basic RPC and pub-sub primitives, leaving more specific behaviour to the applications.
  • Scalability - via redis cluster and a websocket load balancer in front of multiple juggler servers, and independently managed instances of callees, there is scale-out support for juggler-based applications.
  • Focused on web/mobile application development - web browsers and mobile applications are the target clients, embedded devices are not an explicit concern.

Additional information about the design rationale can be found in doc/rationale.md.

Installation

Make sure you have the Go programming language properly installed, then run in a terminal:

$ go get [-u] [-t] github.com/mna/juggler/...

The juggler packages use the following external dependencies (excluding test dependencies):

Documentation

The godoc package documentation is the canonical source of documentation. This README provides additional documentation of high-level usage of the various packages.

Getting Started

Implement an RPC callee
// from package callee, file example_test.go
import (
	"encoding/json"
	"log"
	"sync"
	"time"

	"github.com/mna/juggler/broker/redisbroker"
	"github.com/mna/juggler/callee"
	"github.com/mna/juggler/message"
	"github.com/garyburd/redigo/redis"
)

const (
	redisAddr = ":6379"
	nWorkers  = 10
	calleeURI = "example.echo"
)

func Example() {
	// create a redis pool
	pool := &redis.Pool{
		MaxIdle:     10,
		MaxActive:   100,
		IdleTimeout: 30 * time.Second,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", redisAddr)
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	// create a redis broker
	broker := &redisbroker.Broker{
		Pool:      pool,
		Dial:      pool.Dial,
		ResultCap: 1000,
	}

	// create the callee
	c := &callee.Callee{Broker: broker}

	// get the Calls connection, that will stream the call requests to
	// the specified URIs (here, only one) on a channel.
	cc, err := broker.NewCallsConn(calleeURI)
	if err != nil {
		log.Fatalf("NewCallsConn failed: %v", err)
	}
	defer cc.Close()

	// start n workers
	wg := sync.WaitGroup{}
	wg.Add(nWorkers)
	for i := 0; i < nWorkers; i++ {
		go func() {
			defer wg.Done()

			// cc.Calls returns the same channel on each call, so all
			// workers actually process requests from the same channel.
			ch := cc.Calls()
			for cp := range ch {
				if err := c.InvokeAndStoreResult(cp, echoThunk); err != nil {
					if err != callee.ErrCallExpired {
						log.Printf("InvokeAndStoreResult failed: %v", err)
						continue
					}
					log.Printf("expired request %v %s", cp.MsgUUID, cp.URI)
					continue
				}
				log.Printf("sent result %v %s", cp.MsgUUID, cp.URI)
			}
		}()
	}
	wg.Wait()

	// runs forever, in a main command we could listen to a signal
	// using signal.Notify and close the Calls connection when e.g. SIGINT
	// is received, and then wait on the wait group for proper termination.
}

// the Thunk for the example.echo URI, it simply extracts the arguments
// and acts as the wrapper/type-checker for the actual echo function.
func echoThunk(cp *message.CallPayload) (interface{}, error) {
	var s string
	if err := json.Unmarshal(cp.Args, &s); err != nil {
		return nil, err
	}
	return echo(s), nil
}

// simply return the same value that was received.
func echo(s string) string {
	return s
}
Implement a juggler server
// from package juggler, file example_test.go
import (
	"log"
	"net/http"
	"time"

	"github.com/mna/juggler"
	"github.com/mna/juggler/broker/redisbroker"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/websocket"
)

// This example shows how to set up a juggler server and serve connections.
func Example() {
	const redisAddr = ":6379"

	// create a redis pool
	pool := &redis.Pool{
		MaxIdle:     10,
		MaxActive:   100,
		IdleTimeout: 30 * time.Second,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", redisAddr)
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	// create a redis broker
	broker := &redisbroker.Broker{
		Pool:    pool,
		Dial:    pool.Dial,
		CallCap: 1000,
	}

	// create a juggler server using the broker (in this case, use the
	// same redis server for RPC and pub-sub).
	server := &juggler.Server{
		CallerBroker: broker,
		PubSubBroker: broker,
	}

	// create a websocket connection upgrader, that will negociate the
	// subprotocol using the list of supported juggler subprotocols.
	upgrader := &websocket.Upgrader{
		Subprotocols: juggler.Subprotocols,
	}
	// get the upgrade HTTP handler and register it as handler for the
	// /ws path.
	upgh := juggler.Upgrade(upgrader, server)
	http.Handle("/ws", upgh)

	// start the HTTP server, connect to :9000/ws to make juggler connections.
	if err := http.ListenAndServe(":9000", nil); err != nil {
		log.Fatalf("ListenAndServe failed: %v", err)
	}
}
Experiment in docker

The repository contains docker and docker-compose files to quickly start a test juggler environment. It starts a redis node, a juggler server, a callee and a client. Provided you have docker properly installed, run the following in the juggler repository's root directory:

# for convenience, use the COMPOSE_FILE environment variable to avoid typing
# the compose file every time.
$ export COMPOSE_FILE=docker/docker-compose.1.yml

$ docker-compose build

$ docker-compose up -d

$ docker-compose run client

# once done playing with the test environment, run:
$ docker-compose stop

This will start the interactive client to make calls to the juggler server. This test environment registers 3 RPC URIs:

  • test.echo : returns whatever string was passed as parameter.
  • test.reverse : reverses the string passed as parameter.
  • test.delay : sleeps for N millisecond, N being the number passed as parameter.

Enter connect to start a new connection (you can start many connections in the same interactive session). Enter help to get the list of available commands and expected arguments. Type exit to terminate the session.

Performance

Juggler has been load-tested in various configurations on digital ocean's droplets. Before talking about "how fast" it is, it is useful to first define "fast" in relative terms. Tests have been executed on a DO droplet for redis LPUSH and BRPOP to figure out the maximum throughput, then with tests closer to what juggler does (juggler doesn't just push a value on a list, it marshals/unmarshals a struct to JSON, and runs a lua script in redis to push the value on a list and set an expiration key atomically, so each RPC call has a TTL after which the result is discarded). This helps get a good idea of what the ideal throughput can be, and then compare that to the actual juggler throughput.

All tests were run on the smallest 512Mb droplet in the Toronto region. The redis tests are executed locally. The juggler tests go through the whole layers (client <-> server <-> redis <-> callee) using the following nodes:

  • 1 droplet for redis (3 for redis cluster)
  • 1 droplet for the callee
  • 1 droplet for the server (3 for the load balancing test)
  • 1 droplet for the Caddy load balancer (only for the load balancing test)
  • 1 droplet for the load tester
Scenario Throughput
Pure LPUSH / BRPOP (local test) 13000 / second
LPUSH / BRPOP with JSON marshal/unmarshal of struct (local test) 10000 / second
JSON structs with Lua script that handles the list and the TTL key (local test) 4200 / second
Juggler clients RPC calls (single server, standalone redis) 2000 / second
Juggler clients RPC calls (3 load-balanced servers, 3 nodes redis cluster) 3000 / second

The numbers per second for the juggler tests represent a full CALL - ACK - RES, meaning the call was processed by the callee and the result was received by the client. The client-measured latencies during those juggler load tests was below 100ms per call for the median, and below 400ms for the 99th percentile. Pub-sub was not stress-tested as it uses the native redis feature and there are some benchmarks already available for this. As always with benchmarks, you should run your own in your own environment and representative use-case. There is a script in misc/perf/run-do-test.sh that can help you with this, make sure you understand the script before running it using your digital ocean account. Many raw test results are available in this misc/perf directory.

License

The BSD 3-Clause license, the same as the Go language.

Documentation

Overview

Package juggler implements a websocket-based, redis-backed RPC and pub-sub server. RPC (remote procedure call) requests are routed to a URI, and the response is asynchronous and is only sent to the calling client. Pub-sub events are also asynchronous but are routed to every client subscribed to the channel on which the event is published. Only active clients receive the event - this is not to be confused with a reliable message queue.

All messages sent by the client receive an acknowledge message (ACK) when processed successfully or a negative acknowledge (NACK) if the request was rejected. See the message package documentation for all details regarding the supported messages.

Server

The Server struct defines a juggler server. In its simplest form, the following initializes a ready-to-use server:

broker := &redisbroker.Broker{...} // initialize a broker
server := &juggler.Server{
  PubSubBroker: broker,
  CallerBroker: broker,
}

That is, only the pub-sub and caller brokers must be set for the server to start serving connections. The broker is typically a redisbroker.Broker, although it can be any value that implements the broker.PubSubBroker and broker.CallerBroker interfaces, respectively.

Additional fields allow for more advanced configuration, such as read and write timeouts and limits, and custom message handling, via the Handler. Metrics can be collected by setting the Vars field to an *expvar.Map. See the Server type documentation for all details.

The ServeConn method serves a connection using a configured Server. The Upgrade function creates an http.Handler that upgrades the HTTP connection to a websocket connection, and serves it using the provided Server.

Because HTTP/2 does not support websockets, the HTTP server used to run the juggler server must not use HTTP/2. Since Go1.6, HTTP/2 is automatically enabled over HTTPS. See https://golang.org/doc/go1.6#http2 for details on how to explicitly disable it.

One or many callees must be registered to listen for RPC requests. The callees are decoupled from the server, with redis acting as the broker. The pub-sub part is handled natively by redis. See the callee package for details.

Conn

The Conn struct represents a websocket connection to a juggler server. To be accepted by the server, the connection must accept one of the subprotocols supported by the server (the Subprotocols package variable). The negociated subprotocol is available via the Subprotocol connection method.

A connection listens for its RPC call results, pub-sub events and requests from the client end, and ensures the messages flow from client to server and back as needed.

Some client connections may know ahead of time that they won't make any RPC calls, or won't subscribe to any pub-sub channel, etc. In that case, the Juggler-Allowed-Messages header can be set on the HTTP request that initiates the connection with a restricted list of allowed messages, e.g.:

http.Header{"Juggler-Allowed-Messages": {"call, pub"}}

The value is a comma-separated list of allowed messages. When that header is non-empty and not *, only the specified messages are allowed. This leads to a more efficient server-side connection and ensures the connection behaves as advertised, otherwise the connection is closed.

Handler

By default, when Server.Handler is nil, each message sent or received by the Server goes through the ProcessMsg function, which implements the standard behaviour for each message type.

A custom handler can be set to implement middleware-style behaviour, similar to the stdlib's net/http server. When Handler is not nil, it is the responsibility of the handler to eventually call ProcessMsg so that the messages produce the expected results.

Typical use of handlers can be:

  • to implement logging of requests/responses
  • to recover in case of panics
  • to implement authentication for some requests
  • to implement authorization checks for some requests
  • etc.

If a handler detects that the message cannot be executed as requested, e.g. because the caller is not authenticated or doesn't have access to the requested RPC URI, ProcessMsg must not be called. The message must be "intercepted" before the call to ProcessMsg, and a NACK reply must be returned to the client to let it know that the request will not be processed.

To do so, the Conn offers the Send method, for example:

func CheckAccessHandler(ctx context.Context, c *juggler.Conn, m message.Msg) {
  // assume we detected that the caller doesn't have access, in the ok variable
  if !ok {
    nack := message.NewNack(m, 403, errors.New("caller doesn't have access"))
    c.Send(nack)
    return
  }
}

The Send method makes sure the provided message goes through the handler too, so typically a handler would implement conditional checks depending on the type of the message. Authentication and authorization, for example, only make sense on request messages (messages coming from the client), which have their Type.IsRead method return true. Responses (messages sent by the server) have their Type.IsWrite method return true.

A new context.Context is passed for each message processed to maintain values for the duration of a specific message.

Example

This example shows how to set up a juggler server and serve connections.

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/websocket"
	"github.com/mna/juggler"
	"github.com/mna/juggler/broker/redisbroker"
)

func main() {
	const redisAddr = ":6379"

	// create a redis pool
	pool := &redis.Pool{
		MaxIdle:     10,
		MaxActive:   100,
		IdleTimeout: 30 * time.Second,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", redisAddr)
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	// create a redis broker
	broker := &redisbroker.Broker{
		Pool:    pool,
		Dial:    pool.Dial,
		CallCap: 1000,
	}

	// create a juggler server using the broker (in this case, use the
	// same redis server for RPC and pub-sub).
	server := &juggler.Server{
		CallerBroker: broker,
		PubSubBroker: broker,
	}

	// create a websocket connection upgrader, that will negociate the
	// subprotocol using the list of supported juggler subprotocols.
	upgrader := &websocket.Upgrader{
		Subprotocols: juggler.Subprotocols,
	}
	// get the upgrade HTTP handler and register it as handler for the
	// /ws path.
	upgh := juggler.Upgrade(upgrader, server)
	http.Handle("/ws", upgh)

	// start the HTTP server, connect to :9000/ws to make juggler connections.
	if err := http.ListenAndServe(":9000", nil); err != nil {
		log.Fatalf("ListenAndServe failed: %v", err)
	}
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var SlowProcessMsgThreshold = 100 * time.Millisecond

SlowProcessMsgThreshold defines the threshold at which calls to ProcessMsg are marked as slow in the expvar metrics, if Server.Vars is set. Set to 0 to disable SlowProcessMsg metrics.

View Source
var Subprotocols = []string{
	"juggler.0",
}

Subprotocols is the list of juggler protocol versions supported by this package. It should be set as-is on the websocket.Upgrader Subprotocols field.

Functions

func AllowedMessagesFromHeader

func AllowedMessagesFromHeader(h http.Header) []message.Type

AllowedMessagesFromHeader returns the slice of allowed message types as specified in the Juggler-Allowed-Messages header stored in h. If the header is not present, is empty or is "*", an empty slice is returned, meaning that all messages are allowed. The returned slice can be passed as-is to ServeConn.

func ProcessMsg

func ProcessMsg(c *Conn, m message.Msg)

ProcessMsg implements the standard message processing. For requests (client-sent messages), it calls the appropriate RPC or pub-sub mechanisms. For responses (server-sent messages), it marshals the message and sends it to the client. If a write to the connection fails, the connection is closed and the write error is stored as CloseErr on the connection (unless an earlier error already caused the connection to close).

When a custom Handler is set on the Server, it should at some point call ProcessMsg so the expected behaviour happens.

func Upgrade

func Upgrade(upgrader *websocket.Upgrader, srv *Server) http.Handler

Upgrade returns an http.Handler that upgrades connections to the websocket protocol using upgrader. The websocket connection must be upgraded to a supported juggler subprotocol otherwise the connection is dropped.

Once connected, the websocket connection is served via srv.ServeConn. The websocket connection is closed when the juggler connection is closed.

If the Juggler-Allowed-Messages header is set on the request, the connection is restricted to that set of message types. The value is a comma-separated list of request message types:

Any of "call, sub, unsb, pub"
"*" can be used for any message type (same as if the header wasn't there)

Types

type Conn

type Conn struct {
	// UUID is the unique identifier of the connection.
	UUID uuid.UUID

	// CloseErr is the error, if any, that caused the connection
	// to close. Must only be accessed after the close notification
	// has been received (i.e. after a <-conn.CloseNotify()).
	CloseErr error
	// contains filtered or unexported fields
}

Conn is a juggler connection. Each connection is identified by a UUID and has an underlying websocket connection. It is safe to call methods on a Conn concurrently, but the fields should be treated as read-only.

func (*Conn) Close

func (c *Conn) Close(err error)

Close closes the connection, setting err as CloseErr to identify the reason of the close. It does not send a websocket close message, nor does it close the underlying websocket connection. As with all Conn methods, it is safe to call concurrently, but only the first call will set the CloseErr field to err.

func (*Conn) CloseNotify

func (c *Conn) CloseNotify() <-chan struct{}

CloseNotify returns a signal channel that is closed when the Conn is closed.

func (*Conn) LocalAddr

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

LocalAddr returns the local network address.

func (*Conn) RemoteAddr

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

RemoteAddr returns the remote network address.

func (*Conn) Send

func (c *Conn) Send(m message.Msg)

Send sends the message to the client. It calls the server's Handler if any, or ProcessMsg if nil.

func (*Conn) Subprotocol

func (c *Conn) Subprotocol() string

Subprotocol returns the negotiated protocol for the connection.

func (*Conn) UnderlyingConn

func (c *Conn) UnderlyingConn() *websocket.Conn

UnderlyingConn returns the underlying websocket connection. Care should be taken when using the websocket connection directly, as it may interfere with the normal juggler connection behaviour.

func (*Conn) Writer

func (c *Conn) Writer(timeout time.Duration) io.WriteCloser

Writer returns an io.WriteCloser that can be used to send a message on the connection. Only one writer can be active at any moment for a given connection, so the returned writer will acquire a lock on the first call to Write, and will release it only when Close is called. The timeout controls the time to wait to acquire the lock on the first call to Write. If the lock cannot be acquired within that time, an error is returned and no write is performed.

It is possible to enter a deadlock state if Writer is called with no timeout, an initial Write is executed, and Writer is called again from the same goroutine, without a timeout. To avoid this, make sure each goroutine closes the Writer before asking for another one, and ideally always use a timeout.

The returned writer itself is not safe for concurrent use, but as all Conn methods, Writer can be called concurrently.

type ConnState

type ConnState int

ConnState represents the possible states of a connection.

const (
	Unknown ConnState = iota
	Accepting
	Connected
	Closed
)

The list of possible connection states.

type Handler

type Handler interface {
	Handle(context.Context, *Conn, message.Msg)
}

Handler defines the method required for a server to handle a send or receive of a Msg over a connection.

type HandlerFunc

type HandlerFunc func(context.Context, *Conn, message.Msg)

HandlerFunc is a function signature that implements the Handler interface.

func (HandlerFunc) Handle

func (h HandlerFunc) Handle(ctx context.Context, c *Conn, m message.Msg)

Handle implements Handler for the HandlerFunc by calling the function itself.

type Server

type Server struct {

	// ReadLimit defines the maximum size, in bytes, of incoming
	// messages. If a client sends a message that exceeds this limit,
	// the connection is closed. The default of 0 means no limit.
	ReadLimit int64

	// ReadTimeout is the timeout to read an incoming message. It is
	// set on the websocket connection with SetReadDeadline before
	// reading each message. The default of 0 means no timeout.
	ReadTimeout time.Duration

	// WriteLimit defines the maximum size, in bytes, of outgoing
	// messages. If a message exceeds this limit, the connection is
	// closed. The default of 0 means no limit.
	WriteLimit int64

	// WriteTimeout is the timeout to write an outgoing message. It is
	// set on the websocket connection with SetWriteDeadline before
	// writing each message. The default of 0 means no timeout.
	WriteTimeout time.Duration

	// AcquireWriteLockTimeout is the time to wait for the exclusive
	// write lock for a connection. If the lock cannot be acquired
	// before the timeout, the connection is dropped. The default of
	// 0 means no timeout.
	AcquireWriteLockTimeout time.Duration

	// ConnState specifies an optional callback function that is called
	// when a connection changes state. If non-nil, it is called for
	// Accepting, Connected and Closed states. Closed means the
	// juggler connection is closed, the underlying websocket connection
	// may stay connected. It is safe to access the connection's
	// CloseErr field in the Closed state.
	//
	// The possible state transitions are:
	//
	//     Accepting -> Closed (if the server failed to setup the connection)
	//     Accepting -> Connected
	//     Connected -> Closed
	ConnState func(*Conn, ConnState)

	// Handler is the handler that is called when a message is
	// processed. The ProcessMsg function is called if the default
	// nil value is set. If a custom handler is set, it is assumed
	// that it will call ProcessMsg at some point, or otherwise
	// manually process the messages.
	Handler Handler

	// PubSubBroker is the broker to use for pub-sub messages. It must be
	// set before the Server can be used.
	PubSubBroker broker.PubSubBroker

	// CallerBroker is the broker to use for caller messages. It must be
	// set before the server can be used.
	CallerBroker broker.CallerBroker

	// Vars can be set to an *expvar.Map to collect metrics about the
	// server.
	Vars *expvar.Map
	// contains filtered or unexported fields
}

Server is a juggler server. Once a websocket handshake has been established with a juggler subprotocol over a standard HTTP server, the connections can get served by this server by calling Server.ServeConn.

The fields should not be updated once a server has started serving connections.

func (*Server) ServeConn

func (srv *Server) ServeConn(conn *websocket.Conn, allowedMsgs ...message.Type)

ServeConn serves the websocket connection as a juggler connection. It blocks until the juggler connection is closed, leaving the websocket connection open. If allowedMsgs is not empty, only those message types are allowed on that connection.

Directories

Path Synopsis
Package broker defines the generic interfaces that a broker must implement in order to act as a juggler broker.
Package broker defines the generic interfaces that a broker must implement in order to act as a juggler broker.
redisbroker
Package redisbroker implements a juggler broker using redis as backend.
Package redisbroker implements a juggler broker using redis as backend.
Package callee implements the Callee type to use to listen for and process RPC call requests.
Package callee implements the Callee type to use to listen for and process RPC call requests.
Package client implements a juggler client.
Package client implements a juggler client.
cmd
juggler-callee command
Command juggler-callee implements a testing callee that provides simple test RPC URI endpoints.
Command juggler-callee implements a testing callee that provides simple test RPC URI endpoints.
juggler-client command
Command juggler-client is an interactive command-line tool to send commands to a juggler server.
Command juggler-client is an interactive command-line tool to send commands to a juggler server.
juggler-direct-call command
Command juggler-direct-call implements a test caller that directly sends to redis, without a server and a client.
Command juggler-direct-call implements a test caller that directly sends to redis, without a server and a client.
juggler-load command
Command juggler-load is a juggler load generator.
Command juggler-load is a juggler load generator.
juggler-server command
Command juggler-server implements a juggler server that listens for connections and serves the requests.
Command juggler-server implements a juggler server that listens for connections and serves the requests.
redis-direct command
Command redis-direct implements different load-testing scenarios against a redis database, to determine the upper limit that juggler could theoretically reach.
Command redis-direct implements different load-testing scenarios against a redis database, to determine the upper limit that juggler could theoretically reach.
internal
srvhandler
Package srvhandler implements server handlers used by the juggler-server command and various tests.
Package srvhandler implements server handlers used by the juggler-server command and various tests.
wstest
Package wstest provides test helpers for dealing with websocket servers and connections.
Package wstest provides test helpers for dealing with websocket servers and connections.
wswriter
Package wswriter implements an exclusive writer and a limited writer for websocket connections.
Package wswriter implements an exclusive writer and a limited writer for websocket connections.
Package message defines the supported types of messages in the juggler protocol.
Package message defines the supported types of messages in the juggler protocol.

Jump to

Keyboard shortcuts

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