Co-Authored-By: ta264 <ta264@users.noreply.github.com>pull/5116/head
parent
4d007855bc
commit
f79ae77a3a
@ -1,237 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Host.AccessControl
|
||||
{
|
||||
public interface IUrlAclAdapter
|
||||
{
|
||||
void ConfigureUrls();
|
||||
List<string> Urls { get; }
|
||||
}
|
||||
|
||||
public class UrlAclAdapter : IUrlAclAdapter
|
||||
{
|
||||
private readonly INetshProvider _netshProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IOsInfo _osInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public List<string> Urls
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalUrls.Select(c => c.Url).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<UrlAcl> InternalUrls { get; }
|
||||
private List<UrlAcl> RegisteredUrls { get; set; }
|
||||
|
||||
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public UrlAclAdapter(INetshProvider netshProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IOsInfo osInfo,
|
||||
Logger logger)
|
||||
{
|
||||
_netshProvider = netshProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_osInfo = osInfo;
|
||||
_logger = logger;
|
||||
|
||||
InternalUrls = new List<UrlAcl>();
|
||||
RegisteredUrls = new List<UrlAcl>();
|
||||
}
|
||||
|
||||
public void ConfigureUrls()
|
||||
{
|
||||
var enableSsl = _configFileProvider.EnableSsl;
|
||||
var port = _configFileProvider.Port;
|
||||
var sslPort = _configFileProvider.SslPort;
|
||||
|
||||
if (enableSsl && sslPort == port)
|
||||
{
|
||||
throw new SonarrStartupException("Cannot use the same port for HTTP and HTTPS. Port {0}", port);
|
||||
}
|
||||
|
||||
if (RegisteredUrls.Empty())
|
||||
{
|
||||
GetRegisteredUrls();
|
||||
}
|
||||
|
||||
var localHostHttpUrls = BuildUrlAcls("http", "localhost", port);
|
||||
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, port);
|
||||
|
||||
var localHostHttpsUrls = BuildUrlAcls("https", "localhost", sslPort);
|
||||
var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, sslPort);
|
||||
|
||||
if (!enableSsl)
|
||||
{
|
||||
localHostHttpsUrls.Clear();
|
||||
interfaceHttpsUrls.Clear();
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
|
||||
{
|
||||
var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls;
|
||||
var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls;
|
||||
|
||||
InternalUrls.AddRange(httpUrls);
|
||||
InternalUrls.AddRange(httpsUrls);
|
||||
|
||||
if (_configFileProvider.BindAddress != "*")
|
||||
{
|
||||
if (httpUrls.None(c => c.Address.Equals("localhost")))
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpUrls);
|
||||
}
|
||||
|
||||
if (httpsUrls.None(c => c.Address.Equals("localhost")))
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpsUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalUrls.AddRange(interfaceHttpUrls);
|
||||
InternalUrls.AddRange(interfaceHttpsUrls);
|
||||
|
||||
//Register localhost URLs so the IP Address doesn't need to be used from the local system
|
||||
if (_configFileProvider.BindAddress != "*")
|
||||
{
|
||||
InternalUrls.AddRange(localHostHttpUrls);
|
||||
InternalUrls.AddRange(localHostHttpsUrls);
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
RefreshRegistration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshRegistration()
|
||||
{
|
||||
var osVersion = new Version(_osInfo.Version);
|
||||
if (osVersion.Major < 6) return;
|
||||
|
||||
foreach (var urlAcl in InternalUrls)
|
||||
{
|
||||
if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue;
|
||||
|
||||
RemoveSimilar(urlAcl);
|
||||
RegisterUrl(urlAcl);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRegistered(UrlAcl urlAcl)
|
||||
{
|
||||
return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme &&
|
||||
c.Address == urlAcl.Address &&
|
||||
c.Port == urlAcl.Port &&
|
||||
c.UrlBase == urlAcl.UrlBase);
|
||||
}
|
||||
|
||||
private void GetRegisteredUrls()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RegisteredUrls.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var arguments = string.Format("http show urlacl");
|
||||
var output = _netshProvider.Run(arguments);
|
||||
|
||||
if (output == null || !output.Standard.Any()) return;
|
||||
|
||||
RegisteredUrls = output.Standard.Select(line =>
|
||||
{
|
||||
var match = UrlAclRegex.Match(line.Content);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return new UrlAcl
|
||||
{
|
||||
Scheme = match.Groups["scheme"].Value,
|
||||
Address = match.Groups["address"].Value,
|
||||
Port = Convert.ToInt32(match.Groups["port"].Value),
|
||||
UrlBase = match.Groups["urlbase"].Value.Trim()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}).Where(r => r != null).ToList();
|
||||
}
|
||||
|
||||
private void RegisterUrl(UrlAcl urlAcl)
|
||||
{
|
||||
var arguments = string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl.Url);
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private void RemoveSimilar(UrlAcl urlAcl)
|
||||
{
|
||||
var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme &&
|
||||
InternalUrls.None(x => x.Address == c.Address) &&
|
||||
c.Port == urlAcl.Port &&
|
||||
c.UrlBase == urlAcl.UrlBase);
|
||||
|
||||
foreach (var s in similar)
|
||||
{
|
||||
UnregisterUrl(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterUrl(UrlAcl urlAcl)
|
||||
{
|
||||
_logger.Trace("Removing URL ACL {0}", urlAcl.Url);
|
||||
|
||||
var arguments = string.Format("http delete urlacl {0}", urlAcl.Url);
|
||||
_netshProvider.Run(arguments);
|
||||
}
|
||||
|
||||
private List<UrlAcl> BuildUrlAcls(string scheme, string address, int port)
|
||||
{
|
||||
var urlAcls = new List<UrlAcl>();
|
||||
var urlBase = _configFileProvider.UrlBase;
|
||||
|
||||
if (urlBase.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
urlAcls.Add(new UrlAcl
|
||||
{
|
||||
Scheme = scheme,
|
||||
Address = address,
|
||||
Port = port,
|
||||
UrlBase = urlBase.Trim('/') + "/"
|
||||
});
|
||||
}
|
||||
|
||||
urlAcls.Add(new UrlAcl
|
||||
{
|
||||
Scheme = scheme,
|
||||
Address = address,
|
||||
Port = port,
|
||||
UrlBase = string.Empty
|
||||
});
|
||||
|
||||
return urlAcls;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace NzbDrone.Host.AccessControl
|
||||
{
|
||||
public interface IRemoteAccessAdapter
|
||||
{
|
||||
void MakeAccessible(bool passive);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using Owin;
|
||||
|
||||
namespace NzbDrone.Host.Owin.MiddleWare
|
||||
{
|
||||
public class NzbDroneVersionMiddleWare : IOwinMiddleWare
|
||||
{
|
||||
public int Order => 0;
|
||||
|
||||
public void Attach(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.Use(typeof(AddApplicationVersionHeader));
|
||||
}
|
||||
}
|
||||
|
||||
public class AddApplicationVersionHeader : OwinMiddleware
|
||||
{
|
||||
private readonly KeyValuePair<string, string[]> _versionHeader;
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AddApplicationVersionHeader));
|
||||
|
||||
public AddApplicationVersionHeader(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
_versionHeader = new KeyValuePair<string, string[]>("X-Application-Version", new[] { BuildInfo.Version.ToString() });
|
||||
}
|
||||
|
||||
public override async Task Invoke(IOwinContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Response.Headers.Add(_versionHeader);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.Debug("Unable to set version header");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.SignalR;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.SignalR;
|
||||
using Owin;
|
||||
|
||||
namespace NzbDrone.Host.Owin.MiddleWare
|
||||
{
|
||||
public class SignalRMiddleWare : IOwinMiddleWare
|
||||
{
|
||||
public int Order => 1;
|
||||
|
||||
public SignalRMiddleWare(IContainer container)
|
||||
{
|
||||
SignalRDependencyResolver.Register(container);
|
||||
SignalRJsonSerializer.Register();
|
||||
|
||||
// Note there are some important timeouts involved here:
|
||||
// nginx has a default 60 sec proxy_read_timeout, this means the connection will be terminated if the server doesn't send anything within that time.
|
||||
// Previously we lowered the ConnectionTimeout from 110s to 55s to remedy that, however all we should've done is set an appropriate KeepAlive.
|
||||
// By default KeepAlive is 1/3rd of the DisconnectTimeout, which we set incredibly high 5 years ago, resulting in KeepAlive being 1 minute.
|
||||
// So when adjusting these values in the future, please keep that all in mind.
|
||||
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
|
||||
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(180);
|
||||
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
public void Attach(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.MapSignalR("/signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Host.AccessControl;
|
||||
|
||||
namespace NzbDrone.Host.Owin
|
||||
{
|
||||
public class OwinHostController : IHostController
|
||||
{
|
||||
private readonly IOwinAppFactory _owinAppFactory;
|
||||
private readonly IRemoteAccessAdapter _remoteAccessAdapter;
|
||||
private readonly IUrlAclAdapter _urlAclAdapter;
|
||||
private readonly Logger _logger;
|
||||
private IDisposable _owinApp;
|
||||
|
||||
public OwinHostController(
|
||||
IOwinAppFactory owinAppFactory,
|
||||
IRemoteAccessAdapter remoteAccessAdapter,
|
||||
IUrlAclAdapter urlAclAdapter,
|
||||
Logger logger)
|
||||
{
|
||||
_owinAppFactory = owinAppFactory;
|
||||
_remoteAccessAdapter = remoteAccessAdapter;
|
||||
_urlAclAdapter = urlAclAdapter;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
_remoteAccessAdapter.MakeAccessible(true);
|
||||
|
||||
_logger.Info("Listening on the following URLs:");
|
||||
foreach (var url in _urlAclAdapter.Urls)
|
||||
{
|
||||
_logger.Info(" {0}", url);
|
||||
}
|
||||
|
||||
_owinApp = _owinAppFactory.CreateApp(_urlAclAdapter.Urls);
|
||||
}
|
||||
|
||||
public void StopServer()
|
||||
{
|
||||
if (_owinApp == null) return;
|
||||
|
||||
_logger.Info("Attempting to stop OWIN host");
|
||||
_owinApp.Dispose();
|
||||
_owinApp = null;
|
||||
_logger.Info("Host has stopped");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using Microsoft.Owin.Hosting;
|
||||
using Microsoft.Owin.Hosting.Engine;
|
||||
using Microsoft.Owin.Hosting.Services;
|
||||
using Microsoft.Owin.Hosting.Tracing;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Host.Owin.MiddleWare;
|
||||
using Owin;
|
||||
|
||||
namespace NzbDrone.Host.Owin
|
||||
{
|
||||
public interface IOwinAppFactory
|
||||
{
|
||||
IDisposable CreateApp(List<string> urls);
|
||||
}
|
||||
|
||||
public class OwinAppFactory : IOwinAppFactory
|
||||
{
|
||||
private readonly IEnumerable<IOwinMiddleWare> _owinMiddleWares;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public OwinAppFactory(IEnumerable<IOwinMiddleWare> owinMiddleWares, IConfigFileProvider configFileProvider, Logger logger)
|
||||
{
|
||||
_owinMiddleWares = owinMiddleWares;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IDisposable CreateApp(List<string> urls)
|
||||
{
|
||||
var services = CreateServiceFactory();
|
||||
var engine = services.GetService<IHostingEngine>();
|
||||
|
||||
var options = new StartOptions()
|
||||
{
|
||||
ServerFactory = "Microsoft.Owin.Host.HttpListener"
|
||||
};
|
||||
|
||||
urls.ForEach(options.Urls.Add);
|
||||
|
||||
var context = new StartContext(options) { Startup = BuildApp };
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return engine.Start(context);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
if (ex.InnerException == null)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (ex.InnerException is HttpListenerException)
|
||||
{
|
||||
throw new PortInUseException("Unable to bind to the designated IP Address/Port ({0}:{1}). Please ensure Sonarr is not already running, the bind address is correct (or is set to'*') and the port is not used", ex, _configFileProvider.BindAddress, _configFileProvider.Port);
|
||||
}
|
||||
|
||||
throw ex.InnerException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void BuildApp(IAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.Properties["host.AppName"] = BuildInfo.AppName;
|
||||
|
||||
foreach (var middleWare in _owinMiddleWares.OrderBy(c => c.Order))
|
||||
{
|
||||
_logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
|
||||
middleWare.Attach(appBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IServiceProvider CreateServiceFactory()
|
||||
{
|
||||
var provider = (ServiceProvider)ServicesFactory.Create();
|
||||
provider.Add(typeof(ITraceOutputFactory), typeof(OwinTraceOutputFactory));
|
||||
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace NzbDrone.Host.Middleware
|
||||
{
|
||||
public interface IAspNetCoreMiddleware
|
||||
{
|
||||
int Order { get; }
|
||||
void Attach(IApplicationBuilder appBuilder);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Host.Middleware;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Host.Middleware
|
||||
{
|
||||
public class SignalRMiddleware : IAspNetCoreMiddleware
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
private readonly Logger _logger;
|
||||
private static string API_KEY;
|
||||
public int Order => 1;
|
||||
|
||||
public SignalRMiddleware(IContainer container,
|
||||
IConfigFileProvider configFileProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_container = container;
|
||||
_logger = logger;
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
}
|
||||
|
||||
public void Attach(IApplicationBuilder appBuilder)
|
||||
{
|
||||
appBuilder.UseWebSockets();
|
||||
|
||||
appBuilder.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/signalr") &&
|
||||
!context.Request.Path.Value.EndsWith("/negotiate"))
|
||||
{
|
||||
if (!context.Request.Query.ContainsKey("access_token") ||
|
||||
context.Request.Query["access_token"] != API_KEY)
|
||||
{
|
||||
context.Response.StatusCode = 401;
|
||||
await context.Response.WriteAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
// Demote the exception to trace logging so users don't worry (as much).
|
||||
_logger.Trace(e);
|
||||
}
|
||||
});
|
||||
|
||||
appBuilder.UseSignalR(routes =>
|
||||
{
|
||||
routes.MapHub<MessageHub>("/signalr/messages");
|
||||
});
|
||||
|
||||
// This is a side effect of haing multiple IoC containers, TinyIoC and whatever
|
||||
// Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC
|
||||
// TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac).
|
||||
|
||||
var hubContext = appBuilder.ApplicationServices.GetService<IHubContext<MessageHub>>();
|
||||
_container.Register(hubContext);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Host;
|
||||
using NzbDrone.Host.AccessControl;
|
||||
using NzbDrone.Host.Middleware;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
public class WebHostController : IHostController
|
||||
{
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IFirewallAdapter _firewallAdapter;
|
||||
private readonly IEnumerable<IAspNetCoreMiddleware> _middlewares;
|
||||
private readonly Logger _logger;
|
||||
private IWebHost _host;
|
||||
|
||||
public WebHostController(IRuntimeInfo runtimeInfo,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IFirewallAdapter firewallAdapter,
|
||||
IEnumerable<IAspNetCoreMiddleware> middlewares,
|
||||
Logger logger)
|
||||
{
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
_firewallAdapter = firewallAdapter;
|
||||
_middlewares = middlewares;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
if (_runtimeInfo.IsAdmin)
|
||||
{
|
||||
_firewallAdapter.MakeAccessible();
|
||||
}
|
||||
}
|
||||
|
||||
var bindAddress = _configFileProvider.BindAddress;
|
||||
var enableSsl = _configFileProvider.EnableSsl;
|
||||
var sslCertPath = _configFileProvider.SslCertPath;
|
||||
|
||||
var urls = new List<string>();
|
||||
|
||||
urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port));
|
||||
|
||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort));
|
||||
}
|
||||
|
||||
_host = new WebHostBuilder()
|
||||
.UseUrls(urls.ToArray())
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
options.ConfigureHttpsDefaults(configureOptions =>
|
||||
{
|
||||
var certificate = new X509Certificate2();
|
||||
certificate.Import(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
|
||||
|
||||
configureOptions.ServerCertificate = certificate;
|
||||
});
|
||||
}
|
||||
})
|
||||
.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
serverOptions.AllowSynchronousIO = true;
|
||||
})
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.AddProvider(new NLogLoggerProvider());
|
||||
logging.SetMinimumLevel(LogLevel.Warning);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services
|
||||
.AddSignalR()
|
||||
.AddJsonProtocol(options =>
|
||||
{
|
||||
options.PayloadSerializerSettings = Json.GetSerializerSettings();
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UsePathBase(_configFileProvider.UrlBase);
|
||||
app.Properties["host.AppName"] = BuildInfo.AppName;
|
||||
|
||||
foreach (var middleWare in _middlewares.OrderBy(c => c.Order))
|
||||
{
|
||||
_logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
|
||||
middleWare.Attach(app);
|
||||
}
|
||||
})
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.Build();
|
||||
|
||||
_logger.Info("Listening on the following URLs:");
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
_logger.Info(" {0}", url);
|
||||
}
|
||||
|
||||
_host.Start();
|
||||
}
|
||||
|
||||
public async void StopServer()
|
||||
{
|
||||
_logger.Info("Attempting to stop OWIN host");
|
||||
|
||||
await _host.StopAsync(TimeSpan.FromSeconds(5));
|
||||
_host.Dispose();
|
||||
_host = null;
|
||||
|
||||
_logger.Info("Host has stopped");
|
||||
}
|
||||
|
||||
private string BuildUrl(string scheme, string bindAddress, int port)
|
||||
{
|
||||
return $"{scheme}://{bindAddress}:{port}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public interface IBroadcastSignalRMessage
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
Task BroadcastMessage(SignalRMessage message);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.SignalR
|
||||
{
|
||||
public class SignalRMessageBroadcaster : IBroadcastSignalRMessage
|
||||
{
|
||||
private readonly IHubContext<MessageHub> _hubContext;
|
||||
|
||||
public SignalRMessageBroadcaster(IHubContext<MessageHub> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public async Task BroadcastMessage(SignalRMessage message)
|
||||
{
|
||||
await _hubContext.Clients.All.SendAsync("receiveMessage", message);
|
||||
}
|
||||
|
||||
public bool IsConnected => MessageHub.IsConnected;
|
||||
}
|
||||
|
||||
public class MessageHub : Hub
|
||||
{
|
||||
private static HashSet<string> _connections = new HashSet<string>();
|
||||
|
||||
public static bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
return _connections.Count != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Add(Context.ConnectionId);
|
||||
}
|
||||
|
||||
var message = new SignalRMessage
|
||||
{
|
||||
Name = "version",
|
||||
Body = new
|
||||
{
|
||||
Version = BuildInfo.Version.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
await Clients.All.SendAsync("receiveMessage", message);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
lock (_connections)
|
||||
{
|
||||
_connections.Remove(Context.ConnectionId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue