#96 Enable Owin->AspNet WebSockets.
This commit is contained in:
parent
1ece87ef68
commit
31edabdfcb
|
|
@ -26,6 +26,9 @@
|
|||
<Compile Include="OwinExtensions.cs" />
|
||||
<Compile Include="OwinFeatureCollection.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
<Compile Include="WebSockets\OwinWebSocketAcceptContext.cs" />
|
||||
<Compile Include="WebSockets\OwinWebSocketAdapter.cs" />
|
||||
<Compile Include="WebSockets\OwinWebSocketAcceptAdapter.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -134,6 +134,7 @@ namespace Microsoft.AspNet.Owin
|
|||
// 3.2. Per Request
|
||||
|
||||
public const string Accept = "websocket.Accept";
|
||||
public const string AcceptAlt = "websocket.AcceptAlt"; // Non-spec
|
||||
|
||||
// 4. Accept
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,16 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FeatureModel;
|
||||
using Microsoft.AspNet.HttpFeature;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.FeatureModel;
|
||||
|
||||
namespace Microsoft.AspNet.Owin
|
||||
{
|
||||
|
|
@ -30,6 +31,7 @@ namespace Microsoft.AspNet.Owin
|
|||
IHttpClientCertificateFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpAuthenticationFeature,
|
||||
IHttpWebSocketFeature,
|
||||
IOwinEnvironmentFeature
|
||||
{
|
||||
public IDictionary<string, object> Environment { get; set; }
|
||||
|
|
@ -236,6 +238,32 @@ namespace Microsoft.AspNet.Owin
|
|||
|
||||
IAuthenticationHandler IHttpAuthenticationFeature.Handler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the underlying server supports WebSockets. This is disabled by default.
|
||||
/// The value should be consistant across requests.
|
||||
/// </summary>
|
||||
public bool SupportsWebSockets { get; set; }
|
||||
|
||||
bool IHttpWebSocketFeature.IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
object obj;
|
||||
return Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj);
|
||||
}
|
||||
}
|
||||
|
||||
Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(IWebSocketAcceptContext context)
|
||||
{
|
||||
object obj;
|
||||
if (!Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj))
|
||||
{
|
||||
throw new NotSupportedException("WebSockets are not supported"); // TODO: LOC
|
||||
}
|
||||
var accept = (Func<IWebSocketAcceptContext, Task<WebSocket>>)obj;
|
||||
return accept(context);
|
||||
}
|
||||
|
||||
public int Revision
|
||||
{
|
||||
get { return 0; } // Not modifiable
|
||||
|
|
@ -260,6 +288,10 @@ namespace Microsoft.AspNet.Owin
|
|||
{
|
||||
return SupportsClientCerts;
|
||||
}
|
||||
else if (key == typeof(IHttpWebSocketFeature))
|
||||
{
|
||||
return SupportsWebSockets;
|
||||
}
|
||||
|
||||
// The rest of the features are always supported.
|
||||
return true;
|
||||
|
|
@ -288,6 +320,10 @@ namespace Microsoft.AspNet.Owin
|
|||
{
|
||||
keys.Add(typeof(IHttpClientCertificateFeature));
|
||||
}
|
||||
if (SupportsWebSockets)
|
||||
{
|
||||
keys.Add(typeof(IHttpWebSocketFeature));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.HttpFeature;
|
||||
|
||||
namespace Microsoft.AspNet.Owin
|
||||
{
|
||||
using AppFunc = Func<IDictionary<string, object>, Task>;
|
||||
using WebSocketAccept =
|
||||
Action
|
||||
<
|
||||
IDictionary<string, object>, // WebSocket Accept parameters
|
||||
Func // WebSocketFunc callback
|
||||
<
|
||||
IDictionary<string, object>, // WebSocket environment
|
||||
Task // Complete
|
||||
>
|
||||
>;
|
||||
using WebSocketAcceptAlt =
|
||||
Func
|
||||
<
|
||||
IWebSocketAcceptContext, // WebSocket Accept parameters
|
||||
Task<WebSocket>
|
||||
>;
|
||||
|
||||
public class OwinWebSocketAcceptAdapter
|
||||
{
|
||||
private WebSocketAccept _owinWebSocketAccept;
|
||||
private TaskCompletionSource<int> _requestTcs = new TaskCompletionSource<int>();
|
||||
private TaskCompletionSource<WebSocket> _acceptTcs = new TaskCompletionSource<WebSocket>();
|
||||
private TaskCompletionSource<int> _upstreamWentAsync = new TaskCompletionSource<int>();
|
||||
private string _subProtocol = null;
|
||||
|
||||
private OwinWebSocketAcceptAdapter(WebSocketAccept owinWebSocketAccept)
|
||||
{
|
||||
_owinWebSocketAccept = owinWebSocketAccept;
|
||||
}
|
||||
|
||||
private Task RequestTask { get { return _requestTcs.Task; } }
|
||||
private Task UpstreamTask { get; set; }
|
||||
private TaskCompletionSource<int> UpstreamWentAsyncTcs { get { return _upstreamWentAsync; } }
|
||||
|
||||
private async Task<WebSocket> AcceptWebSocketAsync(IWebSocketAcceptContext context)
|
||||
{
|
||||
IDictionary<string, object> options = null;
|
||||
if (context is OwinWebSocketAcceptContext)
|
||||
{
|
||||
var acceptContext = context as OwinWebSocketAcceptContext;
|
||||
options = acceptContext.Options;
|
||||
_subProtocol = acceptContext.SubProtocol;
|
||||
}
|
||||
else if (context != null && context.SubProtocol != null)
|
||||
{
|
||||
options = new Dictionary<string, object>(1)
|
||||
{
|
||||
{ OwinConstants.WebSocket.SubProtocol, context.SubProtocol }
|
||||
};
|
||||
_subProtocol = context.SubProtocol;
|
||||
}
|
||||
|
||||
// Accept may have been called synchronously on the original request thread, we might not have a task yet. Go async.
|
||||
await _upstreamWentAsync.Task;
|
||||
|
||||
_owinWebSocketAccept(options, OwinAcceptCallback);
|
||||
_requestTcs.TrySetResult(0); // Let the pipeline unwind.
|
||||
|
||||
return await _acceptTcs.Task;
|
||||
}
|
||||
|
||||
private Task OwinAcceptCallback(IDictionary<string, object> webSocketContext)
|
||||
{
|
||||
_acceptTcs.TrySetResult(new OwinWebSocketAdapter(webSocketContext, _subProtocol));
|
||||
return UpstreamTask;
|
||||
}
|
||||
|
||||
// Make sure declined websocket requests complete. This is a no-op for accepted websocket requests.
|
||||
private void EnsureCompleted(Task task)
|
||||
{
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
_requestTcs.TrySetCanceled();
|
||||
}
|
||||
else if (task.IsFaulted)
|
||||
{
|
||||
_requestTcs.TrySetException(task.Exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
_requestTcs.TrySetResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static AppFunc AdaptWebSockets(AppFunc next)
|
||||
{
|
||||
return environment =>
|
||||
{
|
||||
object accept;
|
||||
if (environment.TryGetValue(OwinConstants.WebSocket.Accept, out accept) && accept is WebSocketAccept)
|
||||
{
|
||||
var adapter = new OwinWebSocketAcceptAdapter((WebSocketAccept)accept);
|
||||
|
||||
environment[OwinConstants.WebSocket.AcceptAlt] = new WebSocketAcceptAlt(adapter.AcceptWebSocketAsync);
|
||||
|
||||
try
|
||||
{
|
||||
adapter.UpstreamTask = next(environment);
|
||||
adapter.UpstreamWentAsyncTcs.TrySetResult(0);
|
||||
adapter.UpstreamTask.ContinueWith(adapter.EnsureCompleted, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
adapter.UpstreamWentAsyncTcs.TrySetException(ex);
|
||||
throw;
|
||||
}
|
||||
|
||||
return adapter.RequestTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
return next(environment);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.HttpFeature;
|
||||
|
||||
namespace Microsoft.AspNet.Owin
|
||||
{
|
||||
public class OwinWebSocketAcceptContext : IWebSocketAcceptContext
|
||||
{
|
||||
private IDictionary<string, object> _options = new Dictionary<string, object>(1);
|
||||
|
||||
public OwinWebSocketAcceptContext()
|
||||
{
|
||||
}
|
||||
|
||||
public string SubProtocol
|
||||
{
|
||||
get
|
||||
{
|
||||
object obj;
|
||||
if (_options.TryGetValue(OwinConstants.WebSocket.SubProtocol, out obj))
|
||||
{
|
||||
return (string)obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
_options[OwinConstants.WebSocket.SubProtocol] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Options
|
||||
{
|
||||
get { return _options; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
namespace Microsoft.AspNet.Owin
|
||||
{
|
||||
// http://owin.org/extensions/owin-WebSocket-Extension-v0.4.0.htm
|
||||
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 WebSocketSendAsync =
|
||||
Func<ArraySegment<byte> /* data */,
|
||||
int /* messageType */,
|
||||
bool /* endOfMessage */,
|
||||
CancellationToken /* cancel */,
|
||||
Task>;
|
||||
using RawWebSocketReceiveResult = Tuple<int, // type
|
||||
bool, // end of message?
|
||||
int>; // count
|
||||
|
||||
public class OwinWebSocketAdapter : WebSocket
|
||||
{
|
||||
private IDictionary<string, object> _websocketContext;
|
||||
private WebSocketSendAsync _sendAsync;
|
||||
private WebSocketReceiveAsync _receiveAsync;
|
||||
private WebSocketCloseAsync _closeAsync;
|
||||
private WebSocketState _state;
|
||||
private string _subProtocol;
|
||||
|
||||
public OwinWebSocketAdapter(IDictionary<string, object> websocketContext, string subProtocol)
|
||||
{
|
||||
_websocketContext = websocketContext;
|
||||
_sendAsync = (WebSocketSendAsync)websocketContext[OwinConstants.WebSocket.SendAsync];
|
||||
_receiveAsync = (WebSocketReceiveAsync)websocketContext[OwinConstants.WebSocket.ReceiveAsync];
|
||||
_closeAsync = (WebSocketCloseAsync)websocketContext[OwinConstants.WebSocket.CloseAsync];
|
||||
_state = WebSocketState.Open;
|
||||
_subProtocol = subProtocol;
|
||||
}
|
||||
|
||||
public override WebSocketCloseStatus? CloseStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
object obj;
|
||||
if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseStatus, out obj))
|
||||
{
|
||||
return (WebSocketCloseStatus)obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string CloseStatusDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
object obj;
|
||||
if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseDescription, out obj))
|
||||
{
|
||||
return (string)obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string SubProtocol
|
||||
{
|
||||
get
|
||||
{
|
||||
return _subProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
public override WebSocketState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
var rawResult = await _receiveAsync(buffer, cancellationToken);
|
||||
var messageType = OpCodeToEnum(rawResult.Item1);
|
||||
if (messageType == WebSocketMessageType.Close)
|
||||
{
|
||||
if (State == WebSocketState.Open)
|
||||
{
|
||||
_state = WebSocketState.CloseReceived;
|
||||
}
|
||||
else if (State == WebSocketState.CloseSent)
|
||||
{
|
||||
_state = WebSocketState.Closed;
|
||||
}
|
||||
return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2, CloseStatus, CloseStatusDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
return _sendAsync(buffer, EnumToOpCode(messageType), endOfMessage, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
|
||||
{
|
||||
await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
while (State == WebSocketState.CloseSent)
|
||||
{
|
||||
// Drain until close received
|
||||
await ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Validate state
|
||||
if (State == WebSocketState.Open)
|
||||
{
|
||||
_state = WebSocketState.CloseSent;
|
||||
}
|
||||
else if (State == WebSocketState.CloseReceived)
|
||||
{
|
||||
_state = WebSocketState.Closed;
|
||||
}
|
||||
return _closeAsync((int)closeStatus, statusDescription, cancellationToken);
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
{
|
||||
_state = WebSocketState.Aborted;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_state = WebSocketState.Closed;
|
||||
}
|
||||
|
||||
private static WebSocketMessageType OpCodeToEnum(int messageType)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case 0x1:
|
||||
return WebSocketMessageType.Text;
|
||||
case 0x2:
|
||||
return WebSocketMessageType.Binary;
|
||||
case 0x8:
|
||||
return WebSocketMessageType.Close;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("messageType", messageType, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static int EnumToOpCode(WebSocketMessageType webSocketMessageType)
|
||||
{
|
||||
switch (webSocketMessageType)
|
||||
{
|
||||
case WebSocketMessageType.Text:
|
||||
return 0x1;
|
||||
case WebSocketMessageType.Binary:
|
||||
return 0x2;
|
||||
case WebSocketMessageType.Close:
|
||||
return 0x8;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("webSocketMessageType", webSocketMessageType, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue