Need help with SignalW?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

Spreads
126 Stars 11 Forks Other 197 Commits 2 Opened issues

Description

Even simpler and faster real-time web for ASP.NET Core.

Services available

!
?

Need anything else?

Contributors list

SignalW (not maintained)

Even simpler and faster real-time web for ASP.NET Core.

SignalW is a simplified version of SignalR, with only WebSockets as a transport and

MemoryStream
as a message type.
  • WebSockets work almost everywhere to bother about long polling/SSE/any other transport.
  • Since messages could be framed, we cannot use a single buffer and need a stream to collect all chunks. We use
    RecyclableMemoryStream
    that pools internal buffers.
  • Serialization is out of scope. It is always a pain to abstract it for a general case, but in every concrete case it could be as simple as using JSON.NET (with extension methods for streams) or as flexible as a custom binary encoding.
  • Any generic WebSocket client should work. The SignalW.Client project has a WsChannel class that wraps around the standard WebSocket class and gives methods to work with MemoryStreams instead of ArraySegments.

Instead of multiple methods inside Hubs that clients invoked by name, in SignalW we have a single method

async Task OnReceiveAsync(MemoryStream payload)
. If one uses Angular2 with
@ngrx/store
then a deserialized-to-JSON message will always have a
type
field, and one could use a custom
JsonCreationConverter
to deserialize a message to its correct .NET type. Then one could write multiple methods with the same name that differ only by its parameter type and use
dynamic
keyword to dispatch a message to a correct handler.
public class DispatchHub : Hub {
    public override async Task OnReceiveAsync(MemoryStream payload) {
        // Extension method ReadJsonMessage returns IMessage object instance based on the `type` field in JSON
        object message = payload.ReadJsonMessage();
        // dispose as soon as it is no longer used becasue it uses pooled buffers inside
        payload.Dispose();

    // dynamic will dispatch to the correct method
    dynamic dynMessage = message;
    await OnReceiveAsync(dynMessage);
}

public async void OnReceiveAsync(MessageFoo message) {
    var stream = message.WriteJson();
    await Clients.Group("foo").InvokeAsync(stream);
}

public async void OnReceiveAsync(MessageBar message) {
    var stream = message.WriteJson();
    await Clients.Group("bar").InvokeAsync(stream);
}

}

On the Angular side, one could simply use a

WebSocketSubject
and forward messages to
@ngrx/store
directly if they have a
type
field. No dependencies, no custom deserialization, no hassle!

Authentication with a bearer token

From a C# client

var client = new ClientWebSocket();
var header = new AuthenticationHeaderValue("Bearer", _accessToken);
client.Options.RequestHeaders.Add("Authorization", header.ToString());

From JavaScript:

It is impossible to add headers to WebSocket constructor in JavaScript, but we could use protocol parameters for this. Here we are using RxJS WebSocketSubject:

import { WebSocketSubjectConfig, WebSocketSubject } from 'rxjs/observable/dom/WebSocketSubject';
...
let wsConfig: WebSocketSubjectConfig = {
    url: 'wss://example.com/api/signalw/chat',
    protocol: [
    'access_token',
    token
    ]
};
let ws = new WebSocketSubject(wsConfig);

Then in the very beginning of OWIN pipeline (before any identity middleware) use this trick to populate the correct header:

app.Use((context, next) => {
    if (!context.Request.Headers.ContainsKey("Authorization")
        && context.Request.Headers.ContainsKey("Upgrade")) {
        if (context.WebSockets.WebSocketRequestedProtocols.Count >= 2) {
            var first = context.WebSockets.WebSocketRequestedProtocols[0];
            var second = context.WebSockets.WebSocketRequestedProtocols[1];
            if (first == "access_token") {
                context.Request.Headers.Add("Authorization", "Bearer " + second);
                context.Response.Headers.Add("Sec-WebSocket-Protocol", "access_token");
            }
        }
    }
    return next();
});

To use SignalW, create a custom Hub: ``` [Authorize] public class Chat : Hub { public override Task OnConnectedAsync() { if (!Context.User.Identity.IsAuthenticated) { Context.Connection.Channel.TryComplete(); } return Task.FromResult(0); }

public override Task OnDisconnectedAsync() {
    return Task.FromResult(0);
}

public override async Task OnReceiveAsync(MemoryStream payload) { await Clients.All.InvokeAsync(payload); }

} ```

Then add SignalW to the OWIN pipeline and map hubs to a path. Here we use SignalR together with MVC on the "/api" path: ``` public void ConfigureServices(IServiceCollection services) { ... services.AddSignalW(); ... }

public void Configure(IApplicationBuilder app, ...){ ... app.Map("/api/signalw", signalw => { signalw.UseSignalW((config) => { config.MapHub("chat", Format.Text); }); }); app.Map("/api", apiApp => { apiApp.UseMvc(); }); ... } ```

Open several pages of https://www.websocket.org/echo.html and connect to

https://[host]/api/signalw/chat?connectionId=[any value]
. Each page should broadcast messages to every other page and this is a simple chat.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.