From e520911059b02364812640fbfcd2077eb1d8fa38 Mon Sep 17 00:00:00 2001 From: Tom DuPont Date: Sat, 6 Feb 2016 02:19:17 -0800 Subject: [PATCH 1/5] Added a fleck implementation. --- .../Extensions/FleckExtensions.cs | 23 +++ .../FleckWebSocketConnection.cs | 183 ++++++++++++++++++ .../FleckWebSocketConnectionMiddleware.cs | 53 +++++ .../IOwinWebSocketConnection.cs | 17 ++ .../Owin.WebSocket.Fleck.csproj | 82 ++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ src/Owin.WebSocket.Fleck/packages.config | 7 + src/Owin.WebSocket.sln | 10 +- src/Owin.WebSocket/WebSocketConnection.cs | 4 +- src/UnitTests/FleckTests.cs | 112 +++++++++++ src/UnitTests/UnitTests.csproj | 12 +- src/UnitTests/WebSocketTests.cs | 2 +- src/UnitTests/app.config | 8 +- src/UnitTests/packages.config | 1 + 14 files changed, 539 insertions(+), 11 deletions(-) create mode 100644 src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs create mode 100644 src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs create mode 100644 src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs create mode 100644 src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs create mode 100644 src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj create mode 100644 src/Owin.WebSocket.Fleck/Properties/AssemblyInfo.cs create mode 100644 src/Owin.WebSocket.Fleck/packages.config create mode 100644 src/UnitTests/FleckTests.cs diff --git a/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs b/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs new file mode 100644 index 0000000..e2da135 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Text.RegularExpressions; + +namespace Owin.WebSocket.Extensions +{ + public static class FleckExtensions + { + public static IAppBuilder MapWebSocketRoute(this IAppBuilder app, Action config) + { + return app.MapWebSocketRoute(string.Empty, config); + } + + public static IAppBuilder MapWebSocketRoute(this IAppBuilder app, string route, Action config) + { + return app.Map(route, a => a.Use(config)); + } + + public static IAppBuilder MapWebSocketPattern(this IAppBuilder app, string regexPatternMatch, Action config) + { + return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs new file mode 100644 index 0000000..4d49a51 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading.Tasks; +using Fleck; +using Microsoft.Owin; + +namespace Owin.WebSocket +{ + public sealed class FleckWebSocketConnection : WebSocketConnection, IOwinWebSocketConnection, IWebSocketConnectionInfo + { + private readonly IOwinWebSocketConnection _connection; + + private bool _isAvailable; + + public FleckWebSocketConnection() + { + _connection = this; + } + + #region WebSocketConnection + + public override bool AuthenticateRequest(IOwinRequest request) + { + return _connection.OnAuthenticateRequest?.Invoke(request) ?? true; + } + + public override Task AuthenticateRequestAsync(IOwinRequest request) + { + return _connection.OnAuthenticateRequestAsync?.Invoke(request) ?? Task.FromResult(true); + } + + public override void OnOpen() + { + _isAvailable = true; + _connection.OnOpen?.Invoke(); + } + + public override Task OnOpenAsync() + { + return _connection.OnOpenAsync?.Invoke() ?? Task.FromResult(true); + } + + public override void OnClose(WebSocketCloseStatus? closeStatus, string closeStatusDescription) + { + _isAvailable = false; + _connection.OnClose?.Invoke(); + _connection.OnCloseOwin?.Invoke(closeStatus, closeStatusDescription); + } + + public override Task OnCloseAsync(WebSocketCloseStatus? closeStatus, string closeStatusDescription) + { + return _connection.OnCloseOwinAsync?.Invoke(closeStatus, closeStatusDescription) ?? Task.FromResult(true); + } + + public override Task OnMessageReceived(ArraySegment message, WebSocketMessageType type) + { + if (type == WebSocketMessageType.Binary && _connection.OnBinary != null) + { + var array = message.ToArray(); + _connection.OnBinary.Invoke(array); + } + else if (type == WebSocketMessageType.Text && _connection.OnMessage != null) + { + var @string = Encoding.UTF8.GetString(message.Array, 0, message.Count); + _connection.OnMessage.Invoke(@string); + } + + return Task.FromResult(true); + } + + public override void OnReceiveError(Exception error) + { + _connection.OnError?.Invoke(error); + } + + #endregion + + #region IOwinWebSocketConnection + + Func IOwinWebSocketConnection.OnOpenAsync { get; set; } + Action IOwinWebSocketConnection.OnCloseOwin { get; set; } + Func IOwinWebSocketConnection.OnCloseOwinAsync { get; set; } + Func IOwinWebSocketConnection.OnAuthenticateRequest { get; set; } + Func> IOwinWebSocketConnection.OnAuthenticateRequestAsync { get; set; } + + #endregion + + #region IWebSocketConnection + + Action IWebSocketConnection.OnOpen { get; set; } + Action IWebSocketConnection.OnClose { get; set; } + Action IWebSocketConnection.OnMessage { get; set; } + Action IWebSocketConnection.OnBinary { get; set; } + Action IWebSocketConnection.OnError { get; set; } + + Action IWebSocketConnection.OnPing + { + get { return null; } + set { throw new NotSupportedException(); } + } + + Action IWebSocketConnection.OnPong + { + get { return null; } + set { throw new NotSupportedException(); } + } + + IWebSocketConnectionInfo IWebSocketConnection.ConnectionInfo => this; + bool IWebSocketConnection.IsAvailable => _isAvailable; + + Task IWebSocketConnection.Send(string message) + { + var bytes = Encoding.UTF8.GetBytes(message); + return SendText(bytes, true); + } + + Task IWebSocketConnection.Send(byte[] message) + { + return SendBinary(message, true); + } + + Task IWebSocketConnection.SendPing(byte[] message) + { + throw new NotSupportedException(); + } + + Task IWebSocketConnection.SendPong(byte[] message) + { + throw new NotSupportedException(); + } + + void IWebSocketConnection.Close() + { + Close(WebSocketCloseStatus.NormalClosure, string.Empty).Wait(); + } + + #endregion + + #region IWebSocketConnectionInfo + + string IWebSocketConnectionInfo.SubProtocol + { + get + { + const string protocol = "Sec-WebSocket-Protocol"; + return Context.Request.Headers.ContainsKey(protocol) + ? Context.Request.Headers[protocol] + : string.Empty; + } + } + + string IWebSocketConnectionInfo.Origin + { + get + { + const string origin1 = "Origin"; + if (Context.Request.Headers.ContainsKey(origin1)) + return Context.Request.Headers[origin1]; + + const string origin2 = "Sec-WebSocket-Origin"; + return Context.Request.Headers.ContainsKey(origin2) + ? Context.Request.Headers[origin2] + : string.Empty; + } + } + + + string IWebSocketConnectionInfo.Host => Context.Request.Host.Value; + string IWebSocketConnectionInfo.Path => Context.Request.Path.Value; + string IWebSocketConnectionInfo.ClientIpAddress => Context.Request.RemoteIpAddress; + int IWebSocketConnectionInfo.ClientPort => Context.Request.RemotePort ?? 0; + IDictionary IWebSocketConnectionInfo.Cookies => Context.Request.Cookies.ToDictionary(k => k.Key, v => v.Value); + IDictionary IWebSocketConnectionInfo.Headers => Context.Request.Headers.SelectMany(p => p.Value.Select(v => new { p.Key, Value = v })).ToDictionary(k => k.Key, v => v.Value); + + Guid IWebSocketConnectionInfo.Id { get; } = Guid.NewGuid(); + string IWebSocketConnectionInfo.NegotiatedSubProtocol { get; } = string.Empty; + + #endregion + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs new file mode 100644 index 0000000..09c1c2d --- /dev/null +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Fleck; +using Microsoft.Owin; + +namespace Owin.WebSocket +{ + public class FleckWebSocketConnectionMiddleware : OwinMiddleware + { + private readonly Regex _matchPattern; + + private readonly Action _config; + + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config) + : base(next) + { + this._config = config; + } + + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, Regex matchPattern) + : this(next, config) + { + _matchPattern = matchPattern; + } + + public override Task Invoke(IOwinContext context) + { + var matches = new Dictionary(); + + if (_matchPattern != null) + { + var match = _matchPattern.Match(context.Request.Path.Value); + if (!match.Success) + return Next.Invoke(context); + + for (var i = 1; i <= match.Groups.Count; i++) + { + var name = _matchPattern.GroupNameFromNumber(i); + var value = match.Groups[i]; + matches.Add(name, value.Value); + } + } + + var socketConnection = new FleckWebSocketConnection(); + + _config(socketConnection); + + return socketConnection.AcceptSocketAsync(context, matches); + } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs new file mode 100644 index 0000000..1ba80c2 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs @@ -0,0 +1,17 @@ +using System; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Fleck; +using Microsoft.Owin; + +namespace Owin.WebSocket +{ + public interface IOwinWebSocketConnection : IWebSocketConnection + { + Func OnOpenAsync { get; set; } + Action OnCloseOwin { get; set; } + Func OnCloseOwinAsync { get; set; } + Func OnAuthenticateRequest { get; set; } + Func> OnAuthenticateRequestAsync { get; set; } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj new file mode 100644 index 0000000..6ea42b2 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj @@ -0,0 +1,82 @@ + + + + + Debug + AnyCPU + {32050A9E-3FC9-4A71-85BF-6645AE68B82B} + Library + Properties + Owin.WebSocket + Owin.WebSocket.Fleck + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Fleck.0.13.0.57\lib\net40\Fleck.dll + True + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + {b1d742f7-39fa-498c-b22e-1a7a61c301e7} + Owin.WebSocket + + + + + \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/Properties/AssemblyInfo.cs b/src/Owin.WebSocket.Fleck/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..86a9fb6 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Owin.WebSocket.Fleck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.WebSocket.Fleck")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("32050a9e-3fc9-4a71-85bf-6645ae68b82b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Owin.WebSocket.Fleck/packages.config b/src/Owin.WebSocket.Fleck/packages.config new file mode 100644 index 0000000..58dd4e4 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.WebSocket.sln b/src/Owin.WebSocket.sln index 76019f0..c2a2bdb 100644 --- a/src/Owin.WebSocket.sln +++ b/src/Owin.WebSocket.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.WebSocket", "Owin.WebSocket\Owin.WebSocket.csproj", "{B1D742F7-39FA-498C-B22E-1A7A61C301E7}" EndProject @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{8DCED5 .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.WebSocket.Fleck", "Owin.WebSocket.Fleck\Owin.WebSocket.Fleck.csproj", "{32050A9E-3FC9-4A71-85BF-6645AE68B82B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,6 +30,10 @@ Global {E8AAA2FB-EA74-4120-B0A0-838926422AF1}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8AAA2FB-EA74-4120-B0A0-838926422AF1}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8AAA2FB-EA74-4120-B0A0-838926422AF1}.Release|Any CPU.Build.0 = Release|Any CPU + {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Owin.WebSocket/WebSocketConnection.cs b/src/Owin.WebSocket/WebSocketConnection.cs index a8de6b8..28a417e 100644 --- a/src/Owin.WebSocket/WebSocketConnection.cs +++ b/src/Owin.WebSocket/WebSocketConnection.cs @@ -184,9 +184,7 @@ public virtual void OnReceiveError(Exception error) /// /// Receive one entire message from the web socket /// - - - internal async Task AcceptSocketAsync(IOwinContext context, IDictionary argumentMatches) + public async Task AcceptSocketAsync(IOwinContext context, IDictionary argumentMatches) { var accept = context.Get, Func, Task>>>("websocket.Accept"); if (accept == null) diff --git a/src/UnitTests/FleckTests.cs b/src/UnitTests/FleckTests.cs new file mode 100644 index 0000000..ce20c49 --- /dev/null +++ b/src/UnitTests/FleckTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Concurrent; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using Fleck; +using Microsoft.Owin.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Owin.WebSocket.Extensions; + +namespace UnitTests +{ + [TestClass] + public class FleckTests + { + private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); + private readonly ConcurrentQueue _messages = new ConcurrentQueue(); + + private void Write(int id, string message) + { + _messages.Enqueue(message); + + if (id == -1) + return; + + foreach (var connectionPair in _connections) + { + if (!connectionPair.Value.IsAvailable) + continue; + + if (connectionPair.Key == id) + continue; + + connectionPair.Value.Send(message).Wait(); + } + } + + [TestMethod] + public void IntegrationTest() + { + using (WebApp.Start(new StartOptions("http://localhost:8989"), startup => + { + var idSeed = 0; + + startup.MapWebSocketRoute("/fleck", connection => + { + var id = Interlocked.Increment(ref idSeed); + + connection.OnOpen = () => Write(id, $"Open: {id}"); + connection.OnClose = () => Write(id, $"Close: {id}"); + connection.OnError = ex => Write(-1, $"Error: {id} - {ex.Message}"); + connection.OnMessage = m => Write(id, $"User {id}: {m}"); + connection.OnAuthenticateRequest = r => + { + var result = id % 2 == 1; + Write(id, $"Auth {id}: {result}"); + return result; + }; + + _connections.TryAdd(id, connection); + }); + })) + using (var client1 = new ClientWebSocket()) + using (var client2 = new ClientWebSocket()) + using (var client3 = new ClientWebSocket()) + { + client1.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + + try + { + client2.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + Assert.Fail("Client 2 should not be unauthorized"); + } + catch (AggregateException ex) + { + Assert.AreEqual("Unable to connect to the remote server", ex.InnerException.Message); + } + + client3.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + + var bytes3 = new byte[1024]; + var segment3 = new ArraySegment(bytes3); + var receive3 = client3.ReceiveAsync(segment3, CancellationToken.None); + + var message1 = "Hello world"; + var bytes1 = Encoding.UTF8.GetBytes(message1); + var segment1 = new ArraySegment(bytes1); + client1.SendAsync(segment1, WebSocketMessageType.Text, true, CancellationToken.None).Wait(); + + var result3 = receive3.Result; + Assert.AreEqual(WebSocketMessageType.Text, result3.MessageType); + var message3 = Encoding.UTF8.GetString(segment3.Array, 0, result3.Count); + Assert.AreEqual(message3, "User 1: Hello world"); + + + client1.Abort(); + client3.Abort(); + } + + var messages = _messages.ToArray(); + Assert.AreEqual(8, messages.Length); + Assert.AreEqual("Auth 1: True", messages[0]); + Assert.AreEqual("Open: 1", messages[1]); + Assert.AreEqual("Auth 2: False", messages[2]); + Assert.AreEqual("Auth 3: True", messages[3]); + Assert.AreEqual("Open: 3", messages[4]); + Assert.AreEqual("User 1: Hello world", messages[5]); + Assert.IsTrue(messages[6].StartsWith("Error: 1")); + Assert.IsTrue(messages[7].StartsWith("Error: 3")); + } + } +} diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index b57db46..ffd665e 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -8,7 +8,7 @@ Properties UnitTests UnitTests - v4.5.1 + v4.5.2 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 @@ -18,6 +18,7 @@ UnitTest ..\Owin.WebSocket\ true + true @@ -41,6 +42,10 @@ + + ..\packages\Fleck.0.13.0.57\lib\net40\Fleck.dll + True + ..\packages\FluentAssertions.4.2.1\lib\net45\FluentAssertions.dll True @@ -89,11 +94,16 @@ + + + {32050a9e-3fc9-4a71-85bf-6645ae68b82b} + Owin.WebSocket.Fleck + {b1d742f7-39fa-498c-b22e-1a7a61c301e7} Owin.WebSocket diff --git a/src/UnitTests/WebSocketTests.cs b/src/UnitTests/WebSocketTests.cs index c3e9f32..ea19ea7 100644 --- a/src/UnitTests/WebSocketTests.cs +++ b/src/UnitTests/WebSocketTests.cs @@ -35,7 +35,7 @@ public static void Init(TestContext test) { sResolver = new TestResolver(); - WebApp.Start(new StartOptions("http://localhost:8989"), startup => + sWeb = WebApp.Start(new StartOptions("http://localhost:8989"), startup => { startup.MapWebSocketRoute(); startup.MapWebSocketRoute("/ws", sResolver); diff --git a/src/UnitTests/app.config b/src/UnitTests/app.config index 66e9d66..2687008 100644 --- a/src/UnitTests/app.config +++ b/src/UnitTests/app.config @@ -1,11 +1,11 @@ - + - - + + - \ No newline at end of file + diff --git a/src/UnitTests/packages.config b/src/UnitTests/packages.config index 4f1dcbd..a956b89 100644 --- a/src/UnitTests/packages.config +++ b/src/UnitTests/packages.config @@ -1,6 +1,7 @@  + From 66451e07fcbe4b60832335a2c7b116781539f6ce Mon Sep 17 00:00:00 2001 From: Tom DuPont Date: Sat, 6 Feb 2016 10:51:21 -0800 Subject: [PATCH 2/5] Refactored IOwinWebSocketConnection to separate Fleck implementation from Owin for consumers. --- .../Extensions/AppBuilderExtensions.cs | 39 +++++ .../Extensions/FleckExtensions.cs | 23 --- .../FleckWebSocketConnection.cs | 89 +++++++++--- .../FleckWebSocketConnectionMiddleware.cs | 25 +++- .../IOwinWebSocketConnection.cs | 15 +- .../IOwinWebSocketContext.cs | 12 ++ .../Owin.WebSocket.Fleck.csproj | 3 +- src/Owin.WebSocket/WebSocketConnection.cs | 5 +- src/UnitTests/FleckTests.cs | 105 +------------- src/UnitTests/FleckTestsBase.cs | 136 ++++++++++++++++++ src/UnitTests/OwinFleckTests.cs | 99 +++++++++++++ src/UnitTests/UnitTests.csproj | 2 + 12 files changed, 396 insertions(+), 157 deletions(-) create mode 100644 src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs delete mode 100644 src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs create mode 100644 src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs create mode 100644 src/UnitTests/FleckTestsBase.cs create mode 100644 src/UnitTests/OwinFleckTests.cs diff --git a/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs b/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs new file mode 100644 index 0000000..4f0ce03 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Text.RegularExpressions; +using Fleck; + +namespace Owin.WebSocket.Extensions +{ + public static class AppBuilderExtensions + { + public static IAppBuilder MapFleckRoute(this IAppBuilder app, Action config) + { + return app.MapFleckRoute(string.Empty, config); + } + + public static IAppBuilder MapFleckRoute(this IAppBuilder app, string route, Action config) + { + return app.Map(route, a => a.Use(config)); + } + + public static IAppBuilder MapFleckPattern(this IAppBuilder app, string regexPatternMatch, Action config) + { + return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + } + + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, Action config) + { + return app.MapOwinFleckRoute(string.Empty, config); + } + + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, string route, Action config) + { + return app.Map(route, a => a.Use(config)); + } + + public static IAppBuilder MapOwinFleckPattern(this IAppBuilder app, string regexPatternMatch, Action config) + { + return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs b/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs deleted file mode 100644 index e2da135..0000000 --- a/src/Owin.WebSocket.Fleck/Extensions/FleckExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace Owin.WebSocket.Extensions -{ - public static class FleckExtensions - { - public static IAppBuilder MapWebSocketRoute(this IAppBuilder app, Action config) - { - return app.MapWebSocketRoute(string.Empty, config); - } - - public static IAppBuilder MapWebSocketRoute(this IAppBuilder app, string route, Action config) - { - return app.Map(route, a => a.Use(config)); - } - - public static IAppBuilder MapWebSocketPattern(this IAppBuilder app, string regexPatternMatch, Action config) - { - return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); - } - } -} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs index 4d49a51..afb7b24 100644 --- a/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs @@ -1,35 +1,41 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Threading.Tasks; using Fleck; using Microsoft.Owin; +using Microsoft.Owin.Security; namespace Owin.WebSocket { public sealed class FleckWebSocketConnection : WebSocketConnection, IOwinWebSocketConnection, IWebSocketConnectionInfo { + private const string PingPongError = "Owin handles ping pong messages internally"; + + private readonly OwinWebSocketContext _context; private readonly IOwinWebSocketConnection _connection; private bool _isAvailable; - + public FleckWebSocketConnection() { _connection = this; + _context = new OwinWebSocketContext(_connection.Context, MaxMessageSize); } #region WebSocketConnection public override bool AuthenticateRequest(IOwinRequest request) { - return _connection.OnAuthenticateRequest?.Invoke(request) ?? true; + return _connection.OnAuthenticateRequest?.Invoke() ?? true; } public override Task AuthenticateRequestAsync(IOwinRequest request) { - return _connection.OnAuthenticateRequestAsync?.Invoke(request) ?? Task.FromResult(true); + return _connection.OnAuthenticateRequestAsync?.Invoke() ?? Task.FromResult(true); } public override void OnOpen() @@ -42,30 +48,34 @@ public override Task OnOpenAsync() { return _connection.OnOpenAsync?.Invoke() ?? Task.FromResult(true); } - + public override void OnClose(WebSocketCloseStatus? closeStatus, string closeStatusDescription) { _isAvailable = false; + _context.CloseStatus = closeStatus; + _context.CloseStatusDescription = closeStatusDescription; _connection.OnClose?.Invoke(); - _connection.OnCloseOwin?.Invoke(closeStatus, closeStatusDescription); } public override Task OnCloseAsync(WebSocketCloseStatus? closeStatus, string closeStatusDescription) { - return _connection.OnCloseOwinAsync?.Invoke(closeStatus, closeStatusDescription) ?? Task.FromResult(true); + return _connection.OnCloseAsync?.Invoke() ?? Task.FromResult(true); } public override Task OnMessageReceived(ArraySegment message, WebSocketMessageType type) { - if (type == WebSocketMessageType.Binary && _connection.OnBinary != null) + if (type == WebSocketMessageType.Binary && (_connection.OnBinary != null || _connection.OnBinaryAsync != null)) { var array = message.ToArray(); - _connection.OnBinary.Invoke(array); + _connection.OnBinary?.Invoke(array); + return _connection.OnBinaryAsync?.Invoke(array) ?? Task.FromResult(true); } - else if (type == WebSocketMessageType.Text && _connection.OnMessage != null) + + if (type == WebSocketMessageType.Text && (_connection.OnMessage != null || _connection.OnMessageAsync!= null)) { var @string = Encoding.UTF8.GetString(message.Array, 0, message.Count); - _connection.OnMessage.Invoke(@string); + _connection.OnMessage?.Invoke(@string); + return _connection.OnMessageAsync?.Invoke(@string) ?? Task.FromResult(true); } return Task.FromResult(true); @@ -77,14 +87,16 @@ public override void OnReceiveError(Exception error) } #endregion - + #region IOwinWebSocketConnection + IOwinWebSocketContext IOwinWebSocketConnection.Context => _context; Func IOwinWebSocketConnection.OnOpenAsync { get; set; } - Action IOwinWebSocketConnection.OnCloseOwin { get; set; } - Func IOwinWebSocketConnection.OnCloseOwinAsync { get; set; } - Func IOwinWebSocketConnection.OnAuthenticateRequest { get; set; } - Func> IOwinWebSocketConnection.OnAuthenticateRequestAsync { get; set; } + Func IOwinWebSocketConnection.OnCloseAsync { get; set; } + Func IOwinWebSocketConnection.OnMessageAsync { get; set; } + Func IOwinWebSocketConnection.OnBinaryAsync { get; set; } + Func IOwinWebSocketConnection.OnAuthenticateRequest { get; set; } + Func> IOwinWebSocketConnection.OnAuthenticateRequestAsync { get; set; } #endregion @@ -99,13 +111,13 @@ public override void OnReceiveError(Exception error) Action IWebSocketConnection.OnPing { get { return null; } - set { throw new NotSupportedException(); } + set { throw new NotSupportedException(PingPongError); } } Action IWebSocketConnection.OnPong { get { return null; } - set { throw new NotSupportedException(); } + set { throw new NotSupportedException(PingPongError); } } IWebSocketConnectionInfo IWebSocketConnection.ConnectionInfo => this; @@ -124,12 +136,12 @@ Task IWebSocketConnection.Send(byte[] message) Task IWebSocketConnection.SendPing(byte[] message) { - throw new NotSupportedException(); + throw new NotSupportedException(PingPongError); } Task IWebSocketConnection.SendPong(byte[] message) { - throw new NotSupportedException(); + throw new NotSupportedException(PingPongError); } void IWebSocketConnection.Close() @@ -166,8 +178,7 @@ string IWebSocketConnectionInfo.Origin : string.Empty; } } - - + string IWebSocketConnectionInfo.Host => Context.Request.Host.Value; string IWebSocketConnectionInfo.Path => Context.Request.Path.Value; string IWebSocketConnectionInfo.ClientIpAddress => Context.Request.RemoteIpAddress; @@ -179,5 +190,41 @@ string IWebSocketConnectionInfo.Origin string IWebSocketConnectionInfo.NegotiatedSubProtocol { get; } = string.Empty; #endregion + + private class OwinWebSocketContext : IOwinWebSocketContext + { + private readonly IOwinContext _context; + + public OwinWebSocketContext(IOwinContext context, int maxMessageSize) + { + _context = context; + MaxMessageSize = maxMessageSize; + } + + public T Get(string key) + { + return _context.Get(key); + } + + public IOwinContext Set(string key, T value) + { + return _context.Set(key, value); + } + + public IOwinRequest Request => _context.Request; + public IOwinResponse Response => _context.Response; + public IAuthenticationManager Authentication => _context.Authentication; + public IDictionary Environment => _context.Environment; + + public TextWriter TraceOutput + { + get { return _context.TraceOutput; } + set { _context.TraceOutput = value; } + } + + public int MaxMessageSize { get; } + public WebSocketCloseStatus? CloseStatus { get; set; } + public string CloseStatusDescription { get; set; } + } } } \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs index 09c1c2d..45aa614 100644 --- a/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs @@ -11,12 +11,26 @@ public class FleckWebSocketConnectionMiddleware : OwinMiddleware { private readonly Regex _matchPattern; - private readonly Action _config; + private readonly Action _config; + + private readonly Action _owinConfig; + + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config) + : base(next) + { + _config = config; + } + + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, Regex matchPattern) + : this(next, config) + { + _matchPattern = matchPattern; + } public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config) : base(next) { - this._config = config; + _owinConfig = config; } public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, Regex matchPattern) @@ -43,11 +57,12 @@ public override Task Invoke(IOwinContext context) } } - var socketConnection = new FleckWebSocketConnection(); + var connection = new FleckWebSocketConnection(); - _config(socketConnection); + _config?.Invoke(connection); + _owinConfig?.Invoke(connection); - return socketConnection.AcceptSocketAsync(context, matches); + return connection.AcceptSocketAsync(context, matches); } } } \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs index 1ba80c2..941ca47 100644 --- a/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs +++ b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs @@ -1,17 +1,20 @@ using System; -using System.Net.WebSockets; +using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Fleck; -using Microsoft.Owin; +using Microsoft.Owin.Security; namespace Owin.WebSocket { public interface IOwinWebSocketConnection : IWebSocketConnection { + IOwinWebSocketContext Context { get; } Func OnOpenAsync { get; set; } - Action OnCloseOwin { get; set; } - Func OnCloseOwinAsync { get; set; } - Func OnAuthenticateRequest { get; set; } - Func> OnAuthenticateRequestAsync { get; set; } + Func OnCloseAsync { get; set; } + Func OnMessageAsync { get; set; } + Func OnBinaryAsync { get; set; } + Func OnAuthenticateRequest { get; set; } + Func> OnAuthenticateRequestAsync { get; set; } } } \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs b/src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs new file mode 100644 index 0000000..3bede5a --- /dev/null +++ b/src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs @@ -0,0 +1,12 @@ +using System.Net.WebSockets; +using Microsoft.Owin; + +namespace Owin.WebSocket +{ + public interface IOwinWebSocketContext : IOwinContext + { + int MaxMessageSize { get; } + WebSocketCloseStatus? CloseStatus { get; } + string CloseStatusDescription { get; } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj index 6ea42b2..9ce6cde 100644 --- a/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj +++ b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj @@ -56,10 +56,11 @@ - + + diff --git a/src/Owin.WebSocket/WebSocketConnection.cs b/src/Owin.WebSocket/WebSocketConnection.cs index 28a417e..f369f43 100644 --- a/src/Owin.WebSocket/WebSocketConnection.cs +++ b/src/Owin.WebSocket/WebSocketConnection.cs @@ -34,7 +34,7 @@ public abstract class WebSocketConnection /// Queue of send operations to the client /// public TaskQueue QueueSend { get { return mWebSocket.SendQueue;} } - + protected WebSocketConnection(int maxMessageSize = 1024*64) { mCancellToken = new CancellationTokenSource(); @@ -180,7 +180,7 @@ public virtual Task OnCloseAsync(WebSocketCloseStatus? closeStatus, string close public virtual void OnReceiveError(Exception error) { } - + /// /// Receive one entire message from the web socket /// @@ -217,7 +217,6 @@ public async Task AcceptSocketAsync(IOwinContext context, IDictionary _connections = new ConcurrentDictionary(); - private readonly ConcurrentQueue _messages = new ConcurrentQueue(); - - private void Write(int id, string message) - { - _messages.Enqueue(message); - - if (id == -1) - return; - - foreach (var connectionPair in _connections) - { - if (!connectionPair.Value.IsAvailable) - continue; - - if (connectionPair.Key == id) - continue; - - connectionPair.Value.Send(message).Wait(); - } - } - [TestMethod] - public void IntegrationTest() + public void FeatureParityTest() { - using (WebApp.Start(new StartOptions("http://localhost:8989"), startup => + using (var server = new WebSocketServer("http://127.0.0.1:8989")) { - var idSeed = 0; - - startup.MapWebSocketRoute("/fleck", connection => - { - var id = Interlocked.Increment(ref idSeed); - - connection.OnOpen = () => Write(id, $"Open: {id}"); - connection.OnClose = () => Write(id, $"Close: {id}"); - connection.OnError = ex => Write(-1, $"Error: {id} - {ex.Message}"); - connection.OnMessage = m => Write(id, $"User {id}: {m}"); - connection.OnAuthenticateRequest = r => - { - var result = id % 2 == 1; - Write(id, $"Auth {id}: {result}"); - return result; - }; - - _connections.TryAdd(id, connection); - }); - })) - using (var client1 = new ClientWebSocket()) - using (var client2 = new ClientWebSocket()) - using (var client3 = new ClientWebSocket()) - { - client1.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); - - try - { - client2.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); - Assert.Fail("Client 2 should not be unauthorized"); - } - catch (AggregateException ex) - { - Assert.AreEqual("Unable to connect to the remote server", ex.InnerException.Message); - } - - client3.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); - - var bytes3 = new byte[1024]; - var segment3 = new ArraySegment(bytes3); - var receive3 = client3.ReceiveAsync(segment3, CancellationToken.None); - - var message1 = "Hello world"; - var bytes1 = Encoding.UTF8.GetBytes(message1); - var segment1 = new ArraySegment(bytes1); - client1.SendAsync(segment1, WebSocketMessageType.Text, true, CancellationToken.None).Wait(); - - var result3 = receive3.Result; - Assert.AreEqual(WebSocketMessageType.Text, result3.MessageType); - var message3 = Encoding.UTF8.GetString(segment3.Array, 0, result3.Count); - Assert.AreEqual(message3, "User 1: Hello world"); - - - client1.Abort(); - client3.Abort(); + server.Start(ConfigureIntegrationTestConnection); + SendIntegrationTestMessages(); } - var messages = _messages.ToArray(); - Assert.AreEqual(8, messages.Length); - Assert.AreEqual("Auth 1: True", messages[0]); - Assert.AreEqual("Open: 1", messages[1]); - Assert.AreEqual("Auth 2: False", messages[2]); - Assert.AreEqual("Auth 3: True", messages[3]); - Assert.AreEqual("Open: 3", messages[4]); - Assert.AreEqual("User 1: Hello world", messages[5]); - Assert.IsTrue(messages[6].StartsWith("Error: 1")); - Assert.IsTrue(messages[7].StartsWith("Error: 3")); + AssertIntegrationTestMessages(); } } } diff --git a/src/UnitTests/FleckTestsBase.cs b/src/UnitTests/FleckTestsBase.cs new file mode 100644 index 0000000..737a4f1 --- /dev/null +++ b/src/UnitTests/FleckTestsBase.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Fleck; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests +{ + public abstract class FleckTestsBase + { + private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); + private readonly ConcurrentQueue _messages = new ConcurrentQueue(); + + private int _idSeed; + + protected void AddConnection(int id, IWebSocketConnection connection) + { + _connections.TryAdd(id, connection); + } + + protected void RemoveConnection(int id) + { + IWebSocketConnection connection; + _connections.TryRemove(id, out connection); + } + + protected void Send(int id, string message) + { + _messages.Enqueue(message); + + if (id == -1) + return; + + foreach (var connectionPair in _connections) + { + if (!connectionPair.Value.IsAvailable) + continue; + + if (connectionPair.Key == id) + continue; + + connectionPair.Value.Send(message).Wait(); + } + } + + protected IList DequeueMessages() + { + var results = new List(_messages.Count); + string message; + while (_messages.TryDequeue(out message)) + { + results.Add(message); + } + + return results; + } + + protected void ConfigureIntegrationTestConnection(IWebSocketConnection connection) + { + ConfigureIntegrationTestConnectionAndGetId(connection); + } + + protected int ConfigureIntegrationTestConnectionAndGetId(IWebSocketConnection connection) + { + var id = Interlocked.Increment(ref _idSeed); + + connection.OnOpen = () => + { + AddConnection(id, connection); + Send(id, $"Open: {id}"); + }; + connection.OnClose = () => + { + Send(id, $"Close: {id}"); + RemoveConnection(id); + }; + connection.OnError = ex => + { + Send(-1, $"Error: {id} - {ex.ToString()}"); + }; + connection.OnMessage = m => + { + Send(id, $"User {id}: {m}"); + }; + + return id; + } + + protected void SendIntegrationTestMessages() + { + using (var client1 = new ClientWebSocket()) + using (var client2 = new ClientWebSocket()) + { + client1.ConnectAsync(new Uri("ws://localhost:8989"), CancellationToken.None).Wait(); + client2.ConnectAsync(new Uri("ws://localhost:8989"), CancellationToken.None).Wait(); + + var bytes2 = new byte[1024]; + var segment2 = new ArraySegment(bytes2); + var receive2 = client2.ReceiveAsync(segment2, CancellationToken.None); + + var message1 = "Hello world"; + var bytes1 = Encoding.UTF8.GetBytes(message1); + var segment1 = new ArraySegment(bytes1); + client1.SendAsync(segment1, WebSocketMessageType.Text, true, CancellationToken.None).Wait(); + + var result2 = receive2.Result; + Assert.AreEqual(WebSocketMessageType.Text, result2.MessageType); + var message3 = Encoding.UTF8.GetString(segment2.Array, 0, result2.Count); + Assert.AreEqual(message3, "User 1: Hello world"); + + client2.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait(); + client2.Dispose(); + Task.Delay(100).Wait(); + + client1.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait(); + client1.Dispose(); + Task.Delay(100).Wait(); + } + } + + protected void AssertIntegrationTestMessages() + { + var messages = DequeueMessages(); + Assert.AreEqual(5, messages.Count); + Assert.AreEqual("Open: 1", messages[0]); + Assert.AreEqual("Open: 2", messages[1]); + Assert.AreEqual("User 1: Hello world", messages[2]); + Assert.AreEqual("Close: 2", messages[3]); + Assert.AreEqual("Close: 1", messages[4]); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/OwinFleckTests.cs b/src/UnitTests/OwinFleckTests.cs new file mode 100644 index 0000000..128f3fe --- /dev/null +++ b/src/UnitTests/OwinFleckTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Owin.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Owin.WebSocket.Extensions; + +namespace UnitTests +{ + [TestClass] + public class OwinFleckTests : FleckTestsBase + { + [TestMethod] + public void FeatureParityTest() + { + using (WebApp.Start(new StartOptions("http://localhost:8989"), startup => + { + startup.MapOwinFleckRoute(ConfigureIntegrationTestConnection); + })) + { + SendIntegrationTestMessages(); + AssertIntegrationTestMessages(); + } + } + + [TestMethod] + public void AuthorizationTest() + { + using (WebApp.Start(new StartOptions("http://localhost:8989"), startup => + { + startup.MapOwinFleckRoute("/fleck", connection => + { + var id = ConfigureIntegrationTestConnectionAndGetId(connection); + + connection.OnAuthenticateRequest = () => + { + var result = id % 2 == 1; + Send(id, $"Auth {id}: {result}"); + return result; + }; + + }); + })) + using (var client1 = new ClientWebSocket()) + using (var client2 = new ClientWebSocket()) + using (var client3 = new ClientWebSocket()) + { + client1.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + + try + { + client2.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + Assert.Fail("Client 2 should not be unauthorized"); + } + catch (AggregateException ex) + { + Assert.AreEqual("Unable to connect to the remote server", ex.InnerException.Message); + } + + client3.ConnectAsync(new Uri("ws://localhost:8989/fleck"), CancellationToken.None).Wait(); + + var bytes3 = new byte[1024]; + var segment3 = new ArraySegment(bytes3); + var receive3 = client3.ReceiveAsync(segment3, CancellationToken.None); + + var message1 = "Hello world"; + var bytes1 = Encoding.UTF8.GetBytes(message1); + var segment1 = new ArraySegment(bytes1); + client1.SendAsync(segment1, WebSocketMessageType.Text, true, CancellationToken.None).Wait(); + + var result3 = receive3.Result; + Assert.AreEqual(WebSocketMessageType.Text, result3.MessageType); + var message3 = Encoding.UTF8.GetString(segment3.Array, 0, result3.Count); + Assert.AreEqual(message3, "User 1: Hello world"); + + client3.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait(); + client3.Dispose(); + Task.Delay(100).Wait(); + + client1.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait(); + client1.Dispose(); + Task.Delay(100).Wait(); + } + + var messages = DequeueMessages(); + Assert.AreEqual(8, messages.Count); + Assert.AreEqual("Auth 1: True", messages[0]); + Assert.AreEqual("Open: 1", messages[1]); + Assert.AreEqual("Auth 2: False", messages[2]); + Assert.AreEqual("Auth 3: True", messages[3]); + Assert.AreEqual("Open: 3", messages[4]); + Assert.AreEqual("User 1: Hello world", messages[5]); + Assert.AreEqual("Close: 3", messages[6]); + Assert.AreEqual("Close: 1", messages[7]); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index ffd665e..70ab63d 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -95,6 +95,8 @@ + + From 040d278568093324f3e57783a9909511adbbf39b Mon Sep 17 00:00:00 2001 From: Tom DuPont Date: Sat, 6 Feb 2016 11:20:55 -0800 Subject: [PATCH 3/5] Updated Middle wear to support extension, and broke models into multiple files. --- .../Extensions/AppBuilderExtensions.cs | 41 ++++++--- .../FleckWebSocketConnection.cs | 92 +++---------------- .../FleckWebSocketConnectionMiddleware.cs | 34 +++---- .../IOwinWebSocketConnection.cs | 4 +- .../Models/FleckWebSocketConnectionInfo.cs | 54 +++++++++++ .../{ => Models}/IOwinWebSocketContext.cs | 2 +- .../Models/OwinWebSocketContext.cs | 44 +++++++++ .../Owin.WebSocket.Fleck.csproj | 4 +- 8 files changed, 158 insertions(+), 117 deletions(-) create mode 100644 src/Owin.WebSocket.Fleck/Models/FleckWebSocketConnectionInfo.cs rename src/Owin.WebSocket.Fleck/{ => Models}/IOwinWebSocketContext.cs (89%) create mode 100644 src/Owin.WebSocket.Fleck/Models/OwinWebSocketContext.cs diff --git a/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs b/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs index 4f0ce03..2947114 100644 --- a/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs +++ b/src/Owin.WebSocket.Fleck/Extensions/AppBuilderExtensions.cs @@ -1,39 +1,56 @@ using System; using System.Text.RegularExpressions; using Fleck; +using Microsoft.Practices.ServiceLocation; namespace Owin.WebSocket.Extensions { public static class AppBuilderExtensions { - public static IAppBuilder MapFleckRoute(this IAppBuilder app, Action config) + public static IAppBuilder MapFleckRoute(this IAppBuilder app, Action config, IServiceLocator locator = null, string regexPatternMatch = null) { - return app.MapFleckRoute(string.Empty, config); + return app.MapFleckRoute(string.Empty, config, locator, regexPatternMatch); } - public static IAppBuilder MapFleckRoute(this IAppBuilder app, string route, Action config) + public static IAppBuilder MapFleckRoute(this IAppBuilder app, string route, Action config, IServiceLocator locator = null, string regexPatternMatch = null) { - return app.Map(route, a => a.Use(config)); + return app.MapFleckRoute(route, config, locator, regexPatternMatch); } - public static IAppBuilder MapFleckPattern(this IAppBuilder app, string regexPatternMatch, Action config) + public static IAppBuilder MapFleckRoute(this IAppBuilder app, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + where T : FleckWebSocketConnection, new() { - return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + return app.MapFleckRoute(string.Empty, config, locator, regexPatternMatch); } - public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, Action config) + public static IAppBuilder MapFleckRoute(this IAppBuilder app, string route, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + where T : FleckWebSocketConnection, new() { - return app.MapOwinFleckRoute(string.Empty, config); + var regex = string.IsNullOrWhiteSpace(regexPatternMatch) ? null : new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return app.Map(route, a => a.Use>(config, locator, regex)); + } + + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + { + return app.MapOwinFleckRoute(string.Empty, config, locator, regexPatternMatch); + } + + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, string route, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + { + return app.MapOwinFleckRoute(route, config, locator, regexPatternMatch); } - public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, string route, Action config) + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + where T : FleckWebSocketConnection, new() { - return app.Map(route, a => a.Use(config)); + return app.MapOwinFleckRoute(string.Empty, config, locator, regexPatternMatch); } - public static IAppBuilder MapOwinFleckPattern(this IAppBuilder app, string regexPatternMatch, Action config) + public static IAppBuilder MapOwinFleckRoute(this IAppBuilder app, string route, Action config, IServiceLocator locator = null, string regexPatternMatch = null) + where T : FleckWebSocketConnection, new() { - return app.Use(config, new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase)); + var regex = string.IsNullOrWhiteSpace(regexPatternMatch) ? null : new Regex(regexPatternMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return app.Map(route, a => a.Use>(config, locator, regex)); } } } \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs index afb7b24..c711898 100644 --- a/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnection.cs @@ -1,29 +1,35 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Threading.Tasks; using Fleck; using Microsoft.Owin; -using Microsoft.Owin.Security; +using Owin.WebSocket.Models; namespace Owin.WebSocket { - public sealed class FleckWebSocketConnection : WebSocketConnection, IOwinWebSocketConnection, IWebSocketConnectionInfo + public class FleckWebSocketConnection : WebSocketConnection, IOwinWebSocketConnection { private const string PingPongError = "Owin handles ping pong messages internally"; private readonly OwinWebSocketContext _context; private readonly IOwinWebSocketConnection _connection; + private readonly IWebSocketConnectionInfo _connectionInfo; private bool _isAvailable; public FleckWebSocketConnection() + : this(1024*64) + { + } + + public FleckWebSocketConnection(int maxMessageSize) + : base(maxMessageSize) { _connection = this; _context = new OwinWebSocketContext(_connection.Context, MaxMessageSize); + _connectionInfo = new FleckWebSocketConnectionInfo(_connection.Context); } #region WebSocketConnection @@ -120,7 +126,7 @@ Action IWebSocketConnection.OnPong set { throw new NotSupportedException(PingPongError); } } - IWebSocketConnectionInfo IWebSocketConnection.ConnectionInfo => this; + IWebSocketConnectionInfo IWebSocketConnection.ConnectionInfo => _connectionInfo; bool IWebSocketConnection.IsAvailable => _isAvailable; Task IWebSocketConnection.Send(string message) @@ -150,81 +156,5 @@ void IWebSocketConnection.Close() } #endregion - - #region IWebSocketConnectionInfo - - string IWebSocketConnectionInfo.SubProtocol - { - get - { - const string protocol = "Sec-WebSocket-Protocol"; - return Context.Request.Headers.ContainsKey(protocol) - ? Context.Request.Headers[protocol] - : string.Empty; - } - } - - string IWebSocketConnectionInfo.Origin - { - get - { - const string origin1 = "Origin"; - if (Context.Request.Headers.ContainsKey(origin1)) - return Context.Request.Headers[origin1]; - - const string origin2 = "Sec-WebSocket-Origin"; - return Context.Request.Headers.ContainsKey(origin2) - ? Context.Request.Headers[origin2] - : string.Empty; - } - } - - string IWebSocketConnectionInfo.Host => Context.Request.Host.Value; - string IWebSocketConnectionInfo.Path => Context.Request.Path.Value; - string IWebSocketConnectionInfo.ClientIpAddress => Context.Request.RemoteIpAddress; - int IWebSocketConnectionInfo.ClientPort => Context.Request.RemotePort ?? 0; - IDictionary IWebSocketConnectionInfo.Cookies => Context.Request.Cookies.ToDictionary(k => k.Key, v => v.Value); - IDictionary IWebSocketConnectionInfo.Headers => Context.Request.Headers.SelectMany(p => p.Value.Select(v => new { p.Key, Value = v })).ToDictionary(k => k.Key, v => v.Value); - - Guid IWebSocketConnectionInfo.Id { get; } = Guid.NewGuid(); - string IWebSocketConnectionInfo.NegotiatedSubProtocol { get; } = string.Empty; - - #endregion - - private class OwinWebSocketContext : IOwinWebSocketContext - { - private readonly IOwinContext _context; - - public OwinWebSocketContext(IOwinContext context, int maxMessageSize) - { - _context = context; - MaxMessageSize = maxMessageSize; - } - - public T Get(string key) - { - return _context.Get(key); - } - - public IOwinContext Set(string key, T value) - { - return _context.Set(key, value); - } - - public IOwinRequest Request => _context.Request; - public IOwinResponse Response => _context.Response; - public IAuthenticationManager Authentication => _context.Authentication; - public IDictionary Environment => _context.Environment; - - public TextWriter TraceOutput - { - get { return _context.TraceOutput; } - set { _context.TraceOutput = value; } - } - - public int MaxMessageSize { get; } - public WebSocketCloseStatus? CloseStatus { get; set; } - public string CloseStatusDescription { get; set; } - } } } \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs index 45aa614..6c0d746 100644 --- a/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs +++ b/src/Owin.WebSocket.Fleck/FleckWebSocketConnectionMiddleware.cs @@ -4,38 +4,34 @@ using System.Threading.Tasks; using Fleck; using Microsoft.Owin; +using Microsoft.Practices.ServiceLocation; namespace Owin.WebSocket { - public class FleckWebSocketConnectionMiddleware : OwinMiddleware + public class FleckWebSocketConnectionMiddleware : OwinMiddleware + where T : FleckWebSocketConnection, new() { - private readonly Regex _matchPattern; - private readonly Action _config; - private readonly Action _owinConfig; - - public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config) - : base(next) + private readonly IServiceLocator _locator; + private readonly Regex _matchPattern; + + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, IServiceLocator locator, Regex matchPattern) + : this(next, locator, matchPattern) { _config = config; } - public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, Regex matchPattern) - : this(next, config) - { - _matchPattern = matchPattern; - } - - public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config) - : base(next) + public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, IServiceLocator locator, Regex matchPattern) + : this(next, locator, matchPattern) { _owinConfig = config; } - - public FleckWebSocketConnectionMiddleware(OwinMiddleware next, Action config, Regex matchPattern) - : this(next, config) + + private FleckWebSocketConnectionMiddleware(OwinMiddleware next, IServiceLocator locator, Regex matchPattern) + : base(next) { + _locator = locator; _matchPattern = matchPattern; } @@ -57,7 +53,7 @@ public override Task Invoke(IOwinContext context) } } - var connection = new FleckWebSocketConnection(); + var connection = _locator?.GetInstance() ?? new T(); _config?.Invoke(connection); _owinConfig?.Invoke(connection); diff --git a/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs index 941ca47..3c42355 100644 --- a/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs +++ b/src/Owin.WebSocket.Fleck/IOwinWebSocketConnection.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Fleck; -using Microsoft.Owin.Security; +using Owin.WebSocket.Models; namespace Owin.WebSocket { diff --git a/src/Owin.WebSocket.Fleck/Models/FleckWebSocketConnectionInfo.cs b/src/Owin.WebSocket.Fleck/Models/FleckWebSocketConnectionInfo.cs new file mode 100644 index 0000000..f944311 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Models/FleckWebSocketConnectionInfo.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fleck; +using Microsoft.Owin; + +namespace Owin.WebSocket.Models +{ + public class FleckWebSocketConnectionInfo : IWebSocketConnectionInfo + { + private readonly IOwinContext _context; + + public FleckWebSocketConnectionInfo(IOwinContext context) + { + _context = context; + } + + string IWebSocketConnectionInfo.SubProtocol + { + get + { + const string protocol = "Sec-WebSocket-Protocol"; + return _context.Request.Headers.ContainsKey(protocol) + ? _context.Request.Headers[protocol] + : string.Empty; + } + } + + string IWebSocketConnectionInfo.Origin + { + get + { + const string origin1 = "Origin"; + if (_context.Request.Headers.ContainsKey(origin1)) + return _context.Request.Headers[origin1]; + + const string origin2 = "Sec-WebSocket-Origin"; + return _context.Request.Headers.ContainsKey(origin2) + ? _context.Request.Headers[origin2] + : string.Empty; + } + } + + string IWebSocketConnectionInfo.Host => _context.Request.Host.Value; + string IWebSocketConnectionInfo.Path => _context.Request.Path.Value; + string IWebSocketConnectionInfo.ClientIpAddress => _context.Request.RemoteIpAddress; + int IWebSocketConnectionInfo.ClientPort => _context.Request.RemotePort ?? 0; + IDictionary IWebSocketConnectionInfo.Cookies => _context.Request.Cookies.ToDictionary(k => k.Key, v => v.Value); + IDictionary IWebSocketConnectionInfo.Headers => _context.Request.Headers.SelectMany(p => p.Value.Select(v => new { p.Key, Value = v })).ToDictionary(k => k.Key, v => v.Value); + + Guid IWebSocketConnectionInfo.Id { get; } = Guid.NewGuid(); + string IWebSocketConnectionInfo.NegotiatedSubProtocol { get; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs b/src/Owin.WebSocket.Fleck/Models/IOwinWebSocketContext.cs similarity index 89% rename from src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs rename to src/Owin.WebSocket.Fleck/Models/IOwinWebSocketContext.cs index 3bede5a..7bd0c36 100644 --- a/src/Owin.WebSocket.Fleck/IOwinWebSocketContext.cs +++ b/src/Owin.WebSocket.Fleck/Models/IOwinWebSocketContext.cs @@ -1,7 +1,7 @@ using System.Net.WebSockets; using Microsoft.Owin; -namespace Owin.WebSocket +namespace Owin.WebSocket.Models { public interface IOwinWebSocketContext : IOwinContext { diff --git a/src/Owin.WebSocket.Fleck/Models/OwinWebSocketContext.cs b/src/Owin.WebSocket.Fleck/Models/OwinWebSocketContext.cs new file mode 100644 index 0000000..b05d4e0 --- /dev/null +++ b/src/Owin.WebSocket.Fleck/Models/OwinWebSocketContext.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.WebSocket.Models +{ + public class OwinWebSocketContext : IOwinWebSocketContext + { + private readonly IOwinContext _context; + + public OwinWebSocketContext(IOwinContext context, int maxMessageSize) + { + _context = context; + MaxMessageSize = maxMessageSize; + } + + public T Get(string key) + { + return _context.Get(key); + } + + public IOwinContext Set(string key, T value) + { + return _context.Set(key, value); + } + + public IOwinRequest Request => _context.Request; + public IOwinResponse Response => _context.Response; + public IAuthenticationManager Authentication => _context.Authentication; + public IDictionary Environment => _context.Environment; + + public TextWriter TraceOutput + { + get { return _context.TraceOutput; } + set { _context.TraceOutput = value; } + } + + public int MaxMessageSize { get; } + public WebSocketCloseStatus? CloseStatus { get; set; } + public string CloseStatusDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj index 9ce6cde..82094b6 100644 --- a/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj +++ b/src/Owin.WebSocket.Fleck/Owin.WebSocket.Fleck.csproj @@ -58,9 +58,11 @@ + - + + From 243b1b29f05a6968b9c4d6f476649a78cc0aefe5 Mon Sep 17 00:00:00 2001 From: Tom DuPont Date: Sun, 21 Feb 2016 22:27:47 -0800 Subject: [PATCH 4/5] Created Fleck specific unit test project. --- .gitignore | 7 +- src/Owin.WebSocket.sln | 6 + .../FleckTests.cs | 2 +- .../FleckTestsBase.cs | 8 +- .../OwinFleckTests.cs | 2 +- .../Properties/AssemblyInfo.cs | 36 ++++++ src/UnitTests.Fleck/UnitTests.Fleck.csproj | 112 ++++++++++++++++++ src/UnitTests.Fleck/packages.config | 12 ++ src/UnitTests/UnitTests.csproj | 7 -- src/UnitTests/packages.config | 1 - 10 files changed, 179 insertions(+), 14 deletions(-) rename src/{UnitTests => UnitTests.Fleck}/FleckTests.cs (94%) rename src/{UnitTests => UnitTests.Fleck}/FleckTestsBase.cs (95%) rename src/{UnitTests => UnitTests.Fleck}/OwinFleckTests.cs (99%) create mode 100644 src/UnitTests.Fleck/Properties/AssemblyInfo.cs create mode 100644 src/UnitTests.Fleck/UnitTests.Fleck.csproj create mode 100644 src/UnitTests.Fleck/packages.config diff --git a/.gitignore b/.gitignore index 0479af7..caf28df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ src/Owin.WebSocket/bin src/Owin.WebSocket/obj -src/Owin.WebSocket/packages +src/Owin.WebSocket.Fleck/bin +src/Owin.WebSocket.Fleck/obj src/packages src/UnitTests/bin src/UnitTests/obj -src/build +src/UnitTests.Fleck/bin +src/UnitTests.Fleck/obj +*.DotSettings *.dll *.pdb *.suo diff --git a/src/Owin.WebSocket.sln b/src/Owin.WebSocket.sln index c2a2bdb..0c90524 100644 --- a/src/Owin.WebSocket.sln +++ b/src/Owin.WebSocket.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{8DCED5 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.WebSocket.Fleck", "Owin.WebSocket.Fleck\Owin.WebSocket.Fleck.csproj", "{32050A9E-3FC9-4A71-85BF-6645AE68B82B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Fleck", "UnitTests.Fleck\UnitTests.Fleck.csproj", "{7D1CEED6-9BAE-4A54-8902-71DE01112070}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +36,10 @@ Global {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Debug|Any CPU.Build.0 = Debug|Any CPU {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Release|Any CPU.ActiveCfg = Release|Any CPU {32050A9E-3FC9-4A71-85BF-6645AE68B82B}.Release|Any CPU.Build.0 = Release|Any CPU + {7D1CEED6-9BAE-4A54-8902-71DE01112070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D1CEED6-9BAE-4A54-8902-71DE01112070}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D1CEED6-9BAE-4A54-8902-71DE01112070}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D1CEED6-9BAE-4A54-8902-71DE01112070}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/UnitTests/FleckTests.cs b/src/UnitTests.Fleck/FleckTests.cs similarity index 94% rename from src/UnitTests/FleckTests.cs rename to src/UnitTests.Fleck/FleckTests.cs index 7ae5da1..d0cfa54 100644 --- a/src/UnitTests/FleckTests.cs +++ b/src/UnitTests.Fleck/FleckTests.cs @@ -1,7 +1,7 @@ using Fleck; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace UnitTests +namespace UnitTests.Fleck { [TestClass] public class FleckTests : FleckTestsBase diff --git a/src/UnitTests/FleckTestsBase.cs b/src/UnitTests.Fleck/FleckTestsBase.cs similarity index 95% rename from src/UnitTests/FleckTestsBase.cs rename to src/UnitTests.Fleck/FleckTestsBase.cs index 737a4f1..f3a5766 100644 --- a/src/UnitTests/FleckTestsBase.cs +++ b/src/UnitTests.Fleck/FleckTestsBase.cs @@ -8,7 +8,7 @@ using Fleck; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace UnitTests +namespace UnitTests.Fleck { public abstract class FleckTestsBase { @@ -96,7 +96,10 @@ protected void SendIntegrationTestMessages() using (var client2 = new ClientWebSocket()) { client1.ConnectAsync(new Uri("ws://localhost:8989"), CancellationToken.None).Wait(); + Task.Delay(100).Wait(); + client2.ConnectAsync(new Uri("ws://localhost:8989"), CancellationToken.None).Wait(); + Task.Delay(100).Wait(); var bytes2 = new byte[1024]; var segment2 = new ArraySegment(bytes2); @@ -106,11 +109,12 @@ protected void SendIntegrationTestMessages() var bytes1 = Encoding.UTF8.GetBytes(message1); var segment1 = new ArraySegment(bytes1); client1.SendAsync(segment1, WebSocketMessageType.Text, true, CancellationToken.None).Wait(); + Task.Delay(100).Wait(); var result2 = receive2.Result; Assert.AreEqual(WebSocketMessageType.Text, result2.MessageType); var message3 = Encoding.UTF8.GetString(segment2.Array, 0, result2.Count); - Assert.AreEqual(message3, "User 1: Hello world"); + Assert.AreEqual("User 1: Hello world", message3); client2.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait(); client2.Dispose(); diff --git a/src/UnitTests/OwinFleckTests.cs b/src/UnitTests.Fleck/OwinFleckTests.cs similarity index 99% rename from src/UnitTests/OwinFleckTests.cs rename to src/UnitTests.Fleck/OwinFleckTests.cs index 128f3fe..8dc42c4 100644 --- a/src/UnitTests/OwinFleckTests.cs +++ b/src/UnitTests.Fleck/OwinFleckTests.cs @@ -7,7 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Owin.WebSocket.Extensions; -namespace UnitTests +namespace UnitTests.Fleck { [TestClass] public class OwinFleckTests : FleckTestsBase diff --git a/src/UnitTests.Fleck/Properties/AssemblyInfo.cs b/src/UnitTests.Fleck/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3245973 --- /dev/null +++ b/src/UnitTests.Fleck/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnitTests.Fleck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTests.Fleck")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d1ceed6-9bae-4a54-8902-71de01112070")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/UnitTests.Fleck/UnitTests.Fleck.csproj b/src/UnitTests.Fleck/UnitTests.Fleck.csproj new file mode 100644 index 0000000..d2c55dc --- /dev/null +++ b/src/UnitTests.Fleck/UnitTests.Fleck.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + {7D1CEED6-9BAE-4A54-8902-71DE01112070} + Library + Properties + UnitTests.Fleck + UnitTests.Fleck + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Fleck.0.13.0.57\lib\net40\Fleck.dll + True + + + ..\packages\FluentAssertions.4.2.1\lib\net45\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.2.1\lib\net45\FluentAssertions.Core.dll + True + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.Diagnostics.3.0.1\lib\net45\Microsoft.Owin.Diagnostics.dll + True + + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + True + + + ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll + True + + + False + ..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + {32050a9e-3fc9-4a71-85bf-6645ae68b82b} + Owin.WebSocket.Fleck + + + {b1d742f7-39fa-498c-b22e-1a7a61c301e7} + Owin.WebSocket + + + + + + + + \ No newline at end of file diff --git a/src/UnitTests.Fleck/packages.config b/src/UnitTests.Fleck/packages.config new file mode 100644 index 0000000..b949e11 --- /dev/null +++ b/src/UnitTests.Fleck/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 70ab63d..415bf12 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -42,10 +42,6 @@ - - ..\packages\Fleck.0.13.0.57\lib\net40\Fleck.dll - True - ..\packages\FluentAssertions.4.2.1\lib\net45\FluentAssertions.dll True @@ -94,9 +90,6 @@ - - - diff --git a/src/UnitTests/packages.config b/src/UnitTests/packages.config index a956b89..4f1dcbd 100644 --- a/src/UnitTests/packages.config +++ b/src/UnitTests/packages.config @@ -1,7 +1,6 @@  - From 66cddb66fbbaee8bf8c429a0f7218f6717f94e27 Mon Sep 17 00:00:00 2001 From: Tom DuPont Date: Thu, 25 Feb 2016 08:22:16 -0800 Subject: [PATCH 5/5] Removed test reference to Owin.WebSocket.Fleck --- src/UnitTests/UnitTests.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 415bf12..85e9042 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -95,10 +95,6 @@ - - {32050a9e-3fc9-4a71-85bf-6645ae68b82b} - Owin.WebSocket.Fleck - {b1d742f7-39fa-498c-b22e-1a7a61c301e7} Owin.WebSocket