Browse Source

Add new mono-compatible websocket provider.

pull/909/head
Jarl Gullberg 7 years ago
parent
commit
ed32790cf5
No known key found for this signature in database GPG Key ID: FBB69BD7CAE095A0
4 changed files with 263 additions and 1 deletions
  1. +16
    -1
      Discord.Net.sln
  2. +15
    -0
      src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj
  3. +223
    -0
      src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs
  4. +9
    -0
      src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs

+ 16
- 1
Discord.Net.sln View File

@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.12 VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Providers.WebSocketSharp", "src\Discord.Net.Providers.WebSocketSharp\Discord.Net.Providers.WebSocketSharp.csproj", "{0748E83F-87A1-4C07-B458-6E438841925B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -130,6 +132,18 @@ Global
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.ActiveCfg = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.Build.0 = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.ActiveCfg = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.Build.0 = Debug|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.Build.0 = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.ActiveCfg = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.Build.0 = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.ActiveCfg = Release|Any CPU
{0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -141,6 +155,7 @@ Global
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} {688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
{0748E83F-87A1-4C07-B458-6E438841925B} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}


+ 15
- 0
src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<PropertyGroup>
<AssemblyName>Discord.Net.Providers.WebSocketSharp</AssemblyName>
<RootNamespace>Discord.Providers.WebSocketSharp</RootNamespace>
<Description>An optional WebSocket client provider for Discord.Net using websocket-sharp</Description>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="websocket-sharp-customheaders" Version="1.0.2.31869" />
</ItemGroup>
</Project>

+ 223
- 0
src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs View File

@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Discord.Net;
using Discord.Net.WebSockets;
using WebSocketSharp;

namespace Discord.Providers.WebSocketSharp
{
/// <summary>
/// WebSocket provider using websocket-sharp.
/// </summary>
internal class WebSocketSharpClient : IWebSocketClient, IDisposable
{
/// <inheritdoc />
public event Func<byte[], int, int, Task> BinaryMessage;

/// <inheritdoc />
public event Func<string, Task> TextMessage;

/// <inheritdoc />
public event Func<Exception, Task> Closed;

private readonly SemaphoreSlim _lock;
private readonly Dictionary<string, string> _headers;
private readonly ManualResetEventSlim _waitUntilConnect;

private WebSocket _client;
private CancellationTokenSource _cancelTokenSource;
private CancellationToken _cancelToken;
private CancellationToken _parentToken;

private bool _isDisposed;

/// <summary>
/// Initializes a new instance of the <see cref="WebSocketSharpProvider"/> class.
/// </summary>
public WebSocketSharpClient()
{
_headers = new Dictionary<string, string>();
_lock = new SemaphoreSlim(1, 1);
_cancelTokenSource = new CancellationTokenSource();
_cancelToken = CancellationToken.None;
_parentToken = CancellationToken.None;
_waitUntilConnect = new ManualResetEventSlim();
}

/// <inheritdoc />
public void SetHeader(string key, string value)
{
_headers[key] = value;
}

/// <inheritdoc />
public void SetCancelToken(CancellationToken cancelToken)
{
_parentToken = cancelToken;
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource
(
_parentToken,
_cancelTokenSource.Token
)
.Token;
}

/// <inheritdoc />
public async Task ConnectAsync(string host)
{
await _lock.WaitAsync().ConfigureAwait(false);
try
{
await ConnectInternalAsync(host).ConfigureAwait(false);
}
finally
{
_lock.Release();
}
}

private async Task ConnectInternalAsync(string host)
{
await DisconnectInternalAsync().ConfigureAwait(false);

_cancelTokenSource = new CancellationTokenSource();
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource
(
_parentToken,
_cancelTokenSource.Token
)
.Token;

_client = new WebSocket(host)
{
CustomHeaders = _headers.ToList()
};
_client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;

_client.OnMessage += OnMessage;
_client.OnOpen += OnConnected;
_client.OnClose += OnClosed;

_client.Connect();
_waitUntilConnect.Wait(_cancelToken);
}

/// <inheritdoc />
public async Task DisconnectAsync()
{
await _lock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
_lock.Release();
}
}

private Task DisconnectInternalAsync()
{
_cancelTokenSource.Cancel();
if (_client is null)
{
return Task.CompletedTask;
}

if (_client.ReadyState == WebSocketState.Open)
{
_client.Close();
}

_client.OnMessage -= OnMessage;
_client.OnOpen -= OnConnected;
_client.OnClose -= OnClosed;

_client = null;
_waitUntilConnect.Reset();

return Task.CompletedTask;
}

private void OnMessage(object sender, MessageEventArgs messageEventArgs)
{
if (messageEventArgs.IsBinary)
{
OnBinaryMessage(messageEventArgs);
}
else if (messageEventArgs.IsText)
{
OnTextMessage(messageEventArgs);
}
}

/// <inheritdoc />
public async Task SendAsync(byte[] data, int index, int count, bool isText)
{
await _lock.WaitAsync(_cancelToken).ConfigureAwait(false);
try
{
if (isText)
{
_client.Send(Encoding.UTF8.GetString(data, index, count));
}
else
{
_client.Send(data.Skip(index).Take(count).ToArray());
}
}
finally
{
_lock.Release();
}
}

private void OnTextMessage(MessageEventArgs e)
{
TextMessage?.Invoke(e.Data).GetAwaiter().GetResult();
}

private void OnBinaryMessage(MessageEventArgs e)
{
BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult();
}

private void OnConnected(object sender, EventArgs e)
{
_waitUntilConnect.Set();
}

private void OnClosed(object sender, CloseEventArgs e)
{
if (e.WasClean)
{
Closed?.Invoke(null).GetAwaiter().GetResult();
return;
}

var ex = new WebSocketClosedException(e.Code, e.Reason);
Closed?.Invoke(ex).GetAwaiter().GetResult();
}

/// <inheritdoc />
public void Dispose()
{
if (_isDisposed)
{
return;
}

DisconnectInternalAsync().GetAwaiter().GetResult();

((IDisposable)_client)?.Dispose();
_client = null;

_isDisposed = true;
}
}
}

+ 9
- 0
src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs View File

@@ -0,0 +1,9 @@
using Discord.Net.WebSockets;

namespace Discord.Providers.WebSocketSharp
{
public static class WebSocketSharpProvider
{
public static readonly WebSocketProvider Instance = () => new WebSocketSharpClient();
}
}

Loading…
Cancel
Save