From fca0476936f2cc597eacab2792eb95512f52bdfd Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 2 Dec 2015 14:50:32 -0800 Subject: [PATCH] #137 Relayer and ifdef WebSockets Rename Microsoft.Net.WebSockets to Microsoft.Net.WebSockets.Server. Reverse dependency with Microsoft.Net.Http.Server. ifdef out IHttpWebSocketFeature. --- WebListener.sln | 4 +- samples/HelloWorld/Program.cs | 2 +- samples/HelloWorld/project.json | 3 +- .../FeatureContext.cs | 11 +- .../RequestProcessing/RequestContext.cs | 195 ---------------- src/Microsoft.Net.Http.Server/project.json | 8 +- .../HttpKnownHeaderNames.cs | 0 .../Legacy/SR.cs | 0 .../WebSocketHttpListenerDuplexStream.cs | 0 .../Microsoft.Net.WebSockets.Server.xproj} | 0 .../NativeInterop/SafeLoadLibrary.cs | 0 .../NativeInterop/SafeWebSocketHandle.cs | 0 .../NativeInterop/UnsafeNativeMethods.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ServerWebSocket.cs | 0 .../WebSocketBase.cs | 0 .../WebSocketBuffer.cs | 0 .../WebSocketConstants.cs | 0 .../WebSocketError.cs | 2 +- .../WebSocketException.cs | 4 +- .../WebSocketExtensions.cs | 217 ++++++++++++++++++ .../WebSocketHelpers.cs | 1 - .../WebSocketReceiveResultExtensions.cs | 2 +- .../SafeHandleZeroOrMinusOneIsInvalid.cs | 0 .../fx/System/AccessViolationException.cs | 0 .../fx/System/ExternDll.cs | 0 .../InteropServices/ExternalException.cs | 0 .../fx/System/SafeNativeMethods.cs | 0 .../fx/System/SystemException.cs | 0 .../project.json | 9 +- .../System/ComponentModel/Win32Exception.cs | 129 ----------- .../WebSocketTests.cs | 6 +- .../WebSocketTests.cs | 2 +- .../project.json | 1 + 34 files changed, 245 insertions(+), 351 deletions(-) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/HttpKnownHeaderNames.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/Legacy/SR.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/Legacy/WebSocketHttpListenerDuplexStream.cs (100%) rename src/{Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.xproj => Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj} (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/NativeInterop/SafeLoadLibrary.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/NativeInterop/SafeWebSocketHandle.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/NativeInterop/UnsafeNativeMethods.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/Properties/AssemblyInfo.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/ServerWebSocket.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketBase.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketBuffer.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketConstants.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketError.cs (97%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketException.cs (99%) create mode 100644 src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketHelpers.cs (99%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/WebSocketReceiveResultExtensions.cs (97%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/System/AccessViolationException.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/System/ExternDll.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/System/Runtime/InteropServices/ExternalException.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/System/SafeNativeMethods.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/fx/System/SystemException.cs (100%) rename src/{Microsoft.Net.WebSockets => Microsoft.Net.WebSockets.Server}/project.json (75%) delete mode 100644 src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs diff --git a/WebListener.sln b/WebListener.sln index 9f9ffbfbf5..618e975e97 100644 --- a/WebListener.sln +++ b/WebListener.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22710.0 +VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" EndProject @@ -17,7 +17,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HelloWorld", "samples\Hello EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SelfHostServer", "samples\SelfHostServer\SelfHostServer.xproj", "{1236F93A-AC5C-4A77-9477-C88F040151CA}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.WebSockets", "src\Microsoft.Net.WebSockets\Microsoft.Net.WebSockets.xproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.WebSockets.Server", "src\Microsoft.Net.WebSockets.Server\Microsoft.Net.WebSockets.Server.xproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.WebListener.FunctionalTests", "test\Microsoft.AspNet.Server.WebListener.FunctionalTests\Microsoft.AspNet.Server.WebListener.FunctionalTests.xproj", "{4492FF4C-9032-411D-853F-46B01755E504}" EndProject diff --git a/samples/HelloWorld/Program.cs b/samples/HelloWorld/Program.cs index eb57deedfc..2f62f082fe 100644 --- a/samples/HelloWorld/Program.cs +++ b/samples/HelloWorld/Program.cs @@ -59,7 +59,7 @@ namespace HelloWorld // Response byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now); - if (context.IsWebSocketRequest) + if (context.IsWebSocketRequest()) { Console.WriteLine("WebSocket"); WebSocket webSocket = await context.AcceptWebSocketAsync(); diff --git a/samples/HelloWorld/project.json b/samples/HelloWorld/project.json index 3863f41615..8f8d2299aa 100644 --- a/samples/HelloWorld/project.json +++ b/samples/HelloWorld/project.json @@ -1,6 +1,7 @@ { "dependencies": { - "Microsoft.Net.Http.Server": "1.0.0-*" + "Microsoft.Net.Http.Server": "1.0.0-*", + "Microsoft.Net.WebSockets.Server": "1.0.0-*" }, "commands": { "sample": "HelloWorld" diff --git a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs index dbf84e121d..e311ebf82b 100644 --- a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs +++ b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs @@ -16,7 +16,6 @@ // permissions and limitations under the License. using System; -using System.Collections.Generic; using System.IO; using System.Net; using System.Net.WebSockets; @@ -27,8 +26,6 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Authentication; -using Microsoft.AspNet.Http.Internal; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Server; @@ -43,7 +40,9 @@ namespace Microsoft.AspNet.Server.WebListener ITlsTokenBindingFeature, IHttpBufferingFeature, IHttpRequestLifetimeFeature, +#if WEBSOCKETS IHttpWebSocketFeature, +#endif IHttpAuthenticationFeature, IHttpUpgradeFeature, IHttpRequestIdentifierFeature @@ -422,12 +421,12 @@ namespace Microsoft.AspNet.Server.WebListener { return _requestContext.UpgradeAsync(); } - +#if WEBSOCKETS bool IHttpWebSocketFeature.IsWebSocketRequest { get { - return _requestContext.IsWebSocketRequest; + return _requestContext.IsWebSocketRequest(); } } @@ -441,7 +440,7 @@ namespace Microsoft.AspNet.Server.WebListener } return _requestContext.AcceptWebSocketAsync(subProtocol); } - +#endif ClaimsPrincipal IHttpAuthenticationFeature.User { get diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs index 4c0b0a0c14..3fe3b9a9c8 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/RequestContext.cs @@ -24,14 +24,11 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; -using System.Net.WebSockets; using System.Runtime.InteropServices; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.Net.WebSockets; namespace Microsoft.Net.Http.Server { @@ -174,198 +171,6 @@ namespace Microsoft.Net.Http.Server return Task.FromResult(opaqueStream); } - public bool IsWebSocketRequest - { - get - { - if (!WebSocketHelpers.AreWebSocketsSupported) - { - return false; - } - - if (!IsUpgradableRequest) - { - return false; - } - - if (!string.Equals("GET", Request.Method, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) - string connection = Request.Headers[HttpKnownHeaderNames.Connection]; - if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) - { - return false; - } - - // Upgrade: websocket - string upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade]; - if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Sec-WebSocket-Version: 13 - string version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Sec-WebSocket-Key: {base64string} - string key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - if (!WebSocketHelpers.IsValidWebSocketKey(key)) - { - return false; - } - - return true; - } - } - - // Compare IsWebSocketRequest - private void ValidateWebSocketRequest() - { - if (!WebSocketHelpers.AreWebSocketsSupported) - { - throw new NotSupportedException("WebSockets are not supported on this platform."); - } - - if (!IsUpgradableRequest) - { - throw new InvalidOperationException("This request is not a valid upgrade request."); - } - - if (!string.Equals("GET", Request.Method, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("This request is not a valid upgrade request; invalid verb: " + Request.Method); - } - - // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) - string connection = Request.Headers[HttpKnownHeaderNames.Connection]; - if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) - { - throw new InvalidOperationException("The Connection header is invalid: " + connection); - } - - // Upgrade: websocket - string upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade]; - if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade); - } - - // Sec-WebSocket-Version: 13 - string version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version); - } - - // Sec-WebSocket-Key: {base64string} - string key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - if (!WebSocketHelpers.IsValidWebSocketKey(key)) - { - throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade); - } - } - - public Task AcceptWebSocketAsync() - { - return AcceptWebSocketAsync(null, - WebSocketHelpers.DefaultReceiveBufferSize, - WebSocketHelpers.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol) - { - return AcceptWebSocketAsync(subProtocol, - WebSocketHelpers.DefaultReceiveBufferSize, - WebSocketHelpers.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) - { - return AcceptWebSocketAsync(subProtocol, - WebSocketHelpers.DefaultReceiveBufferSize, - keepAliveInterval); - } - - public Task AcceptWebSocketAsync( - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval) - { - WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); - - ArraySegment internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - return this.AcceptWebSocketAsync(subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - - public Task AcceptWebSocketAsync( - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - if (!IsUpgradableRequest) - { - throw new InvalidOperationException("This request is cannot be upgraded."); - } - WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); - WebSocketHelpers.ValidateArraySegment(internalBuffer, "internalBuffer"); - WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - - return AcceptWebSocketAsyncCore(subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - - private async Task AcceptWebSocketAsyncCore( - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - try - { - ValidateWebSocketRequest(); - - var subProtocols = Request.Headers.GetValues(HttpKnownHeaderNames.SecWebSocketProtocol); - bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol); - if (shouldSendSecWebSocketProtocolHeader) - { - Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = subProtocol; - } - - // negotiate the websocket key return value - string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); - - Response.Headers.Append(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - Response.Headers.Append(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); - Response.Headers.Append(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - Stream opaqueStream = await UpgradeAsync(); - - return WebSocketHelpers.CreateServerWebSocket( - opaqueStream, - subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - catch (Exception ex) - { - LogHelper.LogException(Logger, "AcceptWebSocketAsync", ex); - throw; - } - } - - /* public bool TryGetChannelBinding(ref ChannelBinding value) { diff --git a/src/Microsoft.Net.Http.Server/project.json b/src/Microsoft.Net.Http.Server/project.json index ec77bf8272..e72589e906 100644 --- a/src/Microsoft.Net.Http.Server/project.json +++ b/src/Microsoft.Net.Http.Server/project.json @@ -3,8 +3,7 @@ "description": "Implementation of WebListener, a successor to HttpListener. It is used in the WebListener server package.", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0-*", - "Microsoft.Extensions.Primitives": "1.0.0-*", - "Microsoft.Net.WebSockets": "1.0.0-*" + "Microsoft.Extensions.Primitives": "1.0.0-*" }, "compilationOptions": { "allowUnsafe": true, @@ -15,11 +14,16 @@ "dotnet5.4": { "dependencies": { "Microsoft.Win32.Primitives": "4.0.1-*", + "System.Diagnostics.Contracts": "4.0.1-*", "System.Diagnostics.Debug": "4.0.11-*", + "System.Diagnostics.Tools": "4.0.1-*", + "System.IO": "4.0.11-*", "System.IO.FileSystem": "4.0.1-*", + "System.Net.Primitives": "4.0.11-*", "System.Security.Claims": "4.0.1-*", "System.Security.Cryptography.X509Certificates": "4.0.0-*", "System.Security.Principal.Windows": "4.0.0-*", + "System.Text.Encoding.Extensions": "4.0.11-*", "System.Threading.Overlapped": "4.0.0-*" } } diff --git a/src/Microsoft.Net.WebSockets/HttpKnownHeaderNames.cs b/src/Microsoft.Net.WebSockets.Server/HttpKnownHeaderNames.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/HttpKnownHeaderNames.cs rename to src/Microsoft.Net.WebSockets.Server/HttpKnownHeaderNames.cs diff --git a/src/Microsoft.Net.WebSockets/Legacy/SR.cs b/src/Microsoft.Net.WebSockets.Server/Legacy/SR.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/Legacy/SR.cs rename to src/Microsoft.Net.WebSockets.Server/Legacy/SR.cs diff --git a/src/Microsoft.Net.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs b/src/Microsoft.Net.WebSockets.Server/Legacy/WebSocketHttpListenerDuplexStream.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs rename to src/Microsoft.Net.WebSockets.Server/Legacy/WebSocketHttpListenerDuplexStream.cs diff --git a/src/Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.xproj b/src/Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj similarity index 100% rename from src/Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.xproj rename to src/Microsoft.Net.WebSockets.Server/Microsoft.Net.WebSockets.Server.xproj diff --git a/src/Microsoft.Net.WebSockets/NativeInterop/SafeLoadLibrary.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeLoadLibrary.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/NativeInterop/SafeLoadLibrary.cs rename to src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeLoadLibrary.cs diff --git a/src/Microsoft.Net.WebSockets/NativeInterop/SafeWebSocketHandle.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeWebSocketHandle.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/NativeInterop/SafeWebSocketHandle.cs rename to src/Microsoft.Net.WebSockets.Server/NativeInterop/SafeWebSocketHandle.cs diff --git a/src/Microsoft.Net.WebSockets/NativeInterop/UnsafeNativeMethods.cs b/src/Microsoft.Net.WebSockets.Server/NativeInterop/UnsafeNativeMethods.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/NativeInterop/UnsafeNativeMethods.cs rename to src/Microsoft.Net.WebSockets.Server/NativeInterop/UnsafeNativeMethods.cs diff --git a/src/Microsoft.Net.WebSockets/Properties/AssemblyInfo.cs b/src/Microsoft.Net.WebSockets.Server/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/Properties/AssemblyInfo.cs rename to src/Microsoft.Net.WebSockets.Server/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.Net.WebSockets/ServerWebSocket.cs b/src/Microsoft.Net.WebSockets.Server/ServerWebSocket.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/ServerWebSocket.cs rename to src/Microsoft.Net.WebSockets.Server/ServerWebSocket.cs diff --git a/src/Microsoft.Net.WebSockets/WebSocketBase.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketBase.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/WebSocketBase.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketBase.cs diff --git a/src/Microsoft.Net.WebSockets/WebSocketBuffer.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketBuffer.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/WebSocketBuffer.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketBuffer.cs diff --git a/src/Microsoft.Net.WebSockets/WebSocketConstants.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketConstants.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/WebSocketConstants.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketConstants.cs diff --git a/src/Microsoft.Net.WebSockets/WebSocketError.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketError.cs similarity index 97% rename from src/Microsoft.Net.WebSockets/WebSocketError.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketError.cs index 11c84c27a5..09811b81c9 100644 --- a/src/Microsoft.Net.WebSockets/WebSocketError.cs +++ b/src/Microsoft.Net.WebSockets.Server/WebSocketError.cs @@ -23,7 +23,7 @@ namespace Microsoft.Net.WebSockets { - public enum WebSocketError + internal enum WebSocketError { Success = 0, InvalidMessageType = 1, diff --git a/src/Microsoft.Net.WebSockets/WebSocketException.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketException.cs similarity index 99% rename from src/Microsoft.Net.WebSockets/WebSocketException.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketException.cs index 9724b91448..0ae7e4bb89 100644 --- a/src/Microsoft.Net.WebSockets/WebSocketException.cs +++ b/src/Microsoft.Net.WebSockets.Server/WebSocketException.cs @@ -115,7 +115,7 @@ namespace Microsoft.Net.WebSockets : base(message, innerException) { } - +#if !DOTNET5_4 public override int ErrorCode { get @@ -123,7 +123,7 @@ namespace Microsoft.Net.WebSockets return base.NativeErrorCode; } } - +#endif public WebSocketError WebSocketErrorCode { get diff --git a/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs new file mode 100644 index 0000000000..dccc933241 --- /dev/null +++ b/src/Microsoft.Net.WebSockets.Server/WebSocketExtensions.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Microsoft.Net.WebSockets; + +namespace Microsoft.Net.Http.Server +{ + public static class WebSocketExtensions + { + public static bool IsWebSocketRequest(this RequestContext context) + { + if (!WebSocketHelpers.AreWebSocketsSupported) + { + return false; + } + + if (!context.IsUpgradableRequest) + { + return false; + } + + if (!string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) + string connection = context.Request.Headers[HttpKnownHeaderNames.Connection]; + if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + return false; + } + + // Upgrade: websocket + string upgrade = context.Request.Headers[HttpKnownHeaderNames.Upgrade]; + if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Version: 13 + string version = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; + if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Key: {base64string} + string key = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + if (!WebSocketHelpers.IsValidWebSocketKey(key)) + { + return false; + } + + return true; + } + + // Compare IsWebSocketRequest() + private static void ValidateWebSocketRequest(RequestContext context) + { + if (!WebSocketHelpers.AreWebSocketsSupported) + { + throw new NotSupportedException("WebSockets are not supported on this platform."); + } + + if (!context.IsUpgradableRequest) + { + throw new InvalidOperationException("This request is not a valid upgrade request."); + } + + if (!string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("This request is not a valid upgrade request; invalid verb: " + context.Request.Method); + } + + // Connection: Upgrade (some odd clients send Upgrade,KeepAlive) + string connection = context.Request.Headers[HttpKnownHeaderNames.Connection]; + if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + throw new InvalidOperationException("The Connection header is invalid: " + connection); + } + + // Upgrade: websocket + string upgrade = context.Request.Headers[HttpKnownHeaderNames.Upgrade]; + if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade); + } + + // Sec-WebSocket-Version: 13 + string version = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; + if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version); + } + + // Sec-WebSocket-Key: {base64string} + string key = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + if (!WebSocketHelpers.IsValidWebSocketKey(key)) + { + throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade); + } + } + + public static Task AcceptWebSocketAsync(this RequestContext context) + { + return context.AcceptWebSocketAsync(null, + WebSocketHelpers.DefaultReceiveBufferSize, + WebSocketHelpers.DefaultKeepAliveInterval); + } + + public static Task AcceptWebSocketAsync(this RequestContext context, string subProtocol) + { + return context.AcceptWebSocketAsync(subProtocol, + WebSocketHelpers.DefaultReceiveBufferSize, + WebSocketHelpers.DefaultKeepAliveInterval); + } + + public static Task AcceptWebSocketAsync(this RequestContext context, string subProtocol, TimeSpan keepAliveInterval) + { + return context.AcceptWebSocketAsync(subProtocol, + WebSocketHelpers.DefaultReceiveBufferSize, + keepAliveInterval); + } + + public static Task AcceptWebSocketAsync( + this RequestContext context, + string subProtocol, + int receiveBufferSize, + TimeSpan keepAliveInterval) + { + WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); + + ArraySegment internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); + return context.AcceptWebSocketAsync(subProtocol, + receiveBufferSize, + keepAliveInterval, + internalBuffer); + } + + public static Task AcceptWebSocketAsync( + this RequestContext context, + string subProtocol, + int receiveBufferSize, + TimeSpan keepAliveInterval, + ArraySegment internalBuffer) + { + if (!context.IsUpgradableRequest) + { + throw new InvalidOperationException("This request is cannot be upgraded."); + } + WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); + WebSocketHelpers.ValidateArraySegment(internalBuffer, "internalBuffer"); + WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); + + return AcceptWebSocketAsyncCore(context, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); + } + + private static async Task AcceptWebSocketAsyncCore( + RequestContext context, + string subProtocol, + int receiveBufferSize, + TimeSpan keepAliveInterval, + ArraySegment internalBuffer) + { + ValidateWebSocketRequest(context); + + var subProtocols = context.Request.Headers.GetValues(HttpKnownHeaderNames.SecWebSocketProtocol); + bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol); + if (shouldSendSecWebSocketProtocolHeader) + { + context.Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = subProtocol; + } + + // negotiate the websocket key return value + string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); + + context.Response.Headers.Append(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); + context.Response.Headers.Append(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); + context.Response.Headers.Append(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); + + Stream opaqueStream = await context.UpgradeAsync(); + + return WebSocketHelpers.CreateServerWebSocket( + opaqueStream, + subProtocol, + receiveBufferSize, + keepAliveInterval, + internalBuffer); + } + } +} diff --git a/src/Microsoft.Net.WebSockets/WebSocketHelpers.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs similarity index 99% rename from src/Microsoft.Net.WebSockets/WebSocketHelpers.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs index fc8a830408..21e0b11838 100644 --- a/src/Microsoft.Net.WebSockets/WebSocketHelpers.cs +++ b/src/Microsoft.Net.WebSockets.Server/WebSocketHelpers.cs @@ -92,7 +92,6 @@ namespace Microsoft.Net.WebSockets } } - public static bool IsValidWebSocketKey(string key) { if (string.IsNullOrWhiteSpace(key)) diff --git a/src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs b/src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs similarity index 97% rename from src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs rename to src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs index 00df192c38..12841af2df 100644 --- a/src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs +++ b/src/Microsoft.Net.WebSockets.Server/WebSocketReceiveResultExtensions.cs @@ -27,7 +27,7 @@ using System.Net.WebSockets; namespace Microsoft.Net.WebSockets { - public static class WebSocketReceiveResultExtensions + internal static class WebSocketReceiveResultExtensions { internal static WebSocketReceiveResult DecrementAndClone(ref WebSocketReceiveResult original, int count) { diff --git a/src/Microsoft.Net.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs b/src/Microsoft.Net.WebSockets.Server/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs rename to src/Microsoft.Net.WebSockets.Server/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs diff --git a/src/Microsoft.Net.WebSockets/fx/System/AccessViolationException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/AccessViolationException.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/System/AccessViolationException.cs rename to src/Microsoft.Net.WebSockets.Server/fx/System/AccessViolationException.cs diff --git a/src/Microsoft.Net.WebSockets/fx/System/ExternDll.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/ExternDll.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/System/ExternDll.cs rename to src/Microsoft.Net.WebSockets.Server/fx/System/ExternDll.cs diff --git a/src/Microsoft.Net.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/Runtime/InteropServices/ExternalException.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs rename to src/Microsoft.Net.WebSockets.Server/fx/System/Runtime/InteropServices/ExternalException.cs diff --git a/src/Microsoft.Net.WebSockets/fx/System/SafeNativeMethods.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/SafeNativeMethods.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/System/SafeNativeMethods.cs rename to src/Microsoft.Net.WebSockets.Server/fx/System/SafeNativeMethods.cs diff --git a/src/Microsoft.Net.WebSockets/fx/System/SystemException.cs b/src/Microsoft.Net.WebSockets.Server/fx/System/SystemException.cs similarity index 100% rename from src/Microsoft.Net.WebSockets/fx/System/SystemException.cs rename to src/Microsoft.Net.WebSockets.Server/fx/System/SystemException.cs diff --git a/src/Microsoft.Net.WebSockets/project.json b/src/Microsoft.Net.WebSockets.Server/project.json similarity index 75% rename from src/Microsoft.Net.WebSockets/project.json rename to src/Microsoft.Net.WebSockets.Server/project.json index 0273cd0f0b..140c3637b5 100644 --- a/src/Microsoft.Net.WebSockets/project.json +++ b/src/Microsoft.Net.WebSockets.Server/project.json @@ -1,7 +1,9 @@ { "version": "1.0.0-*", "description": "Implementation of WebSocket abstract base class. Used by WebListener.", - "dependencies": {}, + "dependencies": { + "Microsoft.Net.Http.Server": "1.0.0-*" + }, "compilationOptions": { "allowUnsafe": true, "keyFile": "../../tools/Key.snk" @@ -11,16 +13,11 @@ "dotnet5.4": { "dependencies": { "System.Collections": "4.0.11-*", - "System.Diagnostics.Contracts": "4.0.1-*", - "System.Diagnostics.Tools": "4.0.1-*", - "System.IO": "4.0.11-*", "System.Linq": "4.0.1-*", - "System.Net.Primitives": "4.0.11-*", "System.Net.WebSockets": "4.0.0-*", "System.Resources.ResourceManager": "4.0.1-*", "System.Runtime.Extensions": "4.0.11-*", "System.Security.Cryptography.Algorithms": "4.0.0-*", - "System.Text.Encoding.Extensions": "4.0.11-*", "System.Threading": "4.0.11-*", "System.Threading.Tasks": "4.0.11-*", "System.Threading.Timer": "4.0.1-*", diff --git a/src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs b/src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs deleted file mode 100644 index 5c6d0dea8b..0000000000 --- a/src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING -// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF -// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR -// NON-INFRINGEMENT. -// See the Apache 2 License for the specific language governing -// permissions and limitations under the License. - -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//------------------------------------------------------------------------------ - -#if DOTNET5_4 - -using System.Runtime.InteropServices; -using System.Text; - -namespace System.ComponentModel -{ - internal class Win32Exception : ExternalException - { - /// - /// Represents the Win32 error code associated with this exception. This - /// field is read-only. - /// - private readonly int nativeErrorCode; - - /// - /// Initializes a new instance of the class with the last Win32 error - /// that occured. - /// - public Win32Exception() - : this(Marshal.GetLastWin32Error()) - { - } - /// - /// Initializes a new instance of the class with the specified error. - /// - public Win32Exception(int error) - : this(error, GetErrorMessage(error)) - { - } - /// - /// Initializes a new instance of the class with the specified error and the - /// specified detailed description. - /// - public Win32Exception(int error, string message) - : base(message) - { - nativeErrorCode = error; - } - - /// - /// Initializes a new instance of the Exception class with a specified error message. - /// FxCop CA1032: Multiple constructors are required to correctly implement a custom exception. - /// - public Win32Exception(string message) - : this(Marshal.GetLastWin32Error(), message) - { - } - - /// - /// Initializes a new instance of the Exception class with a specified error message and a - /// reference to the inner exception that is the cause of this exception. - /// FxCop CA1032: Multiple constructors are required to correctly implement a custom exception. - /// - public Win32Exception(string message, Exception innerException) - : base(message, innerException) - { - nativeErrorCode = Marshal.GetLastWin32Error(); - } - - - /// - /// Represents the Win32 error code associated with this exception. This - /// field is read-only. - /// - public int NativeErrorCode - { - get - { - return nativeErrorCode; - } - } - - private static string GetErrorMessage(int error) - { - //get the system error message... - string errorMsg = ""; - StringBuilder sb = new StringBuilder(256); - int result = SafeNativeMethods.FormatMessage( - SafeNativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS | - SafeNativeMethods.FORMAT_MESSAGE_FROM_SYSTEM | - SafeNativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY, - IntPtr.Zero, (uint)error, 0, sb, sb.Capacity + 1, - null); - if (result != 0) - { - int i = sb.Length; - while (i > 0) - { - char ch = sb[i - 1]; - if (ch > 32 && ch != '.') break; - i--; - } - errorMsg = sb.ToString(0, i); - } - else - { - errorMsg = "Unknown error (0x" + Convert.ToString(error, 16) + ")"; - } - - return errorMsg; - } - } -} - -#endif diff --git a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs index b6e55b1875..c39ae4b1d8 100644 --- a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs +++ b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs @@ -14,7 +14,7 @@ // NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing // permissions and limitations under the License. - +#if WEBSOCKETS using System; using System.Net.Http; using System.Net.WebSockets; @@ -22,7 +22,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; -using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Testing.xunit; using Xunit; @@ -165,4 +164,5 @@ namespace Microsoft.AspNet.Server.WebListener return client; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs index 8a9d696905..083d02601f 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/WebSocketTests.cs @@ -62,7 +62,7 @@ namespace Microsoft.Net.Http.Server Task clientTask = SendWebSocketRequestAsync(ConvertToWebSocketAddress(address)); var context = await server.GetContextAsync(); - Assert.True(context.IsWebSocketRequest); + Assert.True(context.IsWebSocketRequest()); WebSocket serverWebSocket = await context.AcceptWebSocketAsync(); WebSocket clientWebSocket = await clientTask; diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/project.json b/test/Microsoft.Net.Http.Server.FunctionalTests/project.json index 586047d801..aba7cd14c8 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/project.json +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/project.json @@ -4,6 +4,7 @@ }, "dependencies": { "Microsoft.Net.Http.Server": "1.0.0-*", + "Microsoft.Net.WebSockets.Server": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "xunit.runner.aspnet": "2.0.0-aspnet-*" },