using Microsoft.AspNetCore.Mvc; namespace DeepDrftPublic.Controllers; /// /// Proxies public image API calls to DeepDrftAPI so the browser never makes /// cross-origin requests. Mirrors : the WASM /// client issues relative api/image/{entryKey} requests against this host, /// which forwards them upstream. SSR prerender calls DeepDrftAPI directly via the /// same named client — no proxy hop needed on the server side. /// [ApiController] [Route("api/image")] public class ImageProxyController : ControllerBase { private readonly HttpClient _upstream; private readonly ILogger _logger; public ImageProxyController(IHttpClientFactory httpClientFactory, ILogger logger) { _upstream = httpClientFactory.CreateClient("DeepDrft.API"); _logger = logger; } /// Proxies image binary streaming by vault entry key from DeepDrftAPI. [HttpGet("{entryKey}")] public async Task GetImage(string entryKey, CancellationToken ct = default) { _logger.LogInformation("Proxying image {EntryKey}", entryKey); var path = $"api/image/{Uri.EscapeDataString(entryKey)}"; HttpResponseMessage upstream; try { upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Upstream call to DeepDrftAPI image/{EntryKey} failed", entryKey); return StatusCode(502, "Upstream unavailable"); } if (!upstream.IsSuccessStatusCode) { upstream.Dispose(); _logger.LogWarning("DeepDrftAPI image/{EntryKey} returned {Status}", entryKey, (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() ?? "application/octet-stream"; var contentLength = upstream.Content.Headers.ContentLength; if (contentLength.HasValue) Response.ContentLength = contentLength.Value; var stream = await upstream.Content.ReadAsStreamAsync(ct); HttpContext.Response.RegisterForDispose(upstream); return File(stream, contentType, enableRangeProcessing: false); } }