forked from SoDOff-Project/sodoff
local asset server
This commit is contained in:
parent
7578ee7e97
commit
6bc5a0b140
13
src/Configuration/AssetServerConfig.cs
Normal file
13
src/Configuration/AssetServerConfig.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace sodoff.Configuration;
|
||||
public class AssetServerConfig {
|
||||
public bool Enabled { get; set; } = false;
|
||||
public int Port { get; set; } = 5001;
|
||||
public string URLPrefix { get; set; } = string.Empty;
|
||||
public AssetServerMode Mode { get; set; }
|
||||
public string ProviderURL { get; set; } = string.Empty;
|
||||
public bool SubstituteMissingLocalAssets { get; set; } = false;
|
||||
}
|
||||
|
||||
public enum AssetServerMode {
|
||||
None, Partial, Full
|
||||
}
|
99
src/Middleware/AssetMiddleware.cs
Normal file
99
src/Middleware/AssetMiddleware.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using sodoff.Configuration;
|
||||
|
||||
namespace sodoff.Middleware;
|
||||
public class AssetMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IOptions<AssetServerConfig> config;
|
||||
|
||||
public AssetMiddleware(RequestDelegate next, IOptions<AssetServerConfig> config)
|
||||
{
|
||||
_next = next;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Connection.LocalPort == config.Value.Port)
|
||||
await GetAssetAsync(context);
|
||||
else
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private async Task GetAssetAsync(HttpContext context)
|
||||
{
|
||||
string path = context.Request.Path;
|
||||
|
||||
if (path is null || !string.IsNullOrEmpty(config.Value.URLPrefix) && !path.StartsWith("/" + config.Value.URLPrefix) || config.Value.Mode == AssetServerMode.None) {
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
string assetPath = path.Remove(0, config.Value.URLPrefix.Length + 1);
|
||||
|
||||
string localPath = GetLocalPath("assets/" + assetPath);
|
||||
|
||||
if (localPath == string.Empty) {
|
||||
if (config.Value.Mode == AssetServerMode.Partial)
|
||||
await GetRemoteAsset(context, assetPath);
|
||||
else
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
else {
|
||||
context.Response.Headers["Content-Type"] = "application/octet-stream";
|
||||
await context.Response.SendFileAsync(Path.GetFullPath(localPath));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetRemoteAsset(HttpContext context, string path)
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
try {
|
||||
var response = await client.GetAsync(config.Value.ProviderURL + path);
|
||||
string? contentType = response.Content.Headers.ContentType?.MediaType;
|
||||
|
||||
if (contentType is null) {
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
else if (contentType.StartsWith("application/octet-stream") || contentType.StartsWith("image/jpeg")) {
|
||||
context.Response.Headers["Content-Type"] = contentType;
|
||||
await response.Content.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
else {
|
||||
context.Response.ContentType = contentType;
|
||||
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
}
|
||||
catch (Exception) {
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLocalPath(string path)
|
||||
{
|
||||
if (File.Exists(path)) return path;
|
||||
|
||||
string[] qualityTiers = { "/High/", "/Mid/", "/Low/" };
|
||||
|
||||
if (config.Value.SubstituteMissingLocalAssets)
|
||||
{
|
||||
foreach (var tier in qualityTiers)
|
||||
{
|
||||
if (path.Contains(tier))
|
||||
{
|
||||
foreach (var otherTier in qualityTiers)
|
||||
{
|
||||
if (otherTier != tier)
|
||||
{
|
||||
string otherPath = path.Replace(tier, otherTier);
|
||||
if (File.Exists(otherPath)) return otherPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using sodoff.Configuration;
|
||||
using sodoff.Middleware;
|
||||
using sodoff.Model;
|
||||
using sodoff.Services;
|
||||
using sodoff.Utils;
|
||||
@ -8,6 +11,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.Configure<AssetServerConfig>(builder.Configuration.GetSection("AssetServer"));
|
||||
builder.Services.AddControllers(options => {
|
||||
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(new XmlWriterSettings() { OmitXmlDeclaration = false }));
|
||||
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
|
||||
@ -27,6 +31,13 @@ builder.Services.AddScoped<InventoryService>();
|
||||
builder.Services.AddScoped<AchievementService>();
|
||||
builder.Services.AddScoped<GameDataService>();
|
||||
|
||||
bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled");
|
||||
int assetPort = builder.Configuration.GetSection("AssetServer").GetValue<int>("Port");
|
||||
if (assetServer)
|
||||
builder.Services.Configure<KestrelServerOptions>(options => {
|
||||
options.ListenAnyIP(assetPort);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using var scope = app.Services.CreateScope();
|
||||
@ -35,6 +46,9 @@ scope.ServiceProvider.GetRequiredService<DBContext>().Database.EnsureCreated();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
if (assetServer)
|
||||
app.UseMiddleware<AssetMiddleware>();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
Loading…
x
Reference in New Issue
Block a user