5
5
6
6
using System . Globalization ;
7
7
using Microsoft . Build . Framework ;
8
+ using Microsoft . AspNetCore . StaticWebAssets . Tasks . Utils ;
8
9
9
10
namespace Microsoft . AspNetCore . StaticWebAssets . Tasks ;
10
11
@@ -19,6 +20,12 @@ public class ApplyCompressionNegotiation : Task
19
20
[ Output ]
20
21
public ITaskItem [ ] UpdatedEndpoints { get ; set ; }
21
22
23
+ private readonly List < StaticWebAssetEndpointSelector > _selectorsList = [ ] ;
24
+ private readonly List < StaticWebAssetEndpointResponseHeader > _headersList = [ ] ;
25
+ private readonly List < StaticWebAssetEndpointResponseHeader > _tempHeadersList = [ ] ;
26
+ private readonly List < StaticWebAssetEndpointProperty > _propertiesList = [ ] ;
27
+ private const int ExpectedCompressionHeadersCount = 2 ;
28
+
22
29
public override bool Execute ( )
23
30
{
24
31
var assetsById = StaticWebAsset . ToAssetDictionary ( CandidateAssets ) ;
@@ -27,9 +34,23 @@ public override bool Execute()
27
34
28
35
var updatedEndpoints = new HashSet < StaticWebAssetEndpoint > ( CandidateEndpoints . Length , StaticWebAssetEndpoint . RouteAndAssetComparer ) ;
29
36
30
- var compressionHeadersByEncoding = new Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > ( 2 ) ;
37
+ var compressionHeadersByEncoding = new Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > ( ExpectedCompressionHeadersCount ) ;
38
+
39
+ using var jsonContext = new JsonWriterContext ( ) ;
31
40
32
- // Add response headers to compressed endpoints
41
+ ProcessCompressedAssets ( assetsById , endpointsByAsset , updatedEndpoints , compressionHeadersByEncoding , jsonContext ) ;
42
+ AddRemainingEndpoints ( endpointsByAsset , updatedEndpoints ) ;
43
+ UpdatedEndpoints = StaticWebAssetEndpoint . ToTaskItems ( updatedEndpoints ) ;
44
+ return true ;
45
+ }
46
+
47
+ private void ProcessCompressedAssets (
48
+ Dictionary < string , StaticWebAsset > assetsById ,
49
+ IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
50
+ HashSet < StaticWebAssetEndpoint > updatedEndpoints ,
51
+ Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > compressionHeadersByEncoding ,
52
+ JsonWriterContext jsonContext )
53
+ {
33
54
foreach ( var compressedAsset in assetsById . Values )
34
55
{
35
56
if ( ! string . Equals ( compressedAsset . AssetTraitName , "Content-Encoding" , StringComparison . Ordinal ) )
@@ -53,11 +74,11 @@ public override bool Execute()
53
74
54
75
if ( ! HasContentEncodingResponseHeader ( compressedEndpoint ) )
55
76
{
56
- // Add the Content-Encoding and Vary headers
57
- compressedEndpoint . ResponseHeaders = [
58
- .. compressedEndpoint . ResponseHeaders ,
59
- .. compressionHeaders
60
- ] ;
77
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
78
+ var currentCompressionHeaders = GetOrCreateCompressionHeaders ( compressionHeadersByEncoding , compressedAsset ) ;
79
+ _headersList . AddRange ( currentCompressionHeaders ) ;
80
+ var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , jsonContext ) ;
81
+ compressedEndpoint . SetResponseHeadersString ( headersString ) ;
61
82
}
62
83
63
84
var compressedHeaders = GetCompressedHeaders ( compressedEndpoint ) ;
@@ -72,7 +93,7 @@ public override bool Execute()
72
93
continue ;
73
94
}
74
95
75
- var endpointCopy = CreateUpdatedEndpoint ( compressedAsset , quality , compressedEndpoint , compressedHeaders , relatedEndpointCandidate ) ;
96
+ var endpointCopy = CreateUpdatedEndpoint ( compressedAsset , quality , compressedEndpoint , compressedHeaders , relatedEndpointCandidate , jsonContext ) ;
76
97
updatedEndpoints . Add ( endpointCopy ) ;
77
98
// Since we are going to remove the endpoints from the associated item group and the route is
78
99
// the ItemSpec, we want to add the original as well so that it gets re-added.
@@ -82,19 +103,24 @@ public override bool Execute()
82
103
}
83
104
}
84
105
}
106
+ }
85
107
86
- // Before we return the updated endpoints we need to capture any other endpoint whose asset is not associated
87
- // with the compressed asset. This is because we are going to remove the endpoints from the associated item group
88
- // and the route is the ItemSpec, so it will cause those endpoints to be removed.
89
- // For example, we have css/app.css and Link/css/app.css where Link=css/app.css and the first asset is a build asset
90
- // and the second asset is a publish asset.
91
- // If we are processing build assets, we'll mistakenly remove the endpoints associated with the publish asset.
108
+ // Before we return the updated endpoints we need to capture any other endpoint whose asset is not associated
109
+ // with the compressed asset. This is because we are going to remove the endpoints from the associated item group
110
+ // and the route is the ItemSpec, so it will cause those endpoints to be removed.
111
+ // For example, we have css/app.css and Link/css/app.css where Link=css/app.css and the first asset is a build asset
112
+ // and the second asset is a publish asset.
113
+ // If we are processing build assets, we'll mistakenly remove the endpoints associated with the publish asset.
92
114
93
- // Iterate over the endpoints and find those endpoints whose route is in the set of updated endpoints but whose asset
94
- // is not, and add them to the updated endpoints.
115
+ // Iterate over the endpoints and find those endpoints whose route is in the set of updated endpoints but whose asset
116
+ // is not, and add them to the updated endpoints.
95
117
96
- // Reuse the map we created at the beginning.
97
- // Remove all the endpoints that were updated to avoid adding them again.
118
+ // Reuse the map we created at the beginning.
119
+ // Remove all the endpoints that were updated to avoid adding them again.
120
+ private void AddRemainingEndpoints (
121
+ IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
122
+ HashSet < StaticWebAssetEndpoint > updatedEndpoints )
123
+ {
98
124
foreach ( var endpoint in updatedEndpoints )
99
125
{
100
126
if ( endpointsByAsset . TryGetValue ( endpoint . AssetFile , out var endpointsToSkip ) )
@@ -131,18 +157,16 @@ public override bool Execute()
131
157
}
132
158
133
159
updatedEndpoints . UnionWith ( additionalUpdatedEndpoints ) ;
134
-
135
- UpdatedEndpoints = StaticWebAssetEndpoint . ToTaskItems ( updatedEndpoints ) ;
136
-
137
- return true ;
138
160
}
139
161
140
- private static HashSet < string > GetCompressedHeaders ( StaticWebAssetEndpoint compressedEndpoint )
162
+ private HashSet < string > GetCompressedHeaders ( StaticWebAssetEndpoint compressedEndpoint )
141
163
{
142
- var result = new HashSet < string > ( compressedEndpoint . ResponseHeaders . Length , StringComparer . Ordinal ) ;
143
- for ( var i = 0 ; i < compressedEndpoint . ResponseHeaders . Length ; i ++ )
164
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
165
+
166
+ var result = new HashSet < string > ( _headersList . Count , StringComparer . Ordinal ) ;
167
+ for ( var i = 0 ; i < _headersList . Count ; i ++ )
144
168
{
145
- var responseHeader = compressedEndpoint . ResponseHeaders [ i ] ;
169
+ var responseHeader = _headersList [ i ] ;
146
170
result . Add ( responseHeader . Name ) ;
147
171
}
148
172
@@ -200,7 +224,8 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
200
224
string quality ,
201
225
StaticWebAssetEndpoint compressedEndpoint ,
202
226
HashSet < string > compressedHeaders ,
203
- StaticWebAssetEndpoint relatedEndpointCandidate )
227
+ StaticWebAssetEndpoint relatedEndpointCandidate ,
228
+ JsonWriterContext jsonContext )
204
229
{
205
230
Log . LogMessage ( MessageImportance . Low , "Processing related endpoint '{0}'" , relatedEndpointCandidate . Route ) ;
206
231
var encodingSelector = new StaticWebAssetEndpointSelector
@@ -210,31 +235,39 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
210
235
Quality = quality
211
236
} ;
212
237
Log . LogMessage ( MessageImportance . Low , " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'" , encodingSelector . Value , encodingSelector . Quality , relatedEndpointCandidate . Route ) ;
238
+
239
+ StaticWebAssetEndpointSelector . PopulateFromMetadataValue ( relatedEndpointCandidate . SelectorsString , _selectorsList ) ;
240
+ _selectorsList . Add ( encodingSelector ) ;
241
+ var selectorsString = StaticWebAssetEndpointSelector . ToMetadataValue ( _selectorsList , jsonContext ) ;
242
+
213
243
var endpointCopy = new StaticWebAssetEndpoint
214
244
{
215
245
AssetFile = compressedAsset . Identity ,
216
246
Route = relatedEndpointCandidate . Route ,
217
- Selectors = [
218
- ..relatedEndpointCandidate . Selectors ,
219
- encodingSelector
220
- ] ,
221
- EndpointProperties = relatedEndpointCandidate . EndpointProperties
222
247
} ;
223
- var headers = new List < StaticWebAssetEndpointResponseHeader > ( 7 ) ;
224
- ApplyCompressedEndpointHeaders ( headers , compressedEndpoint , relatedEndpointCandidate . Route ) ;
225
- ApplyRelatedEndpointCandidateHeaders ( headers , relatedEndpointCandidate , compressedHeaders ) ;
226
- endpointCopy . ResponseHeaders = [ .. headers ] ;
248
+
249
+ endpointCopy . SetSelectorsString ( selectorsString ) ;
250
+ endpointCopy . SetEndpointPropertiesString ( relatedEndpointCandidate . EndpointPropertiesString ) ;
251
+
252
+ // Build headers using reusable list
253
+ _headersList . Clear ( ) ;
254
+ ApplyCompressedEndpointHeaders ( _headersList , compressedEndpoint , relatedEndpointCandidate . Route ) ;
255
+ ApplyRelatedEndpointCandidateHeaders ( _headersList , relatedEndpointCandidate , compressedHeaders ) ;
256
+ var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , jsonContext ) ;
257
+ endpointCopy . SetResponseHeadersString ( headersString ) ;
227
258
228
259
// Update the endpoint
229
260
Log . LogMessage ( MessageImportance . Low , " Updated related endpoint '{0}' with Content-Encoding selector '{1}={2}'" , relatedEndpointCandidate . Route , encodingSelector . Value , encodingSelector . Quality ) ;
230
261
return endpointCopy ;
231
262
}
232
263
233
- private static bool HasContentEncodingResponseHeader ( StaticWebAssetEndpoint compressedEndpoint )
264
+ private bool HasContentEncodingResponseHeader ( StaticWebAssetEndpoint compressedEndpoint )
234
265
{
235
- for ( var i = 0 ; i < compressedEndpoint . ResponseHeaders . Length ; i ++ )
266
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
267
+
268
+ for ( var i = 0 ; i < _headersList . Count ; i ++ )
236
269
{
237
- var responseHeader = compressedEndpoint . ResponseHeaders [ i ] ;
270
+ var responseHeader = _headersList [ i ] ;
238
271
if ( string . Equals ( responseHeader . Name , "Content-Encoding" , StringComparison . Ordinal ) )
239
272
{
240
273
return true ;
@@ -244,11 +277,13 @@ private static bool HasContentEncodingResponseHeader(StaticWebAssetEndpoint comp
244
277
return false ;
245
278
}
246
279
247
- private static bool HasContentEncodingSelector ( StaticWebAssetEndpoint compressedEndpoint )
280
+ private bool HasContentEncodingSelector ( StaticWebAssetEndpoint compressedEndpoint )
248
281
{
249
- for ( var i = 0 ; i < compressedEndpoint . Selectors . Length ; i ++ )
282
+ StaticWebAssetEndpointSelector . PopulateFromMetadataValue ( compressedEndpoint . SelectorsString , _selectorsList ) ;
283
+
284
+ for ( var i = 0 ; i < _selectorsList . Count ; i ++ )
250
285
{
251
- var selector = compressedEndpoint . Selectors [ i ] ;
286
+ var selector = _selectorsList [ i ] ;
252
287
if ( string . Equals ( selector . Name , "Content-Encoding" , StringComparison . Ordinal ) )
253
288
{
254
289
return true ;
@@ -287,16 +322,18 @@ private static bool HasContentEncodingSelector(StaticWebAssetEndpoint compressed
287
322
private static string ResolveQuality ( StaticWebAsset compressedAsset ) =>
288
323
Math . Round ( 1.0 / ( compressedAsset . FileLength + 1 ) , 12 ) . ToString ( "F12" , CultureInfo . InvariantCulture ) ;
289
324
290
- private static bool IsCompatible ( StaticWebAssetEndpoint compressedEndpoint , StaticWebAssetEndpoint relatedEndpointCandidate )
325
+ private bool IsCompatible ( StaticWebAssetEndpoint compressedEndpoint , StaticWebAssetEndpoint relatedEndpointCandidate )
291
326
{
292
- var compressedFingerprint = ResolveFingerprint ( compressedEndpoint ) ;
293
- var relatedFingerprint = ResolveFingerprint ( relatedEndpointCandidate ) ;
327
+ var compressedFingerprint = ResolveFingerprint ( compressedEndpoint , _propertiesList ) ;
328
+ var relatedFingerprint = ResolveFingerprint ( relatedEndpointCandidate , _propertiesList ) ;
294
329
return string . Equals ( compressedFingerprint . Value , relatedFingerprint . Value , StringComparison . Ordinal ) ;
295
330
}
296
331
297
- private static StaticWebAssetEndpointProperty ResolveFingerprint ( StaticWebAssetEndpoint compressedEndpoint )
332
+ private static StaticWebAssetEndpointProperty ResolveFingerprint ( StaticWebAssetEndpoint compressedEndpoint , List < StaticWebAssetEndpointProperty > tempList )
298
333
{
299
- foreach ( var property in compressedEndpoint . EndpointProperties )
334
+ StaticWebAssetEndpointProperty . PopulateFromMetadataValue ( compressedEndpoint . EndpointPropertiesString , tempList ) ;
335
+
336
+ foreach ( var property in tempList )
300
337
{
301
338
if ( string . Equals ( property . Name , "fingerprint" , StringComparison . Ordinal ) )
302
339
{
@@ -308,7 +345,9 @@ private static StaticWebAssetEndpointProperty ResolveFingerprint(StaticWebAssetE
308
345
309
346
private void ApplyCompressedEndpointHeaders ( List < StaticWebAssetEndpointResponseHeader > headers , StaticWebAssetEndpoint compressedEndpoint , string relatedEndpointCandidateRoute )
310
347
{
311
- foreach ( var header in compressedEndpoint . ResponseHeaders )
348
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _tempHeadersList ) ;
349
+
350
+ foreach ( var header in _tempHeadersList )
312
351
{
313
352
if ( string . Equals ( header . Name , "Content-Type" , StringComparison . Ordinal ) )
314
353
{
@@ -326,7 +365,9 @@ private void ApplyCompressedEndpointHeaders(List<StaticWebAssetEndpointResponseH
326
365
327
366
private void ApplyRelatedEndpointCandidateHeaders ( List < StaticWebAssetEndpointResponseHeader > headers , StaticWebAssetEndpoint relatedEndpointCandidate , HashSet < string > compressedHeaders )
328
367
{
329
- foreach ( var header in relatedEndpointCandidate . ResponseHeaders )
368
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( relatedEndpointCandidate . ResponseHeadersString , _tempHeadersList ) ;
369
+
370
+ foreach ( var header in _tempHeadersList )
330
371
{
331
372
// We need to keep the headers that are specific to the compressed asset like Content-Length,
332
373
// Last-Modified and ETag. Any other header we should add it.
0 commit comments