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