mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Return of the HttpListener. (#1423)
Microsoft isn't supporting NuGet-components ASP.NET Core ever since 3.x so using Kestrel is out. New implementation is 100% thread pool compared to the old one which was a single specific thread.
This commit is contained in:
committed by
GitHub
parent
a41f64f30e
commit
2b39c05472
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Robust.Server.Interfaces.ServerStatus
|
||||
{
|
||||
public delegate bool StatusHostHandler(HttpMethod method, HttpRequest request, HttpResponse response);
|
||||
public delegate bool StatusHostHandler(
|
||||
HttpMethod method,
|
||||
HttpListenerRequest request,
|
||||
HttpListenerResponse response);
|
||||
|
||||
public interface IStatusHost
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
|
||||
<PackageReference Include="prometheus-net" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="3.0.0" />
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Robust.Shared;
|
||||
@@ -22,21 +21,20 @@ namespace Robust.Server.ServerStatus
|
||||
AddHandler(HandleInfo);
|
||||
}
|
||||
|
||||
private static bool HandleTeapot(HttpMethod method, HttpRequest request, HttpResponse response)
|
||||
private static bool HandleTeapot(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Path != "/teapot")
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/teapot")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
response.StatusCode = StatusCodes.Status418ImATeapot;
|
||||
response.Respond("I am a teapot.", StatusCodes.Status418ImATeapot);
|
||||
response.Respond(method, "I am a teapot.", (HttpStatusCode) 418);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleStatus(HttpMethod method, HttpRequest request, HttpResponse response)
|
||||
private bool HandleStatus(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Path != "/status")
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/status")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -44,7 +42,7 @@ namespace Robust.Server.ServerStatus
|
||||
if (OnStatusRequest == null)
|
||||
{
|
||||
Logger.WarningS(Sawmill, "OnStatusRequest is not set, responding with a 501.");
|
||||
response.Respond("Not Implemented", HttpStatusCode.NotImplemented);
|
||||
response.Respond(method, "Not Implemented", HttpStatusCode.NotImplemented);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,7 +58,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnStatusRequest?.Invoke(jObject);
|
||||
|
||||
using var streamWriter = new StreamWriter(response.Body, EncodingHelpers.UTF8);
|
||||
using var streamWriter = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
using var jsonWriter = new JsonTextWriter(streamWriter);
|
||||
|
||||
@@ -71,9 +69,9 @@ namespace Robust.Server.ServerStatus
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleInfo(HttpMethod method, HttpRequest request, HttpResponse response)
|
||||
private bool HandleInfo(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Path != "/info")
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/info")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -132,7 +130,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnInfoRequest?.Invoke(jObject);
|
||||
|
||||
using var streamWriter = new StreamWriter(response.Body, EncodingHelpers.UTF8);
|
||||
using var streamWriter = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
using var jsonWriter = new JsonTextWriter(streamWriter);
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
{
|
||||
|
||||
private HttpContextFactory _ctxFactory = default!;
|
||||
|
||||
public HttpContext CreateContext(IFeatureCollection contextFeatures) => _ctxFactory.Create(contextFeatures);
|
||||
|
||||
public void DisposeContext(HttpContext context, Exception exception)
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
Logger.ErrorS(Sawmill, $"Context disposed due to exception: {exception}");
|
||||
}
|
||||
|
||||
_ctxFactory.Dispose(context);
|
||||
}
|
||||
|
||||
private static HttpContextFactory CreateHttpContextFactory()
|
||||
{
|
||||
var ctxFacOptions = Options.Create(new FormOptions
|
||||
{
|
||||
});
|
||||
var ctxFactory = new HttpContextFactory(ctxFacOptions, new HttpContextAccessor());
|
||||
return ctxFactory;
|
||||
}
|
||||
|
||||
private void InitHttpContextThread()
|
||||
{
|
||||
if (SynchronizationContext.Current == _syncCtx)
|
||||
{
|
||||
// maybe assert instead?
|
||||
return;
|
||||
}
|
||||
|
||||
ILogManager? logMgr = null;
|
||||
WaitSync(() =>
|
||||
{
|
||||
logMgr = IoCManager.Resolve<ILogManager>();
|
||||
}, ApplicationStopping);
|
||||
var deps = new DependencyCollection();
|
||||
deps.RegisterInstance<ILogManager>(new ProxyLogManager(logMgr!));
|
||||
deps.BuildGraph();
|
||||
IoCManager.InitThread(deps, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
{
|
||||
|
||||
private readonly CancellationTokenSource _startedSource = new();
|
||||
|
||||
private readonly CancellationTokenSource _stoppedSource = new();
|
||||
|
||||
private readonly CancellationTokenSource _stoppingSource = new();
|
||||
|
||||
public void StopApplication() => Dispose();
|
||||
|
||||
|
||||
private static CancellationTokenSource? _cancelled;
|
||||
private static CancellationToken GetCancelledToken()
|
||||
{
|
||||
if (_cancelled == null)
|
||||
{
|
||||
_cancelled = new CancellationTokenSource();
|
||||
_cancelled.Cancel();
|
||||
}
|
||||
|
||||
return _cancelled.Token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host has fully started and is about to wait
|
||||
/// for a graceful shutdown.
|
||||
/// </summary>
|
||||
public CancellationToken ApplicationStarted => _startedSource?.Token ?? GetCancelledToken();
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// Request may still be in flight. Shutdown will block until this event completes.
|
||||
/// </summary>
|
||||
public CancellationToken ApplicationStopping => _stoppingSource?.Token ?? GetCancelledToken();
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// All requests should be complete at this point. Shutdown will block
|
||||
/// until this event completes.
|
||||
/// </summary>
|
||||
public CancellationToken ApplicationStopped => _stoppedSource?.Token ?? GetCancelledToken();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_stoppingSource?.IsCancellationRequested ?? true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_stoppingSource?.Cancel();
|
||||
_server?.StopAsync(ApplicationStopped);
|
||||
_stoppedSource?.Cancel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LogLevel = Robust.Shared.Log.LogLevel;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
{
|
||||
|
||||
private Dictionary<string, SawmillWrapper> _sawmillCache = new();
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
if (!_sawmillCache.TryGetValue(categoryName, out var wrapper))
|
||||
{
|
||||
var newCatName = categoryName;
|
||||
if (newCatName.StartsWith("Microsoft.AspNetCore.Server.Kestrel"))
|
||||
{
|
||||
newCatName = "http";
|
||||
}
|
||||
else
|
||||
{
|
||||
newCatName = newCatName.Replace("Microsoft.AspNetCore.", "aspnet.");
|
||||
}
|
||||
|
||||
wrapper = new SawmillWrapper(Logger.GetSawmill($"{Sawmill}.{newCatName}"));
|
||||
_sawmillCache[categoryName] = wrapper;
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public void AddProvider(ILoggerProvider provider)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
private static void ConfigureSawmills()
|
||||
{
|
||||
var logMgr = IoCManager.Resolve<ILogManager>();
|
||||
logMgr.GetSawmill("statushost.http").Level = LogLevel.Warning;
|
||||
logMgr.GetSawmill("statushost.aspnet").Level = LogLevel.Warning;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
{
|
||||
|
||||
private class SawmillWrapper : ILogger
|
||||
{
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
public SawmillWrapper(ISawmill sawmill)
|
||||
=> _sawmill = sawmill;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
=> _sawmill.Log((Shared.Log.LogLevel) (int) logLevel, formatter(state, exception));
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
=> (int) logLevel >= (int) (_sawmill.Level ?? (Shared.Log.LogLevel) 0);
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
=> new DummyDisposable();
|
||||
|
||||
// @formatter:off
|
||||
private struct DummyDisposable : IDisposable { public void Dispose() { } }
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
{
|
||||
|
||||
private SynchronizationContext _syncCtx = default!;
|
||||
|
||||
public void DeferSync(Action a)
|
||||
{
|
||||
if (ExecuteInlineIfOnSyncCtx(a))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_syncCtx.Post(x => ((Action) x!)(), a);
|
||||
}
|
||||
|
||||
public void WaitSync(Action a, CancellationToken ct = default)
|
||||
{
|
||||
if (ExecuteInlineIfOnSyncCtx(a))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// throws not implemented
|
||||
//_syncCtx.Send(x => ((Action) x)(), a);
|
||||
|
||||
using var e = new ManualResetEventSlim(false, 0);
|
||||
_syncCtx.Post(_ =>
|
||||
{
|
||||
a();
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
e.Set();
|
||||
}, null);
|
||||
e.Wait(ct);
|
||||
}
|
||||
|
||||
private bool ExecuteInlineIfOnSyncCtx(Action a)
|
||||
{
|
||||
if (_syncCtx == SynchronizationContext.Current)
|
||||
{
|
||||
a();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,20 +5,13 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Robust.Server.Interfaces.ServerStatus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -29,34 +22,26 @@ using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
|
||||
internal sealed partial class StatusHost
|
||||
: IStatusHost, IDisposable,
|
||||
IHttpApplication<HttpContext>,
|
||||
IApplicationLifetime,
|
||||
ILoggerFactory
|
||||
internal sealed partial class StatusHost : IStatusHost, IDisposable
|
||||
{
|
||||
|
||||
private const string Sawmill = "statushost";
|
||||
|
||||
private static readonly JsonSerializer JsonSerializer = new();
|
||||
|
||||
private readonly List<StatusHostHandler> _handlers = new();
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||
|
||||
private KestrelServer _server = default!;
|
||||
private static readonly JsonSerializer JsonSerializer = new();
|
||||
private readonly List<StatusHostHandler> _handlers = new();
|
||||
private HttpListener? _listener;
|
||||
private TaskCompletionSource? _stopSource;
|
||||
private ISawmill _httpSawmill = default!;
|
||||
|
||||
public Task ProcessRequestAsync(HttpContext context)
|
||||
public Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
var response = context.Response;
|
||||
var request = context.Request;
|
||||
var method = new HttpMethod(request.Method);
|
||||
InitHttpContextThread();
|
||||
var method = new HttpMethod(request.HttpMethod);
|
||||
|
||||
Logger.InfoS(Sawmill, $"{method} {context.Request.Path} from " +
|
||||
$"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}");
|
||||
_httpSawmill.Info($"{method} {context.Request.Url?.PathAndQuery} from {request.RemoteEndPoint}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -70,16 +55,18 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
// No handler returned true, assume no handlers care about this.
|
||||
// 404.
|
||||
response.Respond("Not Found", HttpStatusCode.NotFound);
|
||||
response.Respond(method, "Not Found", HttpStatusCode.NotFound);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
response.Respond("Internal Server Error", HttpStatusCode.InternalServerError);
|
||||
response.Respond(method, "Internal Server Error", HttpStatusCode.InternalServerError);
|
||||
Logger.ErrorS(Sawmill, $"Exception in StatusHost: {e}");
|
||||
}
|
||||
|
||||
Logger.DebugS(Sawmill, $"{method} {context.Request.Path} {context.Response.StatusCode} " +
|
||||
$"{(HttpStatusCode) context.Response.StatusCode} to {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}");
|
||||
/*
|
||||
_httpSawmill.Debug(Sawmill, $"{method} {context.Request.Url!.PathAndQuery} {context.Response.StatusCode} " +
|
||||
$"{(HttpStatusCode) context.Response.StatusCode} to {context.Request.RemoteEndPoint}");
|
||||
*/
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -88,10 +75,14 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
public event Action<JObject>? OnInfoRequest;
|
||||
|
||||
public void AddHandler(StatusHostHandler handler) => _handlers.Add(handler);
|
||||
public void AddHandler(StatusHostHandler handler)
|
||||
{
|
||||
_handlers.Add(handler);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_httpSawmill = Logger.GetSawmill($"{Sawmill}.http");
|
||||
RegisterCVars();
|
||||
|
||||
if (!_configurationManager.GetCVar(CVars.StatusEnabled))
|
||||
@@ -99,64 +90,53 @@ namespace Robust.Server.ServerStatus
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigureSawmills();
|
||||
|
||||
_ctxFactory = CreateHttpContextFactory();
|
||||
|
||||
var kestrelOpts = new KestrelServerOptions
|
||||
{
|
||||
AllowSynchronousIO = true,
|
||||
ApplicationSchedulingMode = SchedulingMode.ThreadPool
|
||||
};
|
||||
|
||||
kestrelOpts.Listen(GetBinding());
|
||||
|
||||
_server = new KestrelServer(
|
||||
Options.Create(
|
||||
kestrelOpts
|
||||
),
|
||||
GetSocketTransportFactory(),
|
||||
this
|
||||
);
|
||||
|
||||
RegisterHandlers();
|
||||
|
||||
_server.StartAsync(this, ApplicationStopping);
|
||||
_stopSource = new TaskCompletionSource();
|
||||
_listener = new HttpListener();
|
||||
_listener.Prefixes.Add($"http://{_configurationManager.GetCVar(CVars.StatusBind)}/");
|
||||
_listener.Start();
|
||||
|
||||
_syncCtx = SynchronizationContext.Current!;
|
||||
|
||||
if (_syncCtx == null)
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(_syncCtx = new SynchronizationContext());
|
||||
}
|
||||
Task.Run(ListenerThread);
|
||||
}
|
||||
|
||||
private IPEndPoint GetBinding()
|
||||
// Not a real thread but whatever.
|
||||
private async Task ListenerThread()
|
||||
{
|
||||
var binding = _configurationManager.GetCVar(CVars.StatusBind).Split(':');
|
||||
var ipAddrStr = binding[0];
|
||||
if (ipAddrStr == "+" || ipAddrStr == "*")
|
||||
var maxConnections = _configurationManager.GetCVar(CVars.StatusMaxConnections);
|
||||
var connectionsSemaphore = new SemaphoreSlim(maxConnections, maxConnections);
|
||||
while (true)
|
||||
{
|
||||
ipAddrStr = "0.0.0.0";
|
||||
}
|
||||
var getContextTask = _listener!.GetContextAsync();
|
||||
var task = await Task.WhenAny(getContextTask, _stopSource!.Task);
|
||||
|
||||
var ipAddress = IPAddress.Parse(ipAddrStr);
|
||||
var port = int.Parse(binding[1]);
|
||||
var ipEndPoint = new IPEndPoint(ipAddress, port);
|
||||
return ipEndPoint;
|
||||
}
|
||||
|
||||
private SocketTransportFactory GetSocketTransportFactory()
|
||||
{
|
||||
var transportFactory = new SocketTransportFactory(
|
||||
Options.Create(new SocketTransportOptions
|
||||
if (task == _stopSource.Task)
|
||||
{
|
||||
IOQueueCount = 42
|
||||
}),
|
||||
this,
|
||||
this
|
||||
);
|
||||
return transportFactory;
|
||||
return;
|
||||
}
|
||||
|
||||
await connectionsSemaphore.WaitAsync();
|
||||
|
||||
// Task.Run this so it gets run on another thread pool thread.
|
||||
#pragma warning disable 4014
|
||||
Task.Run(async () =>
|
||||
#pragma warning restore 4014
|
||||
{
|
||||
try
|
||||
{
|
||||
var ctx = await getContextTask;
|
||||
await ProcessRequestAsync(ctx);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_httpSawmill.Error($"Error inside ProcessRequestAsync:\n{e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionsSemaphore.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterCVars()
|
||||
@@ -181,6 +161,17 @@ namespace Robust.Server.ServerStatus
|
||||
_configurationManager.SetCVar(CVars.BuildHashLinux, info?.Hashes.Linux ?? "");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_stopSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_stopSource.SetResult();
|
||||
_listener!.Stop();
|
||||
}
|
||||
|
||||
[JsonObject(ItemRequired = Required.DisallowNull)]
|
||||
private sealed class BuildInfo
|
||||
{
|
||||
@@ -198,5 +189,4 @@ namespace Robust.Server.ServerStatus
|
||||
[JsonProperty("macos")] public string MacOS { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -11,33 +9,45 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
public static class StatusHostHelpers
|
||||
{
|
||||
public static bool IsGetLike(this HttpMethod method) =>
|
||||
method == HttpMethod.Get || method == HttpMethod.Head;
|
||||
public static bool IsGetLike(this HttpMethod method)
|
||||
{
|
||||
return method == HttpMethod.Get || method == HttpMethod.Head;
|
||||
}
|
||||
|
||||
public static void Respond(this HttpResponse response, string text, HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain") =>
|
||||
response.Respond(text, (int) code, contentType);
|
||||
public static void Respond(
|
||||
this HttpListenerResponse response,
|
||||
HttpMethod method,
|
||||
string text,
|
||||
HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain")
|
||||
{
|
||||
response.Respond(method, text, (int) code, contentType);
|
||||
}
|
||||
|
||||
public static void Respond(this HttpResponse response, string text, int code = 200,
|
||||
public static void Respond(
|
||||
this HttpListenerResponse response,
|
||||
HttpMethod method,
|
||||
string text,
|
||||
int code = 200,
|
||||
string contentType = "text/plain")
|
||||
{
|
||||
response.StatusCode = code;
|
||||
response.ContentType = contentType;
|
||||
|
||||
if (response.HttpContext.Request.Method == "HEAD")
|
||||
if (method == HttpMethod.Head)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var writer = new StreamWriter(response.Body, EncodingHelpers.UTF8);
|
||||
using var writer = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
writer.Write(text);
|
||||
}
|
||||
|
||||
[return: MaybeNull]
|
||||
public static T GetFromJson<T>(this HttpRequest request)
|
||||
public static T GetFromJson<T>(this HttpListenerRequest request)
|
||||
{
|
||||
using var streamReader = new StreamReader(request.Body, EncodingHelpers.UTF8);
|
||||
using var streamReader = new StreamReader(request.InputStream, EncodingHelpers.UTF8);
|
||||
using var jsonReader = new JsonTextReader(streamReader);
|
||||
|
||||
var serializer = new JsonSerializer();
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Robust.Server.Interfaces;
|
||||
using Robust.Server.Interfaces.ServerStatus;
|
||||
@@ -42,9 +41,9 @@ namespace Robust.Server.ServerStatus
|
||||
_statusHost.AddHandler(ShutdownHandler);
|
||||
}
|
||||
|
||||
private bool UpdateHandler(HttpMethod method, HttpRequest request, HttpResponse response)
|
||||
private bool UpdateHandler(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
{
|
||||
if (method != HttpMethod.Post || request.Path != "/update")
|
||||
if (method != HttpMethod.Post || request.Url!.AbsolutePath != "/update")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -56,18 +55,11 @@ namespace Robust.Server.ServerStatus
|
||||
}
|
||||
|
||||
var auth = request.Headers["WatchdogToken"];
|
||||
if (auth.Count != 1)
|
||||
{
|
||||
response.StatusCode = (int) HttpStatusCode.BadRequest;
|
||||
return true;
|
||||
}
|
||||
|
||||
var authVal = auth[0];
|
||||
|
||||
if (authVal != _watchdogToken)
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
// Holy shit nobody read these logs please.
|
||||
Logger.InfoS("watchdogApi", @"Failed auth: ""{0}"" vs ""{1}""", authVal, _watchdogToken);
|
||||
Logger.InfoS("watchdogApi", @"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
|
||||
response.StatusCode = (int) HttpStatusCode.Unauthorized;
|
||||
return true;
|
||||
}
|
||||
@@ -79,9 +71,9 @@ namespace Robust.Server.ServerStatus
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShutdownHandler(HttpMethod method, HttpRequest request, HttpResponse response)
|
||||
private bool ShutdownHandler(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
{
|
||||
if (method != HttpMethod.Post || request.Path != "/shutdown")
|
||||
if (method != HttpMethod.Post || request.Url!.AbsolutePath != "/shutdown")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -94,18 +86,11 @@ namespace Robust.Server.ServerStatus
|
||||
}
|
||||
|
||||
var auth = request.Headers["WatchdogToken"];
|
||||
if (auth.Count != 1)
|
||||
{
|
||||
response.StatusCode = (int) HttpStatusCode.BadRequest;
|
||||
return true;
|
||||
}
|
||||
|
||||
var authVal = auth[0];
|
||||
|
||||
if (authVal != _watchdogToken)
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
Logger.WarningS("watchdogApi",
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", authVal,
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
|
||||
_watchdogToken);
|
||||
response.StatusCode = (int) HttpStatusCode.Unauthorized;
|
||||
return true;
|
||||
|
||||
@@ -83,13 +83,16 @@ namespace Robust.Shared
|
||||
CVarDef.Create("metrics.port", 44880);
|
||||
|
||||
public static readonly CVarDef<bool> StatusEnabled =
|
||||
CVarDef.Create("status.enabled", true, CVar.ARCHIVE);
|
||||
CVarDef.Create("status.enabled", true, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<string> StatusBind =
|
||||
CVarDef.Create("status.bind", "*:1212", CVar.ARCHIVE);
|
||||
CVarDef.Create("status.bind", "*:1212", CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<int> StatusMaxConnections =
|
||||
CVarDef.Create("status.max_connections", 5, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<string> StatusConnectAddress =
|
||||
CVarDef.Create("status.connectaddress", "", CVar.ARCHIVE);
|
||||
CVarDef.Create("status.connectaddress", "", CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<string> BuildForkId =
|
||||
CVarDef.Create("build.fork_id", "", CVar.ARCHIVE);
|
||||
|
||||
Reference in New Issue
Block a user