Merge pull request #370 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
601bf83bda
|
|
@ -26,6 +26,11 @@ namespace ResponseCompressionSample
|
||||||
options.Providers.Add<CustomCompressionProvider>();
|
options.Providers.Add<CustomCompressionProvider>();
|
||||||
// .Append(TItem) is only available on Core.
|
// .Append(TItem) is only available on Core.
|
||||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
|
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "image/svg+xml" });
|
||||||
|
|
||||||
|
////Example of using excluded and wildcard MIME types:
|
||||||
|
////Compress all MIME types except various media types, but do compress SVG images.
|
||||||
|
//options.MimeTypes = new[] { "*/*", "image/svg+xml" };
|
||||||
|
//options.ExcludedMimeTypes = new[] { "image/*", "audio/*", "video/*" };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> MimeTypes { get; set; }
|
public IEnumerable<string> MimeTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Response Content-Type MIME types to not compress.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> ExcludedMimeTypes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if responses over HTTPS connections should be compressed. The default is 'false'.
|
/// Indicates if responses over HTTPS connections should be compressed. The default is 'false'.
|
||||||
/// Enable compression on HTTPS connections may expose security problems.
|
/// Enabling compression on HTTPS connections may expose security problems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableForHttps { get; set; } = false;
|
public bool EnableForHttps { get; set; } = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
{
|
{
|
||||||
private readonly ICompressionProvider[] _providers;
|
private readonly ICompressionProvider[] _providers;
|
||||||
private readonly HashSet<string> _mimeTypes;
|
private readonly HashSet<string> _mimeTypes;
|
||||||
|
private readonly HashSet<string> _excludedMimeTypes;
|
||||||
private readonly bool _enableForHttps;
|
private readonly bool _enableForHttps;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -34,7 +35,9 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
_providers = options.Value.Providers.ToArray();
|
var responseCompressionOptions = options.Value;
|
||||||
|
|
||||||
|
_providers = responseCompressionOptions.Providers.ToArray();
|
||||||
if (_providers.Length == 0)
|
if (_providers.Length == 0)
|
||||||
{
|
{
|
||||||
// Use the factory so it can resolve IOptions<GzipCompressionProviderOptions> from DI.
|
// Use the factory so it can resolve IOptions<GzipCompressionProviderOptions> from DI.
|
||||||
|
|
@ -59,14 +62,19 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimeTypes = options.Value.MimeTypes;
|
var mimeTypes = responseCompressionOptions.MimeTypes;
|
||||||
if (mimeTypes == null || !mimeTypes.Any())
|
if (mimeTypes == null || !mimeTypes.Any())
|
||||||
{
|
{
|
||||||
mimeTypes = ResponseCompressionDefaults.MimeTypes;
|
mimeTypes = ResponseCompressionDefaults.MimeTypes;
|
||||||
}
|
}
|
||||||
_mimeTypes = new HashSet<string>(mimeTypes, StringComparer.OrdinalIgnoreCase);
|
_mimeTypes = new HashSet<string>(mimeTypes, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_enableForHttps = options.Value.EnableForHttps;
|
_excludedMimeTypes = new HashSet<string>(
|
||||||
|
responseCompressionOptions.ExcludedMimeTypes ?? Enumerable.Empty<string>(),
|
||||||
|
StringComparer.OrdinalIgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
_enableForHttps = responseCompressionOptions.EnableForHttps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -115,7 +123,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
for (int i = 0; i < _providers.Length; i++)
|
for (int i = 0; i < _providers.Length; i++)
|
||||||
{
|
{
|
||||||
var provider = _providers[i];
|
var provider = _providers[i];
|
||||||
|
|
||||||
// Any provider is a candidate.
|
// Any provider is a candidate.
|
||||||
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
|
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
|
||||||
}
|
}
|
||||||
|
|
@ -175,8 +183,9 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
mimeType = mimeType.Trim();
|
mimeType = mimeType.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO PERF: StringSegments?
|
return ShouldCompressExact(mimeType) //check exact match type/subtype
|
||||||
return _mimeTypes.Contains(mimeType);
|
?? ShouldCompressPartial(mimeType) //check partial match type/*
|
||||||
|
?? _mimeTypes.Contains("*/*"); //check wildcard */*
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -189,6 +198,35 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
||||||
return !string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding]);
|
return !string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool? ShouldCompressExact(string mimeType)
|
||||||
|
{
|
||||||
|
//Check excluded MIME types first, then included
|
||||||
|
if (_excludedMimeTypes.Contains(mimeType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mimeTypes.Contains(mimeType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? ShouldCompressPartial(string mimeType)
|
||||||
|
{
|
||||||
|
int? slashPos = mimeType?.IndexOf('/');
|
||||||
|
|
||||||
|
if (slashPos >= 0)
|
||||||
|
{
|
||||||
|
string partialMimeType = mimeType.Substring(0, slashPos.Value) + "/*";
|
||||||
|
return ShouldCompressExact(partialMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly struct ProviderCandidate : IEquatable<ProviderCandidate>
|
private readonly struct ProviderCandidate : IEquatable<ProviderCandidate>
|
||||||
{
|
{
|
||||||
public ProviderCandidate(string encodingName, double quality, int priority, ICompressionProvider provider)
|
public ProviderCandidate(string encodingName, double quality, int priority, ICompressionProvider provider)
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,153 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
||||||
CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false);
|
CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null, null, "text/plain", true)]
|
||||||
|
[InlineData(null, new string[0], "text/plain", true)]
|
||||||
|
[InlineData(null, new[] { "TEXT/plain" }, "text/plain", false)]
|
||||||
|
[InlineData(null, new[] { "TEXT/*" }, "text/plain", true)]
|
||||||
|
[InlineData(null, new[] { "*/*" }, "text/plain", true)]
|
||||||
|
|
||||||
|
[InlineData(new string[0], null, "text/plain", true)]
|
||||||
|
[InlineData(new string[0], new string[0], "text/plain", true)]
|
||||||
|
[InlineData(new string[0], new[] { "TEXT/plain" }, "text/plain", false)]
|
||||||
|
[InlineData(new string[0], new[] { "TEXT/*" }, "text/plain", true)]
|
||||||
|
[InlineData(new string[0], new[] { "*/*" }, "text/plain", true)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, null, "text/plain", true)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new string[0], "text/plain", true)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/plain" }, "text/plain", false)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/*" }, "text/plain", true)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "*/*" }, "text/plain", true)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "TEXT/*" }, null, "text/plain", true)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new string[0], "text/plain", true)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "TEXT/plain" }, "text/plain", false)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "TEXT/*" }, "text/plain", false)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "*/*" }, "text/plain", true)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "*/*" }, null, "text/plain", true)]
|
||||||
|
[InlineData(new[] { "*/*" }, new string[0], "text/plain", true)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "TEXT/plain" }, "text/plain", false)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "TEXT/*" }, "text/plain", false)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "*/*" }, "text/plain", true)]
|
||||||
|
|
||||||
|
[InlineData(null, null, "text/plain2", false)]
|
||||||
|
[InlineData(null, new string[0], "text/plain2", false)]
|
||||||
|
[InlineData(null, new[] { "TEXT/plain" }, "text/plain2", false)]
|
||||||
|
[InlineData(null, new[] { "TEXT/*" }, "text/plain2", false)]
|
||||||
|
[InlineData(null, new[] { "*/*" }, "text/plain2", false)]
|
||||||
|
|
||||||
|
[InlineData(new string[0], null, "text/plain2", false)]
|
||||||
|
[InlineData(new string[0], new string[0], "text/plain2", false)]
|
||||||
|
[InlineData(new string[0], new[] { "TEXT/plain" }, "text/plain2", false)]
|
||||||
|
[InlineData(new string[0], new[] { "TEXT/*" }, "text/plain2", false)]
|
||||||
|
[InlineData(new string[0], new[] { "*/*" }, "text/plain2", false)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, null, "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new string[0], "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/plain" }, "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "TEXT/*" }, "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "TEXT/plain" }, new[] { "*/*" }, "text/plain2", false)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "TEXT/*" }, null, "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new string[0], "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "TEXT/plain" }, "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "TEXT/*" }, "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "TEXT/*" }, new[] { "*/*" }, "text/plain2", true)]
|
||||||
|
|
||||||
|
[InlineData(new[] { "*/*" }, null, "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "*/*" }, new string[0], "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "TEXT/plain" }, "text/plain2", true)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "TEXT/*" }, "text/plain2", false)]
|
||||||
|
[InlineData(new[] { "*/*" }, new[] { "*/*" }, "text/plain2", true)]
|
||||||
|
public async Task MimeTypes_IncludedAndExcluded(
|
||||||
|
string[] mimeTypes,
|
||||||
|
string[] excludedMimeTypes,
|
||||||
|
string mimeType,
|
||||||
|
bool compress
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(
|
||||||
|
services =>
|
||||||
|
services.AddResponseCompression(
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.MimeTypes = mimeTypes;
|
||||||
|
options.ExcludedMimeTypes = excludedMimeTypes;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.Configure(
|
||||||
|
app =>
|
||||||
|
{
|
||||||
|
app.UseResponseCompression();
|
||||||
|
app.Run(
|
||||||
|
context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
|
||||||
|
context.Response.ContentType = mimeType;
|
||||||
|
return context.Response.WriteAsync(new string('a', 100));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
request.Headers.AcceptEncoding.ParseAdd("gzip");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
if (compress)
|
||||||
|
{
|
||||||
|
CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CheckResponseNotCompressed(response, expectedBodyLength: 100, sendVaryHeader: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoIncludedMimeTypes_UseDefaults()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(
|
||||||
|
services =>
|
||||||
|
services.AddResponseCompression(
|
||||||
|
options => options.ExcludedMimeTypes = new[] { "text/*" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.Configure(
|
||||||
|
app =>
|
||||||
|
{
|
||||||
|
app.UseResponseCompression();
|
||||||
|
app.Run(
|
||||||
|
context =>
|
||||||
|
{
|
||||||
|
context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
|
||||||
|
context.Response.ContentType = TextPlain;
|
||||||
|
return context.Response.WriteAsync(new string('a', 100));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
request.Headers.AcceptEncoding.ParseAdd("gzip");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
CheckResponseCompressed(response, expectedBodyLength: 24, expectedEncoding: "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData("text/plain")]
|
[InlineData("text/plain")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue