From 4fdf58e32700ff7c2db8973e10602698fc9960b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:25:12 +0000 Subject: [PATCH 1/2] Initial plan From ea642697fc9fb5eb5a5086e7d22b232876ce4718 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:35:22 +0000 Subject: [PATCH 2/2] Fix Range request handling in StaticAssetDevelopmentRuntimeHandler Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../StaticAssetDevelopmentRuntimeHandler.cs | 17 ++++- .../test/StaticAssetsIntegrationTests.cs | 66 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs b/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs index 14ed7a0c3baa..c12a2972cfa0 100644 --- a/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs +++ b/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs @@ -191,11 +191,22 @@ public Task SendFileAsync(string path, long offset, long? count, CancellationTok _context.Response.Headers.ETag = ""; // Compute the new ETag, if this is a compressed asset, RuntimeStaticAssetResponseBodyFeature will update it. _context.Response.Headers.ETag = GetETag(fileInfo); - _context.Response.Headers.ContentLength = fileInfo.Length; _context.Response.Headers.LastModified = fileInfo.LastModified.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture); - // Send the modified asset as is. - return _original.SendFileAsync(fileInfo.PhysicalPath!, 0, fileInfo.Length, cancellationToken); + // For range requests (206 Partial Content), we preserve the original offset and count to send the correct range. + // For normal requests (200 OK), we send the entire file. + var isRangeRequest = _context.Response.StatusCode == StatusCodes.Status206PartialContent; + if (isRangeRequest) + { + // Send the requested range from the modified file. + return _original.SendFileAsync(fileInfo.PhysicalPath!, offset, count, cancellationToken); + } + else + { + // Send the entire modified file. + _context.Response.Headers.ContentLength = fileInfo.Length; + return _original.SendFileAsync(fileInfo.PhysicalPath!, 0, fileInfo.Length, cancellationToken); + } } } diff --git a/src/StaticAssets/test/StaticAssetsIntegrationTests.cs b/src/StaticAssets/test/StaticAssetsIntegrationTests.cs index 2fda7c870791..35d741ec453c 100644 --- a/src/StaticAssets/test/StaticAssetsIntegrationTests.cs +++ b/src/StaticAssets/test/StaticAssetsIntegrationTests.cs @@ -393,6 +393,72 @@ public async Task CanModifyAssetsOnTheFlyInDevelopment() Directory.Delete(webRoot, true); } + [Fact] + public async Task RangeRequestReturnsCorrectContentLengthForModifiedAssets() + { + // Arrange + var appName = nameof(RangeRequestReturnsCorrectContentLengthForModifiedAssets); + var (contentRoot, webRoot) = ConfigureAppPaths(appName); + + CreateTestManifest( + appName, + webRoot, + [ + new TestResource("sample.txt", "Hello, World!", false), + ]); + + var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions + { + ApplicationName = appName, + ContentRootPath = contentRoot, + EnvironmentName = "Development", + WebRootPath = webRoot + }); + builder.WebHost.UseSetting(StaticAssetDevelopmentRuntimeHandler.ReloadStaticAssetsAtRuntimeKey, "true"); + builder.WebHost.ConfigureServices(services => + { + services.AddRouting(); + }); + builder.WebHost.UseTestServer(); + + var app = builder.Build(); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapStaticAssets(); + }); + + await app.StartAsync(); + + var client = app.GetTestClient(); + + File.WriteAllText(Path.Combine(webRoot, "sample.txt"), "Hello, World! Modified"); + + // Act - Request first 5 bytes (Range: bytes=0-4) + var message = new HttpRequestMessage(HttpMethod.Get, "/sample.txt"); + message.Headers.Range = new RangeHeaderValue(0, 4); + var response = await client.SendAsync(message); + + // Assert - Should return 206 Partial Content with correct Content-Length of 5 bytes + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.PartialContent, response.StatusCode); + Assert.Equal(5, response.Content.Headers.ContentLength); + Assert.Equal("Hello", await response.Content.ReadAsStringAsync()); + + // Act - Request bytes 7-11 (Range: bytes=7-11) + var message2 = new HttpRequestMessage(HttpMethod.Get, "/sample.txt"); + message2.Headers.Range = new RangeHeaderValue(7, 11); + var response2 = await client.SendAsync(message2); + + // Assert - Should return 206 Partial Content with correct Content-Length of 5 bytes + Assert.NotNull(response2); + Assert.Equal(HttpStatusCode.PartialContent, response2.StatusCode); + Assert.Equal(5, response2.Content.Headers.ContentLength); + Assert.Equal("World", await response2.Content.ReadAsStringAsync()); + + Directory.Delete(webRoot, true); + } + [Fact] public async Task CanModifyAssetsWithCompressedVersionsOnTheFlyInDevelopment() {