Skip to content

Commit 09a7b3b

Browse files
committed
Add logging to the response compression middleware #222
1 parent cc61095 commit 09a7b3b

File tree

5 files changed

+262
-166
lines changed

5 files changed

+262
-166
lines changed

src/Microsoft.AspNetCore.ResponseCompression/Microsoft.AspNetCore.ResponseCompression.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
12+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
1213
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
1314
</ItemGroup>
1415

src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.ResponseCompression.Internal;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Logging;
812
using Microsoft.Extensions.Options;
913
using Microsoft.Extensions.Primitives;
1014
using Microsoft.Net.Http.Headers;
@@ -18,6 +22,7 @@ public class ResponseCompressionProvider : IResponseCompressionProvider
1822
private readonly HashSet<string> _mimeTypes;
1923
private readonly HashSet<string> _excludedMimeTypes;
2024
private readonly bool _enableForHttps;
25+
private readonly ILogger _logger;
2126

2227
/// <summary>
2328
/// If no compression providers are specified then GZip is used by default.
@@ -67,6 +72,7 @@ public ResponseCompressionProvider(IServiceProvider services, IOptions<ResponseC
6772
{
6873
mimeTypes = ResponseCompressionDefaults.MimeTypes;
6974
}
75+
7076
_mimeTypes = new HashSet<string>(mimeTypes, StringComparer.OrdinalIgnoreCase);
7177

7278
_excludedMimeTypes = new HashSet<string>(
@@ -75,6 +81,8 @@ public ResponseCompressionProvider(IServiceProvider services, IOptions<ResponseC
7581
);
7682

7783
_enableForHttps = responseCompressionOptions.EnableForHttps;
84+
85+
_logger = services.GetRequiredService<ILogger<ResponseCompressionProvider>>();
7886
}
7987

8088
/// <inheritdoc />
@@ -83,95 +91,108 @@ public virtual ICompressionProvider GetCompressionProvider(HttpContext context)
8391
// e.g. Accept-Encoding: gzip, deflate, sdch
8492
var accept = context.Request.Headers[HeaderNames.AcceptEncoding];
8593

94+
// Note this is already checked in CheckRequestAcceptsCompression which _should_ prevent any of these other methods from being called.
8695
if (StringValues.IsNullOrEmpty(accept))
8796
{
97+
Debug.Assert(false, "Duplicate check failed.");
98+
_logger.NoAcceptEncoding();
8899
return null;
89100
}
90101

91-
if (StringWithQualityHeaderValue.TryParseList(accept, out var encodings))
102+
if (!StringWithQualityHeaderValue.TryParseList(accept, out var encodings) || !encodings.Any())
92103
{
93-
if (encodings.Count == 0)
104+
_logger.NoAcceptEncoding();
105+
return null;
106+
}
107+
108+
var candidates = new HashSet<ProviderCandidate>();
109+
110+
foreach (var encoding in encodings)
111+
{
112+
var encodingName = encoding.Value;
113+
var quality = encoding.Quality.GetValueOrDefault(1);
114+
115+
if (quality < double.Epsilon)
94116
{
95-
return null;
117+
continue;
96118
}
97119

98-
var candidates = new HashSet<ProviderCandidate>();
99-
100-
foreach (var encoding in encodings)
120+
for (int i = 0; i < _providers.Length; i++)
101121
{
102-
var encodingName = encoding.Value;
103-
var quality = encoding.Quality.GetValueOrDefault(1);
122+
var provider = _providers[i];
104123

105-
if (quality < double.Epsilon)
124+
if (StringSegment.Equals(provider.EncodingName, encodingName, StringComparison.OrdinalIgnoreCase))
106125
{
107-
continue;
126+
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
108127
}
128+
}
109129

130+
// Uncommon but valid options
131+
if (StringSegment.Equals("*", encodingName, StringComparison.Ordinal))
132+
{
110133
for (int i = 0; i < _providers.Length; i++)
111134
{
112135
var provider = _providers[i];
113136

114-
if (StringSegment.Equals(provider.EncodingName, encodingName, StringComparison.OrdinalIgnoreCase))
115-
{
116-
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
117-
}
137+
// Any provider is a candidate.
138+
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
118139
}
119140

120-
// Uncommon but valid options
121-
if (StringSegment.Equals("*", encodingName, StringComparison.Ordinal))
122-
{
123-
for (int i = 0; i < _providers.Length; i++)
124-
{
125-
var provider = _providers[i];
126-
127-
// Any provider is a candidate.
128-
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
129-
}
130-
131-
break;
132-
}
133-
134-
if (StringSegment.Equals("identity", encodingName, StringComparison.OrdinalIgnoreCase))
135-
{
136-
// We add 'identity' to the list of "candidates" with a very low priority and no provider.
137-
// This will allow it to be ordered based on its quality (and priority) later in the method.
138-
candidates.Add(new ProviderCandidate(encodingName.Value, quality, priority: int.MaxValue, provider: null));
139-
}
141+
break;
140142
}
141143

142-
if (candidates.Count <= 1)
144+
if (StringSegment.Equals("identity", encodingName, StringComparison.OrdinalIgnoreCase))
143145
{
144-
return candidates.ElementAtOrDefault(0).Provider;
146+
// We add 'identity' to the list of "candidates" with a very low priority and no provider.
147+
// This will allow it to be ordered based on its quality (and priority) later in the method.
148+
candidates.Add(new ProviderCandidate(encodingName.Value, quality, priority: int.MaxValue, provider: null));
145149
}
150+
}
146151

147-
var accepted = candidates
152+
ICompressionProvider selectedProvider = null;
153+
if (candidates.Count <= 1)
154+
{
155+
selectedProvider = candidates.FirstOrDefault().Provider;
156+
}
157+
else
158+
{
159+
selectedProvider = candidates
148160
.OrderByDescending(x => x.Quality)
149161
.ThenBy(x => x.Priority)
150-
.First();
162+
.First().Provider;
163+
}
151164

152-
return accepted.Provider;
165+
if (selectedProvider == null)
166+
{
167+
// "identity" would match as a candidate but not have a provider implementation
168+
_logger.NoCompressionProvider();
169+
return null;
153170
}
154171

155-
return null;
172+
_logger.CompressingWith(selectedProvider.EncodingName);
173+
return selectedProvider;
156174
}
157175

158176
/// <inheritdoc />
159177
public virtual bool ShouldCompressResponse(HttpContext context)
160178
{
161179
if (context.Response.Headers.ContainsKey(HeaderNames.ContentRange))
162180
{
181+
_logger.NoCompressionDueToHeader(HeaderNames.ContentRange);
163182
return false;
164183
}
165184

166185
if (context.Response.Headers.ContainsKey(HeaderNames.ContentEncoding))
167186
{
187+
_logger.NoCompressionDueToHeader(HeaderNames.ContentEncoding);
168188
return false;
169189
}
170190

171191
var mimeType = context.Response.ContentType;
172192

173193
if (string.IsNullOrEmpty(mimeType))
174194
{
195+
_logger.NoCompressionForContentType(mimeType);
175196
return false;
176197
}
177198

@@ -183,19 +204,37 @@ public virtual bool ShouldCompressResponse(HttpContext context)
183204
mimeType = mimeType.Trim();
184205
}
185206

186-
return ShouldCompressExact(mimeType) //check exact match type/subtype
207+
var shouldCompress = ShouldCompressExact(mimeType) //check exact match type/subtype
187208
?? ShouldCompressPartial(mimeType) //check partial match type/*
188209
?? _mimeTypes.Contains("*/*"); //check wildcard */*
210+
211+
if (shouldCompress)
212+
{
213+
_logger.ShouldCompressResponse(); // Trace, there will be more logs
214+
return true;
215+
}
216+
217+
_logger.NoCompressionForContentType(mimeType);
218+
return false;
189219
}
190220

191221
/// <inheritdoc />
192222
public bool CheckRequestAcceptsCompression(HttpContext context)
193223
{
194224
if (context.Request.IsHttps && !_enableForHttps)
195225
{
226+
_logger.NoCompressionForHttps();
196227
return false;
197228
}
198-
return !string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding]);
229+
230+
if (string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding]))
231+
{
232+
_logger.NoAcceptEncoding();
233+
return false;
234+
}
235+
236+
_logger.RequestAcceptsCompression(); // Trace, there will be more logs
237+
return true;
199238
}
200239

201240
private bool? ShouldCompressExact(string mimeType)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.AspNetCore.ResponseCompression.Internal
8+
{
9+
internal static class ResponseCompressionLoggingExtensions
10+
{
11+
private static readonly Action<ILogger, Exception> _noAcceptEncoding;
12+
private static readonly Action<ILogger, Exception> _noCompressionForHttps;
13+
private static readonly Action<ILogger, Exception> _requestAcceptsCompression;
14+
private static readonly Action<ILogger, string, Exception> _noCompressionDueToHeader;
15+
private static readonly Action<ILogger, string, Exception> _noCompressionForContentType;
16+
private static readonly Action<ILogger, Exception> _shouldCompressResponse;
17+
private static readonly Action<ILogger, Exception> _noCompressionProvider;
18+
private static readonly Action<ILogger, string, Exception> _compressWith;
19+
20+
static ResponseCompressionLoggingExtensions()
21+
{
22+
_noAcceptEncoding = LoggerMessage.Define(LogLevel.Debug, 1, "No response compression available, the Accept-Encoding header is missing or invalid.");
23+
_noCompressionForHttps = LoggerMessage.Define(LogLevel.Debug, 2, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps.");
24+
_requestAcceptsCompression = LoggerMessage.Define(LogLevel.Trace, 3, "This request accepts compression.");
25+
_noCompressionDueToHeader = LoggerMessage.Define<string>(LogLevel.Debug, 4, "Response compression disabled due to the {header} header.");
26+
_noCompressionForContentType = LoggerMessage.Define<string>(LogLevel.Debug, 5, "Response compression is not enabled for the Content-Type '{header}'.");
27+
_shouldCompressResponse = LoggerMessage.Define(LogLevel.Trace, 6, "Response compression is available for this Content-Type.");
28+
_noCompressionProvider = LoggerMessage.Define(LogLevel.Debug, 7, "No matching response compression provider found.");
29+
_compressWith = LoggerMessage.Define<string>(LogLevel.Debug, 8, "The response will be compressed with '{provider}'.");
30+
}
31+
32+
public static void NoAcceptEncoding(this ILogger logger)
33+
{
34+
_noAcceptEncoding(logger, null);
35+
}
36+
37+
public static void NoCompressionForHttps(this ILogger logger)
38+
{
39+
_noCompressionForHttps(logger, null);
40+
}
41+
42+
public static void RequestAcceptsCompression(this ILogger logger)
43+
{
44+
_requestAcceptsCompression(logger, null);
45+
}
46+
47+
public static void NoCompressionDueToHeader(this ILogger logger, string header)
48+
{
49+
_noCompressionDueToHeader(logger, header, null);
50+
}
51+
52+
public static void NoCompressionForContentType(this ILogger logger, string header)
53+
{
54+
_noCompressionForContentType(logger, header, null);
55+
}
56+
57+
public static void ShouldCompressResponse(this ILogger logger)
58+
{
59+
_shouldCompressResponse(logger, null);
60+
}
61+
62+
public static void NoCompressionProvider(this ILogger logger)
63+
{
64+
_noCompressionProvider(logger, null);
65+
}
66+
67+
public static void CompressingWith(this ILogger logger, string provider)
68+
{
69+
_compressWith(logger, provider, null);
70+
}
71+
}
72+
}

test/Microsoft.AspNetCore.ResponseCompression.Tests/Microsoft.AspNetCore.ResponseCompression.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
<ItemGroup>
1616
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />
17+
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
1718
<PackageReference Include="Microsoft.Net.Http.Headers" Version="$(MicrosoftNetHttpHeadersPackageVersion)" />
1819
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
1920
</ItemGroup>

0 commit comments

Comments
 (0)