Proxy WASM track traffic through DeepDrftPublic to DeepDrftAPI

This commit is contained in:
Daniel Harvey
2026-05-25 19:11:00 -04:00
parent 02e230e236
commit e2a7944077
4 changed files with 121 additions and 15 deletions
+6 -10
View File
@@ -4,17 +4,13 @@ using MudBlazor.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
Console.WriteLine(builder.HostEnvironment.BaseAddress);
var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? "https://localhost:7001";
var sqlApiUrl = builder.Configuration["ApiUrls:SqlApi"] ?? "https://localhost:5002";
builder.Services.AddMudServices();
Startup.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
Startup.ConfigureContentServices(builder.Services, contentApiUrl);
// Both named clients point at this host's own origin. DeepDrftPublic proxies the
// public track routes to DeepDrftAPI internally so the browser never makes a
// cross-origin request.
Startup.ConfigureApiHttpClient(builder.Services, builder.HostEnvironment.BaseAddress);
Startup.ConfigureContentServices(builder.Services, builder.HostEnvironment.BaseAddress);
Startup.ConfigureDomainServices(builder.Services);
var app = builder.Build();
await app.RunAsync();
await builder.Build().RunAsync();
@@ -4,9 +4,5 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ApiUrls": {
"ContentApi": "https://media.deepdrft.com/",
"SqlApi": "https://api.deepdrft.com/"
}
}
@@ -0,0 +1,108 @@
using Microsoft.AspNetCore.Mvc;
namespace DeepDrftPublic.Controllers;
/// <summary>
/// Proxies public track API calls to DeepDrftAPI so the browser never makes
/// cross-origin requests. The WASM client points both named HttpClients at
/// this host; this controller forwards unauthenticated public routes upstream.
/// SSR prerender calls DeepDrftAPI directly (server-to-server) via the same
/// named clients — no proxy hop needed on the server side.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class TrackController : ControllerBase
{
private readonly HttpClient _upstream;
private readonly ILogger<TrackController> _logger;
public TrackController(IHttpClientFactory httpClientFactory, ILogger<TrackController> logger)
{
_upstream = httpClientFactory.CreateClient("DeepDrft.API");
_logger = logger;
}
/// <summary>Proxies paged track metadata from DeepDrftAPI.</summary>
[HttpGet("page")]
public async Task<ActionResult> GetPage(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? sortColumn = null,
[FromQuery] bool sortDescending = false,
CancellationToken ct = default)
{
var query = $"api/track/page?page={page}&pageSize={pageSize}&sortDescending={sortDescending}";
if (!string.IsNullOrWhiteSpace(sortColumn))
query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(query, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/page failed");
return StatusCode(502, "Upstream unavailable");
}
using (upstream)
{
if (!upstream.IsSuccessStatusCode)
{
_logger.LogWarning("DeepDrftAPI track/page returned {Status}", (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
var json = await upstream.Content.ReadAsStringAsync(ct);
return Content(json, "application/json");
}
}
/// <summary>
/// Proxies audio streaming from DeepDrftAPI. Passes the optional byte offset
/// so seek-beyond-buffer works through the proxy without buffering.
/// </summary>
[HttpGet("{trackId}")]
public async Task<ActionResult> GetTrack(
string trackId,
[FromQuery] long offset = 0,
CancellationToken ct = default)
{
_logger.LogInformation("Proxying track {TrackId} offset {Offset}", trackId, offset);
var path = offset == 0
? $"api/track/{Uri.EscapeDataString(trackId)}"
: $"api/track/{Uri.EscapeDataString(trackId)}?offset={offset}";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId} failed", trackId);
return StatusCode(502, "Upstream unavailable");
}
if (!upstream.IsSuccessStatusCode)
{
upstream.Dispose();
_logger.LogWarning("DeepDrftAPI track/{TrackId} returned {Status}", trackId, (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
// Do NOT dispose upstream here — File() takes ownership of the response stream
// and disposes it after the body is sent.
var contentType = upstream.Content.Headers.ContentType?.ToString() ?? "audio/wav";
var contentLength = upstream.Content.Headers.ContentLength;
// Forward Content-Length so the WASM player has duration info from the WAV header length.
if (contentLength.HasValue)
Response.ContentLength = contentLength.Value;
var stream = await upstream.Content.ReadAsStreamAsync(ct);
return File(stream, contentType, enableRangeProcessing: false);
}
}
+7 -1
View File
@@ -11,13 +11,17 @@ builder.Services.AddMudServices();
var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? throw new Exception("Content API URL is not configured");
var sqlApiUrl = builder.Configuration["ApiUrls:SqlApi"] ?? throw new Exception("ApiUrls:SqlApi is not configured");
// Server-side, both named clients point straight at DeepDrftAPI (server-to-server,
// no proxy hop). The TrackController below reuses the "DeepDrft.API" client to forward
// the WASM client's public track calls upstream.
DeepDrftPublic.Client.Startup.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services);
DeepDrftPublic.Client.Startup.ConfigureContentServices(builder.Services, contentApiUrl);
DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services);
Startup.ConfigureDomainServices(builder);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
@@ -99,6 +103,8 @@ if (app.Environment.IsDevelopment())
});
}
app.MapControllers();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()