Skip to content

internal/envoy: support multiple HTTP compression algorithms - #7699

Open
anonrig wants to merge 3 commits into
projectcontour:mainfrom
anonrig:compression-algorithms
Open

internal/envoy: support multiple HTTP compression algorithms#7699
anonrig wants to merge 3 commits into
projectcontour:mainfrom
anonrig:compression-algorithms

Conversation

@anonrig

@anonrig anonrig commented Aug 27, 2026

Copy link
Copy Markdown

Thanks for contributing to Contour!

Adds compression.algorithms so Contour can program more than one Envoy compressor filter and negotiate the encoding from Accept-Encoding.

The existing algorithm field is unchanged as a single-algorithm shorthand, including disabled. The two fields are mutually exclusive.

When more than one algorithm is configured:

  • Contour installs one compressor filter per entry
  • List order is the preference when q-values are equal (choose_first on the first entry)
  • Accept-Encoding is stripped on the last filter so backends do not compress first and hide the other encodings

Also documents the brotli and zstd Envoy compressor extensions in the compatibility matrix.

Fixes #7698

@anonrig
anonrig requested a review from a team as a code owner August 27, 2026 21:05
@anonrig
anonrig requested review from sunjayBhatia and tsaarni and removed request for a team August 27, 2026 21:05
@anonrig

anonrig commented Aug 27, 2026

Copy link
Copy Markdown
Author

@tsaarni would you mind taking a look?

@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.03%. Comparing base (c8e9765) to head (042ce5d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #7699      +/-   ##
==========================================
- Coverage   84.04%   84.03%   -0.02%     
==========================================
  Files         131      131              
  Lines       14190    14187       -3     
==========================================
- Hits        11926    11922       -4     
- Misses       2264     2265       +1     
Files with missing lines Coverage Δ
cmd/contour/servecontext.go 91.64% <100.00%> (-0.16%) ⬇️
internal/envoy/v3/listener.go 95.62% <100.00%> (-0.04%) ⬇️
pkg/config/parameters.go 90.82% <100.00%> (-0.13%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tsaarni tsaarni added the release-note/small A small change that needs one line of explanation in the release notes. label Aug 28, 2026
@anonrig
anonrig force-pushed the compression-algorithms branch 2 times, most recently from 2a04f3d to 319ce36 Compare August 28, 2026 15:17
Add compression.algorithms so Envoy can negotiate gzip, brotli, and
zstd from Accept-Encoding. The existing algorithm field remains a
single-algorithm shorthand and is mutually exclusive with algorithms.

Fixes projectcontour#7698

Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig
anonrig force-pushed the compression-algorithms branch from 319ce36 to 042ce5d Compare August 28, 2026 15:20

@tsaarni tsaarni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@anonrig Thanks for contributing!

I've done a partial review and the PR looks good. I left some inline comments.
One larger suggestion: I think algorithms should be a pointer to a slice so it becomes a "tri-state":

  • omitted for Envoy default
  • empty list to disable compression
  • populated list to use the listed algorithms
 Algorithms *[]CompressionAlgorithm `json:"algorithms,omitempty"`

This would let users disable compression via algorithms: [] instead of using different attribute for that. We can mark algorithm field deprecated.

Also same tri-state would make sense for the config file struct.

Comment on lines 22 to 23
// EnvoyCompression defines configuration related to compression in the default HTTP Listener filter chain.
type EnvoyCompression struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could give early feedback by adding this CEL expression here

Suggested change
// EnvoyCompression defines configuration related to compression in the default HTTP Listener filter chain.
type EnvoyCompression struct {
// EnvoyCompression defines configuration related to compression in the default HTTP Listener filter chain.
// +kubebuilder:validation:XValidation:rule="!(has(self.algorithm) && has(self.algorithms))",message="compression algorithm and algorithms are mutually exclusive"
type EnvoyCompression struct {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added. EnvoyCompression now has this CEL rule so the API server rejects algorithm and algorithms together.

})
}

func TestHTTPConnectionManagerCompressionAlgorithms(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To test that DefaultFilters() sets "choose first" and "remove accept-encoding" flags correctly we could have the different scenarios here:

func TestHTTPConnectionManagerCompressionAlgorithms(t *testing.T) {
	envoyGen := NewEnvoyGen(EnvoyGenOpt{
		XDSClusterName: DefaultXDSClusterName,
	})

	type compressorExpectation struct {
		name                  string
		compressorLibraryName string
		compressorLibrary     proto.Message
		chooseFirst           bool
		removeAcceptEncoding  bool
	}

	tests := map[string]struct {
		algorithms []contour_v1alpha1.CompressionAlgorithm
		want       []compressorExpectation
	}{
		"one algorithm": {
			algorithms: []contour_v1alpha1.CompressionAlgorithm{contour_v1alpha1.ZstdCompression},
			want: []compressorExpectation{
				{name: CompressorFilterName, compressorLibraryName: "zstd", compressorLibrary: &envoy_compression_zstd_compressor_v3.Zstd{}},
			},
		},
		"two algorithms": {
			algorithms: []contour_v1alpha1.CompressionAlgorithm{
				contour_v1alpha1.BrotliCompression,
				contour_v1alpha1.GzipCompression,
			},
			want: []compressorExpectation{
				{name: CompressorFilterName + ".brotli", compressorLibraryName: "brotli", compressorLibrary: &envoy_compression_brotli_compressor_v3.Brotli{}, chooseFirst: true},
				{name: CompressorFilterName + ".gzip", compressorLibraryName: "gzip", compressorLibrary: &envoy_compression_gzip_compressor_v3.Gzip{}, removeAcceptEncoding: true},
			},
		},
		"three algorithms": {
			algorithms: []contour_v1alpha1.CompressionAlgorithm{
				contour_v1alpha1.GzipCompression,
				contour_v1alpha1.BrotliCompression,
				contour_v1alpha1.ZstdCompression,
			},
			want: []compressorExpectation{
				{name: CompressorFilterName + ".gzip", compressorLibraryName: "gzip", compressorLibrary: &envoy_compression_gzip_compressor_v3.Gzip{}, chooseFirst: true},
				{name: CompressorFilterName + ".brotli", compressorLibraryName: "brotli", compressorLibrary: &envoy_compression_brotli_compressor_v3.Brotli{}},
				{name: CompressorFilterName + ".zstd", compressorLibraryName: "zstd", compressorLibrary: &envoy_compression_zstd_compressor_v3.Zstd{}, removeAcceptEncoding: true},
			},
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			gotFilter := envoyGen.HTTPConnectionManagerBuilder().
				Compression(&contour_v1alpha1.EnvoyCompression{Algorithms: tc.algorithms}).
				DefaultFilters().
				Get()

			var got envoy_filter_network_http_connection_manager_v3.HttpConnectionManager
			require.NoError(t, gotFilter.GetTypedConfig().UnmarshalTo(&got))
			require.GreaterOrEqual(t, len(got.HttpFilters), len(tc.want)+1)
			assert.Equal(t, GRPCWebFilterName, got.HttpFilters[len(tc.want)].Name)

			for i, want := range tc.want {
				var compressor envoy_filter_http_compressor_v3.Compressor
				require.NoError(t, got.HttpFilters[i].GetTypedConfig().UnmarshalTo(&compressor))

				assert.Equal(t, want.name, got.HttpFilters[i].Name)
				require.NotNil(t, compressor.CompressorLibrary)
				require.NotNil(t, compressor.CompressorLibrary.TypedConfig)
				assert.Equal(t, want.compressorLibraryName, compressor.CompressorLibrary.Name)
				assert.True(t, compressor.CompressorLibrary.TypedConfig.MessageIs(want.compressorLibrary))
				assert.Equal(t, want.chooseFirst, compressor.ChooseFirst)
				assert.Equal(t, want.removeAcceptEncoding, compressor.ResponseDirectionConfig.RemoveAcceptEncodingHeader)
			}
		})
	}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added, including the one/two/three algorithm cases plus an empty-list case now that algorithms: [] disables compression.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For clarity:

Suggested change
// Allowable values are defined as names of well known compression algorithms.

since the same type is used for both Algorithm and Algorithms, where in one case disable is allowed and in the other it is not.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. The type comment no longer mentions disabled, since that value is only valid on the deprecated algorithm field.

@anonrig

anonrig commented Sep 1, 2026

Copy link
Copy Markdown
Author

Sounds good. I'll update the PR. Thank you for the review.

anonrig added a commit to anonrig/contour that referenced this pull request Sep 1, 2026
Address review feedback on projectcontour#7699 by making algorithms a pointer to a
slice so omitted, empty, and populated lists are distinct. An empty
list disables compression. Mark algorithm deprecated, add CEL
mutual-exclusivity validation, and expand compressor filter tests.
@anonrig

anonrig commented Sep 1, 2026

Copy link
Copy Markdown
Author

@tsaarni thanks for the review. I updated the PR to match the feedback:

  • algorithms is now *[]CompressionAlgorithm (same tri-state on the config-file struct): omitted keeps the Envoy default, [] disables compression, and a populated list programs those encodings
  • algorithm is deprecated; algorithm and algorithms remain mutually exclusive, with CEL validation on the CRD
  • added the choose_first / remove_accept_encoding compressor tests you suggested, plus coverage for the empty-list disable path

Address review feedback on projectcontour#7699 by making algorithms a pointer to a
slice so omitted, empty, and populated lists are distinct. An empty
list disables compression. Mark algorithm deprecated, add CEL
mutual-exclusivity validation, and expand compressor filter tests.

Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig
anonrig force-pushed the compression-algorithms branch from e1ebfdc to 02b8c26 Compare September 1, 2026 23:46
staticcheck SA1019 flags the deprecated algorithm field. Keep reading
it for backward compatibility, matching the existing SubjectName
exclusion.

Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig

anonrig commented Sep 2, 2026

Copy link
Copy Markdown
Author

@tsaarni can you re-review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/small A small change that needs one line of explanation in the release notes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple HTTP compression algorithms for Accept-Encoding negotiation

2 participants