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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.21708.0 VisualStudioVersion = 14.0.21730.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}"
EndProject EndProject
@ -19,7 +19,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SelfHostServer", "samples\S
EndProject 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}" 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 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 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}" 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 EndProject

View File

@ -16,7 +16,9 @@
// permissions and limitations under the License. // permissions and limitations under the License.
using System; using System;
using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Threading;
using Microsoft.Net.Server; using Microsoft.Net.Server;
namespace HelloWorld namespace HelloWorld
@ -73,11 +75,23 @@ namespace HelloWorld
// Response // Response
byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now); byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now);
context.Response.ContentLength = bytes.Length; if (context.IsWebSocketRequest)
context.Response.ContentType = "text/plain"; {
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.Response.Body.Write(bytes, 0, bytes.Length);
context.Dispose(); context.Dispose();
}
} }
} }
} }

View File

@ -15,6 +15,10 @@
// See the Apache 2 License for the specific language governing // See the Apache 2 License for the specific language governing
// permissions and limitations under the License. // permissions and limitations under the License.
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using Microsoft.AspNet; using Microsoft.AspNet;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -32,8 +36,20 @@ namespace SelfHostServer
app.Run(async context => app.Run(async context =>
{ {
context.Response.ContentType = "text/plain"; if (context.IsWebSocketRequest)
await context.Response.WriteAsync("Hello world"); {
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.AspNet.Server.WebListener": "",
"Microsoft.Net.Server": "" "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": { "configurations": {
"net45": { "net45": {
}, },

View File

@ -19,6 +19,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -28,7 +29,15 @@ using Microsoft.Net.Server;
namespace Microsoft.AspNet.Server.WebListener 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 RequestContext _requestContext;
private FeatureCollection _features; private FeatureCollection _features;
@ -85,10 +94,13 @@ namespace Microsoft.AspNet.Server.WebListener
_features.Add(typeof(IHttpSendFileFeature), this); _features.Add(typeof(IHttpSendFileFeature), this);
_features.Add(typeof(IHttpRequestLifetimeFeature), this); _features.Add(typeof(IHttpRequestLifetimeFeature), this);
// TODO: If Win8+
_features.Add(typeof(IHttpOpaqueUpgradeFeature), this);
_features.Add(typeof(IHttpWebSocketFeature), this);
// TODO: // TODO:
// _environment.CallCancelled = _cts.Token; // _environment.CallCancelled = _cts.Token;
// _environment.User = _request.User; // _environment.User = _request.User;
// Opaque/WebSockets
// Channel binding // Channel binding
/* /*
@ -348,11 +360,16 @@ namespace Microsoft.AspNet.Server.WebListener
set { Response.StatusCode = value; } set { Response.StatusCode = value; }
} }
#endregion #endregion
#region IHttpSendFileFeature
Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation) Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{ {
return Response.SendFileAsync(path, offset, length, cancellation); return Response.SendFileAsync(path, offset, length, cancellation);
} }
#endregion
#region IHttpRequestLifetimeFeature
public CancellationToken OnRequestAborted public CancellationToken OnRequestAborted
{ {
get { return _requestContext.DisconnectToken; } get { return _requestContext.DisconnectToken; }
@ -362,5 +379,46 @@ namespace Microsoft.AspNet.Server.WebListener
{ {
_requestContext.Abort(); _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); Interlocked.Increment(ref _outstandingRequests);
FeatureContext featureContext = new FeatureContext(requestContext); FeatureContext featureContext = new FeatureContext(requestContext);
await _appFunc(featureContext.Features).SupressContext(); await _appFunc(featureContext.Features).SupressContext();
// TODO: WebSocket/Opaque upgrade - await requestContext.ProcessResponseAsync().SupressContext();
requestContext.Dispose(); requestContext.Dispose();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -16,6 +16,7 @@
"net45": {}, "net45": {},
"k10": { "k10": {
"dependencies": { "dependencies": {
"Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*",
"Microsoft.Win32.Primitives": "4.0.0.0", "Microsoft.Win32.Primitives": "4.0.0.0",
"System.Collections": "4.0.0.0", "System.Collections": "4.0.0.0",
"System.Collections.Concurrent": "4.0.0.0", "System.Collections.Concurrent": "4.0.0.0",
@ -25,12 +26,14 @@
"System.Globalization": "4.0.10.0", "System.Globalization": "4.0.10.0",
"System.IO": "4.0.0.0", "System.IO": "4.0.0.0",
"System.Linq": "4.0.0.0", "System.Linq": "4.0.0.0",
"System.Net.Primitives": "4.0.10.0",
"System.Reflection": "4.0.10.0", "System.Reflection": "4.0.10.0",
"System.Resources.ResourceManager": "4.0.0.0", "System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0", "System.Runtime": "4.0.20.0",
"System.Runtime.Extensions": "4.0.10.0", "System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.Handles": "4.0.0.0", "System.Runtime.Handles": "4.0.0.0",
"System.Runtime.InteropServices": "4.0.20.0", "System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
"System.Security.Principal": "4.0.0.0", "System.Security.Principal": "4.0.0.0",
"System.Text.Encoding": "4.0.20.0", "System.Text.Encoding": "4.0.20.0",
"System.Text.Encoding.Extensions": "4.0.10.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); 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. // 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 // 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. // enable this, but it's unclear what Http.Sys would do.

View File

@ -24,23 +24,24 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Net.WebSockets;
namespace Microsoft.Net.Server namespace Microsoft.Net.Server
{ {
using OpaqueFunc = Func<IDictionary<string, object>, Task>;
public sealed class RequestContext : IDisposable public sealed class RequestContext : IDisposable
{ {
private WebListener _server; private WebListener _server;
private Request _request; private Request _request;
private Response _response; private Response _response;
private NativeRequestContext _memoryBlob; private NativeRequestContext _memoryBlob;
private OpaqueFunc _opaqueCallback;
private bool _disposed; private bool _disposed;
private CancellationTokenSource _requestAbortSource; private CancellationTokenSource _requestAbortSource;
private CancellationToken? _disconnectToken; private CancellationToken? _disconnectToken;
@ -128,17 +129,230 @@ namespace Microsoft.Net.Server
return Request.RequestId; return Request.RequestId;
} }
} }
/*
public bool TryGetOpaqueUpgrade(ref Action<IDictionary<string, object>, OpaqueFunc> value) public bool IsUpgradableRequest
{ {
if (_request.IsUpgradable) get { return _request.IsUpgradable; }
{
value = OpaqueUpgrade;
return true;
}
return false;
} }
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) public bool TryGetChannelBinding(ref ChannelBinding value)
{ {
value = Server.GetChannelBinding(Request.ConnectionId, Request.IsSecureConnection); value = Server.GetChannelBinding(Request.ConnectionId, Request.IsSecureConnection);
@ -221,57 +435,5 @@ namespace Microsoft.Net.Server
// RequestQueueHandle may have been closed // 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-*", "version": "0.1-alpha-*",
"dependencies": { "dependencies": {
"Microsoft.Framework.Logging": "0.1-alpha-*" "Microsoft.Framework.Logging": "0.1-alpha-*",
"Microsoft.Net.WebSockets": ""
}, },
"compilationOptions": { "compilationOptions": {
"allowUnsafe": true "allowUnsafe": true
@ -10,6 +11,7 @@
"net45": {}, "net45": {},
"k10": { "k10": {
"dependencies": { "dependencies": {
"Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*",
"Microsoft.Win32.Primitives": "4.0.0.0", "Microsoft.Win32.Primitives": "4.0.0.0",
"System.Collections": "4.0.0.0", "System.Collections": "4.0.0.0",
"System.Collections.Concurrent": "4.0.0.0", "System.Collections.Concurrent": "4.0.0.0",

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
internal class SafeNativeOverlapped : SafeHandle internal class SafeNativeOverlapped : SafeHandle
{ {

View File

@ -21,10 +21,9 @@
// </copyright> // </copyright>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using Microsoft.AspNet.WebSockets;
using Microsoft.Win32.SafeHandles; 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 // 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. // 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.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
internal static class UnsafeNativeMethods internal static class UnsafeNativeMethods
{ {
@ -161,7 +161,11 @@ namespace Microsoft.AspNet.WebSockets
static WebSocketProtocolComponent() static WebSocketProtocolComponent()
{ {
#if NET45
DllFileName = Path.Combine(Environment.SystemDirectory, WEBSOCKET); DllFileName = Path.Combine(Environment.SystemDirectory, WEBSOCKET);
#else
DllFileName = Path.Combine(Environment.GetEnvironmentVariable("SYSTEMROOT"), "System32", WEBSOCKET);
#endif
WebSocketDllHandle = SafeLoadLibrary.LoadLibraryEx(DllFileName); WebSocketDllHandle = SafeLoadLibrary.LoadLibraryEx(DllFileName);
if (!WebSocketDllHandle.IsInvalid) if (!WebSocketDllHandle.IsInvalid)

View File

@ -27,7 +27,7 @@ using System.Diagnostics.Contracts;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
internal sealed class ServerWebSocket : WebSocketBase internal sealed class ServerWebSocket : WebSocketBase
{ {

View File

@ -29,13 +29,14 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net.WebSockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
internal abstract class WebSocketBase : WebSocket, IDisposable internal abstract class WebSocketBase : WebSocket, IDisposable
{ {
@ -233,7 +234,7 @@ namespace Microsoft.AspNet.WebSockets
{ {
ThrowIfPendingException(); ThrowIfPendingException();
ThrowIfDisposed(); ThrowIfDisposed();
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent); WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent);
bool ownsCancellationTokenSource = false; bool ownsCancellationTokenSource = false;
CancellationToken linkedCancellationToken = CancellationToken.None; CancellationToken linkedCancellationToken = CancellationToken.None;
@ -338,7 +339,7 @@ namespace Microsoft.AspNet.WebSockets
{ {
ThrowIfPendingException(); ThrowIfPendingException();
ThrowIfDisposed(); ThrowIfDisposed();
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
bool ownsCancellationTokenSource = false; bool ownsCancellationTokenSource = false;
CancellationToken linkedCancellationToken = CancellationToken.None; CancellationToken linkedCancellationToken = CancellationToken.None;
@ -468,13 +469,13 @@ namespace Microsoft.AspNet.WebSockets
bool sessionHandleLockTaken = false; bool sessionHandleLockTaken = false;
try try
{ {
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken); TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken);
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
@ -548,7 +549,7 @@ namespace Microsoft.AspNet.WebSockets
try try
{ {
ThrowIfPendingException(); ThrowIfPendingException();
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
@ -566,12 +567,12 @@ namespace Microsoft.AspNet.WebSockets
ThrowIfPendingException(); ThrowIfPendingException();
ThrowIfDisposed(); ThrowIfDisposed();
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived); WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
ownsCloseOutputCancellationTokenSource = _closeOutputOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken); ownsCloseOutputCancellationTokenSource = _closeOutputOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken);
if (!ownsCloseOutputCancellationTokenSource) if (!ownsCloseOutputCancellationTokenSource)
{ {
@ -676,7 +677,7 @@ namespace Microsoft.AspNet.WebSockets
// returns TRUE if the caller should also call StartOnCloseCompleted // returns TRUE if the caller should also call StartOnCloseCompleted
private bool OnCloseOutputCompleted() private bool OnCloseOutputCompleted()
{ {
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return false; return false;
} }
@ -707,7 +708,7 @@ namespace Microsoft.AspNet.WebSockets
{ {
Contract.Assert(thisLockTakenSnapshot, "'thisLockTakenSnapshot' MUST be 'true' at this point."); Contract.Assert(thisLockTakenSnapshot, "'thisLockTakenSnapshot' MUST be 'true' at this point.");
if (IsStateTerminal(_state)) if (WebSocketHelpers.IsStateTerminal(_state))
{ {
return false; return false;
} }
@ -793,7 +794,7 @@ namespace Microsoft.AspNet.WebSockets
try try
{ {
ThrowIfPendingException(); ThrowIfPendingException();
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
@ -806,12 +807,12 @@ namespace Microsoft.AspNet.WebSockets
try try
{ {
ThrowIfPendingException(); ThrowIfPendingException();
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
ThrowIfDisposed(); ThrowIfDisposed();
ThrowOnInvalidState(State, WebSocketHelpers.ThrowOnInvalidState(State,
WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent); WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent);
Task closeOutputTask; Task closeOutputTask;
@ -893,7 +894,7 @@ namespace Microsoft.AspNet.WebSockets
} }
} }
if (IsStateTerminal(State)) if (WebSocketHelpers.IsStateTerminal(State))
{ {
return; return;
} }
@ -970,7 +971,7 @@ namespace Microsoft.AspNet.WebSockets
Monitor.Enter(_thisLock, ref lockTaken); Monitor.Enter(_thisLock, ref lockTaken);
} }
if (!IsStateTerminal(State)) if (!WebSocketHelpers.IsStateTerminal(State))
{ {
bool ownsSendCancellationSource = false; bool ownsSendCancellationSource = false;
try try
@ -1055,7 +1056,7 @@ namespace Microsoft.AspNet.WebSockets
return; return;
} }
if (!IsStateTerminal(State)) if (!WebSocketHelpers.IsStateTerminal(State))
{ {
Abort(); Abort();
} }
@ -1501,13 +1502,13 @@ namespace Microsoft.AspNet.WebSockets
{ {
ThrowIfDisposed(); ThrowIfDisposed();
if (IsStateTerminal(State) || State == WebSocketState.CloseReceived) if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived)
{ {
return false; return false;
} }
Monitor.Enter(_thisLock, ref thisLockTaken); Monitor.Enter(_thisLock, ref thisLockTaken);
if (IsStateTerminal(State) || State == WebSocketState.CloseReceived) if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived)
{ {
return false; return false;
} }

View File

@ -25,11 +25,12 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Globalization; using System.Globalization;
using System.Net.WebSockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; 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 // 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. // 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 // *RBS = ReceiveBufferSize, *SBS = SendBufferSize
// *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes) // *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes)
internal class WebSocketBuffer : IDisposable public class WebSocketBuffer : IDisposable
{ {
private const int NativeOverheadBufferSize = 144; private const int NativeOverheadBufferSize = 144;
internal const int MinSendBufferSize = 16; public const int MinSendBufferSize = 16;
internal const int MinReceiveBufferSize = 256; internal const int MinReceiveBufferSize = 256;
internal const int MaxBufferSize = 64 * 1024; internal const int MaxBufferSize = 64 * 1024;
#if NET45 #if NET45
@ -362,7 +363,7 @@ namespace Microsoft.AspNet.WebSockets
ValidateBufferedPayload(); ValidateBufferedPayload();
int bytesTransferred = Math.Min(buffer.Count, _BufferedPayloadReceiveResult.Count); int bytesTransferred = Math.Min(buffer.Count, _BufferedPayloadReceiveResult.Count);
receiveResult = _BufferedPayloadReceiveResult.Copy(bytesTransferred); receiveResult = WebSocketReceiveResultExtensions.DecrementAndClone(ref _BufferedPayloadReceiveResult, bytesTransferred);
Buffer.BlockCopy(_PayloadBuffer.Array, Buffer.BlockCopy(_PayloadBuffer.Array,
_PayloadBuffer.Offset + _PayloadOffset, _PayloadBuffer.Offset + _PayloadOffset,
@ -664,7 +665,7 @@ namespace Microsoft.AspNet.WebSockets
ReleasePinnedSendBuffer(); 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, Contract.Assert(receiveBufferSize >= MinReceiveBufferSize,
"'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); "'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + ".");
@ -675,7 +676,7 @@ namespace Microsoft.AspNet.WebSockets
return new ArraySegment<byte>(new byte[internalBufferSize]); 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, Contract.Assert(receiveBufferSize >= MinReceiveBufferSize,
"'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + "."); "'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> // </copyright>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
public enum WebSocketError public enum WebSocketError
{ {

View File

@ -23,9 +23,10 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Net.WebSockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Microsoft.AspNet.WebSockets namespace Microsoft.Net.WebSockets
{ {
#if NET45 #if NET45
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]

View File

@ -26,21 +26,20 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net.WebSockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if NET45
using System.Security.Cryptography; using System.Security.Cryptography;
#endif
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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 SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
internal const string WebSocketUpgradeToken = "websocket"; public const string WebSocketUpgradeToken = "websocket";
internal const int DefaultReceiveBufferSize = 16 * 1024; public const int DefaultReceiveBufferSize = 16 * 1024;
internal const int DefaultClientSendBufferSize = 16 * 1024; internal const int DefaultClientSendBufferSize = 16 * 1024;
internal const int MaxControlFramePayloadLength = 123; 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); internal static readonly ArraySegment<byte> EmptyPayload = new ArraySegment<byte>(new byte[] { }, 0, 0);
private static readonly Random KeyGenerator = new Random(); private static readonly Random KeyGenerator = new Random();
/* public static bool AreWebSocketsSupported
internal static Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(HttpListenerContext context,
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> internalBuffer)
{ {
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval); get
WebSocketHelpers.ValidateArraySegment<byte>(internalBuffer, "internalBuffer"); {
WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true); return UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported;
}
return AcceptWebSocketAsyncCore(context, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
} }
private static async Task<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context, public static bool IsValidWebSocketKey(string key)
string subProtocol,
int receiveBufferSize,
TimeSpan keepAliveInterval,
ArraySegment<byte> internalBuffer)
{ {
HttpListenerWebSocketContext webSocketContext = null; if (string.IsNullOrWhiteSpace(key))
/*if (Logging.On)
{ {
Logging.Enter(Logging.WebSockets, context, "AcceptWebSocketAsync", ""); return false;
}* /
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);
}
} }
catch (Exception ex) // TODO:
{ // throw new NotImplementedException();
if (Logging.On) return true;
{
Logging.Exception(Logging.WebSockets, context, "AcceptWebSocketAsync", ex);
}
throw;
}
finally
{
if (Logging.On)
{
Logging.Exit(Logging.WebSockets, context, "AcceptWebSocketAsync", "");
}
}
return webSocketContext;
} }
*/
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed",
Justification = "SHA1 used only for hashing purposes, not for crypto.")] Justification = "SHA1 used only for hashing purposes, not for crypto.")]
internal static string GetSecWebSocketAcceptString(string secWebSocketKey) public static string GetSecWebSocketAcceptString(string secWebSocketKey)
{ {
string retVal; string retVal;
#if NET45
// SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat.
using (SHA1 sha1 = SHA1.Create()) using (SHA1 sha1 = SHA1.Create())
{ {
@ -202,10 +93,14 @@ namespace Microsoft.AspNet.WebSockets
byte[] toHash = Encoding.UTF8.GetBytes(acceptString); byte[] toHash = Encoding.UTF8.GetBytes(acceptString);
retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
} }
#endif
return retVal; 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) internal static string GetTraceMsgForParameters(int offset, int count, CancellationToken cancellationToken)
{ {
return string.Format(CultureInfo.InvariantCulture, 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. // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server.
internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, public static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, string subProtocol)
string subProtocol,
out string acceptProtocol)
{ {
acceptProtocol = string.Empty;
if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) if (string.IsNullOrEmpty(clientSecWebSocketProtocol))
{ {
// client hasn't specified any Sec-WebSocket-Protocol header // 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. // 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. // 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 // 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[] { ',' }, string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries); StringSplitOptions.RemoveEmptyEntries);
acceptProtocol = subProtocol;
// client specified protocols, serverOptions has exactly 1 non-empty entry. Check that // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that
// this exists in the list the client specified. // this exists in the list the client specified.
for (int i = 0; i < requestProtocols.Length; i++) for (int i = 0; i < requestProtocols.Length; i++)
{ {
string currentRequestProtocol = requestProtocols[i].Trim(); string currentRequestProtocol = requestProtocols[i].Trim();
if (string.Compare(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0) if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0)
{ {
return true; return true;
} }
@ -281,7 +172,34 @@ namespace Microsoft.AspNet.WebSockets
// under the caller's synchronization context. // under the caller's synchronization context.
return task.ConfigureAwait(false); 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) internal static void ValidateBuffer(byte[] buffer, int offset, int count)
{ {
if (buffer == null) if (buffer == null)
@ -299,57 +217,6 @@ namespace Microsoft.AspNet.WebSockets
throw new ArgumentOutOfRangeException("count"); 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) 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 receiveBufferSize,
int sendBufferSize, int sendBufferSize,
TimeSpan keepAliveInterval) TimeSpan keepAliveInterval)
@ -511,7 +378,7 @@ namespace Microsoft.AspNet.WebSockets
throw new PlatformNotSupportedException(SR.GetString(SR.net_WebSockets_UnsupportedPlatform)); 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"); 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"); 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"
}
}
}
}