Skip to content

Commit 78fbd22

Browse files
committed
Support HTTPS compression of returned content
1 parent 454ffb6 commit 78fbd22

File tree

6 files changed

+104
-9
lines changed

6 files changed

+104
-9
lines changed

sample2/WebOptimizer.Core.Sample2/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"environmentVariables": {
2222
"ASPNETCORE_ENVIRONMENT": "Development"
2323
},
24-
"applicationUrl": "http://localhost:62609/"
24+
"applicationUrl": "http://localhost:62609/;https://localhost:62610/"
2525
}
2626
}
2727
}

sample2/WebOptimizer.Core.Sample2/Startup.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public Startup(IConfiguration configuration, IWebHostEnvironment hostingEnvironm
2424
public void ConfigureServices(IServiceCollection services)
2525
{
2626
services.AddControllersWithViews();
27-
27+
services.AddResponseCompression();
2828

2929
var cssSettings = new CssBundlingSettings();
3030
var codeSettings = new CodeBundlingSettings
@@ -47,9 +47,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4747
app.UseExceptionHandler("/Home/Error");
4848
}
4949

50+
app.UseResponseCompression();
51+
5052
const string scriptsPath1 = "Scripts1";
5153
const string scriptsPath2 = "Scripts2";
52-
54+
5355
var currentDirectory = Directory.GetCurrentDirectory();
5456
app.UseWebOptimizer(HostingEnvironment, new[]
5557
{
@@ -69,7 +71,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
6971
FileProvider = new EmbeddedFileProvider(Lib.AssemblyTools.GetCurrentAssembly()),
7072
}
7173
});
72-
74+
7375
app.UseStaticFiles();
7476

7577
app.UseRouting();

src/WebOptimizer.Core/AssetMiddleware.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Threading.Tasks;
22
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Http.Features;
34
using Microsoft.Extensions.Logging;
45
using Microsoft.Extensions.Options;
56
using Microsoft.Net.Http.Headers;
@@ -106,8 +107,19 @@ private async Task WriteOutputAsync(HttpContext context, IAsset asset, IAssetRes
106107

107108
if (cachedResponse?.Body?.Length > 0)
108109
{
110+
SetCompressionMode(context, options);
109111
await context.Response.Body.WriteAsync(cachedResponse.Body, 0, cachedResponse.Body.Length);
110112
}
111113
}
114+
115+
// Only called when we expect to serve the body.
116+
private static void SetCompressionMode(HttpContext context, IWebOptimizerOptions options)
117+
{
118+
IHttpsCompressionFeature responseCompressionFeature = context.Features.Get<IHttpsCompressionFeature>();
119+
if (responseCompressionFeature != null)
120+
{
121+
responseCompressionFeature.Mode = options.HttpsCompression;
122+
}
123+
}
112124
}
113125
}

src/WebOptimizer.Core/IWebOptimizerOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Caching.Memory;
1+
using Microsoft.AspNetCore.Http.Features;
2+
using Microsoft.Extensions.Caching.Memory;
23

34
namespace WebOptimizer
45
{
@@ -45,5 +46,14 @@ public interface IWebOptimizerOptions
4546
/// Gets or sets whether empty bundle is allowed to generate instead of throwing an exception
4647
/// </summary>
4748
public bool? AllowEmptyBundle { get; set; }
49+
50+
/// <summary>
51+
/// Indicates if files should be compressed for HTTPS requests when the Response Compression middleware is available.
52+
/// The default value is <see cref="HttpsCompressionMode.Compress"/>.
53+
/// </summary>
54+
/// <remarks>
55+
/// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems.
56+
/// </remarks>
57+
HttpsCompressionMode HttpsCompression { get; set; }
4858
}
4959
}

src/WebOptimizer.Core/WebOptimizerOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Caching.Memory;
1+
using Microsoft.AspNetCore.Http.Features;
2+
using Microsoft.Extensions.Caching.Memory;
23

34
namespace WebOptimizer
45
{
@@ -45,5 +46,14 @@ public class WebOptimizerOptions : IWebOptimizerOptions
4546
/// Gets or sets whether empty bundle is allowed to generate instead of throwing an exception
4647
/// </summary>
4748
public bool? AllowEmptyBundle { get; set; }
49+
50+
/// <summary>
51+
/// Indicates if files should be compressed for HTTPS requests when the Response Compression middleware is available.
52+
/// The default value is <see cref="HttpsCompressionMode.Compress"/>.
53+
/// </summary>
54+
/// <remarks>
55+
/// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems.
56+
/// </remarks>
57+
public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress;
4858
}
4959
}

test/WebOptimizer.Core.Test/AssetMiddlewareTest.cs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System.Collections.Concurrent;
2-
using System.Collections.Generic;
32
using System.IO;
4-
using System.Linq;
5-
using System.Reflection;
63
using System.Threading.Tasks;
74
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Http.Features;
86
using Microsoft.Extensions.Caching.Memory;
97
using Microsoft.Extensions.Logging;
108
using Microsoft.Extensions.Options;
@@ -38,6 +36,7 @@ public async Task AssetMiddleware_NoCache()
3836
.Returns(response.Object);
3937

4038
context.Setup(c => c.Request.Path).Returns("/file.css");
39+
context.Setup(c => c.Features.Get<IHttpsCompressionFeature>()).Returns((IHttpsCompressionFeature) null);
4140

4241
response.SetupGet(c => c.Headers)
4342
.Returns(new HeaderDictionary());
@@ -131,6 +130,7 @@ public async Task AssetMiddleware_Cache()
131130
.Returns(response.Object);
132131

133132
context.Setup(c => c.Request.Path).Returns("/file.css");
133+
context.Setup(c => c.Features.Get<IHttpsCompressionFeature>()).Returns((IHttpsCompressionFeature) null);
134134

135135
var next = new Mock<RequestDelegate>();
136136
var env = new HostingEnvironment();
@@ -247,5 +247,66 @@ public async Task AssetMiddleware_NoAssetMatch()
247247
next.Verify(n => n(context.Object), Times.Once);
248248

249249
}
250+
251+
[Fact2]
252+
public async Task AssetMiddleware_Compression()
253+
{
254+
var cssContent = "*{color:red}".AsByteArray();
255+
256+
var pipeline = new AssetPipeline();
257+
var options = new WebOptimizerOptions() { HttpsCompression = HttpsCompressionMode.Compress };
258+
var asset = new Mock<IAsset>().SetupAllProperties();
259+
asset.SetupGet(a => a.ContentType).Returns("text/css");
260+
asset.SetupGet(a => a.Route).Returns("/file.css");
261+
asset.Setup(a => a.ExecuteAsync(It.IsAny<HttpContext>(), options))
262+
.Returns(Task.FromResult(cssContent));
263+
264+
StringValues values;
265+
var response = new Mock<HttpResponse>().SetupAllProperties();
266+
var context = new Mock<HttpContext>().SetupAllProperties();
267+
context.Setup(s => s.Request.Headers.TryGetValue("Accept-Encoding", out values))
268+
.Returns(false);
269+
context.Setup(c => c.Response)
270+
.Returns(response.Object);
271+
272+
context.Setup(c => c.Request.Path).Returns("/file.css");
273+
274+
var compressionFeature = new HttpsCompressionFeature();
275+
context.Setup(c => c.Features.Get<IHttpsCompressionFeature>()).Returns(compressionFeature);
276+
277+
var next = new Mock<RequestDelegate>();
278+
var cache = new Mock<IMemoryCache>();
279+
var mcr = new AssetResponse(cssContent, null);
280+
281+
object bytes = mcr;
282+
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out bytes))
283+
.Returns(true);
284+
285+
pipeline._assets = new ConcurrentDictionary<string, IAsset>();
286+
pipeline._assets.TryAdd(asset.Object.Route, asset.Object);
287+
288+
var amo = new Mock<IOptionsSnapshot<WebOptimizerOptions>>();
289+
amo.SetupGet(a => a.Value).Returns(options);
290+
291+
var logger = new Mock<ILogger<AssetMiddleware>>();
292+
var builder = new Mock<IAssetBuilder>();
293+
builder.Setup(b => b.BuildAsync(It.IsAny<IAsset>(), context.Object, options)).Returns(Task.FromResult<IAssetResponse>(mcr));
294+
var middleware = new AssetMiddleware(next.Object, pipeline, logger.Object, builder.Object);
295+
var stream = new MemoryStream();
296+
297+
response.Setup(r => r.Body).Returns(stream);
298+
await middleware.InvokeAsync(context.Object, amo.Object);
299+
300+
Assert.Equal("text/css", context.Object.Response.ContentType);
301+
Assert.Equal(cssContent, await stream.AsBytesAsync());
302+
Assert.Equal(0, response.Object.StatusCode);
303+
304+
Assert.Equal(compressionFeature.Mode, HttpsCompressionMode.Compress);
305+
}
306+
307+
private class HttpsCompressionFeature : IHttpsCompressionFeature
308+
{
309+
public HttpsCompressionMode Mode { get; set; }
310+
}
250311
}
251312
}

0 commit comments

Comments
 (0)