Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Websockets using OWIN

All the examples using Microsoft WebSockets over a web-api that I've seen so far use IIS, the implementation is on the get method the HTTP connection is upgraded to a websocket and an instance of websocket handler is passed to the HTTPContext

public HttpResponseMessage Get() {
  if (HttpContext.Current.IsWebSocketRequest) {
     var noteHandler = new NoteSocketHandler();
     HttpContext.Current.AcceptWebSocketRequest(noteHandler);
  }
  return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}

What am trying to achieve is to do the same on an OWIN pipeline. The problem am facing is the connection is being upgraded to use Websockets but it is not utilizing my websocket handler. Where am I going wrong? Please suggest.

Controller utilizing OwinContext (Followed the example WebSockets in Nancy using OWIN),

public HttpResponseMessage Get() {
   IOwinContext owinContext = Request.GetOwinContext();

   WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
   if (acceptToken != null) {
      var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "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());
      }

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

         //should I pass the handler to an event?
         var handler = new NoteSocketHAndler();               
      });

   } else {
      return new HttpResponseMessage(HttpStatusCode.BadRequest);
   }
   return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}

Handler Code:

using System;
using Socket = Microsoft.Web.WebSockets;
using Newtonsoft.Json;

public class NoteSocketHandler : Socket.WebSocketHandler {
   private static Socket.WebSocketCollection connections = new Socket.WebSocketCollection();

   public NoteSocketHandler() {
   }

   public override void OnOpen() {
      connections.Add(this);
   }

   public override void OnClose() {
      connections.Remove(this);
   }

   public override void OnMessage(string message) {
      ChatMessage chatMessage = JsonConvert.DeserializeObject<ChatMessage>(message);
      foreach (var connection in connections) {
         connection.Send(message);
      }
   }
}
like image 633
LearnCode Avatar asked Jan 25 '17 09:01

LearnCode


People also ask

Does OWIN support WebSockets?

NET OWIN web server used in the previous example has support for Web Sockets built in, which can be leveraged by an ASP.NET Core application. The example below shows a simple web app that supports Web Sockets and echoes back everything sent to the server through WebSockets.

What is OWIN used for in web API?

Open Web Interface for . NET (OWIN) defines an abstraction between . NET web servers and web applications. OWIN decouples the web application from the server, which makes OWIN ideal for self-hosting a web application in your own process, outside of IIS.

Does WebSocket use TCP or UDP?

The WebSocket protocol is an independent TCP-based protocol.


1 Answers

I finally figured out how to resolve the issue. You can find the code below, also I've written a basic app which uses websockets on OWIN.

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin;

namespace NoteApp.WebService.Controller {
   using System;
   using System.Net.WebSockets;
   using System.Text;
   using System.Threading;
   using System.Threading.Tasks;
   using NoteApp.WebService.Handler;
   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 WebSocketCloseAsync = System.Func<
                                    int, // closeStatus
                                    string, // closeDescription
                                    System.Threading.CancellationToken, // cancel
                                    System.Threading.Tasks.Task>;
   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
   // closeStatusDescription
   using WebSocketReceiveResult = System.Tuple<int, bool, int>;
   using WebSocketSendAsync = System.Func<
                                       System.ArraySegment<byte>, // data
                                       int, // message type
                                       bool, // end of message
                                       System.Threading.CancellationToken, // cancel
                                       System.Threading.Tasks.Task>;

   public class NoteController : ApiController {
      public HttpResponseMessage Get() {
         IOwinContext owinContext = Request.GetOwinContext();

         WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
         if (acceptToken != null) {
            var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "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());
            }
            acceptToken(acceptOptions, ProcessSocketConnection);


         } else {
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
         }
         return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
      }

      private async Task ProcessSocketConnection(IDictionary<string, object> wsEnv) {
         var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
         var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
         var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
         var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];

         //pass the sendasync tuple and the cancellation token to the handler. The handler uses the sendasync method to send message. Each connected client has access to this
         var handler = new NoteSocketHandler(wsSendAsync, CancellationToken.None);
         handler.OnOpen();
         var buffer = new ArraySegment<byte>(new byte[100]);
         try {
            object status;
            while (!wsEnv.TryGetValue("websocket.ClientCloseStatus", out status) || (int)status == 0) {
               WebSocketReceiveResult webSocketResultTuple = await wsRecieveAsync(buffer, CancellationToken.None);                   
               int count = webSocketResultTuple.Item3;

               handler.OnMessage(Encoding.UTF8.GetString(buffer.Array, 0, count));
            }
         } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            throw ex;
         }
         handler.OnClose();
         await wsCloseAsync((int)WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
      }

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


   }
}
like image 181
LearnCode Avatar answered Oct 16 '22 18:10

LearnCode