From b5c9a811ddd835796f58e1631f09ee807d3c4c94 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 1 Jan 2014 22:56:19 -0800 Subject: [PATCH 1/2] New: Support for running from a sub folder (reverse proxy) --- .../Frontend/Mappers/IndexHtmlMapper.cs | 6 +- .../Frontend/StaticResourceModule.cs | 21 +++++- src/NzbDrone.Api/System/SystemModule.cs | 3 +- .../Configuration/ConfigFileProvider.cs | 16 +++++ .../MediaCover/MediaCoverService.cs | 7 +- .../AccessControl/UrlAclAdapter.cs | 64 +++++++++++++------ src/NzbDrone.Host/Owin/OwinHostController.cs | 18 ++---- src/UI/Content/font.less | 32 +++++----- src/UI/Handlebars/Helpers/Html.js | 11 +++- src/UI/Handlebars/Helpers/Series.js | 5 +- src/UI/Navbar/NavbarTemplate.html | 16 ++--- src/UI/Navbar/NavbarView.js | 2 +- src/UI/Series/SeriesController.js | 1 - src/UI/Settings/Indexers/CollectionView.js | 7 +- src/UI/Settings/Notifications/SchemaModal.js | 9 +-- src/UI/Shared/NotFoundTemplate.html | 3 +- src/UI/Shared/SignalRBroadcaster.js | 2 +- src/UI/System/Logs/Files/ContentsModel.js | 7 +- src/UI/System/Logs/Files/DownloadLogCell.js | 7 +- src/UI/app.js | 2 +- src/UI/index.html | 2 +- src/UI/jQuery/RouteBinder.js | 9 ++- 22 files changed, 161 insertions(+), 89 deletions(-) diff --git a/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs index 6baebd32c..46af86380 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Text.RegularExpressions; using Nancy; using NLog; using NzbDrone.Common; @@ -12,6 +13,7 @@ namespace NzbDrone.Api.Frontend.Mappers private readonly IDiskProvider _diskProvider; private readonly IConfigFileProvider _configFileProvider; private readonly string _indexPath; + private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase); public IndexHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, @@ -47,13 +49,15 @@ namespace NzbDrone.Api.Frontend.Mappers return StringToStream(GetIndexText()); } - private string GetIndexText() { var text = _diskProvider.ReadAllText(_indexPath); + text = ReplaceRegex.Replace(text, match => _configFileProvider.UrlBase + match.Value); + text = text.Replace(".css", ".css?v=" + BuildInfo.Version); text = text.Replace(".js", ".js?v=" + BuildInfo.Version); + text = text.Replace("API_ROOT", _configFileProvider.UrlBase + "/api"); text = text.Replace("API_KEY", _configFileProvider.ApiKey); text = text.Replace("APP_VERSION", BuildInfo.Version.ToString()); diff --git a/src/NzbDrone.Api/Frontend/StaticResourceModule.cs b/src/NzbDrone.Api/Frontend/StaticResourceModule.cs index 3c7542075..227d783f0 100644 --- a/src/NzbDrone.Api/Frontend/StaticResourceModule.cs +++ b/src/NzbDrone.Api/Frontend/StaticResourceModule.cs @@ -1,21 +1,25 @@ using System; using System.Collections.Generic; using System.Linq; +using Nancy.Responses; using NLog; using Nancy; using NzbDrone.Api.Frontend.Mappers; +using NzbDrone.Core.Configuration; namespace NzbDrone.Api.Frontend { public class StaticResourceModule : NancyModule { private readonly IEnumerable _requestMappers; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; - public StaticResourceModule(IEnumerable requestMappers, Logger logger) + public StaticResourceModule(IEnumerable requestMappers, IConfigFileProvider configFileProvider, Logger logger) { _requestMappers = requestMappers; + _configFileProvider = configFileProvider; _logger = logger; Get["/{resource*}"] = x => Index(); @@ -34,8 +38,21 @@ namespace NzbDrone.Api.Frontend return new NotFoundResponse(); } - var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); + //Redirect to the subfolder if the request went to the base URL + if (path.Equals("/")) + { + var urlBase = _configFileProvider.UrlBase; + if (!String.IsNullOrEmpty(urlBase)) + { + if (Request.Url.BasePath != urlBase) + { + return new RedirectResponse(urlBase + "/"); + } + } + } + + var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); if (mapper != null) { diff --git a/src/NzbDrone.Api/System/SystemModule.cs b/src/NzbDrone.Api/System/SystemModule.cs index f409612fd..be22c390e 100644 --- a/src/NzbDrone.Api/System/SystemModule.cs +++ b/src/NzbDrone.Api/System/SystemModule.cs @@ -43,7 +43,8 @@ namespace NzbDrone.Api.System IsWindows = OsInfo.IsWindows, Branch = _configFileProvider.Branch, Authentication = _configFileProvider.AuthenticationEnabled, - StartOfWeek = (int)OsInfo.FirstDayOfWeek + StartOfWeek = (int)OsInfo.FirstDayOfWeek, + UrlBase = _configFileProvider.UrlBase }.AsResponse(); } diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index 400f03a56..1bc4c3cf9 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -31,6 +31,7 @@ namespace NzbDrone.Core.Configuration string ApiKey { get; } bool Torrent { get; } string SslCertHash { get; } + string UrlBase { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -152,6 +153,21 @@ namespace NzbDrone.Core.Configuration get { return GetValue("SslCertHash", ""); } } + public string UrlBase + { + get + { + var urlBase = GetValue("UrlBase", ""); + + if (String.IsNullOrEmpty(urlBase)) + { + return urlBase; + } + + return "/" + urlBase.Trim('/').ToLower(); + } + } + public int GetValueInt(string key, int defaultValue) { return Convert.ToInt32(GetValue(key, defaultValue)); diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index a055dbef6..243b9d87f 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -5,6 +5,7 @@ using System.Net; using NLog; using NzbDrone.Common; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; @@ -19,16 +20,18 @@ namespace NzbDrone.Core.MediaCover private readonly IHttpProvider _httpProvider; private readonly IDiskProvider _diskProvider; private readonly ICoverExistsSpecification _coverExistsSpecification; + private readonly IConfigFileProvider _configFileProvider; private readonly Logger _logger; private readonly string _coverRootFolder; public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, - ICoverExistsSpecification coverExistsSpecification, Logger logger) + ICoverExistsSpecification coverExistsSpecification, IConfigFileProvider configFileProvider, Logger logger) { _httpProvider = httpProvider; _diskProvider = diskProvider; _coverExistsSpecification = coverExistsSpecification; + _configFileProvider = configFileProvider; _logger = logger; _coverRootFolder = appFolderInfo.GetMediaCoverPath(); @@ -96,7 +99,7 @@ namespace NzbDrone.Core.MediaCover { var filePath = GetCoverPath(seriesId, mediaCover.CoverType); - mediaCover.Url = @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; + mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; if (_diskProvider.FileExists(filePath)) { diff --git a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs index cca29eb2b..df6ad3f9d 100644 --- a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.EnvironmentInfo; @@ -9,8 +10,7 @@ namespace NzbDrone.Host.AccessControl public interface IUrlAclAdapter { void ConfigureUrl(); - string Url { get; } - string HttpsUrl { get; } + List Urls { get; } } public class UrlAclAdapter : IUrlAclAdapter @@ -20,13 +20,7 @@ namespace NzbDrone.Host.AccessControl private readonly IRuntimeInfo _runtimeInfo; private readonly Logger _logger; - public string Url { get; private set; } - public string HttpsUrl { get; private set; } - - private string _localUrl; - private string _wildcardUrl; - private string _localHttpsUrl; - private string _wildcardHttpsUrl; + public List Urls { get; private set; } public UrlAclAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, @@ -38,25 +32,31 @@ namespace NzbDrone.Host.AccessControl _runtimeInfo = runtimeInfo; _logger = logger; - _localUrl = String.Format("http://localhost:{0}/", _configFileProvider.Port); - _wildcardUrl = String.Format("http://*:{0}/", _configFileProvider.Port); - _localHttpsUrl = String.Format("https://localhost:{0}/", _configFileProvider.SslPort); - _wildcardHttpsUrl = String.Format("https://*:{0}/", _configFileProvider.SslPort); - - Url = _wildcardUrl; - HttpsUrl = _wildcardHttpsUrl; + Urls = new List(); } public void ConfigureUrl() { + var localHttpUrls = BuildUrls("http", "localhost", _configFileProvider.Port); + var wildcardHttpUrls = BuildUrls("http", "*", _configFileProvider.Port); + + var localHttpsUrls = BuildUrls("https", "localhost", _configFileProvider.SslPort); + var wildcardHttpsUrls = BuildUrls("https", "*", _configFileProvider.SslPort); + if (!_runtimeInfo.IsAdmin) { - if (!IsRegistered(_wildcardUrl)) Url = _localUrl; - if (!IsRegistered(_wildcardHttpsUrl)) HttpsUrl = _localHttpsUrl; + var httpUrls = wildcardHttpUrls.All(IsRegistered) ? wildcardHttpUrls : localHttpUrls; + var httpsUrls = wildcardHttpsUrls.All(IsRegistered) ? wildcardHttpsUrls : localHttpsUrls; + + Urls.AddRange(httpUrls); + Urls.AddRange(httpsUrls); } - if (_runtimeInfo.IsAdmin) + else { + Urls.AddRange(wildcardHttpUrls); + Urls.AddRange(wildcardHttpsUrls); + RefreshRegistration(); } } @@ -66,8 +66,7 @@ namespace NzbDrone.Host.AccessControl if (OsInfo.Version.Major < 6) return; - RegisterUrl(Url); - RegisterUrl(HttpsUrl); + Urls.ForEach(RegisterUrl); } private bool IsRegistered(string urlAcl) @@ -85,5 +84,28 @@ namespace NzbDrone.Host.AccessControl var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl); _netshProvider.Run(arguments); } + + private string BuildUrl(string protocol, string url, int port, string urlBase) + { + var result = protocol + "://" + url + ":" + port; + result += String.IsNullOrEmpty(urlBase) ? "/" : urlBase + "/"; + + return result; + } + + private List BuildUrls(string protocol, string url, int port) + { + var urls = new List(); + var urlBase = _configFileProvider.UrlBase; + + if (!String.IsNullOrEmpty(urlBase)) + { + urls.Add(BuildUrl(protocol, url, port, urlBase)); + } + + urls.Add(BuildUrl(protocol, url, port, "")); + + return urls; + } } } \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs index 1a5d4ae9b..0fe38af72 100644 --- a/src/NzbDrone.Host/Owin/OwinHostController.cs +++ b/src/NzbDrone.Host/Owin/OwinHostController.cs @@ -57,25 +57,21 @@ namespace NzbDrone.Host.Owin _urlAclAdapter.ConfigureUrl(); } - var options = new StartOptions(_urlAclAdapter.Url) + var options = new StartOptions() { ServerFactory = "Microsoft.Owin.Host.HttpListener" }; - if (_configFileProvider.EnableSsl) + _urlAclAdapter.Urls.ForEach(options.Urls.Add); + + _logger.Info("Listening on the following URLs:"); + foreach (var url in options.Urls) { - _logger.Trace("SSL enabled, listening on: {0}", _urlAclAdapter.HttpsUrl); - options.Urls.Add(_urlAclAdapter.HttpsUrl); + _logger.Info(" {0}", url); } - - _logger.Info("starting server on {0}", _urlAclAdapter.Url); - + try { - // options.ServerFactory = new - //_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); - //_host = WebApp.Start(options, BuildApp); - _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); } catch (TargetInvocationException ex) diff --git a/src/UI/Content/font.less b/src/UI/Content/font.less index 163ada0a6..f20b10dc1 100644 --- a/src/UI/Content/font.less +++ b/src/UI/Content/font.less @@ -2,46 +2,46 @@ font-family: 'Open Sans'; font-style: normal; font-weight: 300; - src: url('/Content/fonts/opensans-light.eot'); + src: url('./fonts/opensans-light.eot'); src: local('Open Sans Light'), local('OpenSans-Light'), - url('/Content/fonts/opensans-light.eot?#iefix') format('embedded-opentype'), - url('/Content/fonts/opensans-light.woff') format('woff'), - url('/Content/fonts/opensans-light.ttf') format('truetype'); + url('./fonts/opensans-light.eot?#iefix') format('embedded-opentype'), + url('./fonts/opensans-light.woff') format('woff'), + url('./fonts/opensans-light.ttf') format('truetype'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; - src: url('/Content/fonts/opensans-regular.eot'); + src: url('./fonts/opensans-regular.eot'); src: local('Open Sans'), local('OpenSans'), - url('/Content/fonts/opensans-regular.eot?#iefix') format('embedded-opentype'), - url('/Content/fonts/opensans-regular.woff') format('woff'), - url('/Content/fonts/opensans-regular.ttf') format('truetype') + url('./fonts/opensans-regular.eot?#iefix') format('embedded-opentype'), + url('./fonts/opensans-regular.woff') format('woff'), + url('./fonts/opensans-regular.ttf') format('truetype') } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 600; - src: url('/Content/fonts/opensans-semibold.eot'); + src: url('./fonts/opensans-semibold.eot'); src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), - url('/Content/fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'), - url('/Content/fonts/opensans-semibold.woff') format('woff'), - url('/Content/fonts/opensans-semibold.ttf') format('truetype') + url('./fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'), + url('./fonts/opensans-semibold.woff') format('woff'), + url('./fonts/opensans-semibold.ttf') format('truetype') } @font-face { font-family: 'Ubuntu Mono'; font-style: normal; font-weight: 400; - src: url('/Content/fonts/ubuntumono-regular.eot'); + src: url('./fonts/ubuntumono-regular.eot'); src: local('Open Sans'), local('OpenSans'), - url('/Content/fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'), - url('/Content/fonts/ubuntumono-regular.woff') format('woff'), - url('/Content/fonts/ubuntumono-regular.ttf') format('truetype') + url('./fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'), + url('./fonts/ubuntumono-regular.woff') format('woff'), + url('./fonts/ubuntumono-regular.ttf') format('truetype') } \ No newline at end of file diff --git a/src/UI/Handlebars/Helpers/Html.js b/src/UI/Handlebars/Helpers/Html.js index 9c5556c0f..8f4b2b19a 100644 --- a/src/UI/Handlebars/Helpers/Html.js +++ b/src/UI/Handlebars/Helpers/Html.js @@ -2,10 +2,11 @@ define( [ - 'handlebars' - ], function (Handlebars) { + 'handlebars', + 'System/StatusModel' + ], function (Handlebars, StatusModel) { - var placeHolder = '/Content/Images/poster-dark.jpg'; + var placeHolder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.jpg'; window.NzbDrone.imageError = function (img) { if (!img.src.contains(placeHolder)) { @@ -17,4 +18,8 @@ define( Handlebars.registerHelper('defaultImg', function () { return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)'); }); + + Handlebars.registerHelper('UrlBase', function () { + return new Handlebars.SafeString(StatusModel.get('urlBase')); + }); }); diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index 077b87814..9618ccf96 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -2,8 +2,9 @@ define( [ 'handlebars', + 'System/StatusModel', 'underscore' - ], function (Handlebars, _) { + ], function (Handlebars, StatusModel, _) { Handlebars.registerHelper('poster', function () { var poster = _.where(this.images, {coverType: 'poster'}); @@ -32,7 +33,7 @@ define( }); Handlebars.registerHelper('route', function () { - return '/series/' + this.titleSlug; + return StatusModel.get('urlBase') + '/series/' + this.titleSlug; }); Handlebars.registerHelper('percentOfEpisodes', function () { diff --git a/src/UI/Navbar/NavbarTemplate.html b/src/UI/Navbar/NavbarTemplate.html index 34d3b7631..df18cf702 100644 --- a/src/UI/Navbar/NavbarTemplate.html +++ b/src/UI/Navbar/NavbarTemplate.html @@ -3,47 +3,47 @@