From 1074fc102a2b49010e5e714eff7cd5bb398a0a6b Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Wed, 9 Jul 2014 17:19:41 -0700 Subject: [PATCH] OWIN WebSockets: Cleanup, docs, extension methods. --- .../Microsoft.AspNet.Owin.kproj | 1 + .../OwinEnvironmentFeature.cs | 12 ++++++ src/Microsoft.AspNet.Owin/OwinExtensions.cs | 41 ++++++++++++++++--- .../OwinFeatureCollection.cs | 3 +- .../WebSockets/OwinWebSocketAcceptAdapter.cs | 14 +++++++ .../WebSockets/OwinWebSocketAcceptContext.cs | 4 +- .../WebSockets/WebSocketAcceptAdapter.cs | 6 ++- 7 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/Microsoft.AspNet.Owin/OwinEnvironmentFeature.cs diff --git a/src/Microsoft.AspNet.Owin/Microsoft.AspNet.Owin.kproj b/src/Microsoft.AspNet.Owin/Microsoft.AspNet.Owin.kproj index 931181cfd8..822be75de7 100644 --- a/src/Microsoft.AspNet.Owin/Microsoft.AspNet.Owin.kproj +++ b/src/Microsoft.AspNet.Owin/Microsoft.AspNet.Owin.kproj @@ -23,6 +23,7 @@ + diff --git a/src/Microsoft.AspNet.Owin/OwinEnvironmentFeature.cs b/src/Microsoft.AspNet.Owin/OwinEnvironmentFeature.cs new file mode 100644 index 0000000000..1675852e5f --- /dev/null +++ b/src/Microsoft.AspNet.Owin/OwinEnvironmentFeature.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Owin +{ + public class OwinEnvironmentFeature : IOwinEnvironmentFeature + { + public IDictionary Environment { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Owin/OwinExtensions.cs b/src/Microsoft.AspNet.Owin/OwinExtensions.cs index d48462c16e..674befa4f9 100644 --- a/src/Microsoft.AspNet.Owin/OwinExtensions.cs +++ b/src/Microsoft.AspNet.Owin/OwinExtensions.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Builder { public static AddMiddleware UseOwin(this IBuilder builder) { - return middleware => + AddMiddleware add = middleware => { Func middleware1 = next1 => { @@ -36,11 +36,26 @@ namespace Microsoft.AspNet.Builder var app = middleware(exitMiddlware); return httpContext => { - return app.Invoke(new OwinEnvironment(httpContext)); + // Use the existing OWIN env if there is one. + IDictionary env; + var owinEnvFeature = httpContext.GetFeature(); + if (owinEnvFeature != null) + { + env = owinEnvFeature.Environment; + env[typeof(HttpContext).FullName] = httpContext; + } + else + { + env = new OwinEnvironment(httpContext); + } + return app.Invoke(env); }; }; builder.Use(middleware1); }; + // Adapt WebSockets by default. + add(WebSocketAcceptAdapter.AdaptWebSockets); + return add; } public static IBuilder UseOwin(this IBuilder builder, Action pipeline) @@ -51,6 +66,8 @@ namespace Microsoft.AspNet.Builder public static IBuilder UseBuilder(this AddMiddleware app) { + // Adapt WebSockets by default. + app(OwinWebSocketAcceptAdapter.AdaptWebSockets); var builder = new Builder(serviceProvider: null); CreateMiddleware middleware = CreateMiddlewareFactory(exit => @@ -74,10 +91,22 @@ namespace Microsoft.AspNet.Builder return env => { - return app.Invoke( - new DefaultHttpContext( - new FeatureCollection( - new OwinFeatureCollection(env)))); + // Use the existing HttpContext if there is one. + HttpContext context; + object obj; + if (env.TryGetValue(typeof(HttpContext).FullName, out obj)) + { + context = (HttpContext)obj; + context.SetFeature(new OwinEnvironmentFeature() { Environment = env }); + } + else + { + context = new DefaultHttpContext( + new FeatureCollection( + new OwinFeatureCollection(env))); + } + + return app.Invoke(context); }; }; } diff --git a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs index bec1cd1292..7478a81e68 100644 --- a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs +++ b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs @@ -39,6 +39,7 @@ namespace Microsoft.AspNet.Owin public OwinFeatureCollection(IDictionary environment) { Environment = environment; + SupportsWebSockets = true; } T Prop(string key) @@ -239,7 +240,7 @@ namespace Microsoft.AspNet.Owin IAuthenticationHandler IHttpAuthenticationFeature.Handler { get; set; } /// - /// Gets or sets if the underlying server supports WebSockets. This is disabled by default. + /// Gets or sets if the underlying server supports WebSockets. This is enabled by default. /// The value should be consistant across requests. /// public bool SupportsWebSockets { get; set; } diff --git a/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptAdapter.cs b/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptAdapter.cs index ce45e4fc4f..15ec0a01f8 100644 --- a/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptAdapter.cs +++ b/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptAdapter.cs @@ -27,6 +27,10 @@ namespace Microsoft.AspNet.Owin Task >; + /// + /// This adapts the OWIN WebSocket accept flow to match the ASP.NET WebSocket Accept flow. + /// This enables ASP.NET components to use WebSockets on OWIN based servers. + /// public class OwinWebSocketAcceptAdapter { private WebSocketAccept _owinWebSocketAccept; @@ -94,6 +98,16 @@ namespace Microsoft.AspNet.Owin } } + // 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 pipleline 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 creats a WebSocket adapter complete's the orriginal 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. public static AppFunc AdaptWebSockets(AppFunc next) { return environment => diff --git a/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptContext.cs b/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptContext.cs index d67be74199..1e847e6264 100644 --- a/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptContext.cs +++ b/src/Microsoft.AspNet.Owin/WebSockets/OwinWebSocketAcceptContext.cs @@ -10,8 +10,8 @@ namespace Microsoft.AspNet.Owin { private IDictionary _options; - public OwinWebSocketAcceptContext() : this(new Dictionary(1)) - { + public OwinWebSocketAcceptContext() : this(new Dictionary(1)) + { } public OwinWebSocketAcceptContext(IDictionary options) diff --git a/src/Microsoft.AspNet.Owin/WebSockets/WebSocketAcceptAdapter.cs b/src/Microsoft.AspNet.Owin/WebSockets/WebSocketAcceptAdapter.cs index 5d5b4a5f3b..9660a5d00e 100644 --- a/src/Microsoft.AspNet.Owin/WebSockets/WebSocketAcceptAdapter.cs +++ b/src/Microsoft.AspNet.Owin/WebSockets/WebSocketAcceptAdapter.cs @@ -28,6 +28,10 @@ namespace Microsoft.AspNet.Owin Task >; + /// + /// This adapts the ASP.NET WebSocket Accept flow to match the OWIN WebSocket accept flow. + /// This enables OWIN based components to use WebSockets on ASP.NET servers. + /// public class WebSocketAcceptAdapter { private IDictionary _env; @@ -63,7 +67,7 @@ namespace Microsoft.AspNet.Owin { IWebSocketAcceptContext acceptContext = null; object obj; - if (adapter._options.TryGetValue(typeof(IWebSocketAcceptContext).FullName, out obj)) + if (adapter._options != null && adapter._options.TryGetValue(typeof(IWebSocketAcceptContext).FullName, out obj)) { acceptContext = obj as IWebSocketAcceptContext; }