233 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using MareSynchronosShared.Data;
 | |
| using MareSynchronosShared.Metrics;
 | |
| using MareSynchronosShared.Services;
 | |
| using MareSynchronosShared.Utils;
 | |
| using MareSynchronosStaticFilesServer.Controllers;
 | |
| using MareSynchronosStaticFilesServer.Services;
 | |
| using MareSynchronosStaticFilesServer.Utils;
 | |
| using MessagePack;
 | |
| using MessagePack.Resolvers;
 | |
| using Microsoft.AspNetCore.Authentication.JwtBearer;
 | |
| using Microsoft.AspNetCore.Authorization;
 | |
| using Microsoft.AspNetCore.Mvc.Controllers;
 | |
| using Microsoft.AspNetCore.Server.Kestrel.Core;
 | |
| using Microsoft.AspNetCore.SignalR;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| using Microsoft.IdentityModel.Tokens;
 | |
| using Prometheus;
 | |
| using StackExchange.Redis.Extensions.Core.Configuration;
 | |
| using StackExchange.Redis.Extensions.System.Text.Json;
 | |
| using StackExchange.Redis;
 | |
| using System.Net;
 | |
| using System.Text;
 | |
| 
 | |
| namespace MareSynchronosStaticFilesServer;
 | |
| 
 | |
| public class Startup
 | |
| {
 | |
|     private bool _isMain;
 | |
|     private readonly ILogger<Startup> _logger;
 | |
| 
 | |
|     public Startup(IConfiguration configuration, ILogger<Startup> logger)
 | |
|     {
 | |
|         Configuration = configuration;
 | |
|         _logger = logger;
 | |
|         var mareSettings = Configuration.GetRequiredSection("MareSynchronos");
 | |
|         _isMain = string.IsNullOrEmpty(mareSettings.GetValue(nameof(StaticFilesServerConfiguration.MainFileServerAddress), string.Empty));
 | |
|     }
 | |
| 
 | |
|     public IConfiguration Configuration { get; }
 | |
| 
 | |
|     public void ConfigureServices(IServiceCollection services)
 | |
|     {
 | |
|         services.AddHttpContextAccessor();
 | |
| 
 | |
|         services.AddLogging();
 | |
| 
 | |
|         services.Configure<StaticFilesServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
 | |
|         services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos"));
 | |
|         services.Configure<KestrelServerOptions>(Configuration.GetSection("Kestrel"));
 | |
|         services.AddSingleton(Configuration);
 | |
| 
 | |
|         var mareConfig = Configuration.GetRequiredSection("MareSynchronos");
 | |
| 
 | |
|         services.AddSingleton(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string>
 | |
|         {
 | |
|             MetricsAPI.CounterFileRequests,
 | |
|             MetricsAPI.CounterFileRequestSize
 | |
|         }, new List<string>
 | |
|         {
 | |
|             MetricsAPI.GaugeFilesTotalSize,
 | |
|             MetricsAPI.GaugeFilesTotal,
 | |
|             MetricsAPI.GaugeFilesUniquePastDay,
 | |
|             MetricsAPI.GaugeFilesUniquePastDaySize,
 | |
|             MetricsAPI.GaugeFilesUniquePastHour,
 | |
|             MetricsAPI.GaugeFilesUniquePastHourSize,
 | |
|             MetricsAPI.GaugeCurrentDownloads,
 | |
|             MetricsAPI.GaugeDownloadQueue,
 | |
|             MetricsAPI.GaugeQueueFree,
 | |
|             MetricsAPI.GaugeQueueInactive,
 | |
|             MetricsAPI.GaugeQueueActive,
 | |
|         }));
 | |
|         services.AddSingleton<CachedFileProvider>();
 | |
|         services.AddSingleton<FileStatisticsService>();
 | |
|         services.AddSingleton<RequestFileStreamResultFactory>();
 | |
| 
 | |
|         services.AddHostedService(m => m.GetService<FileStatisticsService>());
 | |
|         services.AddHostedService<FileCleanupService>();
 | |
| 
 | |
|         services.AddDbContextPool<MareDbContext>(options =>
 | |
|         {
 | |
|             options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
 | |
|             {
 | |
|                 builder.MigrationsHistoryTable("_efmigrationshistory", "public");
 | |
|             }).UseSnakeCaseNamingConvention();
 | |
|             options.EnableThreadSafetyChecks(false);
 | |
|         }, mareConfig.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024));
 | |
| 
 | |
|         services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
 | |
|             .Configure<IConfigurationService<MareConfigurationAuthBase>>((o, s) =>
 | |
|             {
 | |
|                 o.TokenValidationParameters = new()
 | |
|                 {
 | |
|                     ValidateIssuer = false,
 | |
|                     ValidateLifetime = false,
 | |
|                     ValidateAudience = false,
 | |
|                     ValidateIssuerSigningKey = true,
 | |
|                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(s.GetValue<string>(nameof(MareConfigurationAuthBase.Jwt)))),
 | |
|                 };
 | |
|             });
 | |
| 
 | |
|         services.AddAuthentication(o =>
 | |
|         {
 | |
|             o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 | |
|             o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 | |
|             o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
 | |
|         }).AddJwtBearer();
 | |
| 
 | |
|         services.AddAuthorization(options =>
 | |
|         {
 | |
|             options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
 | |
|             options.AddPolicy("Internal", new AuthorizationPolicyBuilder().RequireClaim(MareClaimTypes.Internal, "true").Build());
 | |
|         });
 | |
| 
 | |
|         if (_isMain)
 | |
|         {
 | |
|             services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceServer<StaticFilesServerConfiguration>>();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceClient<StaticFilesServerConfiguration>>();
 | |
|             services.AddHostedService(p => (MareConfigurationServiceClient<StaticFilesServerConfiguration>)p.GetService<IConfigurationService<StaticFilesServerConfiguration>>());
 | |
|         }
 | |
| 
 | |
|         services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceClient<MareConfigurationAuthBase>>();
 | |
| 
 | |
|         services.AddSingleton<ServerTokenGenerator>();
 | |
|         services.AddSingleton<RequestQueueService>();
 | |
|         services.AddHostedService(p => p.GetService<RequestQueueService>());
 | |
|         services.AddControllers().ConfigureApplicationPartManager(a =>
 | |
|         {
 | |
|             a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
 | |
|             if (_isMain)
 | |
|             {
 | |
|                 a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareStaticFilesServerConfigurationController), typeof(CacheController), typeof(RequestController), typeof(ServerFilesController)));
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(CacheController), typeof(RequestController)));
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
 | |
| 
 | |
|         services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
 | |
|         var signalRServiceBuilder = services.AddSignalR(hubOptions =>
 | |
|         {
 | |
|             hubOptions.MaximumReceiveMessageSize = long.MaxValue;
 | |
|             hubOptions.EnableDetailedErrors = true;
 | |
|             hubOptions.MaximumParallelInvocationsPerClient = 10;
 | |
|             hubOptions.StreamBufferCapacity = 200;
 | |
|         }).AddMessagePackProtocol(opt =>
 | |
|         {
 | |
|             var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance,
 | |
|                 BuiltinResolver.Instance,
 | |
|                 AttributeFormatterResolver.Instance,
 | |
|                 // replace enum resolver
 | |
|                 DynamicEnumAsStringResolver.Instance,
 | |
|                 DynamicGenericResolver.Instance,
 | |
|                 DynamicUnionResolver.Instance,
 | |
|                 DynamicObjectResolver.Instance,
 | |
|                 PrimitiveObjectResolver.Instance,
 | |
|                 // final fallback(last priority)
 | |
|                 StandardResolver.Instance);
 | |
| 
 | |
|             opt.SerializerOptions = MessagePackSerializerOptions.Standard
 | |
|                 .WithCompression(MessagePackCompression.Lz4Block)
 | |
|                 .WithResolver(resolver);
 | |
|         });
 | |
| 
 | |
|         // configure redis for SignalR
 | |
|         var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
 | |
|         signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options => { });
 | |
| 
 | |
|         var options = ConfigurationOptions.Parse(redisConnection);
 | |
| 
 | |
|         var endpoint = options.EndPoints[0];
 | |
|         string address = "";
 | |
|         int port = 0;
 | |
|         if (endpoint is DnsEndPoint dnsEndPoint) { address = dnsEndPoint.Host; port = dnsEndPoint.Port; }
 | |
|         if (endpoint is IPEndPoint ipEndPoint) { address = ipEndPoint.Address.ToString(); port = ipEndPoint.Port; }
 | |
|         var redisConfiguration = new RedisConfiguration()
 | |
|         {
 | |
|             AbortOnConnectFail = true,
 | |
|             KeyPrefix = "",
 | |
|             Hosts = new RedisHost[]
 | |
|             {
 | |
|                 new RedisHost(){ Host = address, Port = port },
 | |
|             },
 | |
|             AllowAdmin = true,
 | |
|             ConnectTimeout = options.ConnectTimeout,
 | |
|             Database = 0,
 | |
|             Ssl = false,
 | |
|             Password = options.Password,
 | |
|             ServerEnumerationStrategy = new ServerEnumerationStrategy()
 | |
|             {
 | |
|                 Mode = ServerEnumerationStrategy.ModeOptions.All,
 | |
|                 TargetRole = ServerEnumerationStrategy.TargetRoleOptions.Any,
 | |
|                 UnreachableServerAction = ServerEnumerationStrategy.UnreachableServerActionOptions.Throw,
 | |
|             },
 | |
|             MaxValueLength = 1024,
 | |
|             PoolSize = mareConfig.GetValue(nameof(ServerConfiguration.RedisPool), 50),
 | |
|             SyncTimeout = options.SyncTimeout,
 | |
|         };
 | |
| 
 | |
|         services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfiguration);
 | |
| 
 | |
|         services.AddHealthChecks();
 | |
|     }
 | |
| 
 | |
|     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 | |
|     {
 | |
|         app.UseHttpLogging();
 | |
| 
 | |
|         app.UseRouting();
 | |
| 
 | |
|         var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>();
 | |
| 
 | |
|         var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4981));
 | |
|         metricServer.Start();
 | |
| 
 | |
|         app.UseHttpMetrics();
 | |
| 
 | |
|         app.UseAuthentication();
 | |
|         app.UseAuthorization();
 | |
| 
 | |
|         app.UseEndpoints(e =>
 | |
|         {
 | |
|             e.MapHub<MareSynchronosServer.Hubs.MareHub>("/dummyhub");
 | |
|             e.MapControllers();
 | |
|             e.MapHealthChecks("/health").WithMetadata(new AllowAnonymousAttribute());
 | |
|         });
 | |
|     }
 | |
| } | 
