aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs102
-rw-r--r--Jellyfin.Api/MvcRoutePrefix.cs56
-rw-r--r--Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs13
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs9
-rw-r--r--Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs6
-rw-r--r--Jellyfin.Server/Program.cs2
-rw-r--r--Jellyfin.Server/Startup.cs94
-rw-r--r--MediaBrowser.Common/Configuration/IApplicationPaths.cs5
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs6
9 files changed, 75 insertions, 218 deletions
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index 33abe3ccd..3f0fc2e91 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers
{
@@ -26,39 +27,21 @@ namespace Jellyfin.Api.Controllers
{
private readonly ILogger<DashboardController> _logger;
private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _appConfig;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly IResourceFileManager _resourceFileManager;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary>
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
- /// <param name="appConfig">Instance of <see cref="IConfiguration"/> interface.</param>
- /// <param name="resourceFileManager">Instance of <see cref="IResourceFileManager"/> interface.</param>
- /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
public DashboardController(
ILogger<DashboardController> logger,
- IServerApplicationHost appHost,
- IConfiguration appConfig,
- IResourceFileManager resourceFileManager,
- IServerConfigurationManager serverConfigurationManager)
+ IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
- _appConfig = appConfig;
- _resourceFileManager = resourceFileManager;
- _serverConfigurationManager = serverConfigurationManager;
}
/// <summary>
- /// Gets the path of the directory containing the static web interface content, or null if the server is not
- /// hosting the web client.
- /// </summary>
- private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager);
-
- /// <summary>
/// Gets the configuration pages.
/// </summary>
/// <param name="enableInMainMenu">Whether to enable in the main menu.</param>
@@ -169,87 +152,6 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- /// <summary>
- /// Gets the robots.txt.
- /// </summary>
- /// <response code="200">Robots.txt returned.</response>
- /// <returns>The robots.txt.</returns>
- [HttpGet("robots.txt")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetRobotsTxt()
- {
- return GetWebClientResource("robots.txt");
- }
-
- /// <summary>
- /// Gets a resource from the web client.
- /// </summary>
- /// <param name="resourceName">The resource name.</param>
- /// <response code="200">Web client returned.</response>
- /// <response code="404">Server does not host a web client.</response>
- /// <returns>The resource.</returns>
- [HttpGet("web/{*resourceName}")]
- [ApiExplorerSettings(IgnoreApi = true)]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetWebClientResource([FromRoute] string resourceName)
- {
- if (!_appConfig.HostWebClient() || WebClientUiPath == null)
- {
- return NotFound("Server does not host a web client.");
- }
-
- var path = resourceName;
- var basePath = WebClientUiPath;
-
- var requestPathAndQuery = Request.GetEncodedPathAndQuery();
- // Bounce them to the startup wizard if it hasn't been completed yet
- if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted
- && !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase)
- && requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase))
- {
- return Redirect("index.html?start=wizard#!/wizardstart.html");
- }
-
- var stream = new FileStream(_resourceFileManager.GetResourcePath(basePath, path), FileMode.Open, FileAccess.Read);
- return File(stream, MimeTypes.GetMimeType(path));
- }
-
- /// <summary>
- /// Gets the favicon.
- /// </summary>
- /// <response code="200">Favicon.ico returned.</response>
- /// <returns>The favicon.</returns>
- [HttpGet("favicon.ico")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetFavIcon()
- {
- return GetWebClientResource("favicon.ico");
- }
-
- /// <summary>
- /// Gets the path of the directory containing the static web interface content.
- /// </summary>
- /// <param name="appConfig">The app configuration.</param>
- /// <param name="serverConfigManager">The server configuration manager.</param>
- /// <returns>The directory path, or null if the server is not hosting the web client.</returns>
- public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
- {
- if (!appConfig.HostWebClient())
- {
- return null;
- }
-
- if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
- {
- return serverConfigManager.Configuration.DashboardSourcePath;
- }
-
- return serverConfigManager.ApplicationPaths.WebPath;
- }
-
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
{
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
diff --git a/Jellyfin.Api/MvcRoutePrefix.cs b/Jellyfin.Api/MvcRoutePrefix.cs
deleted file mode 100644
index e00973094..000000000
--- a/Jellyfin.Api/MvcRoutePrefix.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ApplicationModels;
-
-namespace Jellyfin.Api
-{
- /// <summary>
- /// Route prefixing for ASP.NET MVC.
- /// </summary>
- public static class MvcRoutePrefix
- {
- /// <summary>
- /// Adds route prefixes to the MVC conventions.
- /// </summary>
- /// <param name="opts">The MVC options.</param>
- /// <param name="prefixes">The list of prefixes.</param>
- public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes)
- {
- opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes));
- }
-
- private class RoutePrefixConvention : IApplicationModelConvention
- {
- private readonly AttributeRouteModel[] _routePrefixes;
-
- public RoutePrefixConvention(IEnumerable<string> prefixes)
- {
- _routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray();
- }
-
- public void Apply(ApplicationModel application)
- {
- foreach (var controller in application.Controllers)
- {
- if (controller.Selectors == null)
- {
- continue;
- }
-
- var newSelectors = new List<SelectorModel>();
- foreach (var selector in controller.Selectors)
- {
- newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector)
- {
- AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel)
- }));
- }
-
- controller.Selectors.Clear();
- newSelectors.ForEach(selector => controller.Selectors.Add(selector));
- }
- }
- }
- }
-}
diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
index 2b002c6f3..c7fbfa4d0 100644
--- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
@@ -1,6 +1,8 @@
+using System.Collections.Generic;
using Jellyfin.Server.Middleware;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Builder;
+using Microsoft.OpenApi.Models;
namespace Jellyfin.Server.Extensions
{
@@ -23,6 +25,7 @@ namespace Jellyfin.Server.Extensions
// specifying the Swagger JSON endpoint.
var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/');
+ var apiDocBaseUrl = serverConfigurationManager.Configuration.BaseUrl;
if (!string.IsNullOrEmpty(baseUrl))
{
baseUrl += '/';
@@ -32,21 +35,25 @@ namespace Jellyfin.Server.Extensions
.UseSwagger(c =>
{
// Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs'
- c.RouteTemplate = $"/{baseUrl}{{documentName}}/openapi.json";
+ c.RouteTemplate = "{documentName}/openapi.json";
+ c.PreSerializeFilters.Add((swagger, httpReq) =>
+ {
+ swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } };
+ });
})
.UseSwaggerUI(c =>
{
c.DocumentTitle = "Jellyfin API";
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
- c.RoutePrefix = $"{baseUrl}api-docs/swagger";
c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
+ c.RoutePrefix = "api-docs/swagger";
})
.UseReDoc(c =>
{
c.DocumentTitle = "Jellyfin API";
c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
- c.RoutePrefix = $"{baseUrl}api-docs/redoc";
c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
+ c.RoutePrefix = "api-docs/redoc";
});
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index bcce19d3c..517d77412 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using Jellyfin.Api;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.DownloadPolicy;
@@ -18,7 +17,6 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
using Jellyfin.Server.Formatters;
using Jellyfin.Server.Models;
-using MediaBrowser.Common;
using MediaBrowser.Common.Json;
using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;
@@ -135,10 +133,9 @@ namespace Jellyfin.Server.Extensions
/// Extension method for adding the jellyfin API to the service collection.
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
- /// <param name="baseUrl">The base url for the API.</param>
- /// <param name="pluginAssemblies">An IEnumberable containing all plugin assemblies with API controllers.</param>
+ /// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
/// <returns>The MVC builder.</returns>
- public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl, IEnumerable<Assembly> pluginAssemblies)
+ public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies)
{
IMvcBuilder mvcBuilder = serviceCollection
.AddCors(options =>
@@ -151,8 +148,6 @@ namespace Jellyfin.Server.Extensions
})
.AddMvc(opts =>
{
- opts.UseGeneralRoutePrefix(baseUrl);
-
// Allow requester to change between camelCase and PascalCase
opts.RespectBrowserAcceptHeader = true;
diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
index 9316737bd..ae3a3a1c5 100644
--- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
@@ -44,11 +44,7 @@ namespace Jellyfin.Server.Middleware
var localPath = httpContext.Request.Path.ToString();
var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl;
- if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
- || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
- || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
- || string.IsNullOrEmpty(localPath)
- || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
+ if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
// Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index b9a90f9db..45959aec2 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -169,7 +169,7 @@ namespace Jellyfin.Server
// If hosting the web client, validate the client content path
if (startupConfig.HostWebClient())
{
- string? webContentPath = DashboardController.GetWebClientUiPath(startupConfig, appHost.ServerConfigurationManager);
+ string? webContentPath = appHost.ServerConfigurationManager.ApplicationPaths.WebPath;
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
{
throw new InvalidOperationException(
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index d05376470..597323b86 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -9,9 +9,12 @@ using Jellyfin.Server.Models;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Prometheus;
@@ -50,9 +53,7 @@ namespace Jellyfin.Server
{
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
- services.AddJellyfinApi(
- _serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'),
- _serverApplicationHost.GetApiPluginAssemblies());
+ services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies());
services.AddJellyfinApiSwagger();
@@ -87,57 +88,78 @@ namespace Jellyfin.Server
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="env">The webhost environment.</param>
+ /// <param name="appConfig">The application config.</param>
public void Configure(
IApplicationBuilder app,
- IWebHostEnvironment env)
+ IWebHostEnvironment env,
+ IConfiguration appConfig)
{
- if (env.IsDevelopment())
+ // Only add base url redirection if a base url is set.
+ if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl))
{
- app.UseDeveloperExceptionPage();
+ app.UseBaseUrlRedirection();
}
- app.UseMiddleware<ExceptionMiddleware>();
+ // Wrap rest of configuration so everything only listens on BaseUrl.
+ app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp =>
+ {
+ if (env.IsDevelopment())
+ {
+ mainApp.UseDeveloperExceptionPage();
+ }
- app.UseMiddleware<ResponseTimeMiddleware>();
+ mainApp.UseMiddleware<ExceptionMiddleware>();
- app.UseWebSockets();
+ mainApp.UseMiddleware<ResponseTimeMiddleware>();
- app.UseResponseCompression();
+ mainApp.UseWebSockets();
- app.UseCors(ServerCorsPolicy.DefaultPolicyName);
+ mainApp.UseResponseCompression();
- if (_serverConfigurationManager.Configuration.RequireHttps
- && _serverApplicationHost.ListenWithHttps)
- {
- app.UseHttpsRedirection();
- }
+ mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName);
- app.UseStaticFiles();
- app.UseAuthentication();
- app.UseJellyfinApiSwagger(_serverConfigurationManager);
- app.UseRouting();
- app.UseAuthorization();
- if (_serverConfigurationManager.Configuration.EnableMetrics)
- {
- // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
- app.UseHttpMetrics();
- }
+ if (_serverConfigurationManager.Configuration.RequireHttps
+ && _serverApplicationHost.ListenWithHttps)
+ {
+ mainApp.UseHttpsRedirection();
+ }
- app.UseLanFiltering();
- app.UseIpBasedAccessValidation();
- app.UseBaseUrlRedirection();
- app.UseWebSocketHandler();
- app.UseServerStartupMessage();
+ mainApp.UseStaticFiles();
+ if (appConfig.HostWebClient())
+ {
+ mainApp.UseStaticFiles(new StaticFileOptions
+ {
+ FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
+ RequestPath = "/web"
+ });
+ }
+
+ mainApp.UseAuthentication();
+ mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
+ mainApp.UseRouting();
+ mainApp.UseAuthorization();
+
+ mainApp.UseLanFiltering();
+ mainApp.UseIpBasedAccessValidation();
+ mainApp.UseWebSocketHandler();
+ mainApp.UseServerStartupMessage();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
- endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics");
+ // Must be registered after any middleware that could change HTTP response codes or the data will be bad
+ mainApp.UseHttpMetrics();
}
- endpoints.MapHealthChecks(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/health");
+ mainApp.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ if (_serverConfigurationManager.Configuration.EnableMetrics)
+ {
+ endpoints.MapMetrics("/metrics");
+ }
+
+ endpoints.MapHealthChecks("/health");
+ });
});
// Add type descriptor for legacy datetime parsing.
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 4cea61682..57c654667 100644
--- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs
+++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
@@ -1,5 +1,3 @@
-using MediaBrowser.Model.Configuration;
-
namespace MediaBrowser.Common.Configuration
{
/// <summary>
@@ -17,8 +15,7 @@ namespace MediaBrowser.Common.Configuration
/// Gets the path to the web UI resources folder.
/// </summary>
/// <remarks>
- /// This value is not relevant if the server is configured to not host any static web content. Additionally,
- /// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
+ /// This value is not relevant if the server is configured to not host any static web content.
/// </remarks>
string WebPath { get; }
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 33975bc1e..97748bd0c 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -167,12 +167,6 @@ namespace MediaBrowser.Model.Configuration
public bool EnableDashboardResponseCaching { get; set; }
/// <summary>
- /// Gets or sets a custom path to serve the dashboard from.
- /// </summary>
- /// <value>The dashboard source path, or null if the default path should be used.</value>
- public string DashboardSourcePath { get; set; }
-
- /// <summary>
/// Gets or sets the image saving convention.
/// </summary>
/// <value>The image saving convention.</value>