@@ -1,4 +1,4 @@ | |||
| |||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio 15 | |||
VisualStudioVersion = 15.0.26014.0 | |||
@@ -25,6 +25,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F66D75C0-E304-46E0-9C3A-294F340DB37D}" | |||
EndProject | |||
Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "Discord.Net.Relay", "src\Discord.Net.Relay\Discord.Net.Relay.csproj", "{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
@@ -143,6 +147,18 @@ Global | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x64.Build.0 = Release|x64 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.ActiveCfg = Release|x86 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.Build.0 = Release|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x64.ActiveCfg = Debug|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x64.Build.0 = Debug|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x86.ActiveCfg = Debug|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x86.Build.0 = Debug|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x64.ActiveCfg = Release|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x64.Build.0 = Release|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x86.ActiveCfg = Release|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x86.Build.0 = Release|x86 | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -154,5 +170,6 @@ Global | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B} = {F66D75C0-E304-46E0-9C3A-294F340DB37D} | |||
EndGlobalSection | |||
EndGlobal |
@@ -1,5 +1,6 @@ | |||
using System.Runtime.CompilerServices; | |||
[assembly: InternalsVisibleTo("Discord.Net.Relay")] | |||
[assembly: InternalsVisibleTo("Discord.Net.Rest")] | |||
[assembly: InternalsVisibleTo("Discord.Net.Rpc")] | |||
[assembly: InternalsVisibleTo("Discord.Net.WebSocket")] | |||
@@ -0,0 +1,20 @@ | |||
using Microsoft.AspNetCore.Builder; | |||
using System; | |||
namespace Discord.Relay | |||
{ | |||
public static class ApplicationBuilderExtensions | |||
{ | |||
public static void UseDiscordRelay(this IApplicationBuilder app, Action<RelayServer> configAction = null) | |||
{ | |||
var server = new RelayServer(configAction); | |||
server.StartAsync(); | |||
app.Use(async (context, next) => | |||
{ | |||
if (context.WebSockets.IsWebSocketRequest) | |||
await server.AcceptAsync(context); | |||
await next(); | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
using System.Runtime.CompilerServices; | |||
[assembly: InternalsVisibleTo("Discord.Net.Tests")] |
@@ -0,0 +1,32 @@ | |||
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VersionPrefix>1.0.0</VersionPrefix> | |||
<VersionSuffix Condition="'$(BuildNumber)' == ''">rc-dev</VersionSuffix> | |||
<VersionSuffix Condition="'$(BuildNumber)' != ''">rc-$(BuildNumber)</VersionSuffix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AssemblyName>Discord.Net.Relay</AssemblyName> | |||
<Authors>RogueException</Authors> | |||
<Description>A core Discord.Net library containing the Relay server.</Description> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<RootNamespace>Discord.Relay</RootNamespace> | |||
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="1.0.0" /> | |||
</ItemGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
</Project> |
@@ -0,0 +1,79 @@ | |||
using Discord.API; | |||
using Discord.API.Gateway; | |||
using Discord.Logging; | |||
using System; | |||
using System.Net.WebSockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using WebSocketClient = System.Net.WebSockets.WebSocket; | |||
namespace Discord.Relay | |||
{ | |||
public class RelayConnection | |||
{ | |||
private readonly RelayServer _server; | |||
private readonly WebSocketClient _socket; | |||
private readonly CancellationTokenSource _cancelToken; | |||
private readonly byte[] _inBuffer, _outBuffer; | |||
private readonly Logger _logger; | |||
internal RelayConnection(RelayServer server, WebSocketClient socket, int id) | |||
{ | |||
_server = server; | |||
_socket = socket; | |||
_cancelToken = new CancellationTokenSource(); | |||
_inBuffer = new byte[4000]; | |||
_outBuffer = new byte[4000]; | |||
_logger = server.LogManager.CreateLogger($"Client #{id}"); | |||
} | |||
internal async Task RunAsync() | |||
{ | |||
await _logger.InfoAsync($"Connected"); | |||
var token = _cancelToken.Token; | |||
try | |||
{ | |||
var segment = new ArraySegment<byte>(_inBuffer); | |||
//Send HELLO | |||
await SendAsync(GatewayOpCode.Hello, new HelloEvent { HeartbeatInterval = 15000 }).ConfigureAwait(false); | |||
while (_socket.State == WebSocketState.Open) | |||
{ | |||
var result = await _socket.ReceiveAsync(segment, token).ConfigureAwait(false); | |||
if (result.MessageType == WebSocketMessageType.Close) | |||
await _logger.WarningAsync($"Received Close {result.CloseStatus} ({result.CloseStatusDescription ?? "No Reason"})").ConfigureAwait(false); | |||
else | |||
await _logger.InfoAsync($"Received {result.Count} bytes"); | |||
} | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
try { await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); } | |||
catch { } | |||
} | |||
catch (Exception ex) | |||
{ | |||
try { await _socket.CloseAsync(WebSocketCloseStatus.InternalServerError, ex.Message, CancellationToken.None).ConfigureAwait(false); } | |||
catch { } | |||
} | |||
finally | |||
{ | |||
await _logger.InfoAsync($"Disconnected"); | |||
} | |||
} | |||
internal void Stop() | |||
{ | |||
_cancelToken.Cancel(); | |||
} | |||
private async Task SendAsync(GatewayOpCode opCode, object payload) | |||
{ | |||
var frame = new SocketFrame { Operation = (int)opCode, Payload = payload }; | |||
var bytes = _server.Serialize(frame, _outBuffer); | |||
var segment = new ArraySegment<byte>(_outBuffer, 0, bytes); | |||
await _socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -0,0 +1,103 @@ | |||
using Discord.API; | |||
using Discord.Logging; | |||
using Discord.Net.Rest; | |||
using Discord.Net.WebSockets; | |||
using Discord.Rest; | |||
using Microsoft.AspNetCore.Http; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using WebSocketClient = System.Net.WebSockets.WebSocket; | |||
namespace Discord.Relay | |||
{ | |||
public class RelayServer | |||
{ | |||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||
private readonly HashSet<RelayConnection> _connections; | |||
private readonly SemaphoreSlim _lock; | |||
private readonly JsonSerializer _serializer; | |||
private readonly DiscordSocketApiClient _discord; | |||
private int _nextId; | |||
internal LogManager LogManager { get; } | |||
internal RelayServer(Action<RelayServer> configAction) | |||
{ | |||
_connections = new HashSet<RelayConnection>(); | |||
_lock = new SemaphoreSlim(1, 1); | |||
_serializer = new JsonSerializer(); | |||
_discord = new DiscordSocketApiClient( | |||
DefaultRestClientProvider.Instance, | |||
DefaultWebSocketProvider.Instance, | |||
DiscordRestConfig.UserAgent); | |||
configAction?.Invoke(this); | |||
LogManager = new LogManager(LogSeverity.Debug); | |||
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
} | |||
internal async Task AcceptAsync(HttpContext context) | |||
{ | |||
WebSocketClient socket; | |||
try | |||
{ | |||
socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); | |||
} | |||
catch { return; } | |||
var _ = Task.Run(async () => | |||
{ | |||
var conn = new RelayConnection(this, socket, Interlocked.Increment(ref _nextId)); | |||
await AddConnection(conn).ConfigureAwait(false); | |||
try | |||
{ | |||
await conn.RunAsync().ConfigureAwait(false); | |||
} | |||
finally { await RemoveConnection(conn).ConfigureAwait(false); } | |||
}); | |||
} | |||
internal void StartAsync() | |||
{ | |||
Task.Run(async () => | |||
{ | |||
await _discord.ConnectAsync().ConfigureAwait(false); | |||
}); | |||
} | |||
internal async Task AddConnection(RelayConnection conn) | |||
{ | |||
await _lock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
_connections.Add(conn); | |||
} | |||
finally { _lock.Release(); } | |||
} | |||
internal async Task RemoveConnection(RelayConnection conn) | |||
{ | |||
await _lock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
_connections.Remove(conn); | |||
} | |||
finally { _lock.Release(); } | |||
} | |||
internal int Serialize(object obj, byte[] buffer) | |||
{ | |||
using (var stream = new MemoryStream(buffer)) | |||
using (var writer = new StreamWriter(stream)) | |||
{ | |||
_serializer.Serialize(writer, obj); | |||
return (int)stream.Position; | |||
} | |||
} | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
using System.Runtime.CompilerServices; | |||
[assembly: InternalsVisibleTo("Discord.Net.Relay")] | |||
[assembly: InternalsVisibleTo("Discord.Net.Tests")] |
@@ -33,10 +33,11 @@ namespace Discord.API | |||
public ConnectionState ConnectionState { get; private set; } | |||
public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider, | |||
RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) | |||
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | |||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) | |||
: base(restClientProvider, userAgent, defaultRetryMode, serializer) | |||
{ | |||
{ | |||
_gatewayUrl = url; | |||
WebSocketClient = webSocketProvider(); | |||
//WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) | |||
WebSocketClient.BinaryMessage += async (data, index, count) => | |||
@@ -115,9 +116,9 @@ namespace Discord.API | |||
ConnectionState = ConnectionState.Connected; | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
_gatewayUrl = null; //Uncache in case the gateway url changed | |||
_gatewayUrl = null; //Uncache in case the gateway url changed | |||
await DisconnectInternalAsync().ConfigureAwait(false); | |||
throw; | |||
} | |||
@@ -142,7 +142,7 @@ namespace Discord.WebSocket | |||
_largeGuilds = new ConcurrentQueue<ulong>(); | |||
} | |||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||
protected override async Task OnLoginAsync(TokenType tokenType, string token) | |||
{ | |||
@@ -2,7 +2,6 @@ | |||
using Discord.Net.Udp; | |||
using Discord.Net.WebSockets; | |||
using Discord.Rest; | |||
using System; | |||
namespace Discord.WebSocket | |||
{ | |||
@@ -10,6 +9,9 @@ namespace Discord.WebSocket | |||
{ | |||
public const string GatewayEncoding = "json"; | |||
/// <summary> Gets or sets the websocket host to connect to. If null, the client will use the /gateway endpoint. | |||
public string GatewayHost { get; set; } = null; | |||
/// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary> | |||
public int ConnectionTimeout { get; set; } = 30000; | |||
@@ -38,41 +40,8 @@ namespace Discord.WebSocket | |||
public DiscordSocketConfig() | |||
{ | |||
#if NETSTANDARD1_3 | |||
WebSocketProvider = () => | |||
{ | |||
try | |||
{ | |||
return new DefaultWebSocketClient(); | |||
} | |||
catch (PlatformNotSupportedException ex) | |||
{ | |||
throw new PlatformNotSupportedException("The default websocket provider is not supported on this platform.", ex); | |||
} | |||
}; | |||
UdpSocketProvider = () => | |||
{ | |||
try | |||
{ | |||
return new DefaultUdpSocket(); | |||
} | |||
catch (PlatformNotSupportedException ex) | |||
{ | |||
throw new PlatformNotSupportedException("The default UDP provider is not supported on this platform.", ex); | |||
} | |||
}; | |||
#else | |||
WebSocketProvider = () => | |||
{ | |||
throw new PlatformNotSupportedException("The default websocket provider is not supported on this platform.\n" + | |||
"You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); | |||
}; | |||
UdpSocketProvider = () => | |||
{ | |||
throw new PlatformNotSupportedException("The default UDP provider is not supported on this platform.\n" + | |||
"You must specify a UdpSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); | |||
}; | |||
#endif | |||
WebSocketProvider = DefaultWebSocketProvider.Instance; | |||
UdpSocketProvider = DefaultUdpSocketProvider.Instance; | |||
} | |||
internal DiscordSocketConfig Clone() => MemberwiseClone() as DiscordSocketConfig; | |||