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)
}
}
Output:
Index ¶
- Variables
- func AllowedMessagesFromHeader(h http.Header) []message.Type
- func ProcessMsg(c *Conn, m message.Msg)
- func Upgrade(upgrader *websocket.Upgrader, srv *Server) http.Handler
- type Conn
- func (c *Conn) Close(err error)
- func (c *Conn) CloseNotify() <-chan struct{}
- func (c *Conn) LocalAddr() net.Addr
- func (c *Conn) RemoteAddr() net.Addr
- func (c *Conn) Send(m message.Msg)
- func (c *Conn) Subprotocol() string
- func (c *Conn) UnderlyingConn() *websocket.Conn
- func (c *Conn) Writer(timeout time.Duration) io.WriteCloser
- type ConnState
- type Handler
- type HandlerFunc
- type Server
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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) RemoteAddr ¶
RemoteAddr returns the remote network address.
func (*Conn) Send ¶
Send sends the message to the client. It calls the server's Handler if any, or ProcessMsg if nil.
func (*Conn) Subprotocol ¶
Subprotocol returns the negotiated protocol for the connection.
func (*Conn) UnderlyingConn ¶
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 Handler ¶
Handler defines the method required for a server to handle a send or receive of a Msg over a connection.
type HandlerFunc ¶
HandlerFunc is a function signature that implements the Handler interface.
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.
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. |

