/// <summary> /// Bind observable websockets to a pipeline. /// </summary> /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> /// <param name="configure">An action called to configure the observable websockets.</param> public static IApplicationBuilder UseObservableWebsockets(this IApplicationBuilder app, Action <ObservableWebsocketOptions> configure) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (configure == null) { throw new ArgumentNullException(nameof(configure)); } var options = new ObservableWebsocketOptions(); configure(options); return(UseObservableWebsockets(app, options)); }
/// <summary> /// Bind observable websockets to a pipeline. /// </summary> /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> /// <param name="options">The observable websocket options</param> public static IApplicationBuilder UseObservableWebsockets(this IApplicationBuilder app, ObservableWebsocketOptions options) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } if (options.Acceptor == null) { throw new InvalidOperationException("The ObservableWebsocketOptions must have Acceptor set. For example, use app.UseObservableWebsockets(c => c.OnAccept(ws => { ... }))"); } return(app.Use(async(context, next) => { if (context.WebSockets.IsWebSocketRequest && (options.ConnectionEvaluator?.Invoke(context) ?? true)) { var webSocket = await context.WebSockets.AcceptWebSocketAsync(); await WebsocketConnector.HandleWebsocketAsync(new NetStandardRequestContext(context), webSocket, options); } else { await next(); } })); }
public WebsocketMiddleware(ObservableWebsocketOptions options) { _options = options; }
/// <summary> /// Bind observable websockets to a pipeline. /// </summary> /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> /// <param name="options">The observable websocket options</param> public static IAppBuilder UseObservableWebsockets(this IAppBuilder app, ObservableWebsocketOptions options) { var mw = new WebsocketMiddleware(options); return(app.Use(mw)); }
internal static async Task HandleWebsocketAsync(RequestContext ctx, WebSocket webSocket, ObservableWebsocketOptions options) { var messageQueue = new System.Collections.Concurrent.ConcurrentQueue <Tuple <byte[], WebSocketMessageType, bool> >(); var messageSentAre = new AsyncAutoResetEvent(); CancellationTokenSource cts = new CancellationTokenSource(); var cancellationToken = cts.Token; var incomingMessages = new Subject <(ArraySegment <byte> message, WebSocketMessageType messageType, bool endOfMessage)>(); // run to complete the observable sequence. Called on error, cancellation, or close. int hasCompleted = 0; void Complete(Exception ex = null) { if (Interlocked.CompareExchange(ref hasCompleted, 1, 0) == 0) { if (ex != null) { try { incomingMessages.OnError(ex); } catch { } } else { try { incomingMessages.OnCompleted(); } catch (Exception e) { try { incomingMessages.OnError(AggregateExceptions(ex, e)); } catch { } } } } } cancellationToken.Register(() => Complete()); bool isClosed() => cts.IsCancellationRequested || Volatile.Read(ref hasCompleted) == 1 || webSocket.State != WebSocketState.Open; async Task ReadLoopAsync() { while (!isClosed()) { try { var buffer = new ArraySegment <byte>(new byte[1024]); var receiveStatus = await webSocket.ReceiveAsync(buffer, cancellationToken); var properSegment = new ArraySegment <byte>(buffer.Array, 0, receiveStatus.Count); try { incomingMessages.OnNext((properSegment, receiveStatus.MessageType, receiveStatus.EndOfMessage)); } catch (Exception ex) { Complete(ex); cts.Cancel(); throw; } } catch (WebSocketException) { if (!isClosed()) { throw; } } } // signal that we are closed cts.Cancel(); } async Task WriteLoopAsync() { while (!isClosed()) { if (messageQueue.TryDequeue(out var nextMessage)) { if (nextMessage != null) { try { var msg = nextMessage.Item1; var tp = nextMessage.Item2; var end = nextMessage.Item3; var arraySegment = new ArraySegment <byte>(msg); await webSocket.SendAsync(arraySegment, tp, true, cancellationToken); } catch (WebSocketException) { if (!isClosed()) { throw; } } } } else { try { await messageSentAre.WaitAsync(cancellationToken); } catch (OperationCanceledException) { } } } // signal that we are closed just in case it hasn't been triggered already cts.Cancel(); } try { var handler = new WebSocketHandler((m, t, e) => { messageQueue?.Enqueue(Tuple.Create(m, t, e)); messageSentAre.Trigger(); }, ctx, incomingMessages.Subscribe); await Task.Yield(); options.Acceptor(handler); await Task.Yield(); var readwait = ReadLoopAsync(); var writewait = WriteLoopAsync(); await Task.WhenAll(readwait, writewait); } catch (OperationCanceledException) { } catch (Exception ex) { Complete(ex); } finally { Complete(); } try { if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default); } } catch { } }