raise upload size cap to ~1.86 GB and nginx timeouts to 1200s
Raise RequestSizeLimit/MultipartBodyLengthLimit on upload+replace-audio, MaxUploadBytes in BatchUpload/BatchEdit, and DefaultResponseTimeoutSeconds to 1200s. Add client_max_body_size 2000m and proxy_read/send_timeout 1200s to the nginx manager/public confs.
This commit is contained in:
@@ -198,14 +198,15 @@ public class TrackController : ControllerBase
|
|||||||
// proxies the upload here so it never touches the vault disk path or SQL directly.
|
// proxies the upload here so it never touches the vault disk path or SQL directly.
|
||||||
// UnifiedTrackService owns the two-database write.
|
// UnifiedTrackService owns the two-database write.
|
||||||
//
|
//
|
||||||
// RequestSizeLimit/MultipartBodyLengthLimit set to 1 GB: audio uploads can be tens to hundreds
|
// RequestSizeLimit/MultipartBodyLengthLimit set to ~1.86 GB: audio uploads can be tens to
|
||||||
// of MB and the framework defaults (~28 MB) reject them outright. The IFormFile path streams
|
// hundreds of MB (or over a GB for high-res WAVs); the framework defaults (~28 MB) reject them
|
||||||
// the body to a temp file once Kestrel surfaces it, so the limit is the per-request ceiling,
|
// outright. The IFormFile path streams the body to a temp file once Kestrel surfaces it, so the
|
||||||
// not a buffered allocation.
|
// limit is the per-request ceiling, not a buffered allocation. 2_000_000_000 stays below
|
||||||
|
// int.MaxValue (2,147,483,647) so it is safe where limits are int-typed.
|
||||||
[ApiKeyAuthorize]
|
[ApiKeyAuthorize]
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
[RequestSizeLimit(1_073_741_824)]
|
[RequestSizeLimit(2_000_000_000)]
|
||||||
[RequestFormLimits(MultipartBodyLengthLimit = 1_073_741_824)]
|
[RequestFormLimits(MultipartBodyLengthLimit = 2_000_000_000)]
|
||||||
public async Task<ActionResult<DeepDrftModels.DTOs.TrackDto>> UploadTrack(
|
public async Task<ActionResult<DeepDrftModels.DTOs.TrackDto>> UploadTrack(
|
||||||
[FromForm] IFormFile? audioFile,
|
[FromForm] IFormFile? audioFile,
|
||||||
[FromForm] string? trackName,
|
[FromForm] string? trackName,
|
||||||
@@ -503,13 +504,13 @@ public class TrackController : ControllerBase
|
|||||||
// Swap an existing track's audio bytes from a raw upload, preserving the track's id, EntryKey,
|
// Swap an existing track's audio bytes from a raw upload, preserving the track's id, EntryKey,
|
||||||
// release membership, position, and metadata. UnifiedTrackService.ReplaceAudioAsync owns the
|
// release membership, position, and metadata. UnifiedTrackService.ReplaceAudioAsync owns the
|
||||||
// vault swap + waveform regen; nothing in SQL is written. Mirrors the upload endpoint's temp-file
|
// vault swap + waveform regen; nothing in SQL is written. Mirrors the upload endpoint's temp-file
|
||||||
// streaming and 1 GB ceiling (a WAV replace is a large-body upload like the original). The
|
// streaming and ~1.86 GB ceiling (a WAV replace is a large-body upload like the original). The
|
||||||
// literal "{id:long}/replace-audio" segment is declared in the literal-route block so it never
|
// literal "{id:long}/replace-audio" segment is declared in the literal-route block so it never
|
||||||
// resolves to the parameterized "{trackId}" GET.
|
// resolves to the parameterized "{trackId}" GET.
|
||||||
[ApiKeyAuthorize]
|
[ApiKeyAuthorize]
|
||||||
[HttpPost("{id:long}/replace-audio")]
|
[HttpPost("{id:long}/replace-audio")]
|
||||||
[RequestSizeLimit(1_073_741_824)]
|
[RequestSizeLimit(2_000_000_000)]
|
||||||
[RequestFormLimits(MultipartBodyLengthLimit = 1_073_741_824)]
|
[RequestFormLimits(MultipartBodyLengthLimit = 2_000_000_000)]
|
||||||
public async Task<ActionResult> ReplaceAudio(
|
public async Task<ActionResult> ReplaceAudio(
|
||||||
long id,
|
long id,
|
||||||
[FromForm] IFormFile? audioFile,
|
[FromForm] IFormFile? audioFile,
|
||||||
|
|||||||
@@ -114,8 +114,8 @@
|
|||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
// 1 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload.
|
// ~1.86 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload.
|
||||||
private const long MaxUploadBytes = 1_073_741_824L;
|
private const long MaxUploadBytes = 2_000_000_000L;
|
||||||
|
|
||||||
// Release-title addressing (Album-mode batch Edit): loads the whole release by title.
|
// Release-title addressing (Album-mode batch Edit): loads the whole release by title.
|
||||||
[Parameter] public string AlbumName { get; set; } = string.Empty;
|
[Parameter] public string AlbumName { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -108,9 +108,9 @@
|
|||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
// 1 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload; the
|
// ~1.86 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload; the
|
||||||
// streaming path means the limit caps the request, not in-memory buffering.
|
// streaming path means the limit caps the request, not in-memory buffering.
|
||||||
private const long MaxUploadBytes = 1_073_741_824L;
|
private const long MaxUploadBytes = 2_000_000_000L;
|
||||||
|
|
||||||
private List<BatchRowModel> _tracks = new();
|
private List<BatchRowModel> _tracks = new();
|
||||||
private int _selectedIndex = -1;
|
private int _selectedIndex = -1;
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
private const int DefaultIdleTimeoutSeconds = 90;
|
private const int DefaultIdleTimeoutSeconds = 90;
|
||||||
|
|
||||||
// Response-wait budget: once the request body is fully on the wire the server runs AudioProcessor
|
// Response-wait budget: once the request body is fully on the wire the server runs AudioProcessor
|
||||||
// decode → vault write → SQL persist. For a several-hundred-MB WAV this can take many minutes.
|
// decode → vault write → SQL persist. For a multi-GB WAV this can exceed 10 minutes.
|
||||||
// The idle heartbeat goes silent after the last byte, so a separate, larger deadline governs the
|
// The idle heartbeat goes silent after the last byte, so a separate, larger deadline governs the
|
||||||
// response-wait phase so a fully-uploaded file is never killed mid-persist.
|
// response-wait phase so a fully-uploaded file is never killed mid-persist.
|
||||||
// Operator-tunable via Upload:ResponseTimeoutSeconds.
|
// Operator-tunable via Upload:ResponseTimeoutSeconds.
|
||||||
private const int DefaultResponseTimeoutSeconds = 600; // 10 minutes
|
private const int DefaultResponseTimeoutSeconds = 1200; // 20 minutes
|
||||||
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILogger<CmsTrackService> _logger;
|
private readonly ILogger<CmsTrackService> _logger;
|
||||||
|
|||||||
@@ -8,5 +8,9 @@
|
|||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ForwardedHeaders": {
|
"ForwardedHeaders": {
|
||||||
"DisableHttpsRedirection": false
|
"DisableHttpsRedirection": false
|
||||||
|
},
|
||||||
|
"Upload": {
|
||||||
|
"IdleTimeoutSeconds": 90,
|
||||||
|
"ResponseTimeoutSeconds": 1200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ server {
|
|||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name __DOMAIN_APP__;
|
server_name __DOMAIN_APP__;
|
||||||
|
|
||||||
|
# Allow audio file uploads up to ~1.86 GB (matches the per-request ceiling on api/track/upload
|
||||||
|
# and api/track/{id}/replace-audio). nginx default is 1 MB, which would 413 any large upload.
|
||||||
|
client_max_body_size 2000m;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:__PORT_MANAGER__;
|
proxy_pass http://localhost:__PORT_MANAGER__;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -15,5 +19,12 @@ server {
|
|||||||
# WebSocket support (Blazor InteractiveServer SignalR circuits)
|
# WebSocket support (Blazor InteractiveServer SignalR circuits)
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $http_connection;
|
proxy_set_header Connection $http_connection;
|
||||||
|
|
||||||
|
# Large audio uploads stream over the SignalR WebSocket circuit for several minutes.
|
||||||
|
# nginx's 60 s default would drop the connection mid-transfer and silently kill the
|
||||||
|
# Blazor circuit — the app never sees an error, so nothing is logged. 1200 s matches
|
||||||
|
# the Upload:ResponseTimeoutSeconds budget already configured in the application.
|
||||||
|
proxy_read_timeout 1200s;
|
||||||
|
proxy_send_timeout 1200s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,5 +15,11 @@ server {
|
|||||||
# WebSocket support (Blazor Server SignalR circuits + WASM interop)
|
# WebSocket support (Blazor Server SignalR circuits + WASM interop)
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $http_connection;
|
proxy_set_header Connection $http_connection;
|
||||||
|
|
||||||
|
# Blazor Server SignalR circuits can be long-lived. Raise timeouts above the 60 s
|
||||||
|
# nginx default so idle-but-active circuits (e.g. during audio streaming) are not
|
||||||
|
# silently dropped.
|
||||||
|
proxy_read_timeout 1200s;
|
||||||
|
proxy_send_timeout 1200s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user