#108 Change ResponseCompression to be DI centric
This commit is contained in:
parent
3e119a87be
commit
9bc8a83a39
|
|
@ -0,0 +1,16 @@
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
|
||||
namespace ResponseCompressionSample
|
||||
{
|
||||
public class CustomCompressionProvider : ICompressionProvider
|
||||
{
|
||||
public string EncodingName => "custom";
|
||||
|
||||
public Stream CreateStream(Stream outputStream)
|
||||
{
|
||||
// Create a custom compression stream wrapper here
|
||||
return outputStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,9 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:49658/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"ResponseCompressionSample": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"commandName": "web",
|
||||
"launchUrl": "http://localhost:5000/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,26 @@ using Microsoft.AspNetCore.Builder;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ResponseCompressionSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<ICompressionProvider, GzipCompressionProvider>();
|
||||
services.AddTransient<ICompressionProvider, CustomCompressionProvider>();
|
||||
services.AddResponseCompression("text/plain", "text/html");
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseResponseCompression(new ResponseCompressionOptions()
|
||||
{
|
||||
ShouldCompressResponse = ResponseCompressionUtils.CreateShouldCompressResponseDelegate(new string[] { "text/plain" })
|
||||
});
|
||||
app.UseResponseCompression();
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
context.Response.Headers["Content-Type"] = "text/plain";
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync(LoremIpsum.Text);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
|
|
@ -21,19 +19,19 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
|
||||
private readonly Stream _bodyOriginalStream;
|
||||
|
||||
private readonly Func<HttpContext, bool> _shouldCompressResponse;
|
||||
private readonly IResponseCompressionProvider _provider;
|
||||
|
||||
private readonly IResponseCompressionProvider _compressionProvider;
|
||||
private readonly ICompressionProvider _compressionProvider;
|
||||
|
||||
private bool _compressionChecked = false;
|
||||
|
||||
private Stream _compressionStream = null;
|
||||
|
||||
internal BodyWrapperStream(HttpResponse response, Stream bodyOriginalStream, Func<HttpContext, bool> shouldCompressResponse, IResponseCompressionProvider compressionProvider)
|
||||
internal BodyWrapperStream(HttpResponse response, Stream bodyOriginalStream, IResponseCompressionProvider provider, ICompressionProvider compressionProvider)
|
||||
{
|
||||
_response = response;
|
||||
_bodyOriginalStream = bodyOriginalStream;
|
||||
_shouldCompressResponse = shouldCompressResponse;
|
||||
_provider = provider;
|
||||
_compressionProvider = compressionProvider;
|
||||
}
|
||||
|
||||
|
|
@ -174,9 +172,8 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
|
||||
private bool IsCompressable()
|
||||
{
|
||||
return _response.Headers[HeaderNames.ContentRange] == StringValues.Empty && // The response is not partial
|
||||
_response.Headers[HeaderNames.ContentEncoding] == StringValues.Empty && // Not specific encoding already set
|
||||
_shouldCompressResponse(_response.HttpContext);
|
||||
return !_response.Headers.ContainsKey(HeaderNames.ContentRange) && // The response is not partial
|
||||
_provider.ShouldCompressResponse(_response.HttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,26 +9,27 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// <summary>
|
||||
/// GZIP compression provider.
|
||||
/// </summary>
|
||||
public class GzipResponseCompressionProvider : IResponseCompressionProvider
|
||||
public class GzipCompressionProvider : ICompressionProvider
|
||||
{
|
||||
private readonly CompressionLevel _level;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new <see cref="GzipResponseCompressionProvider"/>.
|
||||
/// Initialize a new <see cref="GzipCompressionProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="level">The compression level.</param>
|
||||
public GzipResponseCompressionProvider(CompressionLevel level)
|
||||
public GzipCompressionProvider()
|
||||
{
|
||||
_level = level;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string EncodingName { get; } = "gzip";
|
||||
public string EncodingName => "gzip";
|
||||
|
||||
/// <summary>
|
||||
/// What level of compression to use for the stream.
|
||||
/// </summary>
|
||||
public CompressionLevel Level { get; set; } = CompressionLevel.Fastest;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream CreateStream(Stream outputStream)
|
||||
{
|
||||
return new GZipStream(outputStream, _level, true);
|
||||
return new GZipStream(outputStream, Level, leaveOpen: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a specific compression implementation to compress HTTP responses.
|
||||
/// </summary>
|
||||
public interface ICompressionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The encoding name used in the 'Accept-Encoding' request header and 'Content-Encoding' response header.
|
||||
/// </summary>
|
||||
string EncodingName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new compression stream.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The stream where the compressed data have to be written.</param>
|
||||
/// <returns>The compression stream.</returns>
|
||||
Stream CreateStream(Stream outputStream);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to be able to compress HTTP responses.
|
||||
/// Used to examine requests and responses to see if compression should be enabled.
|
||||
/// </summary>
|
||||
public interface IResponseCompressionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The name that will be searched in the 'Accept-Encoding' request header.
|
||||
/// Examines the request and selects an acceptable compression provider, if any.
|
||||
/// </summary>
|
||||
string EncodingName { get; }
|
||||
/// <param name="context"></param>
|
||||
/// <returns>A compression provider or null if compression should not be used.</returns>
|
||||
ICompressionProvider GetCompressionProvider(HttpContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new compression stream.
|
||||
/// Examines the response on first write to see if compression should be used.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The stream where the compressed data have to be written.</param>
|
||||
/// <returns>The new stream.</returns>
|
||||
Stream CreateStream(Stream outputStream);
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
bool ShouldCompressResponse(HttpContext context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
|
|
@ -13,22 +14,53 @@ namespace Microsoft.AspNetCore.Builder
|
|||
public static class ResponseCompressionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows to compress HTTP Responses.
|
||||
/// Add response compression services and enable compression for responses with the given MIME types.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
|
||||
/// <param name="mimeTypes">Response Content-Type MIME types to enable compression for.</param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddResponseCompression(this IServiceCollection services, params string[] mimeTypes)
|
||||
{
|
||||
return services.AddResponseCompression(options =>
|
||||
{
|
||||
options.MimeTypes = mimeTypes;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add response compression services and configure the related options.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
|
||||
/// <param name="configureOptions">A delegate to configure the <see cref="ResponseCompressionOptions"/>.</param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddResponseCompression(this IServiceCollection services, Action<ResponseCompressionOptions> configureOptions)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
if (configureOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configureOptions));
|
||||
}
|
||||
|
||||
services.Configure(configureOptions);
|
||||
services.TryAddTransient<IResponseCompressionProvider, ResponseCompressionProvider>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds middleware for dynamically compressing HTTP Responses.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
||||
/// <param name="options">The <see cref="ResponseCompressionOptions"/>.</param>
|
||||
public static IApplicationBuilder UseResponseCompression(this IApplicationBuilder builder, ResponseCompressionOptions options)
|
||||
public static IApplicationBuilder UseResponseCompression(this IApplicationBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
return builder.UseMiddleware<ResponseCompressionMiddleware>(Options.Create(options));
|
||||
return builder.UseMiddleware<ResponseCompressionMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
|
|
@ -20,9 +15,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
private readonly Dictionary<string, IResponseCompressionProvider> _compressionProviders;
|
||||
|
||||
private readonly Func<HttpContext, bool> _shouldCompressResponse;
|
||||
private readonly IResponseCompressionProvider _provider;
|
||||
|
||||
private readonly bool _enableHttps;
|
||||
|
||||
|
|
@ -30,35 +23,24 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// Initialize the Response Compression middleware.
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="options"></param>
|
||||
public ResponseCompressionMiddleware(RequestDelegate next, IOptions<ResponseCompressionOptions> options)
|
||||
public ResponseCompressionMiddleware(RequestDelegate next, IResponseCompressionProvider provider, IOptions<ResponseCompressionOptions> options)
|
||||
{
|
||||
if (options.Value.ShouldCompressResponse == null)
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(options.Value.ShouldCompressResponse)} is not provided in argument {nameof(options)}");
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
if (provider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(provider));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_shouldCompressResponse = options.Value.ShouldCompressResponse;
|
||||
|
||||
_next = next;
|
||||
|
||||
var providers = options.Value.Providers;
|
||||
if (providers == null)
|
||||
{
|
||||
providers = new IResponseCompressionProvider[]
|
||||
{
|
||||
new GzipResponseCompressionProvider(CompressionLevel.Fastest)
|
||||
};
|
||||
}
|
||||
else if (!providers.Any())
|
||||
{
|
||||
throw new ArgumentException($"{nameof(options.Value.Providers)} cannot be empty in argument {nameof(options)}");
|
||||
}
|
||||
|
||||
_compressionProviders = providers.ToDictionary(p => p.EncodingName, StringComparer.OrdinalIgnoreCase);
|
||||
_compressionProviders.Add("*", providers.First());
|
||||
_compressionProviders.Add("identity", null);
|
||||
|
||||
_provider = provider;
|
||||
_enableHttps = options.Value.EnableHttps;
|
||||
}
|
||||
|
||||
|
|
@ -69,11 +51,11 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// <returns></returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
IResponseCompressionProvider compressionProvider = null;
|
||||
ICompressionProvider compressionProvider = null;
|
||||
|
||||
if (!context.Request.IsHttps || _enableHttps)
|
||||
{
|
||||
compressionProvider = SelectProvider(context.Request.Headers[HeaderNames.AcceptEncoding]);
|
||||
compressionProvider = _provider.GetCompressionProvider(context);
|
||||
}
|
||||
|
||||
if (compressionProvider == null)
|
||||
|
|
@ -84,7 +66,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
|
||||
var bodyStream = context.Response.Body;
|
||||
|
||||
using (var bodyWrapperStream = new BodyWrapperStream(context.Response, bodyStream, _shouldCompressResponse, compressionProvider))
|
||||
using (var bodyWrapperStream = new BodyWrapperStream(context.Response, bodyStream, _provider, compressionProvider))
|
||||
{
|
||||
context.Response.Body = bodyWrapperStream;
|
||||
|
||||
|
|
@ -98,29 +80,5 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IResponseCompressionProvider SelectProvider(StringValues acceptEncoding)
|
||||
{
|
||||
IList<StringWithQualityHeaderValue> unsorted;
|
||||
|
||||
if (StringWithQualityHeaderValue.TryParseList(acceptEncoding, out unsorted) && unsorted != null)
|
||||
{
|
||||
var sorted = unsorted
|
||||
.Where(s => s.Quality.GetValueOrDefault(1) > 0)
|
||||
.OrderByDescending(s => s.Quality.GetValueOrDefault(1));
|
||||
|
||||
foreach (var encoding in sorted)
|
||||
{
|
||||
IResponseCompressionProvider provider;
|
||||
|
||||
if (_compressionProviders.TryGetValue(encoding.Value, out provider))
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
|
|
@ -13,19 +11,13 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
public class ResponseCompressionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when an HTTP request accepts a compatible compression algorithm, and returns True
|
||||
/// if the response should be compressed.
|
||||
/// Response Content-Type MIME types to compress.
|
||||
/// </summary>
|
||||
public Func<HttpContext, bool> ShouldCompressResponse { get; set; }
|
||||
public IEnumerable<string> MimeTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The compression providers. If 'null', the GZIP provider is set as default.
|
||||
/// </summary>
|
||||
public IEnumerable<IResponseCompressionProvider> Providers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 'False' to enable compression only on HTTP requests. Enable compression on HTTPS requests
|
||||
/// may lead to security problems.
|
||||
/// Indicates if responses over HTTPS connections should be compressed. The default is 'false'.
|
||||
/// Enable compression on HTTPS connections may expose security problems.
|
||||
/// </summary>
|
||||
public bool EnableHttps { get; set; } = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ResponseCompressionProvider : IResponseCompressionProvider
|
||||
{
|
||||
private readonly ICompressionProvider[] _providers;
|
||||
private readonly HashSet<string> _mimeTypes;
|
||||
|
||||
/// <summary>
|
||||
/// If no compression providers are specified then GZip is used by default.
|
||||
/// </summary>
|
||||
/// <param name="providers">Compression providers to use, if any.</param>
|
||||
/// <param name="options"></param>
|
||||
public ResponseCompressionProvider(IEnumerable<ICompressionProvider> providers, IOptions<ResponseCompressionOptions> options)
|
||||
{
|
||||
if (providers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(providers));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_providers = providers.ToArray();
|
||||
if (_providers.Length == 0)
|
||||
{
|
||||
_providers = new [] { new GzipCompressionProvider() };
|
||||
}
|
||||
|
||||
if (options.Value.MimeTypes == null || !options.Value.MimeTypes.Any())
|
||||
{
|
||||
throw new InvalidOperationException("No mime types specified");
|
||||
}
|
||||
_mimeTypes = new HashSet<string>(options.Value.MimeTypes, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ICompressionProvider GetCompressionProvider(HttpContext context)
|
||||
{
|
||||
IList<StringWithQualityHeaderValue> unsorted;
|
||||
|
||||
// e.g. Accept-Encoding: gzip, deflate, sdch
|
||||
var accept = context.Request.Headers[HeaderNames.AcceptEncoding];
|
||||
if (!StringValues.IsNullOrEmpty(accept)
|
||||
&& StringWithQualityHeaderValue.TryParseList(accept, out unsorted)
|
||||
&& unsorted != null && unsorted.Count > 0)
|
||||
{
|
||||
// TODO PERF: clients don't usually include quality values so this sort will not have any effect. Fast-path?
|
||||
var sorted = unsorted
|
||||
.Where(s => s.Quality.GetValueOrDefault(1) > 0)
|
||||
.OrderByDescending(s => s.Quality.GetValueOrDefault(1));
|
||||
|
||||
foreach (var encoding in sorted)
|
||||
{
|
||||
// There will rarely be more than three providers, and there's only one by default
|
||||
foreach (var provider in _providers)
|
||||
{
|
||||
if (string.Equals(provider.EncodingName, encoding.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
// Uncommon but valid options
|
||||
if (string.Equals("*", encoding.Value, StringComparison.Ordinal))
|
||||
{
|
||||
// Any
|
||||
return _providers[0];
|
||||
}
|
||||
if (string.Equals("identity", encoding.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// No compression
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool ShouldCompressResponse(HttpContext context)
|
||||
{
|
||||
var mimeType = context.Response.ContentType;
|
||||
|
||||
if (string.IsNullOrEmpty(mimeType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var separator = mimeType.IndexOf(';');
|
||||
if (separator >= 0)
|
||||
{
|
||||
// Remove the content-type optional parameters
|
||||
mimeType = mimeType.Substring(0, separator);
|
||||
mimeType = mimeType.Trim();
|
||||
}
|
||||
|
||||
return _mimeTypes.Contains(mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Response Compression middleware utility methods.
|
||||
/// </summary>
|
||||
public static class ResponseCompressionUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a delegate that propose to compress response, depending on a list of authorized
|
||||
/// MIME types for the HTTP response.
|
||||
/// </summary>
|
||||
public static Func<HttpContext, bool> CreateShouldCompressResponseDelegate(IEnumerable<string> mimeTypes)
|
||||
{
|
||||
if (mimeTypes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mimeTypes));
|
||||
}
|
||||
|
||||
var mimeTypeSet = new HashSet<string>(mimeTypes, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return (httpContext) =>
|
||||
{
|
||||
var mimeType = httpContext.Response.ContentType;
|
||||
|
||||
if (string.IsNullOrEmpty(mimeType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var separator = mimeType.IndexOf(';');
|
||||
if (separator >= 0)
|
||||
{
|
||||
// Remove the content-type optional parameters
|
||||
mimeType = mimeType.Substring(0, separator);
|
||||
mimeType = mimeType.Trim();
|
||||
}
|
||||
|
||||
return mimeTypeSet.Contains(mimeType);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,15 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -21,18 +18,6 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
{
|
||||
private const string TextPlain = "text/plain";
|
||||
|
||||
[Fact]
|
||||
public void Options_NullShouldCompressResponse_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
new ResponseCompressionMiddleware(null, Options.Create(new ResponseCompressionOptions()
|
||||
{
|
||||
ShouldCompressResponse = null
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Options_HttpsDisabledByDefault()
|
||||
{
|
||||
|
|
@ -41,19 +26,6 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
Assert.False(options.EnableHttps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Options_EmptyProviderList_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
new ResponseCompressionMiddleware(null, Options.Create(new ResponseCompressionOptions()
|
||||
{
|
||||
ShouldCompressResponse = _ => true,
|
||||
Providers = new IResponseCompressionProvider[0]
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Request_NoAcceptEncoding_Uncompressed()
|
||||
{
|
||||
|
|
@ -78,6 +50,91 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
CheckResponseNotCompressed(response, expectedBodyLength: 100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMimeTypes_Throws()
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddResponseCompression();
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.ContentType = TextPlain;
|
||||
return context.Response.WriteAsync(new string('a', 100));
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => new TestServer(builder));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("text/plain")]
|
||||
[InlineData("text/PLAIN")]
|
||||
[InlineData("text/plain; charset=ISO-8859-4")]
|
||||
[InlineData("text/plain ; charset=ISO-8859-4")]
|
||||
public async Task ContentType_WithCharset_Compress(string contentType)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddResponseCompression(TextPlain);
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.ContentType = contentType;
|
||||
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);
|
||||
|
||||
Assert.Equal(24, response.Content.ReadAsByteArrayAsync().Result.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("text/plain2")]
|
||||
public async Task MimeTypes_OtherContentTypes_NoMatch(string contentType)
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddResponseCompression(TextPlain);
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.ContentType = contentType;
|
||||
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);
|
||||
|
||||
Assert.Equal(100, response.Content.ReadAsByteArrayAsync().Result.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Request_AcceptStar_Compressed()
|
||||
{
|
||||
|
|
@ -134,7 +191,7 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Response_WithContentEncodingAlreadySet_NotCompressed()
|
||||
public async Task Response_WithContentEncodingAlreadySet_Stacked()
|
||||
{
|
||||
var otherContentEncoding = "something";
|
||||
|
||||
|
|
@ -143,9 +200,9 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
r.Headers[HeaderNames.ContentEncoding] = otherContentEncoding;
|
||||
});
|
||||
|
||||
Assert.NotNull(response.Headers.GetValues(HeaderNames.ContentMD5));
|
||||
Assert.Single(response.Content.Headers.ContentEncoding, otherContentEncoding);
|
||||
Assert.Equal(50, response.Content.Headers.ContentLength);
|
||||
Assert.True(response.Content.Headers.ContentEncoding.Contains(otherContentEncoding));
|
||||
Assert.True(response.Content.Headers.ContentEncoding.Contains("gzip"));
|
||||
Assert.Equal(24, response.Content.Headers.ContentLength);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -153,52 +210,47 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
[InlineData(true, 24)]
|
||||
public async Task Request_Https_CompressedIfEnabled(bool enableHttps, int expectedLength)
|
||||
{
|
||||
var options = new ResponseCompressionOptions()
|
||||
{
|
||||
ShouldCompressResponse = _ => true,
|
||||
Providers = new IResponseCompressionProvider[]
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
new GzipResponseCompressionProvider(CompressionLevel.Optimal)
|
||||
},
|
||||
EnableHttps = enableHttps
|
||||
};
|
||||
services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableHttps = enableHttps;
|
||||
options.MimeTypes = new[] { TextPlain };
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.ContentType = TextPlain;
|
||||
return context.Response.WriteAsync(new string('a', 100));
|
||||
});
|
||||
});
|
||||
|
||||
var middleware = new ResponseCompressionMiddleware(async context =>
|
||||
{
|
||||
context.Response.ContentType = TextPlain;
|
||||
await context.Response.WriteAsync(new string('a', 100));
|
||||
}, Options.Create(options));
|
||||
var server = new TestServer(builder);
|
||||
server.BaseAddress = new Uri("https://localhost/");
|
||||
var client = server.CreateClient();
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Headers[HeaderNames.AcceptEncoding] = "gzip";
|
||||
httpContext.Request.IsHttps = true;
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
request.Headers.AcceptEncoding.ParseAdd("gzip");
|
||||
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
await middleware.Invoke(httpContext);
|
||||
|
||||
Assert.Equal(expectedLength, httpContext.Response.Body.Length);
|
||||
Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length);
|
||||
}
|
||||
|
||||
private Task<HttpResponseMessage> InvokeMiddleware(int uncompressedBodyLength, string[] requestAcceptEncodings, string responseType, Action<HttpResponse> addResponseAction = null)
|
||||
{
|
||||
var options = new ResponseCompressionOptions()
|
||||
{
|
||||
ShouldCompressResponse = ctx =>
|
||||
{
|
||||
var contentType = ctx.Response.Headers[HeaderNames.ContentType];
|
||||
return contentType.ToString().IndexOf(TextPlain) >= 0;
|
||||
},
|
||||
Providers = new IResponseCompressionProvider[]
|
||||
{
|
||||
new GzipResponseCompressionProvider(CompressionLevel.Optimal)
|
||||
}
|
||||
};
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddResponseCompression(TextPlain);
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression(options);
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Response.Headers[HeaderNames.ContentMD5] = "MD5";
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
||||
{
|
||||
public class ResponseCompressionUtilsTest
|
||||
{
|
||||
private const string TextPlain = "text/plain";
|
||||
|
||||
[Fact]
|
||||
public void CreateShouldCompressResponseDelegate_NullMimeTypes_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
ResponseCompressionUtils.CreateShouldCompressResponseDelegate(null);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateShouldCompressResponseDelegate_Empty_DontCompress()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.ContentType = TextPlain;
|
||||
|
||||
var func = ResponseCompressionUtils.CreateShouldCompressResponseDelegate(Enumerable.Empty<string>());
|
||||
|
||||
var result = func(httpContext);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("text/plain")]
|
||||
[InlineData("text/PLAIN")]
|
||||
[InlineData("text/plain; charset=ISO-8859-4")]
|
||||
[InlineData("text/plain ; charset=ISO-8859-4")]
|
||||
public void CreateShouldCompressResponseDelegate_WithCharset_Compress(string contentType)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.ContentType = contentType;
|
||||
|
||||
var func = ResponseCompressionUtils.CreateShouldCompressResponseDelegate(new string[] { TextPlain });
|
||||
|
||||
var result = func(httpContext);
|
||||
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("text/plain2")]
|
||||
public void CreateShouldCompressResponseDelegate_OtherContentTypes_NoMatch(string contentType)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.ContentType = contentType;
|
||||
|
||||
var func = ResponseCompressionUtils.CreateShouldCompressResponseDelegate(new string[] { TextPlain });
|
||||
|
||||
var result = func(httpContext);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue