package
0.0.0-20231208201552-dfbb574e5c80
Repository: https://github.com/mdaxf/signalrsrv.git
Documentation: pkg.go.dev

# README

SignalR

Actions Status codecov PkgGoDev

SignalR is an open-source library that simplifies adding real-time web functionality to apps. Real-time web functionality enables server-side code to push content to clients instantly.

Historically it was tied to ASP.NET Core but the protocol is open and implementable in any language.

This repository contains an implementation of a SignalR server and a SignalR client in go. The implementation is based on the work of David Fowler at https://github.com/davidfowl/signalr-ports. Client and server support transport over WebSockets, Server Sent Events and raw TCP. Protocol encoding in JSON and MessagePack is fully supported.

Install

With a correctly configured Go toolchain:

go get -u github.com/philippseith/signalr

Getting Started

SignalR uses a signalr.HubInterface instance to anchor the connection on the server and a javascript HubConnection object to anchor the connection on the client.

Server side

Implement the HubInterface

The easiest way to implement the signalr.HubInterface in your project is to declare your own type and embed signalr.Hub which implements that interface and will take care of all the signalr plumbing. You can call your custom type anything you want so long as it implements the signalr.HubInterface interface.

package main

import "github.com/philippseith/signalr"

type AppHub struct {
    signalr.Hub
}

Add functions with your custom hub type as a receiver.

func (h *AppHub) SendChatMessage(message string) {
    h.Clients().All().Send("chatMessageReceived", message)
}

These functions must be public so that they can be seen by the signalr server package but can be invoked client-side as lowercase message names. We'll explain setting up the client side in a moment, but as a preview, here's an example of calling our AppHub.SendChatMessage(...) method from the client:

    // javascript snippet invoking that AppHub.Send method from the client
    connection.invoke('sendChatMessage', val);

The signalr.HubInterface contains a pair of methods you can implement to handle connection and disconnection events. signalr.Hub contains empty implementations of them to satisfy the interface, but you can "override" those defaults by implementing your own functions with your custom hub type as a receiver:

func (c *chat) OnConnected(connectionID string) {
    fmt.Printf("%s connected\n", connectionID)
}

func (c *chat) OnDisconnected(connectionID string) {
   fmt.Printf("%s disconnected\n", connectionID)
}

Serve with http.ServeMux

import (
    "net/http"
	
    "github.com/philippseith/signalr"
)

func runHTTPServer() {
    address := 'localhost:8080'
    
    // create an instance of your hub
    hub := AppHub{}
	
    // build a signalr.Server using your hub
    // and any server options you may need
    server, _ := signalr.NewServer(context.TODO(),
        signalr.SimpleHubFactory(hub)
        signalr.KeepAliveInterval(2*time.Second),
        signalr.Logger(kitlog.NewLogfmtLogger(os.Stderr), true))
    )
    
    // create a new http.ServerMux to handle your app's http requests
    router := http.NewServeMux()
    
    // ask the signalr server to map it's server
    // api routes to your custom baseurl
    server.MapHTTP(signalr.WithHTTPServeMux(router), "/chat")

    // in addition to mapping the signalr routes
    // your mux will need to serve the static files
    // which make up your client-side app, including
    // the signalr javascript files. here is an example
    // of doing that using a local `public` package
    // which was created with the go:embed directive
    // 
    // fmt.Printf("Serving static content from the embedded filesystem\n")
    // router.Handle("/", http.FileServer(http.FS(public.FS)))
    
    // bind your mux to a given address and start handling requests
    fmt.Printf("Listening for websocket connections on http://%s\n", address)
    if err := http.ListenAndServe(address, router); err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

Client side: JavaScript/TypeScript

Grab copies of the signalr scripts

Microsoft has published the client-side libraries as a node package with embedded typescript annotations: @microsoft/signalr.

You can install @microsoft/signalr through any node package manager:

package managercommand
npmnpm install @microsoft/signalr@latest
yarnyarn add @microsoft/signalr@latest
LibManlibman install @microsoft/signalr@latest -p unpkg -d wwwroot/js/signalr --files dist/browser/signalr.js --files dist/browser/signalr.min.js --files dist/browser/signalr.map.js
noneyou can download the version we are using in our chatsample from here (the minified version is here)

Use a HubConnection to connect to your server Hub

How you format your client UI is going to depend on your application use case but here is a simple example. It illustrates the basic steps of connecting to your server hub:

  1. import the signalr.js library (or signalr.min.js);

  2. create a connection object using the HubConnectionBuilder;

  3. bind events

    • UI event handlers can use connection.invoke(targetMethod, payload) to send invoke functions on the server hub;
    • connection event handlers can react to the messages sent from the server hub;
  4. start your connection

<html>
<body>
    <!-- you may want the content you send to be dynamic -->
    <input type="text" id="message" />
    
    <!-- you may need a trigger to initiate the send -->
    <input type="button" value="Send" id="send" />
    
    <!-- you may want some container to display received messages -->
    <ul id="messages">
    </ul>

    <!-- 1. you need to import the signalr script which provides
            the HubConnectionBuilder and handles the connection
            plumbing.
    -->
    <script src="js/signalr.js"></script>
    <script>
    (async function () {
        // 2. use the signalr.HubConnectionBuilder to build a hub connection
        //    and point it at the baseurl which you configured in your mux
        const connection = new signalR.HubConnectionBuilder()
                .withUrl('/chat')
                .build();

        // 3. bind events:
        //    - UI events can invoke (i.e. dispatch to) functions on the server hub
        document.getElementById('send').addEventListener('click', sendClicked);
        //    - connection events can handle messages received from the server hub
        connection.on('chatMessageReceived', onChatMessageReceived);

        // 4. call start to initiate the connection and start streaming events
        //    between your server hub and your client connection
        connection.start();
        
        // that's it! your server and client should be able to communicate
        // through the signalr.Hub <--> connection pipeline managed by the
        // signalr package and client-side library.
        
        // --------------------------------------------------------------------
       
        // example UI event handler
        function sendClicked() {
            // prepare your target payload
            const msg = document.getElementById('message').value;
            if (msg) {
                // call invoke on your connection object to dispatch
                // messages to the server hub with two arguments:
                // -  target: name of the hub func to invoke
                // - payload: the message body
                // 
                const target = 'sendChatMessage';
                connection.invoke(target, msg);
            }
        }

        // example server event handler
        function onChatMessageReceived(payload) {
            // the payload is whatever was passed to the inner
            // clients' `Send(...)` method in your server-side
            // hub function.
           
            const li = document.createElement('li');
            li.innerText = payload;
            document.getElementById('messages').appendChild(li);
        }
    })();
    </script>
</body>
</html>

Client side: go

To handle callbacks from the server, create a receiver class which gets the server callbacks mapped to its methods:

type receiver struct {
	signalr.Hub
}

func (c *receiver) Receive(msg string) {
	fmt.Println(msg)
}

Receive gets called when the server does something like this:

hub.Clients().Caller().Send("receive", message)

The client itself might be used like that:

// Create a Connection (with timeout for the negotiation process)
creationCtx, _ := context.WithTimeout(ctx, 2 * time.Second)
conn, err := signalr.NewHTTPConnection(creationCtx, address)
if err != nil {
    return err
}
// Create the client and set a receiver for callbacks from the server
client, err := signalr.NewClient(ctx,
	signalr.WithConnection(conn),
	signalr.WithReceiver(receiver))
if err != nil {
    return err
}
// Start the client loop
c.Start()
// Do some client work
ch := <-c.Invoke("update", data)
// ch gets the result of the update operation

Debugging

Server, Client and the protocol implementations are able to log most of their operations. The logging option is disabled by default in all tests. To configure logging, edit the testLogConf.json file:

{
  "Enabled": false,
  "Debug": false
}
  • If Enabled is set to true, the logging will be enabled. The tests will log to os.Stderr.
  • If Debug ist set to true, the logging will be more detailed.

# Packages

No description provided by the author

# Functions

AllowOriginPatterns lists the host patterns for authorized origins which is used for avoid same origin strategy.
ChanReceiveTimeout is the timeout for processing stream items from the client, after StreamBufferCapacity was reached If the hub method is not able to process a stream item during the timeout duration, the server will send a completion with error.
CtxPipe creates a synchronous in-memory pipe.
No description provided by the author
EnableDetailedErrors If true, detailed exception messages are returned to the other Party when an exception is thrown in a Hub method.
HandshakeTimeout is the interval if the other Party doesn't send an initial handshake message within, the connection is closed.
HttpConnectionFactory is a connectionFactory for WithConnector which first tries to create a connection with WebSockets (if it is allowed by the HttpConnection options) and if this fails, falls back to a SSE based connection.
HTTPTransports sets the list of available transports for http connections.
HubFactory sets the function which returns the hub instance for every hub method invocation The function might create a new hub instance on every invocation.
InsecureSkipVerify disables Accepts origin verification behaviour which is used to avoid same origin strategy.
KeepAliveInterval is the interval if the Party hasn't sent a message within, a ping message is sent automatically to keep the connection open.
Logger sets the logger used by the Party to log info events.
MaximumReceiveMessageSize is the maximum size in bytes of a single incoming hub message.
NewClient builds a new Client.
NewConnectionBase creates a new ConnectionBase.
NewHTTPConnection creates a signalR HTTP Connection for usage with a Client.
NewNetConnection wraps net.Conn into a Connection.
NewServer creates a new server for one type of hub.
ReadWriteWithContext is a wrapper to make blocking io.Writer / io.Reader cancelable.
SimpleHubFactory sets a HubFactory which creates a new hub with the underlying type of hubProto on each hub method invocation.
StreamBufferCapacity is the maximum number of items that can be buffered for client upload streams.
TimeoutInterval is the interval one Party will consider the other Party disconnected if it hasn't received a message (including keep-alive) in it.
TransferFormat sets the transfer format used on the transport.
UseHub sets the hub instance used by the server.
WithBackoff sets the backoff.BackOff used for repeated connection attempts in the client.
WithConnection sets the Connection of the Client.
WithConnector allows the Client to establish a connection using the Connection build by the connectionFactory.
WithHTTPClient sets the http client used to connect to the signalR server.
WithHttpConnection first tries to create a connection with WebSockets (if it is allowed by the HttpConnection options) and if this fails, falls back to a SSE based connection.
WithHTTPHeaders sets the function for providing request headers for HTTP and websocket requests.
WithHTTPServeMux is a MappableRouter factory for MapHTTP which converts a http.ServeMux to a MappableRouter.
WithReceiver sets the object which will receive server side calls to client methods (e.g.
No description provided by the author

# Constants

BinaryTransferMode is for binary messages like MessagePack.
Client states ClientCreated The Client has been created and is not started yet.
Client states ClientCreated The Client has been created and is not started yet.
Client states ClientCreated The Client has been created and is not started yet.
Client states ClientCreated The Client has been created and is not started yet.
TextTransferMode is for UTF-8 encoded text messages like JSON.

# Variables

No description provided by the author
ErrClosedPipe is the error used for read or write operations on a closed pipe.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

ConnectionBase is a baseclass for implementers of the Connection interface.
Hub is a base class for hubs.
InvokeResult is the combined value/error result for async invocations.
A PipeReader is the read half of a pipe.
A PipeWriter is the write half of a pipe.
Receiver is a base class for receivers in the client.
RWJobResult can be used to send the result of an io.Writer / io.Reader operation over a channel.

# Interfaces

Client is the signalR connection used on the client side.
ClientProxy allows the hub to send messages to one or more of its clients.
Connection describes a connection between signalR client and server.
ConnectionWithTransferMode is a Connection with TransferMode (e.g.
Doer is the *http.Client interface.
GroupManager manages the client groups of the hub.
HubClients gives the hub access to various client groups All() gets a ClientProxy that can be used to invoke methods on all clients connected to the hub Caller() gets a ClientProxy that can be used to invoke methods of the current calling client Client() gets a ClientProxy that can be used to invoke methods on the specified client connection Group() gets a ClientProxy that can be used to invoke methods on all connections in the specified group.
HubContext is a context abstraction for a hub Clients gets a HubClients that can be used to invoke methods on clients connected to the hub Groups gets a GroupManager that can be used to add and remove connections to named groups Items holds key/value pairs scoped to the hubs connection ConnectionID gets the ID of the current connection Abort aborts the current connection Logger returns the logger used in this server.
HubInterface is a hubs interface.
HubLifetimeManager is a lifetime manager abstraction for hub instances OnConnected() is called when a connection is started OnDisconnected() is called when a connection is finished InvokeAll() sends an invocation message to all hub connections InvokeClient() sends an invocation message to a specified hub connection InvokeGroup() sends an invocation message to a specified group of hub connections AddToGroup() adds a connection to the specified group RemoveFromGroup() removes a connection from the specified group.
MappableRouter encapsulates the methods used by server.MapHTTP to configure the handlers required by the signalr protocol.
Party is the common base of Server and Client.
ReceiverInterface allows receivers to interact with the server directly from the receiver methods Init(Client) Init is used by the Client to connect the receiver to the server.
Server is a SignalR server for one type of hub.
StructuredLogger is the simplest logging interface for structured logging.

# Type aliases

ClientState is the state of the client.
No description provided by the author
TransferMode is either TextTransferMode or BinaryTransferMode.
No description provided by the author