Proxy WASM track traffic through DeepDrftPublic to DeepDrftAPI
This commit is contained in:
@@ -4,17 +4,13 @@ using MudBlazor.Services;
|
|||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
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();
|
builder.Services.AddMudServices();
|
||||||
|
|
||||||
Startup.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
|
// Both named clients point at this host's own origin. DeepDrftPublic proxies the
|
||||||
Startup.ConfigureContentServices(builder.Services, contentApiUrl);
|
// 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);
|
Startup.ConfigureDomainServices(builder.Services);
|
||||||
|
|
||||||
var app = builder.Build();
|
await builder.Build().RunAsync();
|
||||||
|
|
||||||
await app.RunAsync();
|
|
||||||
|
|||||||
@@ -4,9 +4,5 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,13 +11,17 @@ builder.Services.AddMudServices();
|
|||||||
var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? throw new Exception("Content API URL is not configured");
|
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");
|
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.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
|
||||||
DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services);
|
|
||||||
DeepDrftPublic.Client.Startup.ConfigureContentServices(builder.Services, contentApiUrl);
|
DeepDrftPublic.Client.Startup.ConfigureContentServices(builder.Services, contentApiUrl);
|
||||||
|
DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services);
|
||||||
|
|
||||||
Startup.ConfigureDomainServices(builder);
|
Startup.ConfigureDomainServices(builder);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
.AddInteractiveServerComponents()
|
.AddInteractiveServerComponents()
|
||||||
.AddInteractiveWebAssemblyComponents();
|
.AddInteractiveWebAssemblyComponents();
|
||||||
@@ -99,6 +103,8 @@ if (app.Environment.IsDevelopment())
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode()
|
.AddInteractiveServerRenderMode()
|
||||||
.AddInteractiveWebAssemblyRenderMode()
|
.AddInteractiveWebAssemblyRenderMode()
|
||||||
|
|||||||
Reference in New Issue
Block a user