2
0
Эх сурвалжийг харах

Move appbuilder and service collection to Jellyfin.Server

Claus Vium 5 жил өмнө
parent
commit
27e3cf1558

+ 16 - 94
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;
 
         /// <summary>
         /// Gets the server configuration manager.
@@ -461,7 +460,7 @@ namespace Emby.Server.Implementations
         /// <param name="type">The type.</param>
         /// <returns>System.Object.</returns>
         public object CreateInstance(Type type)
-            => ActivatorUtilities.CreateInstance(_serviceProvider, type);
+            => ActivatorUtilities.CreateInstance(ServiceProvider, type);
 
         /// <summary>
         /// Creates an instance of type and resolves all constructor dependencies
@@ -469,7 +468,7 @@ namespace Emby.Server.Implementations
         /// /// <typeparam name="T">The type.</typeparam>
         /// <returns>T.</returns>
         public T CreateInstance<T>()
-            => ActivatorUtilities.CreateInstance<T>(_serviceProvider);
+            => ActivatorUtilities.CreateInstance<T>(ServiceProvider);
 
         /// <summary>
         /// 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
         /// </summary>
         /// <typeparam name="T">The type</typeparam>
         /// <returns>``0.</returns>
-        public T Resolve<T>() => _serviceProvider.GetService<T>();
+        public T Resolve<T>() => ServiceProvider.GetService<T>();
 
         /// <summary>
         /// 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<WebSocketMiddleware>();
-                    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<Task> next)
+        public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> 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<Task> next)
+        public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
         {
             if (context.WebSockets.IsWebSocketRequest)
             {
@@ -1090,9 +1010,9 @@ namespace Emby.Server.Implementations
         /// <summary>
         /// Finds the parts.
         /// </summary>
-        protected void FindParts()
+        public void FindParts()
         {
-            InstallationManager = _serviceProvider.GetService<IInstallationManager>();
+            InstallationManager = ServiceProvider.GetService<IInstallationManager>();
             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<string> 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; }
+
         /// <summary>
         /// Shuts down.
         /// </summary>

+ 1 - 2
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;

+ 2 - 2
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);
         }

+ 1 - 1
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;

+ 1 - 1
Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs → Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs

@@ -1,4 +1,4 @@
-namespace Jellyfin.Api.Models.Startup
+namespace Jellyfin.Api.Models.StartupDtos
 {
     /// <summary>
     /// The startup configuration DTO.

+ 1 - 1
Jellyfin.Api/Models/Startup/StartupUserDto.cs → Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs

@@ -1,4 +1,4 @@
-namespace Jellyfin.Api.Models.Startup
+namespace Jellyfin.Api.Models.StartupDtos
 {
     /// <summary>
     /// The startup user DTO.

+ 1 - 1
Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs → Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs

@@ -1,6 +1,6 @@
 using Microsoft.AspNetCore.Builder;
 
-namespace Jellyfin.Api.Extensions
+namespace Jellyfin.Server.Extensions
 {
     /// <summary>
     /// Extensions for adding API specific functionality to the application pipeline.

+ 2 - 9
Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs → Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -1,15 +1,14 @@
+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.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Authorization;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.OpenApi.Models;
 
-namespace Jellyfin.Api.Extensions
+namespace Jellyfin.Server.Extensions
 {
     /// <summary>
     /// API specific extensions for the service collection.
@@ -65,14 +64,8 @@ namespace Jellyfin.Api.Extensions
         {
             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())

+ 4 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -20,6 +20,10 @@
     <EmbeddedResource Include="Resources/Configuration/*" />
   </ItemGroup>
 
+  <ItemGroup>
+    <FrameworkReference Include="Microsoft.AspNetCore.App" />
+  </ItemGroup>
+  
   <!-- Code analyzers-->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
     <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />

+ 69 - 1
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<Startup>();
+        }
+
         /// <summary>
         /// 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%

+ 81 - 0
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
+{
+    /// <summary>
+    /// Startup configuration for the Kestrel webhost.
+    /// </summary>
+    public class Startup
+    {
+        private readonly IServerConfigurationManager _serverConfigurationManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Startup" /> class.
+        /// </summary>
+        /// <param name="serverConfigurationManager">The server configuration manager.</param>
+        public Startup(IServerConfigurationManager serverConfigurationManager)
+        {
+            _serverConfigurationManager = serverConfigurationManager;
+        }
+
+        /// <summary>
+        /// Configures the service collection for the webhost.
+        /// </summary>
+        /// <param name="services">The service collection.</param>
+        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();
+        }
+
+        /// <summary>
+        /// Configures the app builder for the webhost.
+        /// </summary>
+        /// <param name="app">The application builder.</param>
+        /// <param name="env">The webhost environment.</param>
+        /// <param name="serverApplicationHost">The server application host.</param>
+        public void Configure(
+            IApplicationBuilder app,
+            IWebHostEnvironment env,
+            IServerApplicationHost serverApplicationHost)
+        {
+            if (env.IsDevelopment())
+            {
+                app.UseDeveloperExceptionPage();
+            }
+
+            app.UseWebSockets();
+
+            app.UseResponseCompression();
+
+            // TODO app.UseMiddleware<WebSocketMiddleware>();
+            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);
+        }
+    }
+}

+ 5 - 0
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<Task> next);
+
+        Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next);
     }
 }