Expose Opaque and WebSockets.
This commit is contained in:
parent
1ef31a943a
commit
d1dab1665e
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.21708.0
|
||||
VisualStudioVersion = 14.0.21730.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "samples\TestClient\TestClient.csproj", "{8B828433-B333-4C19-96AE-00BFFF9D8841}"
|
||||
EndProject
|
||||
|
|
@ -19,7 +19,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SelfHostServer", "samples\S
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.Windows", "src\Microsoft.AspNet.Security.Windows\Microsoft.AspNet.Security.Windows.kproj", "{EFC7538F-7AEB-4A3E-A1E6-6BDCCBD272BF}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.WebSockets", "src\Microsoft.AspNet.WebSockets\Microsoft.AspNet.WebSockets.kproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}"
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.WebSockets", "src\Microsoft.Net.WebSockets\Microsoft.Net.WebSockets.kproj", "{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.WebListener.FunctionalTests", "test\Microsoft.AspNet.Server.WebListener.FunctionalTests\Microsoft.AspNet.Server.WebListener.FunctionalTests.kproj", "{4492FF4C-9032-411D-853F-46B01755E504}"
|
||||
EndProject
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
// permissions and limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.Net.Server;
|
||||
|
||||
namespace HelloWorld
|
||||
|
|
@ -73,11 +75,23 @@ namespace HelloWorld
|
|||
// Response
|
||||
byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now);
|
||||
|
||||
context.Response.ContentLength = bytes.Length;
|
||||
context.Response.ContentType = "text/plain";
|
||||
if (context.IsWebSocketRequest)
|
||||
{
|
||||
Console.WriteLine("WebSocket");
|
||||
WebSocket webSocket = context.AcceptWebSocketAsync().Result;
|
||||
webSocket.SendAsync(new ArraySegment<byte>(bytes, 0, bytes.Length), WebSocketMessageType.Text, true, CancellationToken.None).Wait();
|
||||
webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Goodbye", CancellationToken.None).Wait();
|
||||
webSocket.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Hello World");
|
||||
context.Response.ContentLength = bytes.Length;
|
||||
context.Response.ContentType = "text/plain";
|
||||
|
||||
context.Response.Body.Write(bytes, 0, bytes.Length);
|
||||
context.Dispose();
|
||||
context.Response.Body.Write(bytes, 0, bytes.Length);
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
// See the Apache 2 License for the specific language governing
|
||||
// permissions and limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNet;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
@ -32,8 +36,20 @@ namespace SelfHostServer
|
|||
|
||||
app.Run(async context =>
|
||||
{
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello world");
|
||||
if (context.IsWebSocketRequest)
|
||||
{
|
||||
Console.WriteLine("WebSocket");
|
||||
byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now);
|
||||
WebSocket webSocket = await context.AcceptWebSocketAsync();
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(bytes, 0, bytes.Length), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Goodbye", CancellationToken.None);
|
||||
webSocket.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello world");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"Microsoft.AspNet.Server.WebListener": "",
|
||||
"Microsoft.Net.Server": ""
|
||||
},
|
||||
"commands": { "web": "Microsoft.AspNet.Hosting server.name=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001" },
|
||||
"commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:8080" },
|
||||
"configurations": {
|
||||
"net45": {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -28,7 +29,15 @@ using Microsoft.Net.Server;
|
|||
|
||||
namespace Microsoft.AspNet.Server.WebListener
|
||||
{
|
||||
internal class FeatureContext : IHttpRequestFeature, IHttpConnectionFeature, IHttpResponseFeature, IHttpSendFileFeature, IHttpTransportLayerSecurityFeature, IHttpRequestLifetimeFeature
|
||||
internal class FeatureContext :
|
||||
IHttpRequestFeature,
|
||||
IHttpConnectionFeature,
|
||||
IHttpResponseFeature,
|
||||
IHttpSendFileFeature,
|
||||
IHttpTransportLayerSecurityFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpWebSocketFeature,
|
||||
IHttpOpaqueUpgradeFeature
|
||||
{
|
||||
private RequestContext _requestContext;
|
||||
private FeatureCollection _features;
|
||||
|
|
@ -85,10 +94,13 @@ namespace Microsoft.AspNet.Server.WebListener
|
|||
_features.Add(typeof(IHttpSendFileFeature), this);
|
||||
_features.Add(typeof(IHttpRequestLifetimeFeature), this);
|
||||
|
||||
// TODO: If Win8+
|
||||
_features.Add(typeof(IHttpOpaqueUpgradeFeature), this);
|
||||
_features.Add(typeof(IHttpWebSocketFeature), this);
|
||||
|
||||
// TODO:
|
||||
// _environment.CallCancelled = _cts.Token;
|
||||
// _environment.User = _request.User;
|
||||
// Opaque/WebSockets
|
||||
// Channel binding
|
||||
|
||||
/*
|
||||
|
|
@ -348,11 +360,16 @@ namespace Microsoft.AspNet.Server.WebListener
|
|||
set { Response.StatusCode = value; }
|
||||
}
|
||||
#endregion
|
||||
#region IHttpSendFileFeature
|
||||
|
||||
Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
|
||||
{
|
||||
return Response.SendFileAsync(path, offset, length, cancellation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region IHttpRequestLifetimeFeature
|
||||
|
||||
public CancellationToken OnRequestAborted
|
||||
{
|
||||
get { return _requestContext.DisconnectToken; }
|
||||
|
|
@ -362,5 +379,46 @@ namespace Microsoft.AspNet.Server.WebListener
|
|||
{
|
||||
_requestContext.Abort();
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region IHttpOpaqueUpgradeFeature
|
||||
|
||||
public bool IsUpgradableRequest
|
||||
{
|
||||
get { return _requestContext.IsUpgradableRequest; }
|
||||
}
|
||||
|
||||
public Task<Stream> UpgradeAsync()
|
||||
{
|
||||
if (!IsUpgradableRequest)
|
||||
{
|
||||
throw new InvalidOperationException("This request cannot be upgraded.");
|
||||
}
|
||||
return _requestContext.UpgradeAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region IHttpWebSocketFeature
|
||||
|
||||
public bool IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
return _requestContext.IsWebSocketRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptAsync(IWebSocketAcceptContext context)
|
||||
{
|
||||
// TODO: Advanced params
|
||||
string subProtocol = null;
|
||||
if (context != null)
|
||||
{
|
||||
subProtocol = context.SubProtocol;
|
||||
}
|
||||
return _requestContext.AcceptWebSocketAsync(subProtocol);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ namespace Microsoft.AspNet.Server.WebListener
|
|||
Interlocked.Increment(ref _outstandingRequests);
|
||||
FeatureContext featureContext = new FeatureContext(requestContext);
|
||||
await _appFunc(featureContext.Features).SupressContext();
|
||||
// TODO: WebSocket/Opaque upgrade - await requestContext.ProcessResponseAsync().SupressContext();
|
||||
requestContext.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"net45": {},
|
||||
"k10": {
|
||||
"dependencies": {
|
||||
"Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*",
|
||||
"Microsoft.Win32.Primitives": "4.0.0.0",
|
||||
"System.Collections": "4.0.0.0",
|
||||
"System.Collections.Concurrent": "4.0.0.0",
|
||||
|
|
@ -25,12 +26,14 @@
|
|||
"System.Globalization": "4.0.10.0",
|
||||
"System.IO": "4.0.0.0",
|
||||
"System.Linq": "4.0.0.0",
|
||||
"System.Net.Primitives": "4.0.10.0",
|
||||
"System.Reflection": "4.0.10.0",
|
||||
"System.Resources.ResourceManager": "4.0.0.0",
|
||||
"System.Runtime": "4.0.20.0",
|
||||
"System.Runtime.Extensions": "4.0.10.0",
|
||||
"System.Runtime.Handles": "4.0.0.0",
|
||||
"System.Runtime.InteropServices": "4.0.20.0",
|
||||
"System.Security.Cryptography.X509Certificates": "4.0.0.0",
|
||||
"System.Security.Principal": "4.0.0.0",
|
||||
"System.Text.Encoding": "4.0.20.0",
|
||||
"System.Text.Encoding.Extensions": "4.0.10.0",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -441,6 +441,17 @@ namespace Microsoft.Net.Server
|
|||
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);
|
||||
}
|
||||
|
||||
// TODO: We need an easier to user header collection that has this built in
|
||||
internal string GetHeader(string headerName)
|
||||
{
|
||||
string[] values;
|
||||
if (Headers.TryGetValue(headerName, out values))
|
||||
{
|
||||
return string.Join(", ", values);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Populates the client certificate. The result may be null if there is no client cert.
|
||||
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
||||
// enable this, but it's unclear what Http.Sys would do.
|
||||
|
|
|
|||
|
|
@ -24,23 +24,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Net.WebSockets;
|
||||
|
||||
namespace Microsoft.Net.Server
|
||||
{
|
||||
using OpaqueFunc = Func<IDictionary<string, object>, Task>;
|
||||
|
||||
public sealed class RequestContext : IDisposable
|
||||
{
|
||||
private WebListener _server;
|
||||
private Request _request;
|
||||
private Response _response;
|
||||
private NativeRequestContext _memoryBlob;
|
||||
private OpaqueFunc _opaqueCallback;
|
||||
private bool _disposed;
|
||||
private CancellationTokenSource _requestAbortSource;
|
||||
private CancellationToken? _disconnectToken;
|
||||
|
|
@ -128,17 +129,230 @@ namespace Microsoft.Net.Server
|
|||
return Request.RequestId;
|
||||
}
|
||||
}
|
||||
/*
|
||||
public bool TryGetOpaqueUpgrade(ref Action<IDictionary<string, object>, OpaqueFunc> value)
|
||||
|
||||
public bool IsUpgradableRequest
|
||||
{
|
||||
if (_request.IsUpgradable)
|
||||
{
|
||||
value = OpaqueUpgrade;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
get { return _request.IsUpgradable; }
|
||||
}
|
||||
|
||||
public Task<Stream> UpgradeAsync()
|
||||
{
|
||||
if (!IsUpgradableRequest || _response.SentHeaders)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Set the status code and reason phrase
|
||||
Response.StatusCode = (int)HttpStatusCode.SwitchingProtocols;
|
||||
Response.ReasonPhrase = HttpReasonPhrase.Get(HttpStatusCode.SwitchingProtocols);
|
||||
|
||||
Response.SendOpaqueUpgrade(); // TODO: Async
|
||||
Request.SwitchToOpaqueMode();
|
||||
Response.SwitchToOpaqueMode();
|
||||
Stream opaqueStream = new OpaqueStream(Request.Body, Response.Body);
|
||||
return Task.FromResult(opaqueStream);
|
||||
}
|
||||
|
||||
public bool IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!WebSocketHelpers.AreWebSocketsSupported)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsUpgradableRequest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals("GET", Request.Method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
|
||||
string connection = Request.GetHeader(HttpKnownHeaderNames.Connection);
|
||||
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade: websocket
|
||||
string upgrade = Request.GetHeader(HttpKnownHeaderNames.Upgrade);
|
||||
if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Version: 13
|
||||
string version = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketVersion);
|
||||
if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Key: {base64string}
|
||||
string key = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketKey);
|
||||
if (!WebSocketHelpers.IsValidWebSocketKey(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare IsWebSocketRequest
|
||||
private void ValidateWebSocketRequest()
|
||||
{
|
||||
if (!WebSocketHelpers.AreWebSocketsSupported)
|
||||
{
|
||||
throw new NotSupportedException("WebSockets are not supported on this platform.");
|
||||
}
|
||||
|
||||
if (!IsUpgradableRequest)
|
||||
{
|
||||
throw new InvalidOperationException("This request is not a valid upgrade request.");
|
||||
}
|
||||
|
||||
if (!string.Equals("GET", Request.Method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("This request is not a valid upgrade request; invalid verb: " + Request.Method);
|
||||
}
|
||||
|
||||
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
|
||||
string connection = Request.GetHeader(HttpKnownHeaderNames.Connection);
|
||||
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
throw new InvalidOperationException("The Connection header is invalid: " + connection);
|
||||
}
|
||||
|
||||
// Upgrade: websocket
|
||||
string upgrade = Request.GetHeader(HttpKnownHeaderNames.Upgrade);
|
||||
if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade);
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Version: 13
|
||||
string version = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketVersion);
|
||||
if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version);
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Key: {base64string}
|
||||
string key = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketKey);
|
||||
if (!WebSocketHelpers.IsValidWebSocketKey(key))
|
||||
{
|
||||
throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptWebSocketAsync()
|
||||
{
|
||||
return AcceptWebSocketAsync(null,
|
||||
WebSocketHelpers.DefaultReceiveBufferSize,
|
||||
WebSocket.DefaultKeepAliveInterval);
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptWebSocketAsync(string subProtocol)
|
||||
{
|
||||
return AcceptWebSocketAsync(subProtocol,
|
||||
WebSocketHelpers.DefaultReceiveBufferSize,
|
||||
WebSocket.DefaultKeepAliveInterval);
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
|
||||
{
|
||||
return AcceptWebSocketAsync(subProtocol,
|
||||
WebSocketHelpers.DefaultReceiveBufferSize,
|
||||
keepAliveInterval);
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptWebSocketAsync(
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval)
|
||||
{
|
||||
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
|
||||
|
||||
ArraySegment<byte> internalBuffer = WebSocketBuffer.CreateInternalBufferArraySegment(receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
|
||||
return this.AcceptWebSocketAsync(subProtocol,
|
||||
receiveBufferSize,
|
||||
keepAliveInterval,
|
||||
internalBuffer);
|
||||
}
|
||||
|
||||
public Task<WebSocket> AcceptWebSocketAsync(
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
ArraySegment<byte> internalBuffer)
|
||||
{
|
||||
if (!IsUpgradableRequest)
|
||||
{
|
||||
throw new InvalidOperationException("This request is cannot be upgraded.");
|
||||
}
|
||||
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
|
||||
WebSocketHelpers.ValidateArraySegment<byte>(internalBuffer, "internalBuffer");
|
||||
WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
|
||||
|
||||
return AcceptWebSocketAsyncCore(subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
|
||||
}
|
||||
|
||||
private async Task<WebSocket> AcceptWebSocketAsyncCore(
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
ArraySegment<byte> internalBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: We need a better header collection API.
|
||||
ValidateWebSocketRequest();
|
||||
|
||||
string subProtocols = string.Empty;
|
||||
string[] values;
|
||||
if (Request.Headers.TryGetValue(HttpKnownHeaderNames.SecWebSocketProtocol, out values))
|
||||
{
|
||||
subProtocols = string.Join(", ", values);
|
||||
}
|
||||
|
||||
bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol);
|
||||
if (shouldSendSecWebSocketProtocolHeader)
|
||||
{
|
||||
Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = new[] { subProtocol };
|
||||
}
|
||||
|
||||
// negotiate the websocket key return value
|
||||
string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey].First();
|
||||
string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
|
||||
Response.Headers.Add(HttpKnownHeaderNames.Connection, new[] { HttpKnownHeaderNames.Upgrade });
|
||||
Response.Headers.Add(HttpKnownHeaderNames.Upgrade, new[] { WebSocketHelpers.WebSocketUpgradeToken });
|
||||
Response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, new[] { secWebSocketAccept });
|
||||
|
||||
Stream opaqueStream = await UpgradeAsync();
|
||||
|
||||
return WebSocketHelpers.CreateServerWebSocket(
|
||||
opaqueStream,
|
||||
subProtocol,
|
||||
receiveBufferSize,
|
||||
keepAliveInterval,
|
||||
internalBuffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.LogException(Logger, "AcceptWebSocketAsync", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public bool TryGetChannelBinding(ref ChannelBinding value)
|
||||
{
|
||||
value = Server.GetChannelBinding(Request.ConnectionId, Request.IsSecureConnection);
|
||||
|
|
@ -221,57 +435,5 @@ namespace Microsoft.Net.Server
|
|||
// RequestQueueHandle may have been closed
|
||||
}
|
||||
}
|
||||
/*
|
||||
internal void OpaqueUpgrade(IDictionary<string, object> parameters, OpaqueFunc callback)
|
||||
{
|
||||
// Parameters are ignored for now
|
||||
if (Response.SentHeaders)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException("callback");
|
||||
}
|
||||
|
||||
// Set the status code and reason phrase
|
||||
Response.StatusCode = (int)HttpStatusCode.SwitchingProtocols;
|
||||
Response.ReasonPhrase = HttpReasonPhrase.Get(HttpStatusCode.SwitchingProtocols);
|
||||
|
||||
// Store the callback and process it after the stack unwind.
|
||||
_opaqueCallback = callback;
|
||||
}
|
||||
|
||||
// Called after the AppFunc completes for any necessary post-processing.
|
||||
internal unsafe Task ProcessResponseAsync()
|
||||
{
|
||||
// If an upgrade was requested, perform it
|
||||
if (!Response.SentHeaders && _opaqueCallback != null
|
||||
&& Response.StatusCode == (int)HttpStatusCode.SwitchingProtocols)
|
||||
{
|
||||
Response.SendOpaqueUpgrade();
|
||||
|
||||
IDictionary<string, object> opaqueEnv = CreateOpaqueEnvironment();
|
||||
return _opaqueCallback(opaqueEnv);
|
||||
}
|
||||
|
||||
return Helpers.CompletedTask();
|
||||
}
|
||||
|
||||
private IDictionary<string, object> CreateOpaqueEnvironment()
|
||||
{
|
||||
IDictionary<string, object> opaqueEnv = new Dictionary<string, object>();
|
||||
|
||||
opaqueEnv[Constants.OpaqueVersionKey] = Constants.OpaqueVersion;
|
||||
// TODO: Separate CT?
|
||||
// opaqueEnv[Constants.OpaqueCallCancelledKey] = Environment.CallCancelled;
|
||||
|
||||
Request.SwitchToOpaqueMode();
|
||||
Response.SwitchToOpaqueMode();
|
||||
opaqueEnv[Constants.OpaqueStreamKey] = new OpaqueStream(Request.Body, Response.Body);
|
||||
|
||||
return opaqueEnv;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"version": "0.1-alpha-*",
|
||||
"dependencies": {
|
||||
"Microsoft.Framework.Logging": "0.1-alpha-*"
|
||||
"Microsoft.Framework.Logging": "0.1-alpha-*",
|
||||
"Microsoft.Net.WebSockets": ""
|
||||
},
|
||||
"compilationOptions": {
|
||||
"allowUnsafe": true
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
"net45": {},
|
||||
"k10": {
|
||||
"dependencies": {
|
||||
"Microsoft.Net.WebSocketAbstractions": "0.1-alpha-*",
|
||||
"Microsoft.Win32.Primitives": "4.0.0.0",
|
||||
"System.Collections": "4.0.0.0",
|
||||
"System.Collections.Concurrent": "4.0.0.0",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
// this class contains known header names
|
||||
internal static class HttpKnownHeaderNames
|
||||
|
|
@ -22,7 +22,6 @@
|
|||
<Content Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="fx\Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
|
||||
<Compile Include="fx\System\AccessViolationException.cs" />
|
||||
<Compile Include="fx\System\ComponentModel\Win32Exception.cs" />
|
||||
|
|
@ -31,29 +30,21 @@
|
|||
<Compile Include="fx\System\SafeNativeMethods.cs" />
|
||||
<Compile Include="fx\System\SystemException.cs" />
|
||||
<Compile Include="HttpKnownHeaderNames.cs" />
|
||||
<Compile Include="Legacy\HttpListenerContext.cs" />
|
||||
<Compile Include="Legacy\HttpListenerRequest.cs" />
|
||||
<Compile Include="Legacy\SR.cs" />
|
||||
<Compile Include="Legacy\WebSocketHttpListenerDuplexStream.cs" />
|
||||
<Compile Include="NativeInterop\SafeLoadLibrary.cs" />
|
||||
<Compile Include="NativeInterop\SafeNativeOverlapped.cs" />
|
||||
<Compile Include="NativeInterop\SafeWebSocketHandle.cs" />
|
||||
<Compile Include="NativeInterop\UnsafeNativeMethods.cs" />
|
||||
<Compile Include="OwinWebSocketWrapper.cs" />
|
||||
<Compile Include="ServerWebSocket.cs" />
|
||||
<Compile Include="WebSocket.cs" />
|
||||
<Compile Include="WebSocketBase.cs" />
|
||||
<Compile Include="WebSocketBuffer.cs" />
|
||||
<Compile Include="WebSocketCloseStatus.cs" />
|
||||
<Compile Include="WebSocketConstants.cs" />
|
||||
<Compile Include="WebSocketError.cs" />
|
||||
<Compile Include="WebSocketException.cs" />
|
||||
<Compile Include="WebSocketExtensions.cs" />
|
||||
<Compile Include="WebSocketHelpers.cs" />
|
||||
<Compile Include="WebSocketMessageType.cs" />
|
||||
<Compile Include="WebSocketMiddleware.cs" />
|
||||
<Compile Include="WebSocketReceiveResult.cs" />
|
||||
<Compile Include="WebSocketState.cs" />
|
||||
<Compile Include="WebSocketReceiveResultExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
||||
</Project>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal sealed class SafeLoadLibrary : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
|
|
@ -25,7 +25,7 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal class SafeNativeOverlapped : SafeHandle
|
||||
{
|
||||
|
|
@ -21,10 +21,9 @@
|
|||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNet.WebSockets;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
// This class is a wrapper for a WSPC (WebSocket protocol component) session. WebSocketCreateClientHandle and WebSocketCreateServerHandle return a PVOID and not a real handle
|
||||
// but we use a SafeHandle because it provides us the guarantee that WebSocketDeleteHandle will always get called.
|
||||
|
|
@ -27,7 +27,7 @@ using System.IO;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal static class UnsafeNativeMethods
|
||||
{
|
||||
|
|
@ -161,7 +161,11 @@ namespace Microsoft.AspNet.WebSockets
|
|||
|
||||
static WebSocketProtocolComponent()
|
||||
{
|
||||
#if NET45
|
||||
DllFileName = Path.Combine(Environment.SystemDirectory, WEBSOCKET);
|
||||
#else
|
||||
DllFileName = Path.Combine(Environment.GetEnvironmentVariable("SYSTEMROOT"), "System32", WEBSOCKET);
|
||||
#endif
|
||||
WebSocketDllHandle = SafeLoadLibrary.LoadLibraryEx(DllFileName);
|
||||
|
||||
if (!WebSocketDllHandle.IsInvalid)
|
||||
|
|
@ -27,7 +27,7 @@ using System.Diagnostics.Contracts;
|
|||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal sealed class ServerWebSocket : WebSocketBase
|
||||
{
|
||||
|
|
@ -29,13 +29,14 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal abstract class WebSocketBase : WebSocket, IDisposable
|
||||
{
|
||||
|
|
@ -233,7 +234,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
{
|
||||
ThrowIfPendingException();
|
||||
ThrowIfDisposed();
|
||||
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent);
|
||||
WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent);
|
||||
|
||||
bool ownsCancellationTokenSource = false;
|
||||
CancellationToken linkedCancellationToken = CancellationToken.None;
|
||||
|
|
@ -338,7 +339,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
{
|
||||
ThrowIfPendingException();
|
||||
ThrowIfDisposed();
|
||||
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
|
||||
WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
|
||||
bool ownsCancellationTokenSource = false;
|
||||
CancellationToken linkedCancellationToken = CancellationToken.None;
|
||||
|
||||
|
|
@ -468,13 +469,13 @@ namespace Microsoft.AspNet.WebSockets
|
|||
bool sessionHandleLockTaken = false;
|
||||
try
|
||||
{
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TakeLocks(ref thisLockTaken, ref sessionHandleLockTaken);
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -548,7 +549,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
try
|
||||
{
|
||||
ThrowIfPendingException();
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -566,12 +567,12 @@ namespace Microsoft.AspNet.WebSockets
|
|||
ThrowIfPendingException();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
|
||||
WebSocketHelpers.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
|
||||
ownsCloseOutputCancellationTokenSource = _closeOutputOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken);
|
||||
if (!ownsCloseOutputCancellationTokenSource)
|
||||
{
|
||||
|
|
@ -676,7 +677,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
// returns TRUE if the caller should also call StartOnCloseCompleted
|
||||
private bool OnCloseOutputCompleted()
|
||||
{
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -707,7 +708,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
{
|
||||
Contract.Assert(thisLockTakenSnapshot, "'thisLockTakenSnapshot' MUST be 'true' at this point.");
|
||||
|
||||
if (IsStateTerminal(_state))
|
||||
if (WebSocketHelpers.IsStateTerminal(_state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -793,7 +794,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
try
|
||||
{
|
||||
ThrowIfPendingException();
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -806,12 +807,12 @@ namespace Microsoft.AspNet.WebSockets
|
|||
try
|
||||
{
|
||||
ThrowIfPendingException();
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ThrowIfDisposed();
|
||||
ThrowOnInvalidState(State,
|
||||
WebSocketHelpers.ThrowOnInvalidState(State,
|
||||
WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent);
|
||||
|
||||
Task closeOutputTask;
|
||||
|
|
@ -893,7 +894,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
}
|
||||
}
|
||||
|
||||
if (IsStateTerminal(State))
|
||||
if (WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -970,7 +971,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
Monitor.Enter(_thisLock, ref lockTaken);
|
||||
}
|
||||
|
||||
if (!IsStateTerminal(State))
|
||||
if (!WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
bool ownsSendCancellationSource = false;
|
||||
try
|
||||
|
|
@ -1055,7 +1056,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
return;
|
||||
}
|
||||
|
||||
if (!IsStateTerminal(State))
|
||||
if (!WebSocketHelpers.IsStateTerminal(State))
|
||||
{
|
||||
Abort();
|
||||
}
|
||||
|
|
@ -1501,13 +1502,13 @@ namespace Microsoft.AspNet.WebSockets
|
|||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (IsStateTerminal(State) || State == WebSocketState.CloseReceived)
|
||||
if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Monitor.Enter(_thisLock, ref thisLockTaken);
|
||||
if (IsStateTerminal(State) || State == WebSocketState.CloseReceived)
|
||||
if (WebSocketHelpers.IsStateTerminal(State) || State == WebSocketState.CloseReceived)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -25,11 +25,12 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
// This class helps to abstract the internal WebSocket buffer, which is used to interact with the native WebSocket
|
||||
// protocol component (WSPC). It helps to shield the details of the layout and the involved pointer arithmetic.
|
||||
|
|
@ -43,10 +44,10 @@ namespace Microsoft.AspNet.WebSockets
|
|||
//
|
||||
// *RBS = ReceiveBufferSize, *SBS = SendBufferSize
|
||||
// *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes)
|
||||
internal class WebSocketBuffer : IDisposable
|
||||
public class WebSocketBuffer : IDisposable
|
||||
{
|
||||
private const int NativeOverheadBufferSize = 144;
|
||||
internal const int MinSendBufferSize = 16;
|
||||
public const int MinSendBufferSize = 16;
|
||||
internal const int MinReceiveBufferSize = 256;
|
||||
internal const int MaxBufferSize = 64 * 1024;
|
||||
#if NET45
|
||||
|
|
@ -362,7 +363,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
ValidateBufferedPayload();
|
||||
|
||||
int bytesTransferred = Math.Min(buffer.Count, _BufferedPayloadReceiveResult.Count);
|
||||
receiveResult = _BufferedPayloadReceiveResult.Copy(bytesTransferred);
|
||||
receiveResult = WebSocketReceiveResultExtensions.DecrementAndClone(ref _BufferedPayloadReceiveResult, bytesTransferred);
|
||||
|
||||
Buffer.BlockCopy(_PayloadBuffer.Array,
|
||||
_PayloadBuffer.Offset + _PayloadOffset,
|
||||
|
|
@ -664,7 +665,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
ReleasePinnedSendBuffer();
|
||||
}
|
||||
|
||||
internal static ArraySegment<byte> CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer)
|
||||
public static ArraySegment<byte> CreateInternalBufferArraySegment(int receiveBufferSize, int sendBufferSize, bool isServerBuffer)
|
||||
{
|
||||
Contract.Assert(receiveBufferSize >= MinReceiveBufferSize,
|
||||
"'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + ".");
|
||||
|
|
@ -675,7 +676,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
return new ArraySegment<byte>(new byte[internalBufferSize]);
|
||||
}
|
||||
|
||||
internal static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer)
|
||||
public static void Validate(int count, int receiveBufferSize, int sendBufferSize, bool isServerBuffer)
|
||||
{
|
||||
Contract.Assert(receiveBufferSize >= MinReceiveBufferSize,
|
||||
"'receiveBufferSize' MUST be at least " + MinReceiveBufferSize.ToString(NumberFormatInfo.InvariantInfo) + ".");
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
public static class WebSocketConstants
|
||||
{
|
||||
public static string SupportedProtocolVersion = "13";
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
public enum WebSocketError
|
||||
{
|
||||
|
|
@ -23,9 +23,10 @@
|
|||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
#if NET45
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
|
||||
|
|
@ -26,21 +26,20 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NET45
|
||||
using System.Security.Cryptography;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.WebSockets
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
internal static class WebSocketHelpers
|
||||
public static class WebSocketHelpers
|
||||
{
|
||||
internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
internal const string WebSocketUpgradeToken = "websocket";
|
||||
internal const int DefaultReceiveBufferSize = 16 * 1024;
|
||||
public const string WebSocketUpgradeToken = "websocket";
|
||||
public const int DefaultReceiveBufferSize = 16 * 1024;
|
||||
internal const int DefaultClientSendBufferSize = 16 * 1024;
|
||||
internal const int MaxControlFramePayloadLength = 123;
|
||||
|
||||
|
|
@ -63,138 +62,30 @@ namespace Microsoft.AspNet.WebSockets
|
|||
internal static readonly ArraySegment<byte> EmptyPayload = new ArraySegment<byte>(new byte[] { }, 0, 0);
|
||||
private static readonly Random KeyGenerator = new Random();
|
||||
|
||||
/*
|
||||
internal static Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(HttpListenerContext context,
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
ArraySegment<byte> internalBuffer)
|
||||
public static bool AreWebSocketsSupported
|
||||
{
|
||||
WebSocketHelpers.ValidateOptions(subProtocol, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, keepAliveInterval);
|
||||
WebSocketHelpers.ValidateArraySegment<byte>(internalBuffer, "internalBuffer");
|
||||
WebSocketBuffer.Validate(internalBuffer.Count, receiveBufferSize, WebSocketBuffer.MinSendBufferSize, true);
|
||||
|
||||
return AcceptWebSocketAsyncCore(context, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
|
||||
get
|
||||
{
|
||||
return UnsafeNativeMethods.WebSocketProtocolComponent.IsSupported;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context,
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
ArraySegment<byte> internalBuffer)
|
||||
|
||||
public static bool IsValidWebSocketKey(string key)
|
||||
{
|
||||
HttpListenerWebSocketContext webSocketContext = null;
|
||||
/*if (Logging.On)
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
Logging.Enter(Logging.WebSockets, context, "AcceptWebSocketAsync", "");
|
||||
}* /
|
||||
|
||||
try
|
||||
{
|
||||
// get property will create a new response if one doesn't exist.
|
||||
HttpListenerResponse response = context.Response;
|
||||
HttpListenerRequest request = context.Request;
|
||||
ValidateWebSocketHeaders(context);
|
||||
|
||||
string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
|
||||
// Optional for non-browser client
|
||||
string origin = request.Headers[HttpKnownHeaderNames.Origin];
|
||||
|
||||
List<string> secWebSocketProtocols = new List<string>();
|
||||
string outgoingSecWebSocketProtocolString;
|
||||
bool shouldSendSecWebSocketProtocolHeader =
|
||||
WebSocketHelpers.ProcessWebSocketProtocolHeader(
|
||||
request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol],
|
||||
subProtocol,
|
||||
out outgoingSecWebSocketProtocolString);
|
||||
|
||||
if (shouldSendSecWebSocketProtocolHeader)
|
||||
{
|
||||
secWebSocketProtocols.Add(outgoingSecWebSocketProtocolString);
|
||||
response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol,
|
||||
outgoingSecWebSocketProtocolString);
|
||||
}
|
||||
|
||||
// negotiate the websocket key return value
|
||||
string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
|
||||
response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
|
||||
response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken);
|
||||
response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept);
|
||||
|
||||
response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101
|
||||
response.ComputeCoreHeaders();
|
||||
ulong hresult = SendWebSocketHeaders(response);
|
||||
if (hresult != 0)
|
||||
{
|
||||
throw new WebSocketException((int)hresult,
|
||||
SR.GetString(SR.net_WebSockets_NativeSendResponseHeaders,
|
||||
WebSocketHelpers.MethodNames.AcceptWebSocketAsync,
|
||||
hresult));
|
||||
}
|
||||
|
||||
await response.OutputStream.FlushAsync().SuppressContextFlow(); // TODO:??? FlushAsync was never implemented
|
||||
|
||||
HttpResponseStream responseStream = response.OutputStream as HttpResponseStream;
|
||||
Contract.Assert(responseStream != null, "'responseStream' MUST be castable to System.Net.HttpResponseStream.");
|
||||
((HttpResponseStream)response.OutputStream).SwitchToOpaqueMode();
|
||||
HttpRequestStream requestStream = new HttpRequestStream(context);
|
||||
requestStream.SwitchToOpaqueMode();
|
||||
WebSocketHttpListenerDuplexStream webSocketStream =
|
||||
new WebSocketHttpListenerDuplexStream(requestStream, responseStream, context);
|
||||
WebSocket webSocket = WebSocket.CreateServerWebSocket(webSocketStream,
|
||||
subProtocol,
|
||||
receiveBufferSize,
|
||||
keepAliveInterval,
|
||||
internalBuffer);
|
||||
|
||||
webSocketContext = new HttpListenerWebSocketContext(
|
||||
request.Url,
|
||||
request.Headers,
|
||||
request.Cookies,
|
||||
context.User,
|
||||
request.IsAuthenticated,
|
||||
request.IsLocal,
|
||||
request.IsSecureConnection,
|
||||
origin,
|
||||
secWebSocketProtocols.AsReadOnly(),
|
||||
secWebSocketVersion,
|
||||
secWebSocketKey,
|
||||
webSocket);
|
||||
|
||||
if (Logging.On)
|
||||
{
|
||||
Logging.Associate(Logging.WebSockets, context, webSocketContext);
|
||||
Logging.Associate(Logging.WebSockets, webSocketContext, webSocket);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Logging.On)
|
||||
{
|
||||
Logging.Exception(Logging.WebSockets, context, "AcceptWebSocketAsync", ex);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Logging.On)
|
||||
{
|
||||
Logging.Exit(Logging.WebSockets, context, "AcceptWebSocketAsync", "");
|
||||
}
|
||||
}
|
||||
return webSocketContext;
|
||||
// TODO:
|
||||
// throw new NotImplementedException();
|
||||
return true;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed",
|
||||
Justification = "SHA1 used only for hashing purposes, not for crypto.")]
|
||||
internal static string GetSecWebSocketAcceptString(string secWebSocketKey)
|
||||
public static string GetSecWebSocketAcceptString(string secWebSocketKey)
|
||||
{
|
||||
string retVal;
|
||||
#if NET45
|
||||
// SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat.
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
|
|
@ -202,10 +93,14 @@ namespace Microsoft.AspNet.WebSockets
|
|||
byte[] toHash = Encoding.UTF8.GetBytes(acceptString);
|
||||
retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
|
||||
}
|
||||
#endif
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static WebSocket CreateServerWebSocket(Stream opaqueStream, string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment<byte> internalBuffer)
|
||||
{
|
||||
return new ServerWebSocket(opaqueStream, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
|
||||
}
|
||||
|
||||
internal static string GetTraceMsgForParameters(int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
|
|
@ -216,11 +111,8 @@ namespace Microsoft.AspNet.WebSockets
|
|||
}
|
||||
|
||||
// return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server.
|
||||
internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol,
|
||||
string subProtocol,
|
||||
out string acceptProtocol)
|
||||
public static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, string subProtocol)
|
||||
{
|
||||
acceptProtocol = string.Empty;
|
||||
if (string.IsNullOrEmpty(clientSecWebSocketProtocol))
|
||||
{
|
||||
// client hasn't specified any Sec-WebSocket-Protocol header
|
||||
|
|
@ -236,10 +128,10 @@ namespace Microsoft.AspNet.WebSockets
|
|||
|
||||
// here, we know the client specified something and it's non-empty.
|
||||
|
||||
if (subProtocol == null)
|
||||
if (string.IsNullOrEmpty(subProtocol))
|
||||
{
|
||||
// client specified some protocols, server specified 'null'. So server should send headers.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// here, we know that the client has specified something, it's not empty
|
||||
|
|
@ -247,14 +139,13 @@ namespace Microsoft.AspNet.WebSockets
|
|||
|
||||
string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' },
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
acceptProtocol = subProtocol;
|
||||
|
||||
// client specified protocols, serverOptions has exactly 1 non-empty entry. Check that
|
||||
// this exists in the list the client specified.
|
||||
for (int i = 0; i < requestProtocols.Length; i++)
|
||||
{
|
||||
string currentRequestProtocol = requestProtocols[i].Trim();
|
||||
if (string.Compare(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -281,7 +172,34 @@ namespace Microsoft.AspNet.WebSockets
|
|||
// under the caller's synchronization context.
|
||||
return task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
internal static bool IsStateTerminal(WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.Closed || state == WebSocketState.Aborted;
|
||||
}
|
||||
|
||||
internal static void ThrowOnInvalidState(WebSocketState state, params WebSocketState[] validStates)
|
||||
{
|
||||
string text = string.Empty;
|
||||
if (validStates != null && validStates.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < validStates.Length; i++)
|
||||
{
|
||||
WebSocketState webSocketState = validStates[i];
|
||||
if (state == webSocketState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
text = string.Join<WebSocketState>(", ", validStates);
|
||||
}
|
||||
throw new WebSocketException(SR.GetString("net_WebSockets_InvalidState", new object[]
|
||||
{
|
||||
state,
|
||||
text
|
||||
}));
|
||||
}
|
||||
|
||||
internal static void ValidateBuffer(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
|
|
@ -299,57 +217,6 @@ namespace Microsoft.AspNet.WebSockets
|
|||
throw new ArgumentOutOfRangeException("count");
|
||||
}
|
||||
}
|
||||
/*
|
||||
private static unsafe ulong SendWebSocketHeaders(HttpListenerResponse response)
|
||||
{
|
||||
return response.SendHeaders(null, null,
|
||||
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_OPAQUE |
|
||||
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
|
||||
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA,
|
||||
true);
|
||||
}
|
||||
private static void ValidateWebSocketHeaders(HttpListenerContext context)
|
||||
{
|
||||
EnsureHttpSysSupportsWebSockets();
|
||||
|
||||
if (!context.Request.IsWebSocketRequest)
|
||||
{
|
||||
throw new WebSocketException(WebSocketError.NotAWebSocket,
|
||||
SR.GetString(SR.net_WebSockets_AcceptNotAWebSocket,
|
||||
WebSocketHelpers.MethodNames.ValidateWebSocketHeaders,
|
||||
HttpKnownHeaderNames.Connection,
|
||||
HttpKnownHeaderNames.Upgrade,
|
||||
WebSocketHelpers.WebSocketUpgradeToken,
|
||||
context.Request.Headers[HttpKnownHeaderNames.Upgrade]));
|
||||
}
|
||||
|
||||
string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
if (string.IsNullOrEmpty(secWebSocketVersion))
|
||||
{
|
||||
throw new WebSocketException(WebSocketError.HeaderError,
|
||||
SR.GetString(SR.net_WebSockets_AcceptHeaderNotFound,
|
||||
WebSocketHelpers.MethodNames.ValidateWebSocketHeaders,
|
||||
HttpKnownHeaderNames.SecWebSocketVersion));
|
||||
}
|
||||
|
||||
if (string.Compare(secWebSocketVersion, WebSocketProtocolComponent.SupportedVersion, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
throw new WebSocketException(WebSocketError.UnsupportedVersion,
|
||||
SR.GetString(SR.net_WebSockets_AcceptUnsupportedWebSocketVersion,
|
||||
WebSocketHelpers.MethodNames.ValidateWebSocketHeaders,
|
||||
secWebSocketVersion,
|
||||
WebSocketProtocolComponent.SupportedVersion));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]))
|
||||
{
|
||||
throw new WebSocketException(WebSocketError.HeaderError,
|
||||
SR.GetString(SR.net_WebSockets_AcceptHeaderNotFound,
|
||||
WebSocketHelpers.MethodNames.ValidateWebSocketHeaders,
|
||||
HttpKnownHeaderNames.SecWebSocketKey));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
internal static void ValidateSubprotocol(string subProtocol)
|
||||
{
|
||||
|
|
@ -425,7 +292,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
}
|
||||
}
|
||||
|
||||
internal static void ValidateOptions(string subProtocol,
|
||||
public static void ValidateOptions(string subProtocol,
|
||||
int receiveBufferSize,
|
||||
int sendBufferSize,
|
||||
TimeSpan keepAliveInterval)
|
||||
|
|
@ -511,7 +378,7 @@ namespace Microsoft.AspNet.WebSockets
|
|||
throw new PlatformNotSupportedException(SR.GetString(SR.net_WebSockets_UnsupportedPlatform));
|
||||
}
|
||||
|
||||
internal static void ValidateArraySegment<T>(ArraySegment<T> arraySegment, string parameterName)
|
||||
public static void ValidateArraySegment<T>(ArraySegment<T> arraySegment, string parameterName)
|
||||
{
|
||||
Contract.Requires(!string.IsNullOrEmpty(parameterName), "'parameterName' MUST NOT be NULL or string.Empty");
|
||||
|
||||
|
|
@ -529,11 +396,5 @@ namespace Microsoft.AspNet.WebSockets
|
|||
throw new ArgumentOutOfRangeException(parameterName + ".Count");
|
||||
}
|
||||
}
|
||||
|
||||
internal static class MethodNames
|
||||
{
|
||||
internal const string AcceptWebSocketAsync = "AcceptWebSocketAsync";
|
||||
internal const string ValidateWebSocketHeaders = "ValidateWebSocketHeaders";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue