Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add Brotli support to SocketsHttpHandler #29729

Merged
merged 2 commits into from
May 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@
<Reference Include="System.Diagnostics.Tools" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.Brotli" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.NetworkInformation" />
<Reference Include="System.Net.Primitives" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ internal sealed class DecompressionHandler : HttpMessageHandler

private const string s_gzip = "gzip";
private const string s_deflate = "deflate";
private const string s_brotli = "br";
private static readonly StringWithQualityHeaderValue s_gzipHeaderValue = new StringWithQualityHeaderValue(s_gzip);
private static readonly StringWithQualityHeaderValue s_deflateHeaderValue = new StringWithQualityHeaderValue(s_deflate);
private static readonly StringWithQualityHeaderValue s_brotliHeaderValue = new StringWithQualityHeaderValue(s_brotli);

public DecompressionHandler(DecompressionMethods decompressionMethods, HttpMessageHandler innerHandler)
{
Expand All @@ -33,6 +35,7 @@ public DecompressionHandler(DecompressionMethods decompressionMethods, HttpMessa

internal bool GZipEnabled => (_decompressionMethods & DecompressionMethods.GZip) != 0;
internal bool DeflateEnabled => (_decompressionMethods & DecompressionMethods.Deflate) != 0;
internal bool BrotliEnabled => (_decompressionMethods & DecompressionMethods.Brotli) != 0;

protected internal override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Expand All @@ -44,6 +47,10 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques
{
request.Headers.AcceptEncoding.Add(s_deflateHeaderValue);
}
if (BrotliEnabled)
{
request.Headers.AcceptEncoding.Add(s_brotliHeaderValue);
}

HttpResponseMessage response = await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false);

Expand All @@ -64,6 +71,10 @@ protected internal override async Task<HttpResponseMessage> SendAsync(HttpReques
{
response.Content = new DeflateDecompressedContent(response.Content);
}
else if (BrotliEnabled && last == s_brotli)
{
response.Content = new BrotliDecompressedContent(response.Content);
}
}

return response;
Expand Down Expand Up @@ -167,5 +178,16 @@ public DeflateDecompressedContent(HttpContent originalContent)
protected override Stream GetDecompressedStream(Stream originalStream) =>
new DeflateStream(originalStream, CompressionMode.Decompress);
}

private sealed class BrotliDecompressedContent : DecompressedContent
{
public BrotliDecompressedContent(HttpContent originalContent) :
base(originalContent)
{
}

protected override Stream GetDecompressedStream(Stream originalStream) =>
new BrotliStream(originalStream, CompressionMode.Decompress);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net.Test.Common;
Expand All @@ -12,34 +13,115 @@ namespace System.Net.Http.Functional.Tests
{
public abstract class HttpClientHandler_Decompression_Test : HttpClientTestBase
{
[Fact]
public async Task Brotli_DecompressesResponse_Success()
public static IEnumerable<object[]> DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData()
{
foreach (bool specifyAllMethods in new[] { false, true })
{
yield return new object[]
{
"deflate",
new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.Deflate : ~DecompressionMethods.None
};
yield return new object[]
{
"gzip",
new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.GZip : ~DecompressionMethods.None
};
yield return new object[]
{
"br",
new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
specifyAllMethods ? DecompressionMethods.Brotli : ~DecompressionMethods.None
};
}
}

[Theory]
[MemberData(nameof(DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData))]
public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned(
string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
{
var expectedContent = new byte[12345];
new Random(42).NextBytes(expectedContent);

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using (HttpClient client = CreateHttpClient())
using (HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead))
using (var decodedStream = new BrotliStream(await response.Content.ReadAsStreamAsync(), CompressionMode.Decompress))
using (HttpClientHandler handler = CreateHttpClientHandler())
using (var client = new HttpClient(handler))
{
var data = new MemoryStream();
await decodedStream.CopyToAsync(data);
Assert.Equal(expectedContent, data.ToArray());
handler.AutomaticDecompression = methods;
Assert.Equal<byte>(expectedContent, await client.GetByteArrayAsync(uri));
}
}, async server =>
{
await server.AcceptConnectionAsync(async connection =>
{
await connection.ReadRequestHeaderAsync();
await connection.Writer.WriteAsync("HTTP/1.1 200 OK\r\nContent-Encoding: br\r\n\r\n");
using (var brotli = new BrotliStream(connection.Stream, CompressionLevel.Optimal, leaveOpen: true))
await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
using (Stream compressedStream = compress(connection.Stream))
{
await brotli.WriteAsync(expectedContent);
await compressedStream.WriteAsync(expectedContent);
}
});
});
}

public static IEnumerable<object[]> DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData()
{
yield return new object[]
{
"deflate",
new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
DecompressionMethods.None
};
yield return new object[]
{
"gzip",
new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
DecompressionMethods.Brotli
};
yield return new object[]
{
"br",
new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
DecompressionMethods.Deflate | DecompressionMethods.GZip
};
}

[Theory]
[MemberData(nameof(DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData))]
public async Task DecompressedResponse_MethodNotSpecified_OriginalContentReturned(
string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
{
var expectedContent = new byte[12345];
new Random(42).NextBytes(expectedContent);

var compressedContentStream = new MemoryStream();
using (Stream s = compress(compressedContentStream))
{
await s.WriteAsync(expectedContent);
}
byte[] compressedContent = compressedContentStream.ToArray();

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using (HttpClientHandler handler = CreateHttpClientHandler())
using (var client = new HttpClient(handler))
{
handler.AutomaticDecompression = methods;
Assert.Equal<byte>(compressedContent, await client.GetByteArrayAsync(uri));
}
}, async server =>
{
await server.AcceptConnectionAsync(async connection =>
{
await connection.ReadRequestHeaderAsync();
await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
await connection.Stream.WriteAsync(compressedContent);
});
});
}
}
}
1 change: 1 addition & 0 deletions src/System.Net.Primitives/ref/System.Net.Primitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public void Remove(System.Uri uriPrefix, string authType) { }
[System.FlagsAttribute]
public enum DecompressionMethods
{
Brotli = 4,
Deflate = 2,
GZip = 1,
None = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace System.Net
public enum DecompressionMethods
{
None = 0,
GZip = 1,
Deflate = 2
GZip = 0x1,
Deflate = 0x2,
Brotli = 0x4
}
}