Skip to content
This repository has been archived by the owner on Jan 24, 2021. It is now read-only.

WebSockets in Nancy using OWIN

matneyx edited this page May 5, 2016 · 3 revisions

Note: Requires OWIN host. More information on it can be found at Hosting Nancy with OWIN

This is based on Owin WebSocket Extensions v0.4.0.

It is recommended to use SignalR if possible.

Create an index.html file in views folder

<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <title>Nancy + Owin WebSocket Test</title>
</head>
<body>
    <script>
        //var wsUri = "ws://echo.websocket.org/";
        var wsUri = "ws://localhost:1901/websocket";
        var output, websocket;
        function init() {
            output = document.getElementById("output");
            testWebSocket();
        }
        function testWebSocket() {
            websocket = new WebSocket(wsUri, ['echo', 'chat']);
            websocket.onopen = function (evt) { onOpen(evt); };
            websocket.onclose = function (evt) { onClose(evt); };
            websocket.onmessage = function (evt) { onMessage(evt); };
            websocket.onerror = function (evt) { onError(evt); };
        }
        function onOpen(evt) {
            writeToScreen("CONNECTED");
            doSend("WebSocket rocks");
        }
        function onClose(evt) {
            writeToScreen("DISCONNECTED");
        }
        function onMessage(evt) {
            writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data + '</span>');
            websocket.close();
        }
        function onError(evt) {
            writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
        }
        function doSend(message) {
            writeToScreen("SENT: " + message);
            websocket.send(message);
        }
        function writeToScreen(message) {
            var pre = document.createElement("p");
            pre.style.wordWrap = "break-word";
            pre.innerHTML = message;
            output.appendChild(pre);
        }
        window.addEventListener("load", init, false);
    </script>
    <h2>WebSockets on Nancy using OWIN WebSocket Extension</h2>
    <p>Requires ASP.NET and IIS 8+ on Win8+</p>
    <div id="output"></div>

</body>
</html>

Create a module with route that renders the view and handles the actual web socket connection

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using Nancy;
using Nancy.Owin;

using WebSocketAccept = System.Action<
           System.Collections.Generic.IDictionary<string, object>, // WebSocket Accept parameters
           System.Func< // WebSocketFunc callback
               System.Collections.Generic.IDictionary<string, object>, // WebSocket environment
               System.Threading.Tasks.Task>>;

using WebSocketSendAsync = System.Func<
               System.ArraySegment<byte>, // data
               int, // message type
               bool, // end of message
               System.Threading.CancellationToken, // cancel
               System.Threading.Tasks.Task>;
    // closeStatusDescription

using WebSocketReceiveAsync = System.Func<
            System.ArraySegment<byte>, // data
            System.Threading.CancellationToken, // cancel
            System.Threading.Tasks.Task<
                System.Tuple< // WebSocketReceiveTuple
                    int, // messageType
                    bool, // endOfMessage
                    int?, // count
                    int?, // closeStatus
                    string>>>; // closeStatusDescription

using WebSocketCloseAsync = System.Func<
            int, // closeStatus
            string, // closeDescription
            System.Threading.CancellationToken, // cancel
            System.Threading.Tasks.Task>;

namespace WebApplication6
{
    public class WebSocketModule : NancyModule
    {
        public WebSocketModule()
        {
            Get["/"] = x => View["index"];

            Get["/websocket"] = x => {

                var env = (IDictionary<string, object>)Context.Items[NancyMiddleware.RequestEnvironmentKey];

                object temp;
                if (env.TryGetValue("websocket.Accept", out temp) && temp != null) // check if the owin host supports web sockets
                {
                    var wsAccept = (WebSocketAccept)temp;
                    var requestHeaders = GetValue<IDictionary<string, string[]>>(env, "owin.RequestHeaders");

                    Dictionary<string, object> acceptOptions = null;
                    string[] subProtocols;
                    if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0)
                    {
                        acceptOptions = new Dictionary<string, object>();
                        // Select the first one from the client
                        acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
                    }

                    wsAccept(acceptOptions, async wsEnv => {
                        var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
                        var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
                        var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
                        var wsVersion = (string)wsEnv["websocket.Version"];
                        var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];

                        // note: make sure to catch errors when calling sendAsync, receiveAsync and closeAsync
                        // for simiplicity this code does not handle errors
                        var buffer = new ArraySegment<byte>(new byte[6]);
                        while (true)
                        {
                            var webSocketResultTuple = await wsRecieveAsync(buffer, wsCallCancelled);
                            int wsMessageType = webSocketResultTuple.Item1;
                            bool wsEndOfMessge = webSocketResultTuple.Item2;
                            int? count = webSocketResultTuple.Item3;
                            int? closeStatus = webSocketResultTuple.Item4;
                            string closeStatusDescription = webSocketResultTuple.Item5;

                            Debug.Write(Encoding.UTF8.GetString(buffer.Array, 0, count.Value));

                            await wsSendAsync(new ArraySegment<byte>(buffer.ToArray(), 0, count.Value), 1, wsEndOfMessge, wsCallCancelled);

                            if (wsEndOfMessge)
                                break;
                        }

                        await wsCloseAsync((int)WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    });

                    return 200;
                }
                else
                {
                    return 404;
                }
            };
        }

        private static T GetValue<T>(IDictionary<string, object> env, string key)
        {
            object value;
            return env.TryGetValue(key, out value) && value is T ? (T)value : default(T);
        }

        private static string GetHeader(IDictionary<string, string[]> headers, string key)
        {
            string[] value;
            return headers.TryGetValue(key, out value) && value != null ? string.Join(",", value.ToArray()) : null;
        }

    }
}
Clone this wiki locally