From 82f8345aa5ae65a8a934a4f2d29afdbc3836d875 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 10 Oct 2019 18:47:02 +0300 Subject: Log to debug all HTTP 500 response urls --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Emby.Server.Implementations/HttpServer/HttpListenerHost.cs') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e4f98acb9..0ebdf7d17 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -539,6 +539,10 @@ namespace Emby.Server.Implementations.HttpServer } finally { + if (httpRes.StatusCode >= 500) + { + _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog); + } stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) -- cgit v1.2.3 From 6b6fede2e0b1c1edd06af3bcdbf5c5ad04d07638 Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 16 Oct 2019 16:13:59 +0300 Subject: Address review comments --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 1 + MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations/HttpServer/HttpListenerHost.cs') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 0ebdf7d17..cd2a7dcf0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -543,6 +543,7 @@ namespace Emby.Server.Implementations.HttpServer { _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog); } + stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 2e9c1c3d4..fa8fbe261 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -296,13 +296,13 @@ namespace MediaBrowser.Api.Playback _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream); // Wait for the file to exist before proceeeding - var waitFor = state.WaitForPath ?? outputPath; - Logger.LogDebug("Waiting for the creation of {0}", waitFor); - while (!File.Exists(waitFor) && !transcodingJob.HasExited) + var ffmpegTargetFile = state.WaitForPath ?? outputPath; + Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); + while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) { await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } - Logger.LogDebug("File {0} created or transcoding has finished", waitFor); + Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) { -- cgit v1.2.3 From b14d6d0417b57adcd4eb7587401827b1bf2bba42 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Nov 2019 10:11:45 +0100 Subject: Fix GetPathValue function --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 -- MediaBrowser.Api/ApiEntryPoint.cs | 13 +++++-------- MediaBrowser.Api/BaseApiService.cs | 5 +++-- 3 files changed, 8 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations/HttpServer/HttpListenerHost.cs') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index cd2a7dcf0..dc1a56e27 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,7 +7,6 @@ using System.Net.Sockets; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; @@ -16,7 +15,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 7dca7e814..a5d65a716 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -40,14 +40,13 @@ namespace MediaBrowser.Api internal IHttpResultFactory ResultFactory { get; private set; } /// - /// The application paths + /// Gets the configuration manager. /// - private readonly IServerConfigurationManager _config; + internal IServerConfigurationManager ConfigurationManager { get; } private readonly ISessionManager _sessionManager; private readonly IFileSystem _fileSystem; private readonly IMediaSourceManager _mediaSourceManager; - public readonly IProcessFactory ProcessFactory; /// /// The active transcoding jobs @@ -73,15 +72,13 @@ namespace MediaBrowser.Api IServerConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager, - IProcessFactory processFactory, IHttpResultFactory resultFactory) { Logger = logger; _sessionManager = sessionManager; - _config = config; + ConfigurationManager = config; _fileSystem = fileSystem; _mediaSourceManager = mediaSourceManager; - ProcessFactory = processFactory; ResultFactory = resultFactory; _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; @@ -162,7 +159,7 @@ namespace MediaBrowser.Api public EncodingOptions GetEncodingOptions() { - return ConfigurationManagerExtensions.GetConfiguration(_config, "encoding"); + return ConfigurationManagerExtensions.GetConfiguration(ConfigurationManager, "encoding"); } /// @@ -170,7 +167,7 @@ namespace MediaBrowser.Api /// private void DeleteEncodedMediaCache() { - var path = _config.ApplicationPaths.GetTranscodingTempPath(); + var path = ConfigurationManager.ApplicationPaths.GetTranscodingTempPath(); if (!Directory.Exists(path)) { diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 49f8c6ace..328bda50f 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -299,8 +299,9 @@ namespace MediaBrowser.Api var first = pathInfo[0]; // backwards compatibility - if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) || - string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) + || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) + || string.Equals(first, ApiEntryPoint.Instance.ConfigurationManager.Configuration.BaseUrl)) { index++; } -- cgit v1.2.3 From 27e3cf15588f8ab8fe19aa611d79fa2ccd8ecda8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Nov 2019 15:27:58 +0100 Subject: Move appbuilder and service collection to Jellyfin.Server --- Emby.Server.Implementations/ApplicationHost.cs | 110 +++------------------ .../HttpServer/HttpListenerHost.cs | 3 +- .../Session/SessionWebSocketListener.cs | 4 +- Jellyfin.Api/Controllers/StartupController.cs | 2 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 27 ----- .../Extensions/ApiServiceCollectionExtensions.cs | 96 ------------------ .../Models/Startup/StartupConfigurationDto.cs | 23 ----- Jellyfin.Api/Models/Startup/StartupUserDto.cs | 18 ---- .../Models/StartupDtos/StartupConfigurationDto.cs | 23 +++++ Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs | 18 ++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 27 +++++ .../Extensions/ApiServiceCollectionExtensions.cs | 89 +++++++++++++++++ Jellyfin.Server/Jellyfin.Server.csproj | 4 + Jellyfin.Server/Program.cs | 70 ++++++++++++- Jellyfin.Server/Startup.cs | 81 +++++++++++++++ MediaBrowser.Controller/IServerApplicationHost.cs | 5 + 16 files changed, 336 insertions(+), 264 deletions(-) delete mode 100644 Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs delete mode 100644 Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupUserDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs create mode 100644 Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs create mode 100644 Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server/Startup.cs (limited to 'Emby.Server.Implementations/HttpServer/HttpListenerHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c6cdd4786..3b9ea4121 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,6 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Jellyfin.Api.Extensions; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -232,7 +231,7 @@ namespace Emby.Server.Implementations } } - protected IServiceProvider _serviceProvider; + public IServiceProvider ServiceProvider; /// /// Gets the server configuration manager. @@ -461,7 +460,7 @@ namespace Emby.Server.Implementations /// The type. /// System.Object. public object CreateInstance(Type type) - => ActivatorUtilities.CreateInstance(_serviceProvider, type); + => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// /// Creates an instance of type and resolves all constructor dependencies @@ -469,7 +468,7 @@ namespace Emby.Server.Implementations /// /// The type. /// T. public T CreateInstance() - => ActivatorUtilities.CreateInstance(_serviceProvider); + => ActivatorUtilities.CreateInstance(ServiceProvider); /// /// Creates the instance safe. @@ -481,7 +480,7 @@ namespace Emby.Server.Implementations try { Logger.LogDebug("Creating instance of {Type}", type); - return ActivatorUtilities.CreateInstance(_serviceProvider, type); + return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { @@ -495,7 +494,7 @@ namespace Emby.Server.Implementations /// /// The type /// ``0. - public T Resolve() => _serviceProvider.GetService(); + public T Resolve() => ServiceProvider.GetService(); /// /// Gets the export types. @@ -611,93 +610,14 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection).ConfigureAwait(false); - string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(contentRoot)) + ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(ContentRoot)) { - contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } - - var host = new WebHostBuilder() - .UseKestrel(options => - { - var addresses = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .Where(i => i != null) - .ToList(); - if (addresses.Any()) - { - foreach (var address in addresses) - { - Logger.LogInformation("Kestrel listening on {ipaddr}", address); - options.Listen(address, HttpPort); - - if (EnableHttps && Certificate != null) - { - options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - } - else - { - Logger.LogInformation("Kestrel listening on all interfaces"); - options.ListenAnyIP(HttpPort); - - if (EnableHttps && Certificate != null) - { - options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - }) - .UseContentRoot(contentRoot) - .ConfigureServices(services => - { - services.AddResponseCompression(); - services.AddHttpContextAccessor(); - services.AddJellyfinApi(ServerConfigurationManager.Configuration.BaseUrl.TrimStart('/')); - - services.AddJellyfinApiSwagger(); - - // configure custom legacy authentication - services.AddCustomAuthentication(); - - services.AddJellyfinApiAuthorization(); - - // Merge the external ServiceCollection into ASP.NET DI - services.TryAdd(serviceCollection); - }) - .Configure(app => - { - app.UseWebSockets(); - - app.UseResponseCompression(); - - // TODO app.UseMiddleware(); - app.Use(ExecuteWebsocketHandlerAsync); - - // TODO use when old API is removed: app.UseAuthentication(); - app.UseJellyfinApiSwagger(); - app.UseMvc(); - app.Use(ExecuteHttpHandlerAsync); - }) - .Build(); - - _serviceProvider = host.Services; - FindParts(); - - try - { - await host.StartAsync().ConfigureAwait(false); - } - catch - { - Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); - throw; + ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } } - private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -708,7 +628,7 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { @@ -1090,9 +1010,9 @@ namespace Emby.Server.Implementations /// /// Finds the parts. /// - protected void FindParts() + public void FindParts() { - InstallationManager = _serviceProvider.GetService(); + InstallationManager = ServiceProvider.GetService(); InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) @@ -1221,7 +1141,7 @@ namespace Emby.Server.Implementations private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate2 Certificate { get; private set; } + public X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { @@ -1605,7 +1525,7 @@ namespace Emby.Server.Implementations return resultList; } - private IPAddress NormalizeConfiguredLocalAddress(string address) + public IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1685,6 +1605,8 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } + public string ContentRoot { get; private set; } + /// /// Shuts down. /// diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dc1a56e27..6dd016f8a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryCollection() + QueryString = e.QueryString }; connection.Closed += OnConnectionClosed; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 63ec75762..930f2d35d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session { if (queryString == null) { - throw new ArgumentNullException(nameof(queryString)); + return null; } var token = queryString["api_key"]; @@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session { return null; } + var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 0e7d17a27..50f3dc83c 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -1,6 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using Jellyfin.Api.Models.Startup; +using Jellyfin.Api.Models.StartupDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs deleted file mode 100644 index f70466ebe..000000000 --- a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace Jellyfin.Api.Extensions -{ - /// - /// Extensions for adding API specific functionality to the application pipeline. - /// - public static class ApiApplicationBuilderExtensions - { - /// - /// Adds swagger and swagger UI to the application pipeline. - /// - /// The application builder. - /// The updated application builder. - public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) - { - applicationBuilder.UseSwagger(); - - // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), - // specifying the Swagger JSON endpoint. - return applicationBuilder.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); - }); - } - } -} diff --git a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs deleted file mode 100644 index 38f5f6d39..000000000 --- a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Jellyfin.Api.Auth; -using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; -using Jellyfin.Api.Auth.RequiresElevationPolicy; -using Jellyfin.Api.Controllers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; - -namespace Jellyfin.Api.Extensions -{ - /// - /// API specific extensions for the service collection. - /// - public static class ApiServiceCollectionExtensions - { - /// - /// Adds jellyfin API authorization policies to the DI container. - /// - /// The service collection. - /// The updated service collection. - public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - return serviceCollection.AddAuthorizationCore(options => - { - options.AddPolicy( - "RequiresElevation", - policy => - { - policy.AddAuthenticationSchemes("CustomAuthentication"); - policy.AddRequirements(new RequiresElevationRequirement()); - }); - options.AddPolicy( - "FirstTimeSetupOrElevated", - policy => - { - policy.AddAuthenticationSchemes("CustomAuthentication"); - policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); - }); - }); - } - - /// - /// Adds custom legacy authentication to the service collection. - /// - /// The service collection. - /// The updated service collection. - public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) - { - return serviceCollection.AddAuthentication("CustomAuthentication") - .AddScheme("CustomAuthentication", null); - } - - /// - /// Extension method for adding the jellyfin API to the service collection. - /// - /// The service collection. - /// The base url for the API. - /// The MVC builder. - public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) - { - return serviceCollection.AddMvc(opts => - { - var policy = new AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - opts.Filters.Add(new AuthorizeFilter(policy)); - opts.EnableEndpointRouting = false; - opts.UseGeneralRoutePrefix(baseUrl); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) - - // Clear app parts to avoid other assemblies being picked up - .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) - .AddApplicationPart(typeof(StartupController).Assembly) - .AddControllersAsServices(); - } - - /// - /// Adds Swagger to the service collection. - /// - /// The service collection. - /// The updated service collection. - public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) - { - return serviceCollection.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); - }); - } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs deleted file mode 100644 index dac15e412..000000000 --- a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup configuration DTO. - /// - public class StartupConfigurationDto - { - /// - /// Gets or sets UI language culture. - /// - public string UICulture { get; set; } - - /// - /// Gets or sets the metadata country code. - /// - public string MetadataCountryCode { get; set; } - - /// - /// Gets or sets the preferred language for the metadata. - /// - public string PreferredMetadataLanguage { get; set; } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupUserDto.cs b/Jellyfin.Api/Models/Startup/StartupUserDto.cs deleted file mode 100644 index 7e890d76a..000000000 --- a/Jellyfin.Api/Models/Startup/StartupUserDto.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup user DTO. - /// - public class StartupUserDto - { - /// - /// Gets or sets the username. - /// - public string Name { get; set; } - - /// - /// Gets or sets the user's password. - /// - public string Password { get; set; } - } -} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs new file mode 100644 index 000000000..d048dad0a --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup configuration DTO. + /// + public class StartupConfigurationDto + { + /// + /// Gets or sets UI language culture. + /// + public string UICulture { get; set; } + + /// + /// Gets or sets the metadata country code. + /// + public string MetadataCountryCode { get; set; } + + /// + /// Gets or sets the preferred language for the metadata. + /// + public string PreferredMetadataLanguage { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs new file mode 100644 index 000000000..3a9348037 --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup user DTO. + /// + public class StartupUserDto + { + /// + /// Gets or sets the username. + /// + public string Name { get; set; } + + /// + /// Gets or sets the user's password. + /// + public string Password { get; set; } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs new file mode 100644 index 000000000..db06eb455 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Builder; + +namespace Jellyfin.Server.Extensions +{ + /// + /// Extensions for adding API specific functionality to the application pipeline. + /// + public static class ApiApplicationBuilderExtensions + { + /// + /// Adds swagger and swagger UI to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) + { + applicationBuilder.UseSwagger(); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + return applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); + }); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs new file mode 100644 index 000000000..e5a8937e8 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -0,0 +1,89 @@ +using Jellyfin.Api; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; +using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Controllers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace Jellyfin.Server.Extensions +{ + /// + /// API specific extensions for the service collection. + /// + public static class ApiServiceCollectionExtensions + { + /// + /// Adds jellyfin API authorization policies to the DI container. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.AddAuthorizationCore(options => + { + options.AddPolicy( + "RequiresElevation", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new RequiresElevationRequirement()); + }); + options.AddPolicy( + "FirstTimeSetupOrElevated", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); + }); + }); + } + + /// + /// Adds custom legacy authentication to the service collection. + /// + /// The service collection. + /// The updated service collection. + public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) + { + return serviceCollection.AddAuthentication("CustomAuthentication") + .AddScheme("CustomAuthentication", null); + } + + /// + /// Extension method for adding the jellyfin API to the service collection. + /// + /// The service collection. + /// The base url for the API. + /// The MVC builder. + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) + { + return serviceCollection.AddMvc(opts => + { + opts.UseGeneralRoutePrefix(baseUrl); + }) + + // Clear app parts to avoid other assemblies being picked up + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) + .AddApplicationPart(typeof(StartupController).Assembly) + .AddControllersAsServices(); + } + + /// + /// Adds Swagger to the service collection. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) + { + return serviceCollection.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + }); + } + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 4238d7fe3..dc784becf 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e8bd0cd30..998f1125f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -18,8 +18,10 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; @@ -164,7 +166,24 @@ namespace Jellyfin.Server appConfig); try { - await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); + ServiceCollection serviceCollection = new ServiceCollection(); + await appHost.InitAsync(serviceCollection).ConfigureAwait(false); + + var host = CreateWebHostBuilder(appHost, serviceCollection).Build(); + + // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + appHost.ServiceProvider = host.Services; + appHost.FindParts(); + + try + { + await host.StartAsync().ConfigureAwait(false); + } + catch + { + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); + throw; + } appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); @@ -196,6 +215,55 @@ namespace Jellyfin.Server } } + private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) + { + return new WebHostBuilder() + .UseKestrel(options => + { + var addresses = appHost.ServerConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(appHost.NormalizeConfiguredLocalAddress) + .Where(i => i != null) + .ToList(); + if (addresses.Any()) + { + foreach (var address in addresses) + { + _logger.LogInformation("Kestrel listening on {ipaddr}", address); + options.Listen(address, appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.Listen( + address, + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + } + else + { + _logger.LogInformation("Kestrel listening on all interfaces"); + options.ListenAnyIP(appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.ListenAnyIP( + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + }) + .UseContentRoot(appHost.ContentRoot) + .ConfigureServices(services => + { + // Merge the external ServiceCollection into ASP.NET DI + services.TryAdd(serviceCollection); + }) + .UseStartup(); + } + /// /// Create the data, config and log paths from the variety of inputs(command line args, /// environment variables) or decide on what default to use. For Windows it's %AppPath% diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs new file mode 100644 index 000000000..3ee5fb8b5 --- /dev/null +++ b/Jellyfin.Server/Startup.cs @@ -0,0 +1,81 @@ +using Jellyfin.Server.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Server +{ + /// + /// Startup configuration for the Kestrel webhost. + /// + public class Startup + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server configuration manager. + public Startup(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + /// Configures the service collection for the webhost. + /// + /// The service collection. + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + services.AddHttpContextAccessor(); + services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/')); + + services.AddJellyfinApiSwagger(); + + // configure custom legacy authentication + services.AddCustomAuthentication(); + + services.AddJellyfinApiAuthorization(); + } + + /// + /// Configures the app builder for the webhost. + /// + /// The application builder. + /// The webhost environment. + /// The server application host. + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IServerApplicationHost serverApplicationHost) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseWebSockets(); + + app.UseResponseCompression(); + + // TODO app.UseMiddleware(); + app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); + + // TODO use when old API is removed: app.UseAuthentication(); + app.UseJellyfinApiSwagger(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + } + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 61b2c15ae..b3c56bdd5 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -87,5 +88,9 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); + + Task ExecuteHttpHandlerAsync(HttpContext context, Func next); + + Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next); } } -- cgit v1.2.3