mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 20:47:28 +01:00
Finish documenting DMAPI constructs. Get tests running
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#define DMAPI5_PARAM_RUNTIME_INFORMATION_FILE "tgs_json"
|
||||
#define DMAPI5_TOPIC_DATA "data"
|
||||
|
||||
#define DMAPI5_BRIDGE_COMMAND_NEW_PORT 0
|
||||
#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0
|
||||
#define DMAPI5_BRIDGE_COMMAND_VALIDATE 1
|
||||
#define DMAPI5_BRIDGE_COMMAND_PRIME 2
|
||||
#define DMAPI5_BRIDGE_COMMAND_REBOOT 3
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
|
||||
|
||||
#define DMAPI5_BRIDGE_PARAMETER_COMMAND "commandType"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_NEW_PORT "newPort"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE "commandType"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT "currentPort"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_VERSION "version"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage"
|
||||
#define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Components.Chat.Providers;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
public string Mention { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Components.Chat.ChatChannel"/> the user spoke from
|
||||
/// The <see cref="ChatChannel"/> the user spoke from
|
||||
/// </summary>
|
||||
public ChatChannel Channel { get; set; }
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Components
|
||||
readonly IByondInstaller byondInstaller;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProviderFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// The <see cref="IChatFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IChatFactory chatFactory;
|
||||
|
||||
|
||||
@@ -1,12 +1,38 @@
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the <see cref="BridgeParameters.CommandType"/>.
|
||||
/// </summary>
|
||||
public enum BridgeCommandType
|
||||
{
|
||||
NewPort,
|
||||
/// <summary>
|
||||
/// DreamDaemon notifying us of its current port and requesting a change if necessary.
|
||||
/// </summary>
|
||||
PortUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon responding to an API validation request.
|
||||
/// </summary>
|
||||
Validate,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon notifying the server is primed
|
||||
/// </summary>
|
||||
Prime,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon notifiying the server is calling /world/Reboot().
|
||||
/// </summary>
|
||||
Reboot,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon requesting the process be terminated.
|
||||
/// </summary>
|
||||
Kill,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon requesting a <see cref="ChatMessage"/> be sent.
|
||||
/// </summary>
|
||||
ChatSend
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,34 @@ using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for a bridge request.
|
||||
/// </summary>
|
||||
public sealed class BridgeParameters : DMApiParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="BridgeCommandType"/>.
|
||||
/// </summary>
|
||||
public BridgeCommandType? CommandType { get; set; }
|
||||
|
||||
public ushort? NewPort { get; set; }
|
||||
/// <summary>
|
||||
/// The current port for <see cref="BridgeCommandType.PortUpdate"/> requests.
|
||||
/// </summary>
|
||||
public ushort? CurrentPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DMAPI <see cref="global::System.Version"/> for <see cref="BridgeCommandType.Validate"/> requests.
|
||||
/// </summary>
|
||||
public Version Version { get; set; }
|
||||
|
||||
public ChatMessage ChatMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum required <see cref="DreamDaemonSecurity"/> level for <see cref="BridgeCommandType.Validate"/> requests.
|
||||
/// </summary>
|
||||
public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Interop.ChatMessage"/> for <see cref="BridgeCommandType.ChatSend"/> requests.
|
||||
/// </summary>
|
||||
public ChatMessage ChatMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
{
|
||||
public sealed class BridgeResponse
|
||||
/// <summary>
|
||||
/// A response to a bridge request.
|
||||
/// </summary>
|
||||
public sealed class BridgeResponse : DMApiResponse
|
||||
{
|
||||
public string ErrorMessage { get; set; }
|
||||
/// <summary>
|
||||
/// The new port for <see cref="BridgeCommandType.PortUpdate"/> requests.
|
||||
/// </summary>
|
||||
public ushort? NewPort { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a message to send to one or more <see cref="Chat.ChatChannel"/>s.
|
||||
/// </summary>
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The message <see cref="string"/>.
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
public ICollection<ulong> ChannelIds { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="ICollection{T}"/> of <see cref="Chat.ChatChannel.Id"/>s to sent the <see cref="Text"/> to. Must be safe to parse as <see cref="ulong"/>s.
|
||||
/// </summary>
|
||||
public ICollection<string> ChannelIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
namespace Tgstation.Server.Host.Components.Interop
|
||||
{
|
||||
public class DMApiParameters
|
||||
/// <summary>
|
||||
/// Common base for interop parameters.
|
||||
/// </summary>
|
||||
public abstract class DMApiParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Runtime.RuntimeInformation.AccessIdentifier"/> for interop.
|
||||
/// </summary>
|
||||
public string AccessIdentifier { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Tgstation.Server.Host.Components.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Common base for interop responses.
|
||||
/// </summary>
|
||||
public abstract class DMApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Any errors in the client's parameters.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
interface IBridgeHandler : IBridgeHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="RuntimeInformation.AccessIdentifier"/> for the <see cref="IBridgeHandler"/>.
|
||||
/// The <see cref="Runtime.RuntimeInformation.AccessIdentifier"/> for the <see cref="IBridgeHandler"/>.
|
||||
/// </summary>
|
||||
string AccessIdentifier { get; }
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
public string Params { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Chat.ChatUser"/> that sent the command
|
||||
/// The <see cref="ChatUser"/> that sent the command
|
||||
/// </summary>
|
||||
public ChatUser User { get; }
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// </summary>
|
||||
/// <param name="user">The value of <see cref="User"/>.</param>
|
||||
/// <param name="command">The value of <see cref="Name"/>.</param>
|
||||
/// <param name="parameters">The value of <see cref="Parames"/>.</param>
|
||||
/// <param name="parameters">The value of <see cref="Params"/>.</param>
|
||||
public ChatCommand(ChatUser user, string command, string parameters)
|
||||
{
|
||||
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||
|
||||
@@ -3,16 +3,30 @@ using System.Linq;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
{
|
||||
/// <summary>
|
||||
/// Data structure for <see cref="TopicCommandType.EventNotification"/> requests.
|
||||
/// </summary>
|
||||
sealed class EventNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="EventType"/> triggered.
|
||||
/// </summary>
|
||||
public EventType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The set of parameters.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<object> Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EventNotification"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventType">The value of <see cref="Type"/>.</param>
|
||||
/// <param name="parameters">The <see cref="IEnumerable{T}"/> that forms the value of <see cref="Parameters"/>.</param>
|
||||
public EventNotification(EventType eventType, IEnumerable<object> parameters = null)
|
||||
{
|
||||
Type = eventType;
|
||||
Parameters = parameters?.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// Notification of a TGS event.
|
||||
/// </summary>
|
||||
Event,
|
||||
EventNotification,
|
||||
|
||||
/// <summary>
|
||||
/// Port change request.
|
||||
|
||||
@@ -3,48 +3,94 @@ using Tgstation.Server.Host.Components.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for a topic request.
|
||||
/// </summary>
|
||||
sealed class TopicParameters : DMApiParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="TopicCommandType"/>.
|
||||
/// </summary>
|
||||
public TopicCommandType CommandType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Topic.ChatCommand"/> for <see cref="TopicCommandType.ChatCommand"/> requests.
|
||||
/// </summary>
|
||||
public ChatCommand ChatCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Topic.EventNotification"/> for <see cref="TopicCommandType.EventNotification"/> requests.
|
||||
/// </summary>
|
||||
public EventNotification EventNotification { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new port for <see cref="TopicCommandType.ChangePort"/> requests.
|
||||
/// </summary>
|
||||
public ushort? NewPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RebootState"/> for <see cref="TopicCommandType.ChangeRebootState"/> requests.
|
||||
/// </summary>
|
||||
public RebootState? NewRebootState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new <see cref="Api.Models.Instance.Name"/> for <see cref="TopicCommandType.InstanceRenamed"/> requests.
|
||||
/// </summary>
|
||||
public string NewInstanceName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="commandType">The value of <see cref="CommandType"/>.</param>
|
||||
private TopicParameters(TopicCommandType commandType)
|
||||
{
|
||||
CommandType = commandType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="chatCommand">The value of <see cref="ChatCommand"/>.</param>
|
||||
public TopicParameters(ChatCommand chatCommand)
|
||||
: this(TopicCommandType.ChatCommand)
|
||||
{
|
||||
ChatCommand = chatCommand ?? throw new ArgumentNullException(nameof(chatCommand));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventNotification">The value of <see cref="EventNotification"/>.</param>
|
||||
public TopicParameters(EventNotification eventNotification)
|
||||
: this(TopicCommandType.Event)
|
||||
: this(TopicCommandType.EventNotification)
|
||||
{
|
||||
EventNotification = eventNotification ?? throw new ArgumentNullException(nameof(eventNotification));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="newPort">The value of <see cref="NewPort"/>.</param>
|
||||
public TopicParameters(ushort newPort)
|
||||
: this(TopicCommandType.ChangePort)
|
||||
{
|
||||
NewPort = newPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="newRebootState">The value of <see cref="NewRebootState"/>.</param>
|
||||
public TopicParameters(RebootState newRebootState)
|
||||
: this(TopicCommandType.ChangeRebootState)
|
||||
{
|
||||
NewRebootState = newRebootState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="newInstanceName">The value of <see cref="NewInstanceName"/>.</param>
|
||||
public TopicParameters(string newInstanceName)
|
||||
: this(TopicCommandType.InstanceRenamed)
|
||||
{
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
{
|
||||
sealed class TopicResponse
|
||||
/// <summary>
|
||||
/// A response to a topic request.
|
||||
/// </summary>
|
||||
sealed class TopicResponse : DMApiResponse
|
||||
{
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text to reply with as the result of a <see cref="TopicCommandType.ChatCommand"/> request, if any.
|
||||
/// </summary>
|
||||
public string CommandResponseMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatMessage"/>s to send as the result of a <see cref="TopicCommandType.EventNotification"/> request, if any.
|
||||
/// </summary>
|
||||
public ICollection<ChatMessage> ChatResponses { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -316,6 +317,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ErrorMessage = "Missing channelIds field in chatMessage!"
|
||||
};
|
||||
|
||||
if(parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _)))
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Invalid channelIds in chatMessage!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.Text == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
@@ -324,7 +331,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
await chat.SendMessage(
|
||||
parameters.ChatMessage.Text,
|
||||
parameters.ChatMessage.ChannelIds,
|
||||
parameters.ChatMessage.ChannelIds.Select(UInt64.Parse),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case BridgeCommandType.Prime:
|
||||
@@ -334,10 +341,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
TerminationWasRequested = true;
|
||||
process.Terminate();
|
||||
break;
|
||||
case BridgeCommandType.NewPort:
|
||||
case BridgeCommandType.PortUpdate:
|
||||
lock (this)
|
||||
{
|
||||
if (!parameters.NewPort.HasValue)
|
||||
if (!parameters.CurrentPort.HasValue)
|
||||
{
|
||||
/////UHHHH
|
||||
logger.LogWarning("DreamDaemon sent new port command without providing it's own!");
|
||||
@@ -347,9 +354,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
};
|
||||
}
|
||||
|
||||
var currentPort = parameters.NewPort.Value;
|
||||
var currentPort = parameters.CurrentPort.Value;
|
||||
if (!nextPort.HasValue)
|
||||
reattachInformation.Port = parameters.NewPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
else
|
||||
{
|
||||
// nextPort is ready, tell DD to switch to that
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
JsonFile("chat_channels"),
|
||||
JsonFile("chat_commands"),
|
||||
securityLevelToUse,
|
||||
await serverPortProvider.HttpApiPort.ConfigureAwait(false));
|
||||
serverPortProvider.HttpApiPort);
|
||||
|
||||
var interopJsonFile = JsonFile("interop");
|
||||
|
||||
|
||||
@@ -447,13 +447,27 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (result?.ChatResponses == null)
|
||||
return true;
|
||||
|
||||
await Task.WhenAll(result.ChatResponses.Select(x => Chat.SendMessage(x.Text, x.ChannelIds, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(
|
||||
result.ChatResponses.Select(
|
||||
x => Chat.SendMessage(
|
||||
x.Text,
|
||||
x.ChannelIds
|
||||
.Select(channelIdString =>
|
||||
{
|
||||
if (UInt64.TryParse(channelIdString, out var channelId))
|
||||
return (ulong?)channelId;
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(nullableChannelId => nullableChannelId.HasValue)
|
||||
.Select(nullableChannelId => nullableChannelId.Value),
|
||||
cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> HandleChatCommand(string commandName, string arguments, Chat.ChatUser sender, CancellationToken cancellationToken)
|
||||
public async Task<string> HandleChatCommand(string commandName, string arguments, ChatUser sender, CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -376,7 +375,6 @@ namespace Tgstation.Server.Host.Core
|
||||
IApplicationBuilder applicationBuilder,
|
||||
IServerControl serverControl,
|
||||
ITokenFactory tokenFactory,
|
||||
IServerPortProvider serverPortProvider,
|
||||
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<Application> logger)
|
||||
@@ -388,12 +386,6 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
|
||||
|
||||
if (serverPortProvider == null)
|
||||
throw new ArgumentNullException(nameof(serverPortProvider));
|
||||
|
||||
var addressFeature = applicationBuilder?.ServerFeatures.Get<IServerAddressesFeature>();
|
||||
serverPortProvider.Configure(addressFeature);
|
||||
|
||||
var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
|
||||
var generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the server's <see cref="HttpApiPort"/>.
|
||||
@@ -9,14 +6,8 @@ namespace Tgstation.Server.Host.Core
|
||||
interface IServerPortProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Task{TResult}"/> resulting in the port the server listens on.
|
||||
/// The port the server listens on.
|
||||
/// </summary>
|
||||
Task<ushort> HttpApiPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Configures the <see cref="ServerPortProivder"/>.
|
||||
/// </summary>
|
||||
/// <param name="addressFeature">The <see cref="IServerAddressesFeature"/> to use.</param>
|
||||
void Configure(IServerAddressesFeature addressFeature);
|
||||
ushort HttpApiPort { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
@@ -9,38 +8,25 @@ namespace Tgstation.Server.Host.Core
|
||||
sealed class ServerPortProivder : IServerPortProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<ushort> HttpApiPort => taskCompletionSource.Task;
|
||||
public ushort HttpApiPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Backing <see cref="TaskCompletionSource{TResult}"/> field for <see cref="HttpApiPort"/>/
|
||||
/// Initializes a new instance of the <see cref="ServerPortProivder"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
readonly TaskCompletionSource<ushort> taskCompletionSource;
|
||||
|
||||
/// <summary>
|
||||
/// In
|
||||
/// </summary>
|
||||
public ServerPortProivder()
|
||||
/// <param name="configuration">The <see cref="IConfiguration"/> to use.</param>
|
||||
public ServerPortProivder(IConfiguration configuration)
|
||||
{
|
||||
taskCompletionSource = new TaskCompletionSource<ushort>();
|
||||
}
|
||||
if (configuration == null)
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IServerAddressesFeature addressFeature)
|
||||
{
|
||||
if (addressFeature == null)
|
||||
throw new ArgumentNullException(nameof(addressFeature));
|
||||
var httpEndpoint = configuration
|
||||
.GetSection("Kestrel")
|
||||
.GetSection("Endpoints")
|
||||
.GetSection("Http")
|
||||
.GetSection("Url")
|
||||
.Value;
|
||||
|
||||
var enumerator = addressFeature.Addresses.Select(GetPortFromAddress);
|
||||
var newPort = enumerator.FirstOrDefault(x => x.HasValue);
|
||||
|
||||
if(!newPort.HasValue)
|
||||
throw new InvalidOperationException("At least one plain HTTP endpoint must be configured. Neded for BYOND -> Server communications!");
|
||||
|
||||
if (!addressFeature.Addresses.Select(GetPortFromAddress).All(x => !x.HasValue || x == newPort))
|
||||
throw new InvalidOperationException("All configured HTTP server addresses must use the same port!");
|
||||
|
||||
// Will fail if set twice
|
||||
taskCompletionSource.SetResult(newPort.Value);
|
||||
HttpApiPort = GetPortFromAddress(httpEndpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,18 +34,14 @@ namespace Tgstation.Server.Host.Core
|
||||
/// </summary>
|
||||
/// <param name="address">The address <see cref="string"/>.</param>
|
||||
/// <returns>The parsed port.</returns>
|
||||
static ushort? GetPortFromAddress(string address)
|
||||
static ushort GetPortFromAddress(string address)
|
||||
{
|
||||
var splits = address.Split(":", StringSplitOptions.RemoveEmptyEntries);
|
||||
if (splits.First().Equals("https", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
var portString = splits.Last();
|
||||
portString = portString.TrimEnd('/');
|
||||
if (UInt16.TryParse(portString, out var result))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
if (!UInt16.TryParse(portString, out var result))
|
||||
throw new InvalidOperationException("Failed to parse HTTP API port!");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -40,27 +41,24 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
var app = new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object, Mock.Of<IIOManager>());
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.ConfigureServices(null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null, null, null, null));
|
||||
|
||||
var mockAppBuilder = new Mock<IApplicationBuilder>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null));
|
||||
|
||||
var mockServerControl = new Mock<IServerControl>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null));
|
||||
|
||||
var mockTokenFactory = new Mock<ITokenFactory>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null));
|
||||
|
||||
var mockServerPortProvider = new Mock<IServerPortProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null));
|
||||
|
||||
var mockControlPanelOptions = new Mock<IOptions<ControlPanelConfiguration>>();
|
||||
mockControlPanelOptions.SetupGet(x => x.Value).Returns(new ControlPanelConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockControlPanelOptions.Object, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockControlPanelOptions.Object, null, null));
|
||||
|
||||
var mockGeneralOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockControlPanelOptions.Object, mockGeneralOptions.Object, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockControlPanelOptions.Object, mockGeneralOptions.Object, null));
|
||||
mockControlPanelOptions.VerifyAll();
|
||||
mockGeneralOptions.VerifyAll();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ namespace Tgstation.Server.Tests
|
||||
Directory = Path.GetTempFileName();
|
||||
File.Delete(Directory);
|
||||
System.IO.Directory.CreateDirectory(Directory);
|
||||
Url = new Uri("http://localhost:5001");
|
||||
const string UrlString = "http://localhost:5001";
|
||||
Url = new Uri(UrlString);
|
||||
|
||||
//so we need a db
|
||||
//we have to rely on env vars
|
||||
@@ -56,7 +57,7 @@ namespace Tgstation.Server.Tests
|
||||
|
||||
var args = new List<string>()
|
||||
{
|
||||
String.Format(CultureInfo.InvariantCulture, "Kestrel:EndPoints:Http:Url={0}", Url),
|
||||
String.Format(CultureInfo.InvariantCulture, "Kestrel:EndPoints:Http:Url={0}", UrlString),
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType),
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString),
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:DropDatabase={0}", true),
|
||||
|
||||
Reference in New Issue
Block a user