Expose Opaque and WebSockets.

This commit is contained in:
Chris Ross 2014-06-16 14:24:20 -07:00
parent 1ef31a943a
commit d1dab1665e
48 changed files with 697 additions and 1233 deletions

View File

@ -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

View File

@ -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<byte>(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();
}
}
}
}

View File

@ -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<byte>(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");
}
});
}
}

View File

@ -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": {
},

View File

@ -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<Stream> 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<WebSocket> AcceptAsync(IWebSocketAcceptContext context)
{
// TODO: Advanced params
string subProtocol = null;
if (context != null)
{
subProtocol = context.SubProtocol;
}
return _requestContext.AcceptWebSocketAsync(subProtocol);
}
#endregion
}
}

View File

@ -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)

View File

@ -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",

View File

@ -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
{
/// <summary>
/// Standard keys and values for use within the OWIN interfaces
/// </summary>
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";
}
}

View File

@ -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 file="HttpListenerContext.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
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<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol)
{
return this.AcceptWebSocketAsync(subProtocol,
WebSocketHelpers.DefaultReceiveBufferSize,
WebSocket.DefaultKeepAliveInterval);
}
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
{
return this.AcceptWebSocketAsync(subProtocol,
WebSocketHelpers.DefaultReceiveBufferSize,
keepAliveInterval);
}
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval)
{
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
ArraySegment<byte> internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
return this.AcceptWebSocketAsync(subProtocol,
receiveBufferSize,
keepAliveInterval,
internalBuffer);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> internalBuffer)
{
return WebSocketHelpers.AcceptWebSocketAsync(this,
subProtocol,
receiveBufferSize,
keepAliveInterval,
internalBuffer);
}
}
}
*/

View File

@ -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 file="HttpListenerRequest.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
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;
}
}
}
}
*/

View File

@ -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<int /* closeStatus */,
string /* closeDescription */,
CancellationToken /* cancel */,
Task>;
using WebSocketReceiveAsync =
Func<ArraySegment<byte> /* data */,
CancellationToken /* cancel */,
Task<Tuple<int /* messageType */,
bool /* endOfMessage */,
int /* count */>>>;
using WebSocketReceiveTuple =
Tuple<int /* messageType */,
bool /* endOfMessage */,
int /* count */>;
using WebSocketSendAsync =
Func<ArraySegment<byte> /* data */,
int /* messageType */,
bool /* endOfMessage */,
CancellationToken /* cancel */,
Task>;
internal class OwinWebSocketWrapper
{
private readonly WebSocket _webSocket;
private readonly IDictionary<string, object> _environment;
private readonly CancellationToken _cancellationToken;
internal OwinWebSocketWrapper(WebSocket webSocket, CancellationToken ct)
{
_webSocket = webSocket;
_cancellationToken = ct;
_environment = new Dictionary<string, object>();
_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<string, object> Environment
{
get { return _environment; }
}
internal Task SendAsync(ArraySegment<byte> 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<WebSocketReceiveTuple> ReceiveAsync(ArraySegment<byte> 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<byte> 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);
}
}
}
}

View File

@ -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 file="WebSocket.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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<byte> CreateClientBuffer(int receiveBufferSize, int sendBufferSize)
{
WebSocketHelpers.ValidateBufferSizes(receiveBufferSize, sendBufferSize);
return WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, sendBufferSize, false);
}
public static ArraySegment<byte> 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<byte> internalBuffer)
{
if (!UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported)
{
WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC();
}
WebSocketHelpers.ValidateInnerStream(innerStream);
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
WebSocketHelpers.ValidateArraySegment<byte>(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<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer,
CancellationToken cancellationToken);
public abstract Task SendAsync(ArraySegment<byte> 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;
}
}
}

View File

@ -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 file="WebSocketCloseStatus.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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.
}
}

View File

@ -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<WebSocketMiddleware>();
}
}
}
#endif
*/

View File

@ -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 file="WebSocketMessageType.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.WebSockets
{
public enum WebSocketMessageType
{
Text = 0x1,
Binary = 0x2,
Close = 0x8,
}
}

View File

@ -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<IDictionary<string, object>, Task>;
using OpaqueUpgrade =
Action
<
IDictionary<string, object>, // Parameters
Func // OpaqueFunc callback
<
IDictionary<string, object>, // Opaque environment
Task // Complete
>
>;
using WebSocketAccept =
Action
<
IDictionary<string, object>, // WebSocket Accept parameters
Func // WebSocketFunc callback
<
IDictionary<string, object>, // WebSocket environment
Task // Complete
>
>;
using WebSocketFunc =
Func
<
IDictionary<string, object>, // WebSocket Environment
Task // Complete
>;
public class WebSocketMiddleware
{
private AppFunc _next;
public WebSocketMiddleware(AppFunc next)
{
_next = next;
}
public Task Invoke(IDictionary<string, object> 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<OpaqueUpgrade>("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<string> 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<byte> _internalBuffer;
internal UpgradeHandshake(IOwinContext context, OpaqueUpgrade upgrade)
{
_context = context;
_upgrade = upgrade;
}
internal void AcceptWebSocket(IDictionary<string, object> 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<byte>)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<string, object> 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<string, object> opaqueEnv)
{
Stream stream = (Stream)opaqueEnv["opaque.Stream"];
return new ServerWebSocket(stream, _subProtocol, _receiveBufferSize, _keepAliveInterval, _internalBuffer);
}
}
}
}
*/

View File

@ -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 file="WebSocketReceiveResult.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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);
}
}
}

View File

@ -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 file="WebSocketState.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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,
}
}

View File

@ -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": {
}
}
}
}

View File

@ -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.

View File

@ -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<IDictionary<string, object>, 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<IDictionary<string, object>, OpaqueFunc> value)
public bool IsUpgradableRequest
{
if (_request.IsUpgradable)
{
value = OpaqueUpgrade;
return true;
}
return false;
get { return _request.IsUpgradable; }
}
public Task<Stream> 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<WebSocket> AcceptWebSocketAsync()
{
return AcceptWebSocketAsync(null,
WebSocketHelpers.DefaultReceiveBufferSize,
WebSocket.DefaultKeepAliveInterval);
}
public Task<WebSocket> AcceptWebSocketAsync(string subProtocol)
{
return AcceptWebSocketAsync(subProtocol,
WebSocketHelpers.DefaultReceiveBufferSize,
WebSocket.DefaultKeepAliveInterval);
}
public Task<WebSocket> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
{
return AcceptWebSocketAsync(subProtocol,
WebSocketHelpers.DefaultReceiveBufferSize,
keepAliveInterval);
}
public Task<WebSocket> AcceptWebSocketAsync(
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval)
{
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
ArraySegment<byte> internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
return this.AcceptWebSocketAsync(subProtocol,
receiveBufferSize,
keepAliveInterval,
internalBuffer);
}
public Task<WebSocket> AcceptWebSocketAsync(
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> internalBuffer)
{
if (!IsUpgradableRequest)
{
throw new InvalidOperationException("This request is cannot be upgraded.");
}
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
WebSocketHelpers.ValidateArraySegment<byte>(internalBuffer, "internalBuffer");
WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
return AcceptWebSocketAsyncCore(subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
}
private async Task<WebSocket> AcceptWebSocketAsyncCore(
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> 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<string, object> 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<string, object> opaqueEnv = CreateOpaqueEnvironment();
return _opaqueCallback(opaqueEnv);
}
return Helpers.CompletedTask();
}
private IDictionary<string, object> CreateOpaqueEnvironment()
{
IDictionary<string, object> opaqueEnv = new Dictionary<string, object>();
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;
}
*/
}
}

View File

@ -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",

View File

@ -21,7 +21,7 @@
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.WebSockets
namespace Microsoft.Net.WebSockets
{
// this class contains known header names
internal static class HttpKnownHeaderNames

View File

@ -22,7 +22,6 @@
<Content Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Constants.cs" />
<Compile Include="fx\Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
<Compile Include="fx\System\AccessViolationException.cs" />
<Compile Include="fx\System\ComponentModel\Win32Exception.cs" />
@ -31,29 +30,21 @@
<Compile Include="fx\System\SafeNativeMethods.cs" />
<Compile Include="fx\System\SystemException.cs" />
<Compile Include="HttpKnownHeaderNames.cs" />
<Compile Include="Legacy\HttpListenerContext.cs" />
<Compile Include="Legacy\HttpListenerRequest.cs" />
<Compile Include="Legacy\SR.cs" />
<Compile Include="Legacy\WebSocketHttpListenerDuplexStream.cs" />
<Compile Include="NativeInterop\SafeLoadLibrary.cs" />
<Compile Include="NativeInterop\SafeNativeOverlapped.cs" />
<Compile Include="NativeInterop\SafeWebSocketHandle.cs" />
<Compile Include="NativeInterop\UnsafeNativeMethods.cs" />
<Compile Include="OwinWebSocketWrapper.cs" />
<Compile Include="ServerWebSocket.cs" />
<Compile Include="WebSocket.cs" />
<Compile Include="WebSocketBase.cs" />
<Compile Include="WebSocketBuffer.cs" />
<Compile Include="WebSocketCloseStatus.cs" />
<Compile Include="WebSocketConstants.cs" />
<Compile Include="WebSocketError.cs" />
<Compile Include="WebSocketException.cs" />
<Compile Include="WebSocketExtensions.cs" />
<Compile Include="WebSocketHelpers.cs" />
<Compile Include="WebSocketMessageType.cs" />
<Compile Include="WebSocketMiddleware.cs" />
<Compile Include="WebSocketReceiveResult.cs" />
<Compile Include="WebSocketState.cs" />
<Compile Include="WebSocketReceiveResultExtensions.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -23,7 +23,7 @@
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.WebSockets
namespace Microsoft.Net.WebSockets
{
internal sealed class SafeLoadLibrary : SafeHandleZeroOrMinusOneIsInvalid
{

View File

@ -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
{

View File

@ -21,10 +21,9 @@
// </copyright>
//------------------------------------------------------------------------------
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.

View File

@ -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)

View File

@ -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
{

View File

@ -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;
}

View File

@ -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<byte> CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer)
public static ArraySegment<byte> 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<byte>(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) + ".");

View File

@ -0,0 +1,9 @@
using System;
namespace Microsoft.Net.WebSockets
{
public static class WebSocketConstants
{
public static string SupportedProtocolVersion = "13";
}
}

View File

@ -21,7 +21,7 @@
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.WebSockets
namespace Microsoft.Net.WebSockets
{
public enum WebSocketError
{

View File

@ -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")]

View File

@ -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<byte> EmptyPayload = new ArraySegment<byte>(new byte[] { }, 0, 0);
private static readonly Random KeyGenerator = new Random();
/*
internal static Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(HttpListenerContext context,
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> internalBuffer)
public static bool AreWebSocketsSupported
{
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
WebSocketHelpers.ValidateArraySegment<byte>(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<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context,
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> 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<string> secWebSocketProtocols = new List<string>();
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<byte> 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<WebSocketState>(", ", 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<T>(ArraySegment<T> arraySegment, string parameterName)
public static void ValidateArraySegment<T>(ArraySegment<T> 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";
}
}
}

View File

@ -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<IHttpOpaqueUpgradeFeature>();
if (upgradeFeature != null)
{
context.SetFeature<IHttpWebSocketFeature>(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<byte>? _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<string> 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<WebSocket> 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<byte>? Buffer { get; set; }
}
}
}*/

View File

@ -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 file="WebSocketReceiveResult.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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);
}
}
}

View File

@ -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"
}
}
}
}