parent
7fb3d57f54
commit
6db129a588
|
|
@ -131,6 +131,12 @@ namespace Microsoft.AspNetCore.Http.Features
|
|||
public T Fetch(Microsoft.AspNetCore.Http.Features.IFeatureCollection features) { throw null; }
|
||||
public T Update(Microsoft.AspNetCore.Http.Features.IFeatureCollection features, T feature) { throw null; }
|
||||
}
|
||||
public enum HttpsCompressionMode
|
||||
{
|
||||
Compress = 2,
|
||||
Default = 0,
|
||||
DoNotCompress = 1,
|
||||
}
|
||||
public partial interface IFeatureCollection : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, object>>, System.Collections.IEnumerable
|
||||
{
|
||||
bool IsReadOnly { get; }
|
||||
|
|
@ -207,6 +213,10 @@ namespace Microsoft.AspNetCore.Http.Features
|
|||
{
|
||||
Microsoft.AspNetCore.Http.IHeaderDictionary Trailers { get; set; }
|
||||
}
|
||||
public partial interface IHttpsCompressionFeature
|
||||
{
|
||||
Microsoft.AspNetCore.Http.Features.HttpsCompressionMode Mode { get; set; }
|
||||
}
|
||||
public partial interface IHttpSendFileFeature
|
||||
{
|
||||
System.Threading.Tasks.Task SendFileAsync(string path, long offset, long? count, System.Threading.CancellationToken cancellation);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Use to dynamically control response compression for HTTPS requests.
|
||||
/// </summary>
|
||||
public enum HttpsCompressionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// No value has been specified, use the configured defaults.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Opts out of compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
|
||||
/// may expose security problems.
|
||||
/// </summary>
|
||||
DoNotCompress,
|
||||
|
||||
/// <summary>
|
||||
/// Opts into compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
|
||||
/// may expose security problems.
|
||||
/// </summary>
|
||||
Compress,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures response compression behavior for HTTPS on a per-request basis.
|
||||
/// </summary>
|
||||
public interface IHttpsCompressionFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HttpsCompressionMode"/> to use.
|
||||
/// </summary>
|
||||
HttpsCompressionMode Mode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// <summary>
|
||||
/// Stream wrapper that create specific compression stream only if necessary.
|
||||
/// </summary>
|
||||
internal class BodyWrapperStream : Stream, IHttpBufferingFeature, IHttpSendFileFeature, IHttpResponseStartFeature
|
||||
internal class BodyWrapperStream : Stream, IHttpBufferingFeature, IHttpSendFileFeature, IHttpResponseStartFeature, IHttpsCompressionFeature
|
||||
{
|
||||
private readonly HttpContext _context;
|
||||
private readonly Stream _bodyOriginalStream;
|
||||
|
|
@ -46,6 +46,8 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
return _compressionStream?.DisposeAsync() ?? new ValueTask();
|
||||
}
|
||||
|
||||
HttpsCompressionMode IHttpsCompressionFeature.Mode { get; set; } = HttpsCompressionMode.Default;
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
|
|
|||
|
|
@ -55,11 +55,13 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
var originalBufferFeature = context.Features.Get<IHttpBufferingFeature>();
|
||||
var originalSendFileFeature = context.Features.Get<IHttpSendFileFeature>();
|
||||
var originalStartFeature = context.Features.Get<IHttpResponseStartFeature>();
|
||||
var originalCompressionFeature = context.Features.Get<IHttpsCompressionFeature>();
|
||||
|
||||
var bodyWrapperStream = new BodyWrapperStream(context, bodyStream, _provider,
|
||||
originalBufferFeature, originalSendFileFeature, originalStartFeature);
|
||||
context.Response.Body = bodyWrapperStream;
|
||||
context.Features.Set<IHttpBufferingFeature>(bodyWrapperStream);
|
||||
context.Features.Set<IHttpsCompressionFeature>(bodyWrapperStream);
|
||||
if (originalSendFileFeature != null)
|
||||
{
|
||||
context.Features.Set<IHttpSendFileFeature>(bodyWrapperStream);
|
||||
|
|
@ -79,6 +81,7 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
{
|
||||
context.Response.Body = bodyStream;
|
||||
context.Features.Set(originalBufferFeature);
|
||||
context.Features.Set(originalCompressionFeature);
|
||||
if (originalSendFileFeature != null)
|
||||
{
|
||||
context.Features.Set(originalSendFileFeature);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCompression
|
||||
{
|
||||
|
|
@ -22,8 +23,11 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
|
||||
/// <summary>
|
||||
/// Indicates if responses over HTTPS connections should be compressed. The default is 'false'.
|
||||
/// Enabling compression on HTTPS connections may expose security problems.
|
||||
/// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be overridden per request using <see cref="IHttpsCompressionFeature"/>.
|
||||
/// </remarks>
|
||||
public bool EnableForHttps { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.ResponseCompression.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -170,6 +171,17 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// <inheritdoc />
|
||||
public virtual bool ShouldCompressResponse(HttpContext context)
|
||||
{
|
||||
var httpsMode = context.Features.Get<IHttpsCompressionFeature>()?.Mode ?? HttpsCompressionMode.Default;
|
||||
|
||||
// Check if the app has opted into or out of compression over HTTPS
|
||||
if (context.Request.IsHttps
|
||||
&& (httpsMode == HttpsCompressionMode.DoNotCompress
|
||||
|| !(_enableForHttps || httpsMode == HttpsCompressionMode.Compress)))
|
||||
{
|
||||
_logger.NoCompressionForHttps();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.Response.Headers.ContainsKey(HeaderNames.ContentRange))
|
||||
{
|
||||
_logger.NoCompressionDueToHeader(HeaderNames.ContentRange);
|
||||
|
|
@ -215,12 +227,6 @@ namespace Microsoft.AspNetCore.ResponseCompression
|
|||
/// <inheritdoc />
|
||||
public bool CheckRequestAcceptsCompression(HttpContext context)
|
||||
{
|
||||
if (context.Request.IsHttps && !_enableForHttps)
|
||||
{
|
||||
_logger.NoCompressionForHttps();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(context.Request.Headers[HeaderNames.AcceptEncoding]))
|
||||
{
|
||||
_logger.NoAcceptEncoding();
|
||||
|
|
|
|||
|
|
@ -453,7 +453,123 @@ namespace Microsoft.AspNetCore.ResponseCompression.Tests
|
|||
}
|
||||
else
|
||||
{
|
||||
AssertLog(logMessages.Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps.");
|
||||
AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps.");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpsCompressionMode.Default, 100)]
|
||||
[InlineData(HttpsCompressionMode.DoNotCompress, 100)]
|
||||
[InlineData(HttpsCompressionMode.Compress, 30)]
|
||||
public async Task Request_Https_CompressedIfOptIn(HttpsCompressionMode mode, int expectedLength)
|
||||
{
|
||||
var sink = new TestSink(
|
||||
TestSink.EnableWithTypeName<ResponseCompressionProvider>,
|
||||
TestSink.EnableWithTypeName<ResponseCompressionProvider>);
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = false;
|
||||
options.MimeTypes = new[] { TextPlain };
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpsCompressionFeature>();
|
||||
feature.Mode = mode;
|
||||
context.Response.ContentType = TextPlain;
|
||||
return context.Response.WriteAsync(new string('a', 100));
|
||||
});
|
||||
});
|
||||
|
||||
var server = new TestServer(builder)
|
||||
{
|
||||
BaseAddress = new Uri("https://localhost/")
|
||||
};
|
||||
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
request.Headers.AcceptEncoding.ParseAdd("gzip");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length);
|
||||
|
||||
var logMessages = sink.Writes.ToList();
|
||||
if (mode == HttpsCompressionMode.Compress)
|
||||
{
|
||||
AssertCompressedWithLog(logMessages, "gzip");
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps.");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpsCompressionMode.Default, 30)]
|
||||
[InlineData(HttpsCompressionMode.Compress, 30)]
|
||||
[InlineData(HttpsCompressionMode.DoNotCompress, 100)]
|
||||
public async Task Request_Https_NotCompressedIfOptOut(HttpsCompressionMode mode, int expectedLength)
|
||||
{
|
||||
var sink = new TestSink(
|
||||
TestSink.EnableWithTypeName<ResponseCompressionProvider>,
|
||||
TestSink.EnableWithTypeName<ResponseCompressionProvider>);
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.MimeTypes = new[] { TextPlain };
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.Run(context =>
|
||||
{
|
||||
var feature = context.Features.Get<IHttpsCompressionFeature>();
|
||||
feature.Mode = mode;
|
||||
context.Response.ContentType = TextPlain;
|
||||
return context.Response.WriteAsync(new string('a', 100));
|
||||
});
|
||||
});
|
||||
|
||||
var server = new TestServer(builder)
|
||||
{
|
||||
BaseAddress = new Uri("https://localhost/")
|
||||
};
|
||||
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||
request.Headers.AcceptEncoding.ParseAdd("gzip");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(expectedLength, response.Content.ReadAsByteArrayAsync().Result.Length);
|
||||
|
||||
var logMessages = sink.Writes.ToList();
|
||||
if (mode == HttpsCompressionMode.DoNotCompress)
|
||||
{
|
||||
AssertLog(logMessages.Skip(1).Single(), LogLevel.Debug, "No response compression available for HTTPS requests. See ResponseCompressionOptions.EnableForHttps.");
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertCompressedWithLog(logMessages, "gzip");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
public StaticFileOptions(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions sharedOptions) : base (default(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions)) { }
|
||||
public Microsoft.AspNetCore.StaticFiles.IContentTypeProvider ContentTypeProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public string DefaultContentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public Microsoft.AspNetCore.Http.Features.HttpsCompressionMode HttpsCompression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public System.Action<Microsoft.AspNetCore.StaticFiles.StaticFileResponseContext> OnPrepareResponse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
public bool ServeUnknownFileTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ namespace StaticFilesSample
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDirectoryBrowser();
|
||||
services.AddResponseCompression();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment host)
|
||||
{
|
||||
Console.WriteLine("webroot: " + host.WebRootPath);
|
||||
|
||||
app.UseResponseCompression();
|
||||
|
||||
app.UseFileServer(new FileServerOptions
|
||||
{
|
||||
EnableDirectoryBrowsing = true
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.ResponseCompression" />
|
||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
|
||||
public async Task SendAsync()
|
||||
{
|
||||
SetCompressionMode();
|
||||
ApplyResponseHeaders(Constants.Status200Ok);
|
||||
string physicalPath = _fileInfo.PhysicalPath;
|
||||
var sendFile = _context.Features.Get<IHttpSendFileFeature>();
|
||||
|
|
@ -366,6 +367,7 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
|
||||
_responseHeaders.ContentRange = ComputeContentRange(_range, out var start, out var length);
|
||||
_response.ContentLength = length;
|
||||
SetCompressionMode();
|
||||
ApplyResponseHeaders(Constants.Status206PartialContent);
|
||||
|
||||
string physicalPath = _fileInfo.PhysicalPath;
|
||||
|
|
@ -404,5 +406,15 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
length = end - start + 1;
|
||||
return new ContentRangeHeaderValue(start, end, _length);
|
||||
}
|
||||
|
||||
// Only called when we expect to serve the body.
|
||||
private void SetCompressionMode()
|
||||
{
|
||||
var responseCompressionFeature = _context.Features.Get<IHttpsCompressionFeature>();
|
||||
if (responseCompressionFeature != null)
|
||||
{
|
||||
responseCompressionFeature.Mode = _options.HttpsCompression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.AspNetCore.StaticFiles.Infrastructure;
|
||||
|
||||
|
|
@ -46,6 +47,15 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// </summary>
|
||||
public bool ServeUnknownFileTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if files should be compressed for HTTPS requests when the Response Compression middleware is available.
|
||||
/// The default value is <see cref="HttpsCompressionMode.Compress"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Enabling compression on HTTPS requests for remotely manipulable content may expose security problems.
|
||||
/// </remarks>
|
||||
public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress;
|
||||
|
||||
/// <summary>
|
||||
/// Called after the status code and headers have been set, but before the body has been written.
|
||||
/// This can be used to add or change the response headers.
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -54,6 +56,54 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnablesHttpsCompression_IfMatched()
|
||||
{
|
||||
var options = new StaticFileOptions();
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/foo.txt", new TestFileInfo
|
||||
{
|
||||
LastModified = new DateTimeOffset(2014, 1, 2, 3, 4, 5, TimeSpan.Zero)
|
||||
});
|
||||
var pathString = new PathString("/test");
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var httpsCompressionFeature = new TestHttpsCompressionFeature();
|
||||
httpContext.Features.Set<IHttpsCompressionFeature>(httpsCompressionFeature);
|
||||
httpContext.Request.Path = new PathString("/test/foo.txt");
|
||||
var context = new StaticFileContext(httpContext, options, pathString, NullLogger.Instance, fileProvider, new FileExtensionContentTypeProvider());
|
||||
|
||||
context.ValidatePath();
|
||||
var result = context.LookupFileInfo();
|
||||
Assert.True(result);
|
||||
|
||||
await context.SendAsync();
|
||||
|
||||
Assert.Equal(HttpsCompressionMode.Compress, httpsCompressionFeature.Mode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkipsHttpsCompression_IfNotMatched()
|
||||
{
|
||||
var options = new StaticFileOptions();
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/foo.txt", new TestFileInfo
|
||||
{
|
||||
LastModified = new DateTimeOffset(2014, 1, 2, 3, 4, 5, TimeSpan.Zero)
|
||||
});
|
||||
var pathString = new PathString("/test");
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var httpsCompressionFeature = new TestHttpsCompressionFeature();
|
||||
httpContext.Features.Set<IHttpsCompressionFeature>(httpsCompressionFeature);
|
||||
httpContext.Request.Path = new PathString("/test/bar.txt");
|
||||
var context = new StaticFileContext(httpContext, options, pathString, NullLogger.Instance, fileProvider, new FileExtensionContentTypeProvider());
|
||||
|
||||
context.ValidatePath();
|
||||
var result = context.LookupFileInfo();
|
||||
Assert.False(result);
|
||||
|
||||
Assert.Equal(HttpsCompressionMode.Default, httpsCompressionFeature.Mode);
|
||||
}
|
||||
|
||||
private sealed class TestFileProvider : IFileProvider
|
||||
{
|
||||
private readonly Dictionary<string, IFileInfo> _files = new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
||||
|
|
@ -162,8 +212,13 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
|
||||
public Stream CreateReadStream()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new MemoryStream();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHttpsCompressionFeature : IHttpsCompressionFeature
|
||||
{
|
||||
public HttpsCompressionMode Mode { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue