using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking.Configuration;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Infrastructure;
using Jellyfin.Server.Middleware;
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.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Prometheus;
namespace Jellyfin.Server
{
///
/// Startup configuration for the Kestrel webhost.
///
public class Startup
{
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IServerApplicationHost _serverApplicationHost;
///
/// Initializes a new instance of the class.
///
/// The server configuration manager.
/// The server application host.
public Startup(
IServerConfigurationManager serverConfigurationManager,
IServerApplicationHost serverApplicationHost)
{
_serverConfigurationManager = serverConfigurationManager;
_serverApplicationHost = serverApplicationHost;
}
///
/// Configures the service collection for the webhost.
///
/// The service collection.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
services.AddHttpsRedirection(options =>
{
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
// TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
services.AddSingleton, SymlinkFollowingPhysicalFileResultExecutor>();
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
services.AddJellyfinApiSwagger();
// configure custom legacy authentication
services.AddCustomAuthentication();
services.AddJellyfinApiAuthorization();
var productHeader = new ProductInfoHeaderValue(
_serverApplicationHost.Name.Replace(' ', '-'),
_serverApplicationHost.ApplicationVersionString);
var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0);
var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9);
var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8);
Func defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler()
{
AutomaticDecompression = DecompressionMethods.All,
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
};
services
.AddHttpClient(NamedClient.Default, c =>
{
c.DefaultRequestHeaders.UserAgent.Add(productHeader);
c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader);
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
services.AddHttpClient(NamedClient.MusicBrainz, c =>
{
c.DefaultRequestHeaders.UserAgent.Add(productHeader);
c.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({_serverApplicationHost.ApplicationUserAgentAddress})"));
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
services.AddHttpClient(NamedClient.Dlna, c =>
{
c.DefaultRequestHeaders.UserAgent.ParseAdd(
string.Format(
CultureInfo.InvariantCulture,
"{0}/{1} UPnP/1.0 {2}/{3}",
MediaBrowser.Common.System.OperatingSystem.Name,
Environment.OSVersion,
_serverApplicationHost.Name,
_serverApplicationHost.ApplicationVersionString));
c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
services.AddHealthChecks()
.AddDbContextCheck();
services.AddHlsPlaylistGenerator();
}
///
/// Configures the app builder for the webhost.
///
/// The application builder.
/// The webhost environment.
/// The application config.
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IConfiguration appConfig)
{
app.UseBaseUrlRedirection();
// Wrap rest of configuration so everything only listens on BaseUrl.
var config = _serverConfigurationManager.GetNetworkConfiguration();
app.Map(config.BaseUrl, mainApp =>
{
if (env.IsDevelopment())
{
mainApp.UseDeveloperExceptionPage();
}
mainApp.UseForwardedHeaders();
mainApp.UseMiddleware();
mainApp.UseMiddleware();
mainApp.UseWebSockets();
mainApp.UseResponseCompression();
mainApp.UseCors();
if (config.RequireHttps && _serverApplicationHost.ListenWithHttps)
{
mainApp.UseHttpsRedirection();
}
// This must be injected before any path related middleware.
mainApp.UsePathTrim();
mainApp.UseStaticFiles();
if (appConfig.HostWebClient())
{
var extensionProvider = new FileExtensionContentTypeProvider();
// subtitles octopus requires .data, .mem files.
extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet);
extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
mainApp.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
RequestPath = "/web",
ContentTypeProvider = extensionProvider
});
mainApp.UseRobotsRedirection();
}
mainApp.UseAuthentication();
mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
mainApp.UseQueryStringDecoding();
mainApp.UseRouting();
mainApp.UseAuthorization();
mainApp.UseLanFiltering();
mainApp.UseIpBasedAccessValidation();
mainApp.UseWebSocketHandler();
mainApp.UseServerStartupMessage();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
// Must be registered after any middleware that could change HTTP response codes or the data will be bad
mainApp.UseHttpMetrics();
}
mainApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
endpoints.MapMetrics("/metrics");
}
endpoints.MapHealthChecks("/health");
});
});
}
}
}