| File: WebSockets\OwinWebSocketAcceptAdapter.cs | Web Access |
| Project: src\aspnetcore\src\Http\Owin\src\Microsoft.AspNetCore.Owin.csproj (Microsoft.AspNetCore.Owin) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.WebSockets; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Owin; using AppFunc = Func<IDictionary<string, object>, Task>; using WebSocketAccept = Action < IDictionary<string, object>, // WebSocket Accept parameters Func // WebSocketFunc callback < IDictionary<string, object>, // WebSocket environment Task // Complete > >; using WebSocketAcceptAlt = Func < WebSocketAcceptContext, // WebSocket Accept parameters Task<WebSocket> >; /// <summary> /// This adapts the OWIN WebSocket accept flow to match the ASP.NET Core WebSocket Accept flow. /// This enables ASP.NET Core components to use WebSockets on OWIN based servers. /// </summary> public class OwinWebSocketAcceptAdapter { private readonly WebSocketAccept _owinWebSocketAccept; private readonly TaskCompletionSource<int> _requestTcs = new TaskCompletionSource<int>(); private readonly TaskCompletionSource<WebSocket> _acceptTcs = new TaskCompletionSource<WebSocket>(); private readonly TaskCompletionSource<int> _upstreamWentAsync = new TaskCompletionSource<int>(); private string _subProtocol; private OwinWebSocketAcceptAdapter(WebSocketAccept owinWebSocketAccept) { _owinWebSocketAccept = owinWebSocketAccept; } private Task RequestTask { get { return _requestTcs.Task; } } private Task UpstreamTask { get; set; } private TaskCompletionSource<int> UpstreamWentAsyncTcs { get { return _upstreamWentAsync; } } private async Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext context) { IDictionary<string, object> options = null; if (context is OwinWebSocketAcceptContext) { var acceptContext = context as OwinWebSocketAcceptContext; options = acceptContext.Options; _subProtocol = acceptContext.SubProtocol; } else if (context?.SubProtocol != null) { options = new Dictionary<string, object>(1) { { OwinConstants.WebSocket.SubProtocol, context.SubProtocol } }; _subProtocol = context.SubProtocol; } // Accept may have been called synchronously on the original request thread, we might not have a task yet. Go async. await _upstreamWentAsync.Task; _owinWebSocketAccept(options, OwinAcceptCallback); _requestTcs.TrySetResult(0); // Let the pipeline unwind. return await _acceptTcs.Task; } private Task OwinAcceptCallback(IDictionary<string, object> webSocketContext) { _acceptTcs.TrySetResult(new OwinWebSocketAdapter(webSocketContext, _subProtocol)); return UpstreamTask; } // Make sure declined websocket requests complete. This is a no-op for accepted websocket requests. private void EnsureCompleted(Task task) { if (task.IsCanceled) { _requestTcs.TrySetCanceled(); } else if (task.IsFaulted) { _requestTcs.TrySetException(task.Exception); } else { _requestTcs.TrySetResult(0); } } // Order of operations: // 1. A WebSocket handshake request is received by the middleware. // 2. The middleware inserts an alternate Accept signature into the OWIN environment. // 3. The middleware invokes Next and stores Next's Task locally. It then returns an alternate Task to the server. // 4. The OwinFeatureCollection adapts the alternate Accept signature to IHttpWebSocketFeature.AcceptAsync. // 5. A component later in the pipeline invokes IHttpWebSocketFeature.AcceptAsync (mapped to AcceptWebSocketAsync). // 6. The middleware calls the OWIN Accept, providing a local callback, and returns an incomplete Task. // 7. The middleware completes the alternate Task it returned from Invoke, telling the server that the request pipeline has completed. // 8. The server invokes the middleware's callback, which creates a WebSocket adapter and completes the original Accept Task with it. // 9. The middleware waits while the application uses the WebSocket, where the end is signaled by the Next's Task completion. // /// <summary> /// Adapt web sockets to OWIN. /// </summary> /// <param name="next">The next OWIN app delegate.</param> /// <returns>An OWIN app delegate.</returns> public static AppFunc AdaptWebSockets(AppFunc next) { return environment => { object accept; if (environment.TryGetValue(OwinConstants.WebSocket.Accept, out accept) && accept is WebSocketAccept) { var adapter = new OwinWebSocketAcceptAdapter((WebSocketAccept)accept); environment[OwinConstants.WebSocket.AcceptAlt] = new WebSocketAcceptAlt(adapter.AcceptWebSocketAsync); try { adapter.UpstreamTask = next(environment); adapter.UpstreamWentAsyncTcs.TrySetResult(0); adapter.UpstreamTask.ContinueWith(adapter.EnsureCompleted, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } catch (Exception ex) { adapter.UpstreamWentAsyncTcs.TrySetException(ex); throw; } return adapter.RequestTask; } else { return next(environment); } }; } }