Team XSockets.NET

Native WebSockets

In the previous section we took a look at how you can add a custom websocket protocol by using a subprotocol. It is our opinion that you always should use a subprotocol if you expect the data sent over the socket to have a specific format.

The websocket protocol needs the concept of subprotocols to make sure the client and server are sending messages they both understand. Kind of a contract that make sure that they will both understand each other.

The native websocket will not guarantee any format. You can pass anything and the server will not have any idea about the format. The only time native websockets is useful is when you just broadcast data coming in to all clients. The server does not care about the content, it just dispatch messages.

Adding Native WebSocket Support

As already stated above we will not know anything about the data we receive from the client. So, to be able to dispatch the messages coming in we need to transform the message going in into an IMessage and pass it to a controller. Then we need to transform the IMessage back to the original message when it goes out to the clients.

If you remember the introduction to protocols that is the responsibility of a protocol. To transform incoming data into IMessage and outgoing IMessage into the format the clients expect.

Add the Protocol

1.

We need to inherit the Rfc6455Protocol and then override the Match method to make sure that there is no subprotocol in the handshake. If there is no subprotocol included in the handshake the client is using raw/native websockets.

2.

We need to convert incoming data into a text/binary IMessage. The IMessage needs to use a controller, we will use the generic controller since that one will dispatch the incoming data to all connected clients.

public override async Task OnMessage(IMessage message)
{
    await this.InvokeToAll(message);
}

3.

We need to convert outgoing IMessage into the payload sent (text/binary)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using XSockets.Core.Common.Protocol;
using XSockets.Core.Common.Socket.Event.Arguments;
using XSockets.Core.Common.Socket.Event.Interface;
using XSockets.Core.XSocket.Model;
using XSockets.Plugin.Framework;
using XSockets.Plugin.Framework.Attributes;
using XSockets.Protocol.Rfc6455;

[Export(typeof(IXSocketProtocol), Rewritable = Rewritable.No)]
public class NativeWebSocketsProtocol : Rfc6455Protocol
{
    /// <summary>
    /// The handshake should contain Sec-WebSocket-Version:\13
    /// The handshake should NOT contain any subprotocol
    /// </summary>
    /// <param name="handshake"></param>
    /// <returns></returns>
    public override async Task<bool> Match(IList<byte> handshake)
    {
        var s = Encoding.UTF8.GetString(handshake.ToArray());
        return
            Regex.Match(s, @"(^Sec-WebSocket-Version:\s13)", RegexOptions.Multiline).Success
            &&
            !Regex.Match(s, @"(^Sec-WebSocket-Protocol)", RegexOptions.Multiline).Success;
    }

    /// <summary>
    /// Convert the incoming data to an IMessage of with correct type (text/binary)
    /// </summary>
    /// <param name="payload"></param>
    /// <param name="messageType"></param>
    /// <returns></returns>
    public override IMessage OnIncomingFrame(IEnumerable<byte> payload, MessageType messageType)
    {
        if(messageType == MessageType.Binary)
        {
            return new Message(payload, string.Empty, "generic");
        }
        else
        {
            return new Message(Encoding.UTF8.GetString(payload.ToArray()), string.Empty,"generic");
        }
    }

    /// <summary>
    /// Create a RFC6455 dataframe from the IMessage and only pass out the payload
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public override byte[] OnOutgoingFrame(IMessage message)
    {
        return GetFrame(message).ToBytes();
    }

    /// <summary>
    /// Create text/binary frame
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    private Rfc6455DataFrame GetFrame(IMessage message)
    {
        var rnd = new Random().Next(0, 34298);
        return new Rfc6455DataFrame
        {
            FrameType = (message.MessageType == MessageType.Text) ? FrameType.Text : FrameType.Binary,
            IsFinal = true,
            MaskKey = rnd,
            Payload = (message.MessageType == MessageType.Text) ? Encoding.UTF8.GetBytes(message.Data) : message.Blob.ToArray(),
        };
    }

    public override IXSocketProtocol NewInstance()
    {
        return new NativeWebSocketsProtocol();
    }
}

Test the Native WebSockets Protocol

To test this we can just open up the Chrome console and write some javascript.

1. Connection

//Create a websocket (no subprotocol)
var c = new WebSocket('ws://localhost:4502');
//Hook up the OnMessage event
c.onmessage = function(d){console.log(d.data)}
(d){console.log(d.data)}

2. Binary test

//Create an arraybuffer for binary data test
var a = new ArrayBuffer(8);
//Send binary data
c.send(a)
//That will fire onmessage with: 
Blob {}size: 8type: ""__proto__: Blob

3. Text test

c.send('just a random string')
//Send text data
c.send('just a random string')
//That will fire onmessage with:
"just a random string"

Result

Native WebSocket Test

results matching ""

    No results matching ""