Skip to content

Commit 470889b

Browse files
authored
[Blazor] Correct ETag and Content-Length computation, add more precision to quality definition (#40321)
1 parent 9a965db commit 470889b

File tree

5 files changed

+49
-62
lines changed

5 files changed

+49
-62
lines changed

src/StaticWebAssetsSdk/Tasks/ApplyCompressionNegotiation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public override bool Execute()
9898
{
9999
Name = "Content-Encoding",
100100
Value = compressedAsset.AssetTraitValue,
101-
Quality = Math.Round(1.0 / (length + 1), 6).ToString("F6")
101+
Quality = Math.Round(1.0 / (length + 1), 12).ToString("F12")
102102
};
103103
Log.LogMessage(MessageImportance.Low, " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'", encodingSelector.Value, encodingSelector.Quality, relatedEndpointCandidate.Route);
104104
var endpointCopy = new StaticWebAssetEndpoint
@@ -230,7 +230,7 @@ private void ApplyRelatedEndpointCandidateHeaders(List<StaticWebAssetEndpointRes
230230
headers.Add(new StaticWebAssetEndpointResponseHeader
231231
{
232232
Name = "ETag",
233-
Value = $"W/\"{header.Value}\""
233+
Value = $"W/{header.Value}"
234234
});
235235
}
236236
else if (string.Equals(header.Name, "Content-Type", StringComparison.Ordinal))

src/StaticWebAssetsSdk/Tasks/DefineStaticWebAssetEndpoints.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private StaticWebAssetEndpoint CreateEndpoint(StaticWebAsset asset, ContentTypeM
107107
new()
108108
{
109109
Name = "ETag",
110-
Value = asset.Integrity,
110+
Value = $"\"{asset.Integrity}\"",
111111
},
112112
new()
113113
{
@@ -162,8 +162,16 @@ private string GetFileLength(StaticWebAsset asset)
162162
return TestLengthResolver(asset.Identity).ToString(CultureInfo.InvariantCulture);
163163
}
164164

165-
var path = File.Exists(asset.OriginalItemSpec) ? asset.OriginalItemSpec : asset.Identity;
166-
return new FileInfo(path).Length.ToString(CultureInfo.InvariantCulture);
165+
if (File.Exists(asset.Identity))
166+
{
167+
Log.LogMessage(MessageImportance.Low, $"File {asset.Identity} exists.");
168+
return new FileInfo(asset.Identity).Length.ToString(CultureInfo.InvariantCulture);
169+
}
170+
else
171+
{
172+
Log.LogMessage(MessageImportance.Low, $"File {asset.Identity} does not exist. Using {asset.OriginalItemSpec} instead.");
173+
return new FileInfo(asset.OriginalItemSpec).Length.ToString(CultureInfo.InvariantCulture);
174+
}
167175
}
168176

169177
private string ResolveContentType(StaticWebAsset asset, ContentTypeMapping[] contentTypeMappings)
@@ -188,7 +196,7 @@ private string ResolveContentType(StaticWebAsset asset, ContentTypeMapping[] con
188196

189197
private class ContentTypeMapping
190198
{
191-
private Matcher _matcher;
199+
private readonly Matcher _matcher;
192200

193201
public ContentTypeMapping(string mimeType, string pattern, int priority)
194202
{
@@ -197,7 +205,6 @@ public ContentTypeMapping(string mimeType, string pattern, int priority)
197205
Priority = priority;
198206
_matcher = new Matcher();
199207
_matcher.AddInclude(pattern);
200-
201208
}
202209

203210
public string Pattern { get; set; }
@@ -206,13 +213,10 @@ public ContentTypeMapping(string mimeType, string pattern, int priority)
206213

207214
public int Priority { get; }
208215

209-
internal static ContentTypeMapping FromTaskItem(ITaskItem contentTypeMappings)
210-
{
211-
return new ContentTypeMapping(
216+
internal static ContentTypeMapping FromTaskItem(ITaskItem contentTypeMappings) => new(
212217
contentTypeMappings.ItemSpec,
213218
contentTypeMappings.GetMetadata(nameof(Pattern)),
214219
int.Parse(contentTypeMappings.GetMetadata(nameof(Priority))));
215-
}
216220

217221
internal bool Matches(string identity) => _matcher.Match(identity).HasMatches;
218222
}

test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetEndpointsIntegrationTest.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Globalization;
45
using System.Text.Json;
56
using Microsoft.AspNetCore.StaticWebAssets.Tasks;
67
using Microsoft.NET.Sdk.StaticWebAssets.Tasks;
@@ -73,6 +74,16 @@ public void Publish_CreatesEndpointsForAssets()
7374
var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
7475

7576
var endpoints = manifest.Endpoints;
77+
78+
foreach (var endpoint in endpoints)
79+
{
80+
var contentLength = endpoint.ResponseHeaders.Single(rh => rh.Name == "Content-Length");
81+
var length = long.Parse(contentLength.Value, CultureInfo.InvariantCulture);
82+
var file = new FileInfo(endpoint.AssetFile);
83+
file.Should().Exist();
84+
file.Length.Should().Be(length, $"because {endpoint.Route} {file.FullName}");
85+
}
86+
7687
endpoints.Should().HaveCount(15);
7788
var appJsEndpoints = endpoints.Where(ep => ep.Route.EndsWith("app.js"));
7889
appJsEndpoints.Should().HaveCount(3);
@@ -111,7 +122,7 @@ public void Publish_CreatesEndpointsForAssets()
111122
);
112123
gzipCompressedAppJsEndpoint.Single().ResponseHeaders.Any(h => h.Name == "ETag" && h.Value.StartsWith("W/")).Should().BeTrue();
113124
var gzipWeakEtag = gzipCompressedAppJsEndpoint.Single().ResponseHeaders.Single(h => h.Name == "ETag" && h.Value.StartsWith("W/")).Value;
114-
gzipWeakEtag[3..^1].Should().Be(eTagHeader.Value);
125+
gzipWeakEtag[2..].Should().Be(eTagHeader.Value);
115126

116127
var brotliCompressedAppJsEndpoint = appJsEndpoints.Where(ep => ep.Selectors.Length == 1 && ep.Selectors[0].Value == "br");
117128
brotliCompressedAppJsEndpoint.Should().HaveCount(1);
@@ -129,7 +140,7 @@ public void Publish_CreatesEndpointsForAssets()
129140
);
130141
brotliCompressedAppJsEndpoint.Single().ResponseHeaders.Any(h => h.Name == "ETag" && h.Value.StartsWith("W/")).Should().BeTrue();
131142
var brWeakEtag = brotliCompressedAppJsEndpoint.Single().ResponseHeaders.Single(h => h.Name == "ETag" && h.Value.StartsWith("W/")).Value;
132-
brWeakEtag[3..^1].Should().Be(eTagHeader.Value);
143+
brWeakEtag[2..].Should().Be(eTagHeader.Value);
133144

134145
var bundleEndpoints = endpoints.Where(ep => ep.Route.EndsWith("bundle.scp.css"));
135146
bundleEndpoints.Should().HaveCount(3);

test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ApplyCompressionNegotiationTest.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ public void AppliesContentNegotiationRules_ForExistingAssets()
5050
CreateCandidateEndpoint(
5151
"candidate.js",
5252
Path.Combine("wwwroot", "candidate.js"),
53-
CreateHeaders("text/javascript")),
53+
CreateHeaders("text/javascript", [("Content-Length", "20")])),
5454

5555
CreateCandidateEndpoint(
5656
"candidate.js.gz",
5757
Path.Combine("compressed", "candidate.js.gz"),
58-
CreateHeaders("text/javascript"))
58+
CreateHeaders("text/javascript", [("Content-Length", "9")]))
5959
],
6060
TestResolveFileLength = value => value switch
6161
{
@@ -79,19 +79,21 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
7979
ResponseHeaders =
8080
[
8181
new () { Name = "Content-Encoding", Value = "gzip" },
82+
new () { Name = "Content-Length", Value = "9" },
8283
new () { Name = "Content-Type", Value = "text/javascript" },
8384
new () { Name = "Vary", Value = "Content-Encoding" }
8485
],
8586
EndpointProperties = [],
86-
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
87+
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
8788
},
8889
new ()
8990
{
9091
Route = "candidate.js",
9192
AssetFile = Path.GetFullPath(Path.Combine("wwwroot", "candidate.js")),
9293
ResponseHeaders =
9394
[
94-
new () { Name = "Content-Type", Value = "text/javascript" }
95+
new () { Name = "Content-Length", Value = "20" },
96+
new () { Name = "Content-Type", Value = "text/javascript" },
9597
],
9698
EndpointProperties = [],
9799
Selectors = [],
@@ -103,6 +105,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
103105
ResponseHeaders =
104106
[
105107
new () { Name = "Content-Encoding", Value = "gzip" },
108+
new () { Name = "Content-Length", Value = "9" },
106109
new () { Name = "Content-Type", Value = "text/javascript" },
107110
new () { Name = "Vary", Value = "Content-Encoding" }
108111
],
@@ -188,7 +191,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
188191
new () { Name = "Vary", Value = "Content-Encoding" }
189192
],
190193
EndpointProperties = [],
191-
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
194+
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
192195
},
193196
new ()
194197
{
@@ -212,7 +215,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
212215
new () { Name = "Vary", Value = "Content-Encoding" }
213216
],
214217
EndpointProperties = [],
215-
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
218+
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
216219
},
217220
new ()
218221
{
@@ -288,7 +291,7 @@ public void AppliesContentNegotiationRules_IgnoresAlreadyProcessedEndpoints()
288291
new (){ Name = "Vary", Value = "Content-Encoding" }
289292
],
290293
EndpointProperties = [],
291-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
294+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
292295
},
293296
new() {
294297
Route = "candidate.js",
@@ -310,7 +313,7 @@ public void AppliesContentNegotiationRules_IgnoresAlreadyProcessedEndpoints()
310313
new (){ Name = "Vary", Value = "Content-Encoding" }
311314
],
312315
EndpointProperties = [],
313-
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
316+
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
314317
},
315318
new() {
316319
Route = "candidate.fingerprint.js",
@@ -361,7 +364,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
361364
new () { Name = "Vary", Value = "Content-Encoding" }
362365
],
363366
EndpointProperties = [],
364-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
367+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
365368
},
366369
new StaticWebAssetEndpoint
367370
{
@@ -385,7 +388,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
385388
new () { Name = "Vary", Value = "Content-Encoding" }
386389
],
387390
EndpointProperties = [],
388-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
391+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
389392
},
390393
new StaticWebAssetEndpoint
391394
{
@@ -473,7 +476,7 @@ public void AppliesContentNegotiationRules_ProcessesNewCompressedFormatsWhenAvai
473476
new (){ Name = "Vary", Value = "Content-Encoding" }
474477
],
475478
EndpointProperties = [],
476-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
479+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
477480
},
478481
new() {
479482
Route = "candidate.js",
@@ -495,7 +498,7 @@ public void AppliesContentNegotiationRules_ProcessesNewCompressedFormatsWhenAvai
495498
new (){ Name = "Vary", Value = "Content-Encoding" }
496499
],
497500
EndpointProperties = [],
498-
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
501+
Selectors = [ new () { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
499502
},
500503
new() {
501504
Route = "candidate.fingerprint.js",
@@ -557,7 +560,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
557560
new () { Name = "Vary", Value = "Content-Encoding" }
558561
],
559562
EndpointProperties = [],
560-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
563+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
561564
},
562565
new StaticWebAssetEndpoint
563566
{
@@ -570,7 +573,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
570573
new () { Name = "Vary", Value = "Content-Encoding" }
571574
],
572575
EndpointProperties = [],
573-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "br", Quality = "0.100000" } ],
576+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "br", Quality = "0.100000000000" } ],
574577
},
575578
new StaticWebAssetEndpoint
576579
{
@@ -594,7 +597,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
594597
new () { Name = "Vary", Value = "Content-Encoding" }
595598
],
596599
EndpointProperties = [],
597-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000" } ],
600+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip", Quality = "0.100000000000" } ],
598601
},
599602
new StaticWebAssetEndpoint
600603
{
@@ -607,7 +610,7 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
607610
new () { Name = "Vary", Value = "Content-Encoding" }
608611
],
609612
EndpointProperties = [],
610-
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "br", Quality = "0.100000" } ],
613+
Selectors = [ new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "br", Quality = "0.100000000000" } ],
611614
},
612615
new StaticWebAssetEndpoint
613616
{
@@ -657,7 +660,7 @@ private StaticWebAssetEndpointSelector[] CreateContentEcondingSelector(string na
657660
{
658661
Name = name,
659662
Value = value,
660-
Quality = "0.100000"
663+
Quality = "0.100000000000"
661664
}
662665
];
663666
}

test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DefineStaticWebAssetEndpointsTest.cs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void DefinesEndpointsForAssets(string sourceType)
6464
new StaticWebAssetEndpointResponseHeader
6565
{
6666
Name = "ETag",
67-
Value = "integrity"
67+
Value = "\"integrity\""
6868
},
6969
new StaticWebAssetEndpointResponseHeader
7070
{
@@ -74,37 +74,6 @@ public void DefinesEndpointsForAssets(string sourceType)
7474
]);
7575
}
7676

77-
//[Theory]
78-
//[InlineData(StaticWebAsset.SourceTypes.Project)]
79-
//[InlineData(StaticWebAsset.SourceTypes.Package)]
80-
//public void DoesNotDefineEndpointsForProjectOrPackageAssets(string sourceType)
81-
//{
82-
// var errorMessages = new List<string>();
83-
// var buildEngine = new Mock<IBuildEngine>();
84-
// buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
85-
// .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
86-
87-
// var lastWrite = new DateTime(1990, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc);
88-
89-
// var task = new DefineStaticWebAssetEndpoints
90-
// {
91-
// BuildEngine = buildEngine.Object,
92-
// CandidateAssets = [CreateCandidate(Path.Combine("wwwroot", "candidate.js"), "MyPackage", sourceType, "candidate.js", "All", "All")],
93-
// ExistingEndpoints = [],
94-
// ContentTypeMappings = [CreateContentMapping("**/*.js", "text/javascript")],
95-
// TestLengthResolver = asset => asset.EndsWith("candidate.js") ? 10 : throw new InvalidOperationException(),
96-
// TestLastWriteResolver = asset => asset.EndsWith("candidate.js") ? lastWrite : throw new InvalidOperationException(),
97-
// };
98-
99-
// // Act
100-
// var result = task.Execute();
101-
102-
// // Assert
103-
// result.Should().Be(true);
104-
// var endpoints = StaticWebAssetEndpoint.FromItemGroup(task.Endpoints);
105-
// endpoints.Should().BeEmpty();
106-
//}
107-
10877
[Fact]
10978
public void DoesNotDefineNewEndpointsWhenAnExistingEndpointAlreadyExists()
11079
{

0 commit comments

Comments
 (0)