Websockets using OWIN

2020-07-02 10:44发布

问题:

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);
      }
   }
}

回答1:

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);
      }


   }
}