Skip to content
This repository was archived by the owner on Dec 20, 2018. It is now read-only.

Add Brotli compression provider #342

Merged
merged 11 commits into from
Jul 10, 2018
Merged

Conversation

khellang
Copy link
Contributor

Probably needs a few more tests, like for ordering. Should the Brotli provider be added by default?

Closes #217

public string EncodingName => "br";

/// <inheritdoc />
public bool SupportsFlush => false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I tried to find it in the source, but looked at a different file (without .Compress)🤦🏻‍♂️

// 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.

#if NETCOREAPP2_1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public types should be NETSTANDARD, let's keep the NETCOREAPP regions as constrained as possible. You should be able to limit them to ResponseCompressionProvider.ctor and BrotliCompressionProvider.CreateStream

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what will they do on non-netcoreapp2.1? Throw?

Copy link
Member

@Tratcher Tratcher Jun 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateStream is the only place that would need to throw (NotImplementedException, NotSupportedException?). And that exception will be avoided by having ResponseCompressionProvider skip adding the provider. It would only throw if someone added it manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would only throw if someone added it manually.

Good point 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming that also means that you're fine with adding Brotli as a default provider?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, as long as it's second on the list.

new CompressionProviderFactory(typeof(GzipCompressionProvider)),
#if NETCOREAPP2_1
new CompressionProviderFactory(typeof(BrotliCompressionProvider))
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List all TFMs and include the else and error cases so we know if these need updating.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the error case, but what should the other TFMs do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other TFMs can be empty.

@khellang khellang force-pushed the brotli-provider branch 2 times, most recently from 920f84f to f89a2be Compare June 26, 2018 16:50
public Stream CreateStream(Stream outputStream)
{
#if NET461
throw new PlatformNotSupportedException();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these require a message?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not worried about it. I don't expect people to hit this.
You can combine net461 and netstandard2_0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if that would work. I think you need to check NETCOREAPP2_1 first, then check NETSTANDARD2_0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvrm, I didn't realize ifdefs are not inherited.

@khellang khellang force-pushed the brotli-provider branch 4 times, most recently from c51e066 to 170b08a Compare June 26, 2018 18:55
@khellang
Copy link
Contributor Author

Any pointers on tests you'd like to see?

@Tratcher
Copy link
Member

Can you recycle some of the existing Flush tests?
And at least one that shows what happens when the accept-encoding header includes gzip and br.

@khellang
Copy link
Contributor Author

And at least one that shows what happens when the accept-encoding header includes gzip and br.

Is the intention that gzip will be preferred over Brotli and that you have to explicitly configure it if you want it the other way around?

@Tratcher
Copy link
Member

Right

@khellang khellang force-pushed the brotli-provider branch 2 times, most recently from 31062f8 to 48a2160 Compare June 27, 2018 11:12
@khellang
Copy link
Contributor Author

I guess this will also fix #216, as I had to take the "registration" order into account when selecting a compression provider when you have multiple matching providers with the same (or missing) quality value.

Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good. I wonder about the repetitive complexity and if we need fast paths or caching for the common case.
@shirhatti is doing some analysis of other implementations to see if the priority system needs to be more flexible. We'll follow up with you.

var encodingName = encoding.Value;
var quality = encoding.Quality.GetValueOrDefault(1);

if (Math.Abs(quality) < double.Epsilon)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the Abs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, I blame R# 🤣

{
return provider;
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to become a lot of work on every response, and you'd get the same results most of the time, no? e.g. gzip, deflate, br or gzip, deflate are always going to evaluate to the same provider. Is it worth caching a few results based on the raw header string to avoid this redundant processing? Or at least having a fast path for headers that do not contain quality, then you can do a Contains for each available provider and be done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's much more work, it's just doing it the other way around by matching first and ordering after, but we can certainly try to be smarter to avoid some work 👍🏻

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth checking/considering making _providers a Dictionary? Besides that, I don't see much you can do.

}
}

if (candidates.Count == 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be combine with ElementAtOrDefault(0)


public double Quality { get; }

public int Order { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Priority?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL, I had Priority and changed it to Order last minute 😂

[Theory]
[InlineData("gzip", "br")]
[InlineData("br", "gzip")]
public async Task Request_AcceptMixed_CompressedGzip(string encoding1, string encoding2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the opposite test where you configure the providers in the other order and ensure br is selected. (NETCOREAPP2_2 only)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's still missing. I'm reluctant to copy-pasting the setup code more, but I'm not sure if there there's a good way to reuse the setup as there are quite a few permutations.

@khellang
Copy link
Contributor Author

khellang commented Jun 27, 2018

@Tratcher I added some benchmarks and ran them on the dev branch (before) and this branch (after) and they look pretty similar:

Before

BenchmarkDotNet=v0.10.14, OS=macOS High Sierra 10.13.4 (17E202) [Darwin 17.5.0]
Intel Core i7-4870HQ CPU 2.50GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.1.300
  [Host]     : .NET Core 2.1.1 (CoreCLR 4.6.26606.02, CoreFX 4.6.26606.05), 64bit RyuJIT
  Job-BPKSVL : .NET Core 2.1.1 (CoreCLR 4.6.26606.02, CoreFX 4.6.26606.05), 64bit RyuJIT

Runtime=Core  Server=True  Toolchain=.NET Core 2.1  
RunStrategy=Throughput  
Method AcceptEncoding Mean Error StdDev Op/s Gen 0 Allocated
GetCompressionProvider * 1.660 us 0.0092 us 0.0086 us 602,399.9 0.0076 1.52 KB
GetCompressionProvider br, compress, gzip 2.145 us 0.0149 us 0.0139 us 466,270.2 0.0076 1.71 KB
GetCompressionProvider gzip, compress 1.862 us 0.0089 us 0.0079 us 537,140.7 0.0095 1.64 KB
GetCompressionProvider gzip, compress, br 2.085 us 0.0197 us 0.0184 us 479,597.7 0.0076 1.71 KB
GetCompressionProvider gzip;q=0.8, compress;q=0.6, br;q=0.4 2.193 us 0.0171 us 0.0152 us 456,060.6 0.0076 1.71 KB
GetCompressionProvider identity 1.499 us 0.0093 us 0.0082 us 667,231.6 0.0076 1.52 KB

After

BenchmarkDotNet=v0.10.14, OS=macOS High Sierra 10.13.4 (17E202) [Darwin 17.5.0]
Intel Core i7-4870HQ CPU 2.50GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.1.300
  [Host]     : .NET Core 2.1.1 (CoreCLR 4.6.26606.02, CoreFX 4.6.26606.05), 64bit RyuJIT
  Job-YFUDVB : .NET Core 2.1.1 (CoreCLR 4.6.26606.02, CoreFX 4.6.26606.05), 64bit RyuJIT

Runtime=Core  Server=True  Toolchain=.NET Core 2.1  
RunStrategy=Throughput  
Method AcceptEncoding Mean Error StdDev Op/s Gen 0 Allocated
GetCompressionProvider * 1.582 us 0.0092 us 0.0081 us 632,169.5 0.0095 1.64 KB
GetCompressionProvider br, compress, gzip 2.052 us 0.0124 us 0.0116 us 487,361.5 0.0114 1.73 KB
GetCompressionProvider gzip, compress 1.569 us 0.0092 us 0.0082 us 637,229.3 0.0076 1.48 KB
GetCompressionProvider gzip, compress, br 2.066 us 0.0173 us 0.0153 us 484,111.3 0.0076 1.73 KB
GetCompressionProvider gzip;q=0.8, compress;q=0.6, br;q=0.4 2.193 us 0.0159 us 0.0133 us 456,019.0 0.0076 1.73 KB
GetCompressionProvider identity 1.304 us 0.0087 us 0.0081 us 766,637.0 0.0076 1.44 KB

@natemcmaster natemcmaster changed the base branch from dev to master July 2, 2018 17:44
Copy link
Contributor

@jkotalik jkotalik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking clean.

#if NETCOREAPP2_1
return new BrotliStream(outputStream, Options.Level, leaveOpen: true);
#elif NET461 || NETSTANDARD2_0
throw new PlatformNotSupportedException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment to the exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment explaining why we throw, or an exception message?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception message 😄

namespace Microsoft.AspNetCore.ResponseCompression
{
/// <summary>
/// Options for the <see cref="BrotliCompressionProvider"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relating to options, can you add a comment to ResponseCompressionOptions saying the Providers's priority is based on their ordering in the list?

{
// No compression
return null;
candidates.Add(new ProviderCandidate(encodingName.Value, quality, int.MaxValue, null));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a comment here about what each of these values mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arguments?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah for the arguments. Or add named params.

{
return provider;
candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth checking/considering making _providers a Dictionary? Besides that, I don't see much you can do.

@@ -0,0 +1,54 @@
using System.Collections.Generic;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: copyright header.

@@ -0,0 +1 @@
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: copyright header.

@khellang khellang force-pushed the brotli-provider branch 2 times, most recently from 3474e3e to af8bec1 Compare July 6, 2018 07:58
@khellang
Copy link
Contributor Author

khellang commented Jul 6, 2018

Anything more I can do to get this over the finish line 🏁? 👼

@Tratcher
Copy link
Member

Tratcher commented Jul 6, 2018

Just waiting on a feature sign-off from @shirhatti. He'll be back Monday.

Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase.

{
new CompressionProviderFactory(typeof(GzipCompressionProvider)),
#if NETCOREAPP2_1
new CompressionProviderFactory(typeof(BrotliCompressionProvider))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided to make Brotli higher priority than Gzip. We'll have time to evaluate the impact in the preview releases. Please switch the order and then we can get this merged.

@khellang
Copy link
Contributor Author

Rebased and swapped defaults 👌🏻

@Tratcher Tratcher changed the base branch from master to release/2.2 July 10, 2018 16:19
@Tratcher
Copy link
Member

Sorry, I just realized we'd swapped the branches under you and this was targeting 3.0 (master). I changed the base branch back to 2.2 since we want this sooner than later 😁, but unfortunately that requires one more rebase. I can get to it later today if you don't.

@khellang khellang force-pushed the brotli-provider branch 2 times, most recently from 66d9783 to cb26243 Compare July 10, 2018 19:35
@khellang
Copy link
Contributor Author

Rebased. Hopfully it went OK 🙏

@Tratcher Tratcher merged commit 767b3ef into aspnet:release/2.2 Jul 10, 2018
@Tratcher
Copy link
Member

Great, thanks.

@khellang khellang deleted the brotli-provider branch July 10, 2018 19:48
@khellang
Copy link
Contributor Author

Wee! Thank you 😄

@shirhatti
Copy link

By adding brotli as the default we might have broken anyone running on full Fx. Whoops

@Tratcher
Copy link
Member

We do not add brotli on net461.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants