signalr

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2018 License: MIT Imports: 17 Imported by: 0

README

GoDoc CircleCI Go Report Card Maintainability codecov

Overview

This is my personal attempt at implementating the client side of the WebSocket portion of the SignalR protocol. I use it for various virtual currency trading platforms that use SignalR.

It supports CloudFlare-protected sites by default.

Example basic usage

package main

import (
	"log"

	"github.com/carterjones/signalr"
)

func main() {
	host := "myhost.not-real-tld"
	protocol := "some-protocol-version-123"
	endpoint := "/usually/something/like/this"
	connectionData := `{"custom":"data"}`

	// Prepare a SignalR client.
	c := signalr.New(host, protocol, endpoint, connectionData)

	// Start the connection.
	msgs, errs, err := c.Run()
	if err != nil {
		log.Panic(err)
	}

	// Process messages and errors.
	for {
		select {
		case msg := <-msgs:
			// Handle the message.
			log.Println(msg)
		case err := <-errs:
			// Handle the error.
			log.Panic(err)
		}
	}
}

Example complex usage

package main

import (
	"log"

	"github.com/carterjones/signalr"
)

func main() {
	host := "myhost.not-real-tld"
	protocol := "some-protocol-version-123"
	endpoint := "/usually/something/like/this"
	connectionData := `{"custom":"data"}`

	// Prepare a SignalR client.
	c := signalr.New(host, protocol, endpoint, connectionData)

	// Perform any optional modifications to the client here. Read the docs for
	// all the available options that are exposed via public fields.

	// Manually perform the initialization routine.
	err := c.Negotiate()
	if err != nil {
		log.Panic(err)
	}
	conn, err := c.Connect()
	if err != nil {
		log.Panic(err)
	}
	err = c.Start(conn)
	if err != nil {
		log.Panic(err)
	}

	// Create message and error channels.
	msgs := make(chan signalr.Message)
	errs := make(chan error)

	// Begin the message reading loop.
	go c.ReadMessages(msgs, errs)

	// Process messages and errors.
	for {
		select {
		case msg := <-msgs:
			// Handle the message.
			log.Println(msg)
		case err := <-errs:
			// Handle the error.
			log.Panic(err)
		}
	}
}

Documentation

Contribute

If anything is unclear or could be improved, please open an issue or submit a pull request. Thanks!

Documentation

Overview

Package signalr provides the client side implementation of the WebSocket portion of the SignalR protocol.

First things first: this was almost entirely written using https://blog.3d-logic.com/2015/03/29/signalr-on-the-wire-an-informal-description-of-the-signalr-protocol/ as a reference guide. It is an excellent technical write-up. Many thanks to Pawel Kadluczka for writing that and sharing it with the public. If you want deep-dive technical details of how this all works, read that blog. I won't try to replicate it here.

At a high level, the WebSocket portion of SignalR goes through the following steps:

  • negotiate: use HTTP/HTTPS to get connection info for how to connect to the websocket endpoint
  • connect: attempt to connect to the websocket endpoint
  • start: make the WebSocket connection usable by SignalR connections

See the provided examples for how to use this library.

Example (Basic)

This example shows the most basic way to start a websocket connection.

package main

import (
	"log"

	"github.com/carterjones/signalr"
)

func main() {
	host := "myhost.not-real-tld"
	protocol := "some-protocol-version-123"
	endpoint := "/usually/something/like/this"
	connectionData := `{"custom":"data"}`

	// Prepare a SignalR client.
	c := signalr.New(host, protocol, endpoint, connectionData)

	// Start the connection.
	msgs, errs, err := c.Run()
	if err != nil {
		log.Panic(err)
	}

	// Process messages and errors.
	for {
		select {
		case msg := <-msgs:
			// Handle the message.
			log.Println(msg)
		case err := <-errs:
			// Handle the error.
			log.Panic(err)
		}
	}
}
Output:

Example (Complex)

This example shows how to manually perform each of the initialization steps.

package main

import (
	"log"

	"github.com/carterjones/signalr"
)

func main() {
	host := "myhost.not-real-tld"
	protocol := "some-protocol-version-123"
	endpoint := "/usually/something/like/this"
	connectionData := `{"custom":"data"}`

	// Prepare a SignalR client.
	c := signalr.New(host, protocol, endpoint, connectionData)

	// Perform any optional modifications to the client here. Read the docs for
	// all the available options that are exposed via public fields.

	// Manually perform the initialization routine.
	err := c.Negotiate()
	if err != nil {
		log.Panic(err)
	}
	conn, err := c.Connect()
	if err != nil {
		log.Panic(err)
	}
	err = c.Start(conn)
	if err != nil {
		log.Panic(err)
	}

	// Create message and error channels.
	msgs := make(chan signalr.Message)
	errs := make(chan error)

	// Begin the message reading loop.
	go c.ReadMessages(msgs, errs)

	// Process messages and errors.
	for {
		select {
		case msg := <-msgs:
			// Handle the message.
			log.Println(msg)
		case err := <-errs:
			// Handle the error.
			log.Panic(err)
		}
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func TestCompleteHandler added in v0.1.4

func TestCompleteHandler(w http.ResponseWriter, r *http.Request)

TestCompleteHandler combines the negotiate, connect, reconnect, and start handlers found in this package into one complete response handler.

func TestConnect added in v0.1.4

func TestConnect(w http.ResponseWriter, r *http.Request)

TestConnect provides a sample "/connect" handling function.

If an error occurs while upgrading the websocket, it will panic.

func TestNegotiate added in v0.1.4

func TestNegotiate(w http.ResponseWriter, r *http.Request)

TestNegotiate provides a sample "/negotiate" handling function.

If an error occurs while writing the response data, it will panic.

func TestReconnect added in v0.1.4

func TestReconnect(w http.ResponseWriter, r *http.Request)

TestReconnect provides a sample "/reconnect" handling function. It simply calls TestConnect.

func TestStart added in v0.1.4

func TestStart(w http.ResponseWriter, r *http.Request)

TestStart provides a sample "/start" handling function.

If an error occurs while writing the response data, it will panic.

Types

type Client

type Client struct {
	// The host providing the SignalR service.
	Host string

	// The relative path where the SignalR service is provided.
	Endpoint string

	// The websockets protocol version.
	Protocol string

	ConnectionData string

	// The HTTPClient used to initialize the websocket connection.
	HTTPClient *http.Client

	// An optional setting to provide a non-default TLS configuration to use
	// when connecting to the websocket.
	TLSClientConfig *tls.Config

	// Either HTTPS or HTTP.
	Scheme Scheme

	// The maximum number of times to re-attempt a negotiation.
	MaxNegotiateRetries int

	// The maximum number of times to re-attempt a connection.
	MaxConnectRetries int

	// The maximum number of times to re-attempt a reconnection.
	MaxReconnectRetries int

	// The maximum number of times to re-attempt a start command.
	MaxStartRetries int

	// The time to wait before retrying, in the event that an error occurs
	// when contacting the SignalR service.
	RetryWaitDuration time.Duration

	// This is the connection token set during the negotiate phase of the
	// protocol and used to uniquely identify the connection to the server
	// in all subsequent phases of the connection.
	ConnectionToken string

	// This is the ID of the connection. It is set during the negotiate
	// phase and then ignored by all subsequent steps.
	ConnectionID string

	// Header values that should be applied to all HTTP requests.
	Headers map[string]string

	// This value is not part of the SignalR protocol. If this value is set,
	// it will be used in debug messages.
	CustomID string
	// contains filtered or unexported fields
}

Client represents a SignlR client. It manages connections so that the caller doesn't have to.

func New

func New(host, protocol, endpoint, connectionData string) *Client

New creates and initializes a SignalR client.

func (*Client) Close

func (c *Client) Close()

Close sends a signal to the loop reading WebSocket messages to indicate that the loop should terminate.

func (*Client) Conn

func (c *Client) Conn() WebsocketConn

Conn returns the underlying websocket connection.

func (*Client) Connect

func (c *Client) Connect() (*websocket.Conn, error)

Connect implements the connect step of the SignalR connection sequence.

func (*Client) Negotiate

func (c *Client) Negotiate() error

Negotiate implements the negotiate step of the SignalR connection sequence.

func (*Client) ReadMessages

func (c *Client) ReadMessages(msgCh chan Message, errCh chan error)

ReadMessages processes WebSocket messages from the underlying websocket connection. When a message is processed, it is passed along the msgCh channel. When an error ocurrs, it is sent along the errCh channel.

func (*Client) Reconnect

func (c *Client) Reconnect() (*websocket.Conn, error)

Reconnect implements the reconnect step of the SignalR connection sequence.

func (*Client) Run

func (c *Client) Run() (chan Message, chan error, error)

Run connects to the host and performs the websocket initialization routines that are part of the SignalR specification. It returns channels that:

  • receive messages from the websocket connection
  • receive errors encountered while processing the weblocket connection
Example
package main

import (
	"log"
	"time"

	"github.com/carterjones/signalr"
)

func main() {
	// Prepare a SignalR client.
	c := signalr.New("fake-server.definitely-not-real", "123", "my-endpoint", "special connection data")

	// Start the connection.
	msgs, errs, err := c.Run()
	if err != nil {
		log.Panic(err)
	}

	// Process messages and errors.
	for {
		select {
		case msg := <-msgs:
			// Handle the message.
			log.Println(msg)
		case err := <-errs:
			// Handle the error.
			log.Panic(err)
		case <-time.After(2 * time.Second):
			log.Println("exiting, since we haven't sente messages")
		}
	}
}
Output:

func (*Client) Send

func (c *Client) Send(m hubs.ClientMsg) error

Send sends a message to the websocket connection.

func (*Client) SetConn

func (c *Client) SetConn(conn WebsocketConn)

SetConn changes the underlying websocket connection to the specified connection. This is done using a mutex to wait until existing read operations have completed.

func (*Client) Start

func (c *Client) Start(conn WebsocketConn) error

Start implements the start step of the SignalR connection sequence.

type JSONWriter

type JSONWriter interface {
	WriteJSON(v interface{}) error
}

JSONWriter is the interface that wraps WriteJSON.

WriteJSON is defined at https://godoc.org/github.com/gorilla/websocket#Conn.WriteJSON

At a high level, it writes a structure to the underlying websocket and returns any error that was encountered during the write operation.

type Message

type Message struct {
	// message id, present for all non-KeepAlive messages
	C string

	// an array containing actual data
	M []hubs.ClientMsg

	// indicates that the transport was initialized (a.k.a. init message)
	S int

	// groups token – an encrypted string representing group membership
	G string

	// other miscellaneous variables that sometimes are sent by the server
	I string
	E string
	R json.RawMessage
	H json.RawMessage // could be bool or string depending on a message type
	D json.RawMessage
	T json.RawMessage
}

Message represents a message sent from the server to the persistent websocket connection.

type MessageReader

type MessageReader interface {
	ReadMessage() (messageType int, p []byte, err error)
}

MessageReader is the interface that wraps ReadMessage.

ReadMessage is defined at https://godoc.org/github.com/gorilla/websocket#Conn.ReadMessage

At a high level, it reads messages and returns:

  • the type of message read
  • the bytes that were read
  • any errors encountered during reading the message

type Scheme

type Scheme string

Scheme represents a type of transport scheme. For the purposes of this project, we only provide constants for schemes relevant to HTTP and websockets.

const (
	// HTTPS is the literal string, "https".
	HTTPS Scheme = "https"

	// HTTP is the literal string, "http".
	HTTP Scheme = "http"

	// WSS is the literal string, "wss".
	WSS Scheme = "wss"

	// WS is the literal string, "ws".
	WS Scheme = "ws"
)

type WebsocketConn

type WebsocketConn interface {
	MessageReader
	JSONWriter
}

WebsocketConn is a combination of MessageReader and JSONWriter. It is used to provide an interface to objects that can read from and write to a websocket connection.

Directories

Path Synopsis
Package hubs provides functionality used by the SignalR Hubs API.
Package hubs provides functionality used by the SignalR Hubs API.

Jump to

Keyboard shortcuts

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