Move appbuilder and service collection to Jellyfin.Server

This commit is contained in:
Claus Vium 2019-11-24 15:27:58 +01:00
parent 111b46599a
commit 27e3cf1558
12 changed files with 184 additions and 112 deletions

View File

@ -47,7 +47,6 @@ using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.SocketSharp;
using Emby.Server.Implementations.TV; using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Extensions;
using MediaBrowser.Api; using MediaBrowser.Api;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -232,7 +231,7 @@ namespace Emby.Server.Implementations
} }
} }
protected IServiceProvider _serviceProvider; public IServiceProvider ServiceProvider;
/// <summary> /// <summary>
/// Gets the server configuration manager. /// Gets the server configuration manager.
@ -461,7 +460,7 @@ namespace Emby.Server.Implementations
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object CreateInstance(Type type) public object CreateInstance(Type type)
=> ActivatorUtilities.CreateInstance(_serviceProvider, type); => ActivatorUtilities.CreateInstance(ServiceProvider, type);
/// <summary> /// <summary>
/// Creates an instance of type and resolves all constructor dependencies /// Creates an instance of type and resolves all constructor dependencies
@ -469,7 +468,7 @@ namespace Emby.Server.Implementations
/// /// <typeparam name="T">The type.</typeparam> /// /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns> /// <returns>T.</returns>
public T CreateInstance<T>() public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider); => ActivatorUtilities.CreateInstance<T>(ServiceProvider);
/// <summary> /// <summary>
/// Creates the instance safe. /// Creates the instance safe.
@ -481,7 +480,7 @@ namespace Emby.Server.Implementations
try try
{ {
Logger.LogDebug("Creating instance of {Type}", type); Logger.LogDebug("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type); return ActivatorUtilities.CreateInstance(ServiceProvider, type);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -495,7 +494,7 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
/// <typeparam name="T">The type</typeparam> /// <typeparam name="T">The type</typeparam>
/// <returns>``0.</returns> /// <returns>``0.</returns>
public T Resolve<T>() => _serviceProvider.GetService<T>(); public T Resolve<T>() => ServiceProvider.GetService<T>();
/// <summary> /// <summary>
/// Gets the export types. /// Gets the export types.
@ -611,93 +610,14 @@ namespace Emby.Server.Implementations
await RegisterResources(serviceCollection).ConfigureAwait(false); await RegisterResources(serviceCollection).ConfigureAwait(false);
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
if (string.IsNullOrEmpty(contentRoot)) if (string.IsNullOrEmpty(ContentRoot))
{ {
contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; 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;
} }
} }
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
{ {
if (!context.WebSockets.IsWebSocketRequest) if (!context.WebSockets.IsWebSocketRequest)
{ {
@ -708,7 +628,7 @@ namespace Emby.Server.Implementations
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); 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) if (context.WebSockets.IsWebSocketRequest)
{ {
@ -1090,9 +1010,9 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Finds the parts. /// Finds the parts.
/// </summary> /// </summary>
protected void FindParts() public void FindParts()
{ {
InstallationManager = _serviceProvider.GetService<IInstallationManager>(); InstallationManager = ServiceProvider.GetService<IInstallationManager>();
InstallationManager.PluginInstalled += PluginInstalled; InstallationManager.PluginInstalled += PluginInstalled;
if (!ServerConfigurationManager.Configuration.IsPortAuthorized) if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
@ -1221,7 +1141,7 @@ namespace Emby.Server.Implementations
private CertificateInfo CertificateInfo { get; set; } private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate2 Certificate { get; private set; } public X509Certificate2 Certificate { get; private set; }
private IEnumerable<string> GetUrlPrefixes() private IEnumerable<string> GetUrlPrefixes()
{ {
@ -1605,7 +1525,7 @@ namespace Emby.Server.Implementations
return resultList; return resultList;
} }
private IPAddress NormalizeConfiguredLocalAddress(string address) public IPAddress NormalizeConfiguredLocalAddress(string address)
{ {
var index = address.Trim('/').IndexOf('/'); var index = address.Trim('/').IndexOf('/');
@ -1685,6 +1605,8 @@ namespace Emby.Server.Implementations
public int HttpsPort { get; private set; } public int HttpsPort { get; private set; }
public string ContentRoot { get; private set; }
/// <summary> /// <summary>
/// Shuts down. /// Shuts down.
/// </summary> /// </summary>

View File

@ -18,7 +18,6 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer
{ {
OnReceive = ProcessWebSocketMessageReceived, OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url, Url = e.Url,
QueryString = e.QueryString ?? new QueryCollection() QueryString = e.QueryString
}; };
connection.Closed += OnConnectionClosed; connection.Closed += OnConnectionClosed;

View File

@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session
{ {
if (queryString == null) if (queryString == null)
{ {
throw new ArgumentNullException(nameof(queryString)); return null;
} }
var token = queryString["api_key"]; var token = queryString["api_key"];
@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session
{ {
return null; return null;
} }
var deviceId = queryString["deviceId"]; var deviceId = queryString["deviceId"];
return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint);
} }

View File

@ -1,6 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.Startup; using Jellyfin.Api.Models.StartupDtos;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

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

View File

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

View File

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

View File

@ -1,15 +1,14 @@
using Jellyfin.Api;
using Jellyfin.Api.Auth; using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Controllers; using Jellyfin.Api.Controllers;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
namespace Jellyfin.Api.Extensions namespace Jellyfin.Server.Extensions
{ {
/// <summary> /// <summary>
/// API specific extensions for the service collection. /// API specific extensions for the service collection.
@ -65,14 +64,8 @@ namespace Jellyfin.Api.Extensions
{ {
return serviceCollection.AddMvc(opts => return serviceCollection.AddMvc(opts =>
{ {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
opts.Filters.Add(new AuthorizeFilter(policy));
opts.EnableEndpointRouting = false;
opts.UseGeneralRoutePrefix(baseUrl); opts.UseGeneralRoutePrefix(baseUrl);
}) })
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
// Clear app parts to avoid other assemblies being picked up // Clear app parts to avoid other assemblies being picked up
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())

View File

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

View File

@ -18,8 +18,10 @@ using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Serilog.Extensions.Logging; using Serilog.Extensions.Logging;
@ -164,7 +166,24 @@ namespace Jellyfin.Server
appConfig); appConfig);
try 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); 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> /// <summary>
/// Create the data, config and log paths from the variety of inputs(command line args, /// 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% /// environment variables) or decide on what default to use. For Windows it's %AppPath%

View File

@ -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);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller namespace MediaBrowser.Controller
{ {
@ -87,5 +88,9 @@ namespace MediaBrowser.Controller
string ExpandVirtualPath(string path); string ExpandVirtualPath(string path);
string ReverseVirtualPath(string path); string ReverseVirtualPath(string path);
Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next);
Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next);
} }
} }