diff --git a/WebListener.sln b/WebListener.sln index 26c7fe384f..3846c0df71 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.21708.0 +VisualStudioVersion = 14.0.21730.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" EndProject @@ -19,7 +19,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SelfHostServer", "samples\S EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.Windows", "src\Microsoft.AspNet.Security.Windows\Microsoft.AspNet.Security.Windows.kproj", "{EFC7538F-7AEB-4A3E-A1E6-6BDCCBD272BF}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.WebSockets", "src\Microsoft.AspNet.WebSockets\Microsoft.AspNet.WebSockets.kproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.WebSockets", "src\Microsoft.Net.WebSockets\Microsoft.Net.WebSockets.kproj", "{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.kproj", "{4492FF4C-9032-411D-853F-46B01755E504}" EndProject diff --git a/samples/HelloWorld/Program.cs b/samples/HelloWorld/Program.cs index d411f251a0..65336e9be3 100644 --- a/samples/HelloWorld/Program.cs +++ b/samples/HelloWorld/Program.cs @@ -16,7 +16,9 @@ // permissions and limitations under the License. using System; +using System.Net.WebSockets; using System.Text; +using System.Threading; using Microsoft.Net.Server; namespace HelloWorld @@ -73,11 +75,23 @@ namespace HelloWorld // Response byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now); - context.Response.ContentLength = bytes.Length; - context.Response.ContentType = "text/plain"; + if (context.IsWebSocketRequest) + { + Console.WriteLine("WebSocket"); + WebSocket webSocket = context.AcceptWebSocketAsync().Result; + webSocket.SendAsync(new ArraySegment(bytes, 0, bytes.Length), WebSocketMessageType.Text, true, CancellationToken.None).Wait(); + webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Goodbye", CancellationToken.None).Wait(); + webSocket.Dispose(); + } + else + { + Console.WriteLine("Hello World"); + context.Response.ContentLength = bytes.Length; + context.Response.ContentType = "text/plain"; - context.Response.Body.Write(bytes, 0, bytes.Length); - context.Dispose(); + context.Response.Body.Write(bytes, 0, bytes.Length); + context.Dispose(); + } } } } diff --git a/samples/SelfHostServer/Startup.cs b/samples/SelfHostServer/Startup.cs index 6b5f26b1d0..3a6c87a4ab 100644 --- a/samples/SelfHostServer/Startup.cs +++ b/samples/SelfHostServer/Startup.cs @@ -15,6 +15,10 @@ // See the Apache 2 License for the specific language governing // permissions and limitations under the License. +using System; +using System.Net.WebSockets; +using System.Text; +using System.Threading; using Microsoft.AspNet; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; @@ -32,8 +36,20 @@ namespace SelfHostServer app.Run(async context => { - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Hello world"); + if (context.IsWebSocketRequest) + { + Console.WriteLine("WebSocket"); + byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now); + WebSocket webSocket = await context.AcceptWebSocketAsync(); + await webSocket.SendAsync(new ArraySegment(bytes, 0, bytes.Length), WebSocketMessageType.Text, true, CancellationToken.None); + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Goodbye", CancellationToken.None); + webSocket.Dispose(); + } + else + { + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Hello world"); + } }); } } diff --git a/samples/SelfHostServer/project.json b/samples/SelfHostServer/project.json index 9945c5dac2..002bc7304b 100644 --- a/samples/SelfHostServer/project.json +++ b/samples/SelfHostServer/project.json @@ -6,7 +6,7 @@ "Microsoft.AspNet.Server.WebListener": "", "Microsoft.Net.Server": "" }, - "commands": { "web": "Microsoft.AspNet.Hosting server.name=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001" }, + "commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:8080" }, "configurations": { "net45": { }, diff --git a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs index e217b38495..720ed6e51c 100644 --- a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs +++ b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -28,7 +29,15 @@ using Microsoft.Net.Server; namespace Microsoft.AspNet.Server.WebListener { - internal class FeatureContext : IHttpRequestFeature, IHttpConnectionFeature, IHttpResponseFeature, IHttpSendFileFeature, IHttpTransportLayerSecurityFeature, IHttpRequestLifetimeFeature + internal class FeatureContext : + IHttpRequestFeature, + IHttpConnectionFeature, + IHttpResponseFeature, + IHttpSendFileFeature, + IHttpTransportLayerSecurityFeature, + IHttpRequestLifetimeFeature, + IHttpWebSocketFeature, + IHttpOpaqueUpgradeFeature { private RequestContext _requestContext; private FeatureCollection _features; @@ -85,10 +94,13 @@ namespace Microsoft.AspNet.Server.WebListener _features.Add(typeof(IHttpSendFileFeature), this); _features.Add(typeof(IHttpRequestLifetimeFeature), this); + // TODO: If Win8+ + _features.Add(typeof(IHttpOpaqueUpgradeFeature), this); + _features.Add(typeof(IHttpWebSocketFeature), this); + // TODO: // _environment.CallCancelled = _cts.Token; // _environment.User = _request.User; - // Opaque/WebSockets // Channel binding /* @@ -348,11 +360,16 @@ namespace Microsoft.AspNet.Server.WebListener set { Response.StatusCode = value; } } #endregion + #region IHttpSendFileFeature + Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation) { return Response.SendFileAsync(path, offset, length, cancellation); } + #endregion + #region IHttpRequestLifetimeFeature + public CancellationToken OnRequestAborted { get { return _requestContext.DisconnectToken; } @@ -362,5 +379,46 @@ namespace Microsoft.AspNet.Server.WebListener { _requestContext.Abort(); } + + #endregion + #region IHttpOpaqueUpgradeFeature + + public bool IsUpgradableRequest + { + get { return _requestContext.IsUpgradableRequest; } + } + + public Task UpgradeAsync() + { + if (!IsUpgradableRequest) + { + throw new InvalidOperationException("This request cannot be upgraded."); + } + return _requestContext.UpgradeAsync(); + } + + #endregion + #region IHttpWebSocketFeature + + public bool IsWebSocketRequest + { + get + { + return _requestContext.IsWebSocketRequest; + } + } + + public Task AcceptAsync(IWebSocketAcceptContext context) + { + // TODO: Advanced params + string subProtocol = null; + if (context != null) + { + subProtocol = context.SubProtocol; + } + return _requestContext.AcceptWebSocketAsync(subProtocol); + } + + #endregion } } diff --git a/src/Microsoft.AspNet.Server.WebListener/MessagePump.cs b/src/Microsoft.AspNet.Server.WebListener/MessagePump.cs index 890f9b9e63..f1de9d6423 100644 --- a/src/Microsoft.AspNet.Server.WebListener/MessagePump.cs +++ b/src/Microsoft.AspNet.Server.WebListener/MessagePump.cs @@ -156,7 +156,6 @@ namespace Microsoft.AspNet.Server.WebListener Interlocked.Increment(ref _outstandingRequests); FeatureContext featureContext = new FeatureContext(requestContext); await _appFunc(featureContext.Features).SupressContext(); - // TODO: WebSocket/Opaque upgrade - await requestContext.ProcessResponseAsync().SupressContext(); requestContext.Dispose(); } catch (Exception ex) diff --git a/src/Microsoft.AspNet.Server.WebListener/project.json b/src/Microsoft.AspNet.Server.WebListener/project.json index 459e0d16de..e7840b9642 100644 --- a/src/Microsoft.AspNet.Server.WebListener/project.json +++ b/src/Microsoft.AspNet.Server.WebListener/project.json @@ -16,6 +16,7 @@ "net45": {}, "k10": { "dependencies": { + "Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*", "Microsoft.Win32.Primitives": "4.0.0.0", "System.Collections": "4.0.0.0", "System.Collections.Concurrent": "4.0.0.0", @@ -25,12 +26,14 @@ "System.Globalization": "4.0.10.0", "System.IO": "4.0.0.0", "System.Linq": "4.0.0.0", + "System.Net.Primitives": "4.0.10.0", "System.Reflection": "4.0.10.0", "System.Resources.ResourceManager": "4.0.0.0", "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", "System.Runtime.Handles": "4.0.0.0", "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Cryptography.X509Certificates": "4.0.0.0", "System.Security.Principal": "4.0.0.0", "System.Text.Encoding": "4.0.20.0", "System.Text.Encoding.Extensions": "4.0.10.0", diff --git a/src/Microsoft.AspNet.WebSockets/Constants.cs b/src/Microsoft.AspNet.WebSockets/Constants.cs deleted file mode 100644 index 8f4e0df564..0000000000 --- a/src/Microsoft.AspNet.WebSockets/Constants.cs +++ /dev/null @@ -1,36 +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. - -namespace Microsoft.AspNet.WebSockets -{ - /// - /// Standard keys and values for use within the OWIN interfaces - /// - internal static class Constants - { - internal const string WebSocketAcceptKey = "websocket.Accept"; - internal const string WebSocketSubProtocolKey = "websocket.SubProtocol"; - internal const string WebSocketSendAsyncKey = "websocket.SendAsync"; - internal const string WebSocketReceiveAyncKey = "websocket.ReceiveAsync"; - internal const string WebSocketCloseAsyncKey = "websocket.CloseAsync"; - internal const string WebSocketCallCancelledKey = "websocket.CallCancelled"; - internal const string WebSocketVersionKey = "websocket.Version"; - internal const string WebSocketVersion = "1.0"; - internal const string WebSocketCloseStatusKey = "websocket.ClientCloseStatus"; - internal const string WebSocketCloseDescriptionKey = "websocket.ClientCloseDescription"; - } -} diff --git a/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerContext.cs b/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerContext.cs deleted file mode 100644 index 891c8739f7..0000000000 --- a/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerContext.cs +++ /dev/null @@ -1,75 +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. -// -//------------------------------------------------------------------------------ -/* -namespace Microsoft.Net -{ - using Microsoft.AspNet.WebSockets; - using System; - using System.ComponentModel; - using System.Threading.Tasks; - - public sealed unsafe class HttpListenerContext { - private HttpListenerRequest m_Request; - - public Task AcceptWebSocketAsync(string subProtocol) - { - return this.AcceptWebSocketAsync(subProtocol, - WebSocketHelpers.DefaultReceiveBufferSize, - WebSocket.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) - { - return this.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); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public Task AcceptWebSocketAsync(string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - return WebSocketHelpers.AcceptWebSocketAsync(this, - subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - } -} -*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerRequest.cs b/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerRequest.cs deleted file mode 100644 index 41a6c1a6a6..0000000000 --- a/src/Microsoft.AspNet.WebSockets/Legacy/HttpListenerRequest.cs +++ /dev/null @@ -1,83 +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. -// -//------------------------------------------------------------------------------ - -/* -namespace Microsoft.Net -{ - using System; - using System.Collections; - using System.Collections.Specialized; - using System.IO; - using System.Runtime.InteropServices; - using System.Globalization; - using System.Text; - using System.Security.Principal; - using System.Security.Cryptography.X509Certificates; - using System.Net; - using Microsoft.AspNet.WebSockets; - - public sealed unsafe class HttpListenerRequest { - - public bool IsWebSocketRequest - { - get - { - if (!WebSocketProtocolComponent.IsSupported) - { - return false; - } - - bool foundConnectionUpgradeHeader = false; - if (string.IsNullOrEmpty(this.Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(this.Headers[HttpKnownHeaderNames.Upgrade])) - { - return false; - } - - foreach (string connection in this.Headers.GetValues(HttpKnownHeaderNames.Connection)) - { - if (string.Compare(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) == 0) - { - foundConnectionUpgradeHeader = true; - break; - } - } - - if (!foundConnectionUpgradeHeader) - { - return false; - } - - foreach (string upgrade in this.Headers.GetValues(HttpKnownHeaderNames.Upgrade)) - { - if (string.Compare(upgrade, WebSocketHelpers.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase) == 0) - { - return true; - } - } - - return false; - } - } - } -} -*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/OwinWebSocketWrapper.cs b/src/Microsoft.AspNet.WebSockets/OwinWebSocketWrapper.cs deleted file mode 100644 index 5947567acf..0000000000 --- a/src/Microsoft.AspNet.WebSockets/OwinWebSocketWrapper.cs +++ /dev/null @@ -1,152 +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. - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNet.WebSockets -{ - using WebSocketCloseAsync = - Func; - using WebSocketReceiveAsync = - Func /* data */, - CancellationToken /* cancel */, - Task>>; - using WebSocketReceiveTuple = - Tuple; - using WebSocketSendAsync = - Func /* data */, - int /* messageType */, - bool /* endOfMessage */, - CancellationToken /* cancel */, - Task>; - - internal class OwinWebSocketWrapper - { - private readonly WebSocket _webSocket; - private readonly IDictionary _environment; - private readonly CancellationToken _cancellationToken; - - internal OwinWebSocketWrapper(WebSocket webSocket, CancellationToken ct) - { - _webSocket = webSocket; - _cancellationToken = ct; - - _environment = new Dictionary(); - _environment[Constants.WebSocketSendAsyncKey] = new WebSocketSendAsync(SendAsync); - _environment[Constants.WebSocketReceiveAyncKey] = new WebSocketReceiveAsync(ReceiveAsync); - _environment[Constants.WebSocketCloseAsyncKey] = new WebSocketCloseAsync(CloseAsync); - _environment[Constants.WebSocketCallCancelledKey] = ct; - _environment[Constants.WebSocketVersionKey] = Constants.WebSocketVersion; - } - - internal IDictionary Environment - { - get { return _environment; } - } - - internal Task SendAsync(ArraySegment buffer, int messageType, bool endOfMessage, CancellationToken cancel) - { - // Remap close messages to CloseAsync. System.Net.WebSockets.WebSocket.SendAsync does not allow close messages. - if (messageType == 0x8) - { - return RedirectSendToCloseAsync(buffer, cancel); - } - else if (messageType == 0x9 || messageType == 0xA) - { - // Ping & Pong, not allowed by the underlying APIs, silently discard. - return Task.FromResult(0); - } - - return _webSocket.SendAsync(buffer, (WebSocketMessageType)messageType, endOfMessage, cancel); - } - - internal async Task ReceiveAsync(ArraySegment buffer, CancellationToken cancel) - { - WebSocketReceiveResult nativeResult = await _webSocket.ReceiveAsync(buffer, cancel); - - if (nativeResult.MessageType == WebSocketMessageType.Close) - { - _environment[Constants.WebSocketCloseStatusKey] = (int)(nativeResult.CloseStatus ?? WebSocketCloseStatus.NormalClosure); - _environment[Constants.WebSocketCloseDescriptionKey] = nativeResult.CloseStatusDescription ?? string.Empty; - } - - return new WebSocketReceiveTuple( - (int)nativeResult.MessageType, - nativeResult.EndOfMessage, - nativeResult.Count); - } - - internal Task CloseAsync(int status, string description, CancellationToken cancel) - { - return _webSocket.CloseOutputAsync((WebSocketCloseStatus)status, description, cancel); - } - - private Task RedirectSendToCloseAsync(ArraySegment buffer, CancellationToken cancel) - { - if (buffer.Array == null || buffer.Count == 0) - { - return CloseAsync(1000, string.Empty, cancel); - } - else if (buffer.Count >= 2) - { - // Unpack the close message. - int statusCode = - (buffer.Array[buffer.Offset] << 8) - | buffer.Array[buffer.Offset + 1]; - string description = Encoding.UTF8.GetString(buffer.Array, buffer.Offset + 2, buffer.Count - 2); - - return CloseAsync(statusCode, description, cancel); - } - else - { - throw new ArgumentOutOfRangeException("buffer"); - } - } - - internal async Task CleanupAsync() - { - switch (_webSocket.State) - { - case WebSocketState.Closed: // Closed gracefully, no action needed. - case WebSocketState.Aborted: // Closed abortively, no action needed. - break; - case WebSocketState.CloseReceived: - // Echo what the client said, if anything. - await _webSocket.CloseAsync(_webSocket.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - _webSocket.CloseStatusDescription ?? string.Empty, _cancellationToken); - break; - case WebSocketState.Open: - case WebSocketState.CloseSent: // No close received, abort so we don't have to drain the pipe. - _webSocket.Abort(); - break; - default: - throw new ArgumentOutOfRangeException("state", _webSocket.State, string.Empty); - } - } - } -} diff --git a/src/Microsoft.AspNet.WebSockets/WebSocket.cs b/src/Microsoft.AspNet.WebSockets/WebSocket.cs deleted file mode 100644 index 8050263d87..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocket.cs +++ /dev/null @@ -1,141 +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. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNet.WebSockets -{ - public abstract class WebSocket : IDisposable - { - private static TimeSpan? defaultKeepAliveInterval; - - public abstract WebSocketCloseStatus? CloseStatus { get; } - public abstract string CloseStatusDescription { get; } - public abstract string SubProtocol { get; } - public abstract WebSocketState State { get; } - - public static TimeSpan DefaultKeepAliveInterval - { - [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", - Justification = "This is a harmless read-only operation")] - get - { - if (defaultKeepAliveInterval == null) - { - if (UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported) - { - defaultKeepAliveInterval = UnsafeNativeMethods.WebSocketProtocolComponent.WebSocketGetDefaultKeepAliveInterval(); - } - else - { - defaultKeepAliveInterval = Timeout.InfiniteTimeSpan; - } - } - return defaultKeepAliveInterval.Value; - } - } - - public static ArraySegment CreateClientBuffer(int receiveBufferSize, int sendBufferSize) - { - WebSocketHelpers.ValidateBufferSizes(receiveBufferSize, sendBufferSize); - - return WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, sendBufferSize, false); - } - - public static ArraySegment CreateServerBuffer(int receiveBufferSize) - { - WebSocketHelpers.ValidateBufferSizes(receiveBufferSize, WebSocketBuffer.MinSendBufferSize); - - return WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - } - - internal static WebSocket CreateServerWebSocket(Stream innerStream, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) - { - if (!UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported) - { - WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC(); - } - - WebSocketHelpers.ValidateInnerStream(innerStream); - WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); - WebSocketHelpers.ValidateArraySegment(internalBuffer, "internalBuffer"); - WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - - return new ServerWebSocket(innerStream, - subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - } - - public abstract void Abort(); - public abstract Task CloseAsync(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken); - public abstract Task CloseOutputAsync(WebSocketCloseStatus closeStatus, - string statusDescription, - CancellationToken cancellationToken); - [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "This rule is outdated")] - public abstract void Dispose(); - public abstract Task ReceiveAsync(ArraySegment buffer, - CancellationToken cancellationToken); - public abstract Task SendAsync(ArraySegment buffer, - WebSocketMessageType messageType, - bool endOfMessage, - CancellationToken cancellationToken); - - protected static void ThrowOnInvalidState(WebSocketState state, params WebSocketState[] validStates) - { - string validStatesText = string.Empty; - - if (validStates != null && validStates.Length > 0) - { - foreach (WebSocketState currentState in validStates) - { - if (state == currentState) - { - return; - } - } - - validStatesText = string.Join(", ", validStates); - } - - throw new WebSocketException(SR.GetString(SR.net_WebSockets_InvalidState, state, validStatesText)); - } - - protected static bool IsStateTerminal(WebSocketState state) - { - return state == WebSocketState.Closed || - state == WebSocketState.Aborted; - } - } -} diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketCloseStatus.cs b/src/Microsoft.AspNet.WebSockets/WebSocketCloseStatus.cs deleted file mode 100644 index 313efa3507..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketCloseStatus.cs +++ /dev/null @@ -1,57 +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. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.AspNet.WebSockets -{ - [SuppressMessage("Microsoft.Design", - "CA1008:EnumsShouldHaveZeroValue", - Justification = "This enum is reflecting the IETF's WebSocket specification. " + - "'0' is a disallowed value for the close status code")] - public enum WebSocketCloseStatus - { - NormalClosure = 1000, - EndpointUnavailable = 1001, - ProtocolError = 1002, - InvalidMessageType = 1003, - Empty = 1005, - // AbnormalClosure = 1006, // 1006 is reserved and should never be used by user - InvalidPayloadData = 1007, - PolicyViolation = 1008, - MessageTooBig = 1009, - MandatoryExtension = 1010, - InternalServerError = 1011 - // TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user - - // 0 - 999 Status codes in the range 0-999 are not used. - // 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol. - // 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions. - // 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The - // interpretation of these codes is undefined by this protocol. End applications MUST - // NOT use status codes in this range. - // 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretaion - // of these codes is undefined by this protocol. - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketExtensions.cs b/src/Microsoft.AspNet.WebSockets/WebSocketExtensions.cs deleted file mode 100644 index ccacef2231..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketExtensions.cs +++ /dev/null @@ -1,32 +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. -/* -#if NET45 -using Microsoft.AspNet.WebSockets; - -namespace Owin -{ - public static class WebSocketExtensions - { - public static IAppBuilder UseWebSockets(this IAppBuilder app) - { - return app.Use(); - } - } -} -#endif -*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketMessageType.cs b/src/Microsoft.AspNet.WebSockets/WebSocketMessageType.cs deleted file mode 100644 index 2c362f7299..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketMessageType.cs +++ /dev/null @@ -1,32 +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. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.AspNet.WebSockets -{ - public enum WebSocketMessageType - { - Text = 0x1, - Binary = 0x2, - Close = 0x8, - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketMiddleware.cs b/src/Microsoft.AspNet.WebSockets/WebSocketMiddleware.cs deleted file mode 100644 index 8ed52f1ba7..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketMiddleware.cs +++ /dev/null @@ -1,185 +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. -/* -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Owin; - -namespace Microsoft.AspNet.WebSockets -{ - using AppFunc = Func, Task>; - using OpaqueUpgrade = - Action - < - IDictionary, // Parameters - Func // OpaqueFunc callback - < - IDictionary, // Opaque environment - Task // Complete - > - >; - using WebSocketAccept = - Action - < - IDictionary, // WebSocket Accept parameters - Func // WebSocketFunc callback - < - IDictionary, // WebSocket environment - Task // Complete - > - >; - using WebSocketFunc = - Func - < - IDictionary, // WebSocket Environment - Task // Complete - >; - - public class WebSocketMiddleware - { - private AppFunc _next; - - public WebSocketMiddleware(AppFunc next) - { - _next = next; - } - - public Task Invoke(IDictionary environment) - { - IOwinContext context = new OwinContext(environment); - // Detect if an opaque upgrade is available, and if websocket upgrade headers are present. - // If so, add a websocket upgrade. - OpaqueUpgrade upgrade = context.Get("opaque.Upgrade"); - if (upgrade != null) - { - // Headers and values: - // Connection: Upgrade - // Upgrade: WebSocket - // Sec-WebSocket-Version: (WebSocketProtocolComponent.SupportedVersion) - // Sec-WebSocket-Key: (hash, see WebSocketHelpers.GetSecWebSocketAcceptString) - // Sec-WebSocket-Protocol: (optional, list) - IList connectionHeaders = context.Request.Headers.GetCommaSeparatedValues(HttpKnownHeaderNames.Connection); // "Upgrade, KeepAlive" - string upgradeHeader = context.Request.Headers[HttpKnownHeaderNames.Upgrade]; - string versionHeader = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - string keyHeader = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - - if (connectionHeaders != null && connectionHeaders.Count > 0 - && connectionHeaders.Contains(HttpKnownHeaderNames.Upgrade, StringComparer.OrdinalIgnoreCase) - && string.Equals(upgradeHeader, WebSocketHelpers.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase) - && string.Equals(versionHeader, UnsafeNativeMethods.WebSocketProtocolComponent.SupportedVersion, StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrWhiteSpace(keyHeader)) - { - environment["websocket.Accept"] = new WebSocketAccept(new UpgradeHandshake(context, upgrade).AcceptWebSocket); - } - } - - return _next(environment); - } - - private class UpgradeHandshake - { - private IOwinContext _context; - private OpaqueUpgrade _upgrade; - private WebSocketFunc _webSocketFunc; - - private string _subProtocol; - private int _receiveBufferSize = WebSocketHelpers.DefaultReceiveBufferSize; - private TimeSpan _keepAliveInterval = WebSocket.DefaultKeepAliveInterval; - private ArraySegment _internalBuffer; - - internal UpgradeHandshake(IOwinContext context, OpaqueUpgrade upgrade) - { - _context = context; - _upgrade = upgrade; - } - - internal void AcceptWebSocket(IDictionary options, WebSocketFunc webSocketFunc) - { - _webSocketFunc = webSocketFunc; - - // Get options - object temp; - if (options != null && options.TryGetValue("websocket.SubProtocol", out temp)) - { - _subProtocol = temp as string; - } - if (options != null && options.TryGetValue("websocket.ReceiveBufferSize", out temp)) - { - _receiveBufferSize = (int)temp; - } - if (options != null && options.TryGetValue("websocket.KeepAliveInterval", out temp)) - { - _keepAliveInterval = (TimeSpan)temp; - } - if (options != null && options.TryGetValue("websocket.Buffer", out temp)) - { - _internalBuffer = (ArraySegment)temp; - } - else - { - _internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(_receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); - } - - // Set WebSocket upgrade response headers - - string outgoingSecWebSocketProtocolString; - bool shouldSendSecWebSocketProtocolHeader = - WebSocketHelpers.ProcessWebSocketProtocolHeader( - _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], - _subProtocol, - out outgoingSecWebSocketProtocolString); - - if (shouldSendSecWebSocketProtocolHeader) - { - _context.Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = outgoingSecWebSocketProtocolString; - } - - string secWebSocketKey = _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); - - _context.Response.Headers[HttpKnownHeaderNames.Connection] = HttpKnownHeaderNames.Upgrade; - _context.Response.Headers[HttpKnownHeaderNames.Upgrade] = WebSocketHelpers.WebSocketUpgradeToken; - _context.Response.Headers[HttpKnownHeaderNames.SecWebSocketAccept] = secWebSocketAccept; - - _context.Response.StatusCode = 101; // Switching Protocols; - - _upgrade(options, OpaqueCallback); - } - - internal async Task OpaqueCallback(IDictionary opaqueEnv) - { - // Create WebSocket wrapper around the opaque env - WebSocket webSocket = CreateWebSocket(opaqueEnv); - OwinWebSocketWrapper wrapper = new OwinWebSocketWrapper(webSocket, (CancellationToken)opaqueEnv["opaque.CallCancelled"]); - await _webSocketFunc(wrapper.Environment); - // Close down the WebSocekt, gracefully if possible - await wrapper.CleanupAsync(); - } - - private WebSocket CreateWebSocket(IDictionary opaqueEnv) - { - Stream stream = (Stream)opaqueEnv["opaque.Stream"]; - return new ServerWebSocket(stream, _subProtocol, _receiveBufferSize, _keepAliveInterval, _internalBuffer); - } - } - } -} -*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketReceiveResult.cs b/src/Microsoft.AspNet.WebSockets/WebSocketReceiveResult.cs deleted file mode 100644 index 7513597105..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketReceiveResult.cs +++ /dev/null @@ -1,72 +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. -// -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.WebSockets -{ - public class WebSocketReceiveResult - { - public WebSocketReceiveResult(int count, WebSocketMessageType messageType, bool endOfMessage) - : this(count, messageType, endOfMessage, null, null) - { - } - - public WebSocketReceiveResult(int count, - WebSocketMessageType messageType, - bool endOfMessage, - WebSocketCloseStatus? closeStatus, - string closeStatusDescription) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException("count"); - } - - this.Count = count; - this.EndOfMessage = endOfMessage; - this.MessageType = messageType; - this.CloseStatus = closeStatus; - this.CloseStatusDescription = closeStatusDescription; - } - - public int Count { get; private set; } - public bool EndOfMessage { get; private set; } - public WebSocketMessageType MessageType { get; private set; } - public WebSocketCloseStatus? CloseStatus { get; private set; } - public string CloseStatusDescription { get; private set; } - - internal WebSocketReceiveResult Copy(int count) - { - Contract.Assert(count >= 0, "'count' MUST NOT be negative."); - Contract.Assert(count <= this.Count, "'count' MUST NOT be bigger than 'this.Count'."); - this.Count -= count; - return new WebSocketReceiveResult(count, - this.MessageType, - this.Count == 0 && this.EndOfMessage, - this.CloseStatus, - this.CloseStatusDescription); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketState.cs b/src/Microsoft.AspNet.WebSockets/WebSocketState.cs deleted file mode 100644 index 53d8d2daa5..0000000000 --- a/src/Microsoft.AspNet.WebSockets/WebSocketState.cs +++ /dev/null @@ -1,36 +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. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.AspNet.WebSockets -{ - public enum WebSocketState - { - None = 0, - Connecting = 1, - Open = 2, - CloseSent = 3, // WebSocket close handshake started form local endpoint - CloseReceived = 4, // WebSocket close message received from remote endpoint. Waiting for app to call close - Closed = 5, - Aborted = 6, - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/project.json b/src/Microsoft.AspNet.WebSockets/project.json deleted file mode 100644 index da531b3e9e..0000000000 --- a/src/Microsoft.AspNet.WebSockets/project.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "0.1-alpha-*", - "dependencies": { - "Microsoft.AspNet.Http" : "0.1-alpha-*", - "Microsoft.AspNet.HttpFeature" : "0.1-alpha-*" - }, - "compilationOptions" : { "allowUnsafe": true }, - "configurations": { - "net45" : { - "dependencies": { - } - } - } -} diff --git a/src/Microsoft.Net.Server/RequestProcessing/Request.cs b/src/Microsoft.Net.Server/RequestProcessing/Request.cs index 934c4e7728..2b3f4f1262 100644 --- a/src/Microsoft.Net.Server/RequestProcessing/Request.cs +++ b/src/Microsoft.Net.Server/RequestProcessing/Request.cs @@ -441,6 +441,17 @@ namespace Microsoft.Net.Server return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress); } + // TODO: We need an easier to user header collection that has this built in + internal string GetHeader(string headerName) + { + string[] values; + if (Headers.TryGetValue(headerName, out values)) + { + return string.Join(", ", values); + } + return string.Empty; + } + // Populates the client certificate. The result may be null if there is no client cert. // TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to // enable this, but it's unclear what Http.Sys would do. diff --git a/src/Microsoft.Net.Server/RequestProcessing/RequestContext.cs b/src/Microsoft.Net.Server/RequestProcessing/RequestContext.cs index ca09228b6d..ad3d7fc7e8 100644 --- a/src/Microsoft.Net.Server/RequestProcessing/RequestContext.cs +++ b/src/Microsoft.Net.Server/RequestProcessing/RequestContext.cs @@ -24,23 +24,24 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net.WebSockets; using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Microsoft.Framework.Logging; +using Microsoft.Net.WebSockets; namespace Microsoft.Net.Server { - using OpaqueFunc = Func, Task>; - public sealed class RequestContext : IDisposable { private WebListener _server; private Request _request; private Response _response; private NativeRequestContext _memoryBlob; - private OpaqueFunc _opaqueCallback; private bool _disposed; private CancellationTokenSource _requestAbortSource; private CancellationToken? _disconnectToken; @@ -128,17 +129,230 @@ namespace Microsoft.Net.Server return Request.RequestId; } } - /* - public bool TryGetOpaqueUpgrade(ref Action, OpaqueFunc> value) + + public bool IsUpgradableRequest { - if (_request.IsUpgradable) - { - value = OpaqueUpgrade; - return true; - } - return false; + get { return _request.IsUpgradable; } } + public Task UpgradeAsync() + { + if (!IsUpgradableRequest || _response.SentHeaders) + { + throw new InvalidOperationException(); + } + + // Set the status code and reason phrase + Response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; + Response.ReasonPhrase = HttpReasonPhrase.Get(HttpStatusCode.SwitchingProtocols); + + Response.SendOpaqueUpgrade(); // TODO: Async + Request.SwitchToOpaqueMode(); + Response.SwitchToOpaqueMode(); + Stream opaqueStream = new OpaqueStream(Request.Body, Response.Body); + 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.GetHeader(HttpKnownHeaderNames.Connection); + if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + return false; + } + + // Upgrade: websocket + string upgrade = Request.GetHeader(HttpKnownHeaderNames.Upgrade); + if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Version: 13 + string version = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketVersion); + if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Sec-WebSocket-Key: {base64string} + string key = Request.GetHeader(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.GetHeader(HttpKnownHeaderNames.Connection); + if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) + { + throw new InvalidOperationException("The Connection header is invalid: " + connection); + } + + // Upgrade: websocket + string upgrade = Request.GetHeader(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.GetHeader(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.GetHeader(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, + WebSocket.DefaultKeepAliveInterval); + } + + public Task AcceptWebSocketAsync(string subProtocol) + { + return AcceptWebSocketAsync(subProtocol, + WebSocketHelpers.DefaultReceiveBufferSize, + WebSocket.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 + { + // TODO: We need a better header collection API. + ValidateWebSocketRequest(); + + string subProtocols = string.Empty; + string[] values; + if (Request.Headers.TryGetValue(HttpKnownHeaderNames.SecWebSocketProtocol, out values)) + { + subProtocols = string.Join(", ", values); + } + + bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol); + if (shouldSendSecWebSocketProtocolHeader) + { + Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = new[] { subProtocol }; + } + + // negotiate the websocket key return value + string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey].First(); + string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); + + Response.Headers.Add(HttpKnownHeaderNames.Connection, new[] { HttpKnownHeaderNames.Upgrade }); + Response.Headers.Add(HttpKnownHeaderNames.Upgrade, new[] { WebSocketHelpers.WebSocketUpgradeToken }); + Response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, new[] { 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) { value = Server.GetChannelBinding(Request.ConnectionId, Request.IsSecureConnection); @@ -221,57 +435,5 @@ namespace Microsoft.Net.Server // RequestQueueHandle may have been closed } } - /* - internal void OpaqueUpgrade(IDictionary parameters, OpaqueFunc callback) - { - // Parameters are ignored for now - if (Response.SentHeaders) - { - throw new InvalidOperationException(); - } - if (callback == null) - { - throw new ArgumentNullException("callback"); - } - - // Set the status code and reason phrase - Response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; - Response.ReasonPhrase = HttpReasonPhrase.Get(HttpStatusCode.SwitchingProtocols); - - // Store the callback and process it after the stack unwind. - _opaqueCallback = callback; - } - - // Called after the AppFunc completes for any necessary post-processing. - internal unsafe Task ProcessResponseAsync() - { - // If an upgrade was requested, perform it - if (!Response.SentHeaders && _opaqueCallback != null - && Response.StatusCode == (int)HttpStatusCode.SwitchingProtocols) - { - Response.SendOpaqueUpgrade(); - - IDictionary opaqueEnv = CreateOpaqueEnvironment(); - return _opaqueCallback(opaqueEnv); - } - - return Helpers.CompletedTask(); - } - - private IDictionary CreateOpaqueEnvironment() - { - IDictionary opaqueEnv = new Dictionary(); - - opaqueEnv[Constants.OpaqueVersionKey] = Constants.OpaqueVersion; - // TODO: Separate CT? - // opaqueEnv[Constants.OpaqueCallCancelledKey] = Environment.CallCancelled; - - Request.SwitchToOpaqueMode(); - Response.SwitchToOpaqueMode(); - opaqueEnv[Constants.OpaqueStreamKey] = new OpaqueStream(Request.Body, Response.Body); - - return opaqueEnv; - } - */ } } diff --git a/src/Microsoft.Net.Server/project.json b/src/Microsoft.Net.Server/project.json index a6363b7940..0316ca03aa 100644 --- a/src/Microsoft.Net.Server/project.json +++ b/src/Microsoft.Net.Server/project.json @@ -1,7 +1,8 @@ { "version": "0.1-alpha-*", "dependencies": { - "Microsoft.Framework.Logging": "0.1-alpha-*" + "Microsoft.Framework.Logging": "0.1-alpha-*", + "Microsoft.Net.WebSockets": "" }, "compilationOptions": { "allowUnsafe": true @@ -10,6 +11,7 @@ "net45": {}, "k10": { "dependencies": { + "Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*", "Microsoft.Win32.Primitives": "4.0.0.0", "System.Collections": "4.0.0.0", "System.Collections.Concurrent": "4.0.0.0", diff --git a/src/Microsoft.AspNet.WebSockets/HttpKnownHeaderNames.cs b/src/Microsoft.Net.WebSockets/HttpKnownHeaderNames.cs similarity index 99% rename from src/Microsoft.AspNet.WebSockets/HttpKnownHeaderNames.cs rename to src/Microsoft.Net.WebSockets/HttpKnownHeaderNames.cs index 3382395c8b..fb6fa1d755 100644 --- a/src/Microsoft.AspNet.WebSockets/HttpKnownHeaderNames.cs +++ b/src/Microsoft.Net.WebSockets/HttpKnownHeaderNames.cs @@ -21,7 +21,7 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { // this class contains known header names internal static class HttpKnownHeaderNames diff --git a/src/Microsoft.AspNet.WebSockets/Legacy/SR.cs b/src/Microsoft.Net.WebSockets/Legacy/SR.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/Legacy/SR.cs rename to src/Microsoft.Net.WebSockets/Legacy/SR.cs diff --git a/src/Microsoft.AspNet.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs b/src/Microsoft.Net.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs rename to src/Microsoft.Net.WebSockets/Legacy/WebSocketHttpListenerDuplexStream.cs diff --git a/src/Microsoft.AspNet.WebSockets/Microsoft.AspNet.WebSockets.kproj b/src/Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.kproj similarity index 83% rename from src/Microsoft.AspNet.WebSockets/Microsoft.AspNet.WebSockets.kproj rename to src/Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.kproj index fb4bf3edd4..031b4e912b 100644 --- a/src/Microsoft.AspNet.WebSockets/Microsoft.AspNet.WebSockets.kproj +++ b/src/Microsoft.Net.WebSockets/Microsoft.Net.WebSockets.kproj @@ -22,7 +22,6 @@ - @@ -31,29 +30,21 @@ - - - - - + - - - - + - - + \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeLoadLibrary.cs b/src/Microsoft.Net.WebSockets/NativeInterop/SafeLoadLibrary.cs similarity index 98% rename from src/Microsoft.AspNet.WebSockets/NativeInterop/SafeLoadLibrary.cs rename to src/Microsoft.Net.WebSockets/NativeInterop/SafeLoadLibrary.cs index 6b0e6e2ee2..dcb231e78b 100644 --- a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeLoadLibrary.cs +++ b/src/Microsoft.Net.WebSockets/NativeInterop/SafeLoadLibrary.cs @@ -23,7 +23,7 @@ using Microsoft.Win32.SafeHandles; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { internal sealed class SafeLoadLibrary : SafeHandleZeroOrMinusOneIsInvalid { diff --git a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeNativeOverlapped.cs b/src/Microsoft.Net.WebSockets/NativeInterop/SafeNativeOverlapped.cs similarity index 98% rename from src/Microsoft.AspNet.WebSockets/NativeInterop/SafeNativeOverlapped.cs rename to src/Microsoft.Net.WebSockets/NativeInterop/SafeNativeOverlapped.cs index 7e404fb597..2bbf1c58bf 100644 --- a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeNativeOverlapped.cs +++ b/src/Microsoft.Net.WebSockets/NativeInterop/SafeNativeOverlapped.cs @@ -25,7 +25,7 @@ using System; using System.Runtime.InteropServices; using System.Threading; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { internal class SafeNativeOverlapped : SafeHandle { diff --git a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeWebSocketHandle.cs b/src/Microsoft.Net.WebSockets/NativeInterop/SafeWebSocketHandle.cs similarity index 96% rename from src/Microsoft.AspNet.WebSockets/NativeInterop/SafeWebSocketHandle.cs rename to src/Microsoft.Net.WebSockets/NativeInterop/SafeWebSocketHandle.cs index fffd04af4f..1219624f00 100644 --- a/src/Microsoft.AspNet.WebSockets/NativeInterop/SafeWebSocketHandle.cs +++ b/src/Microsoft.Net.WebSockets/NativeInterop/SafeWebSocketHandle.cs @@ -21,10 +21,9 @@ // //------------------------------------------------------------------------------ -using Microsoft.AspNet.WebSockets; using Microsoft.Win32.SafeHandles; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { // This class is a wrapper for a WSPC (WebSocket protocol component) session. WebSocketCreateClientHandle and WebSocketCreateServerHandle return a PVOID and not a real handle // but we use a SafeHandle because it provides us the guarantee that WebSocketDeleteHandle will always get called. diff --git a/src/Microsoft.AspNet.WebSockets/NativeInterop/UnsafeNativeMethods.cs b/src/Microsoft.Net.WebSockets/NativeInterop/UnsafeNativeMethods.cs similarity index 99% rename from src/Microsoft.AspNet.WebSockets/NativeInterop/UnsafeNativeMethods.cs rename to src/Microsoft.Net.WebSockets/NativeInterop/UnsafeNativeMethods.cs index d9875f0afc..c2002b7f22 100644 --- a/src/Microsoft.AspNet.WebSockets/NativeInterop/UnsafeNativeMethods.cs +++ b/src/Microsoft.Net.WebSockets/NativeInterop/UnsafeNativeMethods.cs @@ -27,7 +27,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { internal static class UnsafeNativeMethods { @@ -161,7 +161,11 @@ namespace Microsoft.AspNet.WebSockets static WebSocketProtocolComponent() { +#if NET45 DllFileName = Path.Combine(Environment.SystemDirectory, WEBSOCKET); +#else + DllFileName = Path.Combine(Environment.GetEnvironmentVariable("SYSTEMROOT"), "System32", WEBSOCKET); +#endif WebSocketDllHandle = SafeLoadLibrary.LoadLibraryEx(DllFileName); if (!WebSocketDllHandle.IsInvalid) diff --git a/src/Microsoft.AspNet.WebSockets/ServerWebSocket.cs b/src/Microsoft.Net.WebSockets/ServerWebSocket.cs similarity index 98% rename from src/Microsoft.AspNet.WebSockets/ServerWebSocket.cs rename to src/Microsoft.Net.WebSockets/ServerWebSocket.cs index b722573934..ebf8542848 100644 --- a/src/Microsoft.AspNet.WebSockets/ServerWebSocket.cs +++ b/src/Microsoft.Net.WebSockets/ServerWebSocket.cs @@ -27,7 +27,7 @@ using System.Diagnostics.Contracts; using System.IO; using System.Runtime.InteropServices; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { internal sealed class ServerWebSocket : WebSocketBase { diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketBase.cs b/src/Microsoft.Net.WebSockets/WebSocketBase.cs similarity index 98% rename from src/Microsoft.AspNet.WebSockets/WebSocketBase.cs rename to src/Microsoft.Net.WebSockets/WebSocketBase.cs index 66e5aa4760..fe2ab6d1e2 100644 --- a/src/Microsoft.AspNet.WebSockets/WebSocketBase.cs +++ b/src/Microsoft.Net.WebSockets/WebSocketBase.cs @@ -29,13 +29,14 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; +using System.Net.WebSockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { internal abstract class WebSocketBase : WebSocket, IDisposable { @@ -233,7 +234,7 @@ namespace Microsoft.AspNet.WebSockets { ThrowIfPendingException(); ThrowIfDisposed(); - ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent); + WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent); bool ownsCancellationTokenSource = false; CancellationToken linkedCancellationToken = CancellationToken.None; @@ -338,7 +339,7 @@ namespace Microsoft.AspNet.WebSockets { ThrowIfPendingException(); ThrowIfDisposed(); - ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); + WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); bool ownsCancellationTokenSource = false; CancellationToken linkedCancellationToken = CancellationToken.None; @@ -468,13 +469,13 @@ namespace Microsoft.AspNet.WebSockets bool sessionHandleLockTaken = false; try { - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } @@ -548,7 +549,7 @@ namespace Microsoft.AspNet.WebSockets try { ThrowIfPendingException(); - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } @@ -566,12 +567,12 @@ namespace Microsoft.AspNet.WebSockets ThrowIfPendingException(); ThrowIfDisposed(); - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } - ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); + WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); ownsCloseOutputCancellationTokenSource = _closeOutputOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); if (!ownsCloseOutputCancellationTokenSource) { @@ -676,7 +677,7 @@ namespace Microsoft.AspNet.WebSockets // returns TRUE if the caller should also call StartOnCloseCompleted private bool OnCloseOutputCompleted() { - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return false; } @@ -707,7 +708,7 @@ namespace Microsoft.AspNet.WebSockets { Contract.Assert(thisLockTakenSnapshot, "'thisLockTakenSnapshot' MUST be 'true' at this point."); - if (IsStateTerminal(_state)) + if (WebSocketHelpers.IsStateTerminal(_state)) { return false; } @@ -793,7 +794,7 @@ namespace Microsoft.AspNet.WebSockets try { ThrowIfPendingException(); - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } @@ -806,12 +807,12 @@ namespace Microsoft.AspNet.WebSockets try { ThrowIfPendingException(); - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } ThrowIfDisposed(); - ThrowOnInvalidState(State, + WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent); Task closeOutputTask; @@ -893,7 +894,7 @@ namespace Microsoft.AspNet.WebSockets } } - if (IsStateTerminal(State)) + if (WebSocketHelpers.IsStateTerminal(State)) { return; } @@ -970,7 +971,7 @@ namespace Microsoft.AspNet.WebSockets Monitor.Enter(_thisLock, ref lockTaken); } - if (!IsStateTerminal(State)) + if (!WebSocketHelpers.IsStateTerminal(State)) { bool ownsSendCancellationSource = false; try @@ -1055,7 +1056,7 @@ namespace Microsoft.AspNet.WebSockets return; } - if (!IsStateTerminal(State)) + if (!WebSocketHelpers.IsStateTerminal(State)) { Abort(); } @@ -1501,13 +1502,13 @@ namespace Microsoft.AspNet.WebSockets { ThrowIfDisposed(); - if (IsStateTerminal(State) || State == WebSocketState.CloseReceived) + if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived) { return false; } Monitor.Enter(_thisLock, ref thisLockTaken); - if (IsStateTerminal(State) || State == WebSocketState.CloseReceived) + if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived) { return false; } diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketBuffer.cs b/src/Microsoft.Net.WebSockets/WebSocketBuffer.cs similarity index 98% rename from src/Microsoft.AspNet.WebSockets/WebSocketBuffer.cs rename to src/Microsoft.Net.WebSockets/WebSocketBuffer.cs index 2f9522fd40..a34b458f7c 100644 --- a/src/Microsoft.AspNet.WebSockets/WebSocketBuffer.cs +++ b/src/Microsoft.Net.WebSockets/WebSocketBuffer.cs @@ -25,11 +25,12 @@ using System; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Globalization; +using System.Net.WebSockets; using System.Runtime.InteropServices; using System.Text; using System.Threading; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { // This class helps to abstract the internal WebSocket buffer, which is used to interact with the native WebSocket // protocol component (WSPC). It helps to shield the details of the layout and the involved pointer arithmetic. @@ -43,10 +44,10 @@ namespace Microsoft.AspNet.WebSockets // // *RBS = ReceiveBufferSize, *SBS = SendBufferSize // *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes) - internal class WebSocketBuffer : IDisposable + public class WebSocketBuffer : IDisposable { private const int NativeOverheadBufferSize = 144; - internal const int MinSendBufferSize = 16; + public const int MinSendBufferSize = 16; internal const int MinReceiveBufferSize = 256; internal const int MaxBufferSize = 64 * 1024; #if NET45 @@ -362,7 +363,7 @@ namespace Microsoft.AspNet.WebSockets ValidateBufferedPayload(); int bytesTransferred = Math.Min(buffer.Count, _BufferedPayloadReceiveResult.Count); - receiveResult = _BufferedPayloadReceiveResult.Copy(bytesTransferred); + receiveResult = WebSocketReceiveResultExtensions.DecrementAndClone(ref _BufferedPayloadReceiveResult, bytesTransferred); Buffer.BlockCopy(_PayloadBuffer.Array, _PayloadBuffer.Offset + _PayloadOffset, @@ -664,7 +665,7 @@ namespace Microsoft.AspNet.WebSockets ReleasePinnedSendBuffer(); } - internal static ArraySegment CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) + public static ArraySegment CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer) { Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); @@ -675,7 +676,7 @@ namespace Microsoft.AspNet.WebSockets return new ArraySegment(new byte[internalBufferSize]); } - internal static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer) + public static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer) { Contract.Assert(receiveBufferSize >= MinReceiveBufferSize, "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); diff --git a/src/Microsoft.Net.WebSockets/WebSocketConstants.cs b/src/Microsoft.Net.WebSockets/WebSocketConstants.cs new file mode 100644 index 0000000000..c5aa4d8a85 --- /dev/null +++ b/src/Microsoft.Net.WebSockets/WebSocketConstants.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.Net.WebSockets +{ + public static class WebSocketConstants + { + public static string SupportedProtocolVersion = "13"; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketError.cs b/src/Microsoft.Net.WebSockets/WebSocketError.cs similarity index 97% rename from src/Microsoft.AspNet.WebSockets/WebSocketError.cs rename to src/Microsoft.Net.WebSockets/WebSocketError.cs index 16ce90e9fd..11c84c27a5 100644 --- a/src/Microsoft.AspNet.WebSockets/WebSocketError.cs +++ b/src/Microsoft.Net.WebSockets/WebSocketError.cs @@ -21,7 +21,7 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { public enum WebSocketError { diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketException.cs b/src/Microsoft.Net.WebSockets/WebSocketException.cs similarity index 99% rename from src/Microsoft.AspNet.WebSockets/WebSocketException.cs rename to src/Microsoft.Net.WebSockets/WebSocketException.cs index b1691a1e1b..1e923616ad 100644 --- a/src/Microsoft.AspNet.WebSockets/WebSocketException.cs +++ b/src/Microsoft.Net.WebSockets/WebSocketException.cs @@ -23,9 +23,10 @@ using System; using System.ComponentModel; +using System.Net.WebSockets; using System.Runtime.InteropServices; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { #if NET45 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")] diff --git a/src/Microsoft.AspNet.WebSockets/WebSocketHelpers.cs b/src/Microsoft.Net.WebSockets/WebSocketHelpers.cs similarity index 58% rename from src/Microsoft.AspNet.WebSockets/WebSocketHelpers.cs rename to src/Microsoft.Net.WebSockets/WebSocketHelpers.cs index 331f007ed6..7d61cff7a1 100644 --- a/src/Microsoft.AspNet.WebSockets/WebSocketHelpers.cs +++ b/src/Microsoft.Net.WebSockets/WebSocketHelpers.cs @@ -26,21 +26,20 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; +using System.Net.WebSockets; using System.Runtime.CompilerServices; -#if NET45 using System.Security.Cryptography; -#endif using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNet.WebSockets +namespace Microsoft.Net.WebSockets { - internal static class WebSocketHelpers + public static class WebSocketHelpers { internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - internal const string WebSocketUpgradeToken = "websocket"; - internal const int DefaultReceiveBufferSize = 16 * 1024; + public const string WebSocketUpgradeToken = "websocket"; + public const int DefaultReceiveBufferSize = 16 * 1024; internal const int DefaultClientSendBufferSize = 16 * 1024; internal const int MaxControlFramePayloadLength = 123; @@ -63,138 +62,30 @@ namespace Microsoft.AspNet.WebSockets internal static readonly ArraySegment EmptyPayload = new ArraySegment(new byte[] { }, 0, 0); private static readonly Random KeyGenerator = new Random(); -/* - internal static Task AcceptWebSocketAsync(HttpListenerContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) + public static bool AreWebSocketsSupported { - 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); + get + { + return UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported; + } } - - private static async Task AcceptWebSocketAsyncCore(HttpListenerContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment internalBuffer) + + public static bool IsValidWebSocketKey(string key) { - HttpListenerWebSocketContext webSocketContext = null; - /*if (Logging.On) + if (string.IsNullOrWhiteSpace(key)) { - Logging.Enter(Logging.WebSockets, context, "AcceptWebSocketAsync", ""); - }* / - - try - { - // get property will create a new response if one doesn't exist. - HttpListenerResponse response = context.Response; - HttpListenerRequest request = context.Request; - ValidateWebSocketHeaders(context); - - string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - - // Optional for non-browser client - string origin = request.Headers[HttpKnownHeaderNames.Origin]; - - List secWebSocketProtocols = new List(); - string outgoingSecWebSocketProtocolString; - bool shouldSendSecWebSocketProtocolHeader = - WebSocketHelpers.ProcessWebSocketProtocolHeader( - request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], - subProtocol, - out outgoingSecWebSocketProtocolString); - - if (shouldSendSecWebSocketProtocolHeader) - { - secWebSocketProtocols.Add(outgoingSecWebSocketProtocolString); - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, - outgoingSecWebSocketProtocolString); - } - - // negotiate the websocket key return value - string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); - - response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101 - response.ComputeCoreHeaders(); - ulong hresult = SendWebSocketHeaders(response); - if (hresult != 0) - { - throw new WebSocketException((int)hresult, - SR.GetString(SR.net_WebSockets_NativeSendResponseHeaders, - WebSocketHelpers.MethodNames.AcceptWebSocketAsync, - hresult)); - } - - await response.OutputStream.FlushAsync().SuppressContextFlow(); // TODO:??? FlushAsync was never implemented - - HttpResponseStream responseStream = response.OutputStream as HttpResponseStream; - Contract.Assert(responseStream != null, "'responseStream' MUST be castable to System.Net.HttpResponseStream."); - ((HttpResponseStream)response.OutputStream).SwitchToOpaqueMode(); - HttpRequestStream requestStream = new HttpRequestStream(context); - requestStream.SwitchToOpaqueMode(); - WebSocketHttpListenerDuplexStream webSocketStream = - new WebSocketHttpListenerDuplexStream(requestStream, responseStream, context); - WebSocket webSocket = WebSocket.CreateServerWebSocket(webSocketStream, - subProtocol, - receiveBufferSize, - keepAliveInterval, - internalBuffer); - - webSocketContext = new HttpListenerWebSocketContext( - request.Url, - request.Headers, - request.Cookies, - context.User, - request.IsAuthenticated, - request.IsLocal, - request.IsSecureConnection, - origin, - secWebSocketProtocols.AsReadOnly(), - secWebSocketVersion, - secWebSocketKey, - webSocket); - - if (Logging.On) - { - Logging.Associate(Logging.WebSockets, context, webSocketContext); - Logging.Associate(Logging.WebSockets, webSocketContext, webSocket); - } + return false; } - catch (Exception ex) - { - if (Logging.On) - { - Logging.Exception(Logging.WebSockets, context, "AcceptWebSocketAsync", ex); - } - throw; - } - finally - { - if (Logging.On) - { - Logging.Exit(Logging.WebSockets, context, "AcceptWebSocketAsync", ""); - } - } - return webSocketContext; + // TODO: + // throw new NotImplementedException(); + return true; } - -*/ + [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", Justification = "SHA1 used only for hashing purposes, not for crypto.")] - internal static string GetSecWebSocketAcceptString(string secWebSocketKey) + public static string GetSecWebSocketAcceptString(string secWebSocketKey) { string retVal; -#if NET45 // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. using (SHA1 sha1 = SHA1.Create()) { @@ -202,10 +93,14 @@ namespace Microsoft.AspNet.WebSockets byte[] toHash = Encoding.UTF8.GetBytes(acceptString); retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); } -#endif return retVal; } + public static WebSocket CreateServerWebSocket(Stream opaqueStream, string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment internalBuffer) + { + return new ServerWebSocket(opaqueStream, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); + } + internal static string GetTraceMsgForParameters(int offset, int count, CancellationToken cancellationToken) { return string.Format(CultureInfo.InvariantCulture, @@ -216,11 +111,8 @@ namespace Microsoft.AspNet.WebSockets } // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. - internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, - string subProtocol, - out string acceptProtocol) + public static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, string subProtocol) { - acceptProtocol = string.Empty; if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) { // client hasn't specified any Sec-WebSocket-Protocol header @@ -236,10 +128,10 @@ namespace Microsoft.AspNet.WebSockets // here, we know the client specified something and it's non-empty. - if (subProtocol == null) + if (string.IsNullOrEmpty(subProtocol)) { // client specified some protocols, server specified 'null'. So server should send headers. - return true; + return false; } // here, we know that the client has specified something, it's not empty @@ -247,14 +139,13 @@ namespace Microsoft.AspNet.WebSockets string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - acceptProtocol = subProtocol; // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that // this exists in the list the client specified. for (int i = 0; i < requestProtocols.Length; i++) { string currentRequestProtocol = requestProtocols[i].Trim(); - if (string.Compare(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0) { return true; } @@ -281,7 +172,34 @@ namespace Microsoft.AspNet.WebSockets // under the caller's synchronization context. return task.ConfigureAwait(false); } - + + internal static bool IsStateTerminal(WebSocketState state) + { + return state == WebSocketState.Closed || state == WebSocketState.Aborted; + } + + internal static void ThrowOnInvalidState(WebSocketState state, params WebSocketState[] validStates) + { + string text = string.Empty; + if (validStates != null && validStates.Length > 0) + { + for (int i = 0; i < validStates.Length; i++) + { + WebSocketState webSocketState = validStates[i]; + if (state == webSocketState) + { + return; + } + } + text = string.Join(", ", validStates); + } + throw new WebSocketException(SR.GetString("net_WebSockets_InvalidState", new object[] + { + state, + text + })); + } + internal static void ValidateBuffer(byte[] buffer, int offset, int count) { if (buffer == null) @@ -299,57 +217,6 @@ namespace Microsoft.AspNet.WebSockets throw new ArgumentOutOfRangeException("count"); } } - /* - private static unsafe ulong SendWebSocketHeaders(HttpListenerResponse response) - { - return response.SendHeaders(null, null, - UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_OPAQUE | - UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA | - UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA, - true); - } - private static void ValidateWebSocketHeaders(HttpListenerContext context) - { - EnsureHttpSysSupportsWebSockets(); - - if (!context.Request.IsWebSocketRequest) - { - throw new WebSocketException(WebSocketError.NotAWebSocket, - SR.GetString(SR.net_WebSockets_AcceptNotAWebSocket, - WebSocketHelpers.MethodNames.ValidateWebSocketHeaders, - HttpKnownHeaderNames.Connection, - HttpKnownHeaderNames.Upgrade, - WebSocketHelpers.WebSocketUpgradeToken, - context.Request.Headers[HttpKnownHeaderNames.Upgrade])); - } - - string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (string.IsNullOrEmpty(secWebSocketVersion)) - { - throw new WebSocketException(WebSocketError.HeaderError, - SR.GetString(SR.net_WebSockets_AcceptHeaderNotFound, - WebSocketHelpers.MethodNames.ValidateWebSocketHeaders, - HttpKnownHeaderNames.SecWebSocketVersion)); - } - - if (string.Compare(secWebSocketVersion, WebSocketProtocolComponent.SupportedVersion, StringComparison.OrdinalIgnoreCase) != 0) - { - throw new WebSocketException(WebSocketError.UnsupportedVersion, - SR.GetString(SR.net_WebSockets_AcceptUnsupportedWebSocketVersion, - WebSocketHelpers.MethodNames.ValidateWebSocketHeaders, - secWebSocketVersion, - WebSocketProtocolComponent.SupportedVersion)); - } - - if (string.IsNullOrWhiteSpace(context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey])) - { - throw new WebSocketException(WebSocketError.HeaderError, - SR.GetString(SR.net_WebSockets_AcceptHeaderNotFound, - WebSocketHelpers.MethodNames.ValidateWebSocketHeaders, - HttpKnownHeaderNames.SecWebSocketKey)); - } - } - */ internal static void ValidateSubprotocol(string subProtocol) { @@ -425,7 +292,7 @@ namespace Microsoft.AspNet.WebSockets } } - internal static void ValidateOptions(string subProtocol, + public static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval) @@ -511,7 +378,7 @@ namespace Microsoft.AspNet.WebSockets throw new PlatformNotSupportedException(SR.GetString(SR.net_WebSockets_UnsupportedPlatform)); } - internal static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) + public static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) { Contract.Requires(!string.IsNullOrEmpty(parameterName), "'parameterName' MUST NOT be NULL or string.Empty"); @@ -529,11 +396,5 @@ namespace Microsoft.AspNet.WebSockets throw new ArgumentOutOfRangeException(parameterName + ".Count"); } } - - internal static class MethodNames - { - internal const string AcceptWebSocketAsync = "AcceptWebSocketAsync"; - internal const string ValidateWebSocketHeaders = "ValidateWebSocketHeaders"; - } } } diff --git a/src/Microsoft.Net.WebSockets/WebSocketMiddleware.cs b/src/Microsoft.Net.WebSockets/WebSocketMiddleware.cs new file mode 100644 index 0000000000..bac7e7cf32 --- /dev/null +++ b/src/Microsoft.Net.WebSockets/WebSocketMiddleware.cs @@ -0,0 +1,159 @@ +// 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. +/* TODO: +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.HttpFeature; +using Microsoft.Net.WebSockets; + +namespace Microsoft.AspNet.WebSockets +{ + public class WebSocketMiddleware + { + private RequestDelegate _next; + + public WebSocketMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task Invoke(HttpContext context) + { + // Detect if an opaque upgrade is available, and if websocket upgrade headers are present. + // If so, add a websocket upgrade. + var upgradeFeature = context.GetFeature(); + if (upgradeFeature != null) + { + context.SetFeature(new UpgradeHandshake(context, upgradeFeature)); + } + + return _next(context); + } + + private class UpgradeHandshake : IHttpWebSocketFeature + { + private HttpContext _context; + private IHttpOpaqueUpgradeFeature _upgrade; + + private string _subProtocol; + private int _receiveBufferSize = WebSocketHelpers.DefaultReceiveBufferSize; + private TimeSpan _keepAliveInterval = WebSocket.DefaultKeepAliveInterval; + private ArraySegment? _internalBuffer; + + internal UpgradeHandshake(HttpContext context, IHttpOpaqueUpgradeFeature upgrade) + { + _context = context; + _upgrade = upgrade; + } + + public bool IsWebSocketRequest + { + get + { + // Headers and values: + // Connection: Upgrade + // Upgrade: WebSocket + // Sec-WebSocket-Version: (WebSocketProtocolComponent.SupportedVersion) + // Sec-WebSocket-Key: (hash, see WebSocketHelpers.GetSecWebSocketAcceptString) + // Sec-WebSocket-Protocol: (optional, list) + IList connectionHeaders = _context.Request.Headers.GetCommaSeparatedValues(HttpKnownHeaderNames.Connection); // "Upgrade, KeepAlive" + string upgradeHeader = _context.Request.Headers[HttpKnownHeaderNames.Upgrade]; + string versionHeader = _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; + string keyHeader = _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + + if (connectionHeaders != null && connectionHeaders.Count > 0 + && connectionHeaders.Contains(HttpKnownHeaderNames.Upgrade, StringComparer.OrdinalIgnoreCase) + && string.Equals(upgradeHeader, WebSocketHelpers.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase) + && string.Equals(versionHeader, UnsafeNativeMethods.WebSocketProtocolComponent.SupportedVersion, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrWhiteSpace(keyHeader)) + { + return true; + } + return false; + } + } + + public async Task AcceptAsync(IWebSocketAcceptContext acceptContext) + { + // Get options + if (acceptContext != null) + { + _subProtocol = acceptContext.SubProtocol; + } + + var advancedAcceptContext = acceptContext as WebSocketAcceptContext; + if (advancedAcceptContext != null) + { + if (advancedAcceptContext.ReceiveBufferSize.HasValue) + { + _receiveBufferSize = advancedAcceptContext.ReceiveBufferSize.Value; + } + if (advancedAcceptContext.KeepAliveInterval.HasValue) + { + _keepAliveInterval = advancedAcceptContext.KeepAliveInterval.Value; + } + _internalBuffer = advancedAcceptContext.Buffer; + } + + if (!_internalBuffer.HasValue) + { + _internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(_receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); + } + + // Set WebSocket upgrade response headers + + string outgoingSecWebSocketProtocolString; + bool shouldSendSecWebSocketProtocolHeader = + WebSocketHelpers.ProcessWebSocketProtocolHeader( + _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], + _subProtocol, + out outgoingSecWebSocketProtocolString); + + if (shouldSendSecWebSocketProtocolHeader) + { + _context.Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = outgoingSecWebSocketProtocolString; + } + + string secWebSocketKey = _context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; + string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); + + _context.Response.Headers[HttpKnownHeaderNames.Connection] = HttpKnownHeaderNames.Upgrade; + _context.Response.Headers[HttpKnownHeaderNames.Upgrade] = WebSocketHelpers.WebSocketUpgradeToken; + _context.Response.Headers[HttpKnownHeaderNames.SecWebSocketAccept] = secWebSocketAccept; + + // 101 Switching Protocols; + Stream opaqueTransport = await _upgrade.UpgradeAsync(); + return new ServerWebSocket(opaqueTransport, _subProtocol, _receiveBufferSize, _keepAliveInterval, _internalBuffer.Value); + } + } + + public class WebSocketAcceptContext : IWebSocketAcceptContext + { + public string SubProtocol { get; set; } + public int? ReceiveBufferSize { get; set; } + public TimeSpan? KeepAliveInterval { get; set; } + public ArraySegment? Buffer { get; set; } + } + } +}*/ \ No newline at end of file diff --git a/src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs b/src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs new file mode 100644 index 0000000000..00df192c38 --- /dev/null +++ b/src/Microsoft.Net.WebSockets/WebSocketReceiveResultExtensions.cs @@ -0,0 +1,49 @@ +// 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.Diagnostics.Contracts; +using System.Net.WebSockets; + +namespace Microsoft.Net.WebSockets +{ + public static class WebSocketReceiveResultExtensions + { + internal static WebSocketReceiveResult DecrementAndClone(ref WebSocketReceiveResult original, int count) + { + Contract.Assert(count >= 0, "'count' MUST NOT be negative."); + Contract.Assert(count <= original.Count, "'count' MUST NOT be bigger than 'this.Count'."); + int remaining = original.Count - count; + original = new WebSocketReceiveResult(remaining, + original.MessageType, + original.EndOfMessage, + original.CloseStatus, + original.CloseStatusDescription); + return new WebSocketReceiveResult(count, + original.MessageType, + remaining == 0 && original.EndOfMessage, + original.CloseStatus, + original.CloseStatusDescription); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.WebSockets/build.cmd b/src/Microsoft.Net.WebSockets/build.cmd similarity index 100% rename from src/Microsoft.AspNet.WebSockets/build.cmd rename to src/Microsoft.Net.WebSockets/build.cmd diff --git a/src/Microsoft.AspNet.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs b/src/Microsoft.Net.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs rename to src/Microsoft.Net.WebSockets/fx/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/AccessViolationException.cs b/src/Microsoft.Net.WebSockets/fx/System/AccessViolationException.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/AccessViolationException.cs rename to src/Microsoft.Net.WebSockets/fx/System/AccessViolationException.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/ComponentModel/Win32Exception.cs b/src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/ComponentModel/Win32Exception.cs rename to src/Microsoft.Net.WebSockets/fx/System/ComponentModel/Win32Exception.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/ExternDll.cs b/src/Microsoft.Net.WebSockets/fx/System/ExternDll.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/ExternDll.cs rename to src/Microsoft.Net.WebSockets/fx/System/ExternDll.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs b/src/Microsoft.Net.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs rename to src/Microsoft.Net.WebSockets/fx/System/Runtime/InteropServices/ExternalException.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/SafeNativeMethods.cs b/src/Microsoft.Net.WebSockets/fx/System/SafeNativeMethods.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/SafeNativeMethods.cs rename to src/Microsoft.Net.WebSockets/fx/System/SafeNativeMethods.cs diff --git a/src/Microsoft.AspNet.WebSockets/fx/System/SystemException.cs b/src/Microsoft.Net.WebSockets/fx/System/SystemException.cs similarity index 100% rename from src/Microsoft.AspNet.WebSockets/fx/System/SystemException.cs rename to src/Microsoft.Net.WebSockets/fx/System/SystemException.cs diff --git a/src/Microsoft.Net.WebSockets/project.json b/src/Microsoft.Net.WebSockets/project.json new file mode 100644 index 0000000000..c3b93c599f --- /dev/null +++ b/src/Microsoft.Net.WebSockets/project.json @@ -0,0 +1,39 @@ +{ + "version": "0.1-alpha-*", + "dependencies": { + }, + "compilationOptions" : { "allowUnsafe": true }, + "configurations": { + "net45" : { }, + "k10" : { + "dependencies": { + "Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*", + "Microsoft.Win32.Primitives": "4.0.0.0", + "System.Collections": "4.0.0.0", + "System.Collections.Concurrent": "4.0.0.0", + "System.Diagnostics.Contracts": "4.0.0.0", + "System.Diagnostics.Debug": "4.0.10.0", + "System.Diagnostics.Tools": "4.0.0.0", + "System.Globalization": "4.0.10.0", + "System.IO": "4.0.0.0", + "System.Linq": "4.0.0.0", + "System.Net.Primitives": "4.0.10.0", + "System.Reflection": "4.0.10.0", + "System.Resources.ResourceManager": "4.0.0.0", + "System.Runtime": "4.0.20.0", + "System.Runtime.Extensions": "4.0.10.0", + "System.Runtime.Handles": "4.0.0.0", + "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Cryptography.HashAlgorithms.SHA1": "4.0.0.0", + "System.Security.Principal": "4.0.0.0", + "System.Text.Encoding": "4.0.20.0", + "System.Text.Encoding.Extensions": "4.0.10.0", + "System.Threading": "4.0.0.0", + "System.Threading.Overlapped": "4.0.0.0", + "System.Threading.Tasks": "4.0.10.0", + "System.Threading.Timer": "4.0.0.0", + "System.Threading.ThreadPool": "4.0.10.0" + } + } + } +}