Skip to content
10 changes: 6 additions & 4 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1590,13 +1590,14 @@ func buildCompression(compression, compressor []*egv1a1.Compression) []*ir.Compr
// Handle the Compressor field first (higher priority)
if len(compressor) > 0 {
irCompression := make([]*ir.Compression, 0, len(compressor))
for _, c := range compressor {
for i, c := range compressor {
// Only add compression if the corresponding compressor not null
if (c.Type == egv1a1.GzipCompressorType && c.Gzip != nil) ||
(c.Type == egv1a1.BrotliCompressorType && c.Brotli != nil) ||
(c.Type == egv1a1.ZstdCompressorType && c.Zstd != nil) {
irCompression = append(irCompression, &ir.Compression{
Type: c.Type,
Type: c.Type,
ChooseFirst: i == 0, // only the first compressor is marked as ChooseFirst
})
}
}
Expand All @@ -1608,9 +1609,10 @@ func buildCompression(compression, compressor []*egv1a1.Compression) []*ir.Compr
return nil
}
irCompression := make([]*ir.Compression, 0, len(compression))
for _, c := range compression {
for i, c := range compression {
irCompression = append(irCompression, &ir.Compression{
Type: c.Type,
Type: c.Type,
ChooseFirst: i == 0, // only the first compressor is marked as ChooseFirst
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Zstd
readyListener:
address: 0.0.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ xdsIR:
prefix: /admin
traffic:
compression:
- type: Gzip
- chooseFirst: true
type: Gzip
- destination:
metadata:
kind: HTTPRoute
Expand Down Expand Up @@ -406,7 +407,8 @@ xdsIR:
prefix: /api
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Zstd
- destination:
metadata:
Expand Down Expand Up @@ -439,7 +441,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Zstd
- type: Gzip
readyListener:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Gzip
- type: Zstd
readyListener:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Gzip
- type: Zstd
readyListener:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Gzip
- chooseFirst: true
type: Gzip
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Zstd
- chooseFirst: true
type: Zstd
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ xdsIR:
prefix: /admin
traffic:
compression:
- type: Gzip
- chooseFirst: true
type: Gzip
- destination:
metadata:
kind: HTTPRoute
Expand Down Expand Up @@ -407,7 +408,8 @@ xdsIR:
prefix: /api
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- destination:
metadata:
kind: HTTPRoute
Expand Down Expand Up @@ -439,7 +441,8 @@ xdsIR:
prefix: /
traffic:
compression:
- type: Brotli
- chooseFirst: true
type: Brotli
- type: Gzip
readyListener:
address: 0.0.0.0
Expand Down
2 changes: 2 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,8 @@ type HeaderBasedSessionPersistence struct {
type Compression struct {
// Type of compression to be used.
Type egv1a1.CompressorType `json:"type" yaml:"type"`
// ChooseFirst indicates this compressor is preferred when q-values in Accept-Encoding are equal.
ChooseFirst bool `json:"chooseFirst,omitempty" yaml:"chooseFirst,omitempty"`
}

// TrafficFeatures holds the information associated with the Backend Traffic Policy.
Expand Down
17 changes: 11 additions & 6 deletions internal/xds/translator/compressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package translator
import (
"errors"
"fmt"
"slices"
"strings"

corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
Expand Down Expand Up @@ -51,11 +52,10 @@ func (*compressor) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTT

for _, route := range irListener.Routes {
if route.Traffic != nil && route.Traffic.Compression != nil {
for i, irComp := range route.Traffic.Compression {
for _, irComp := range route.Traffic.Compression {
filterName := compressorFilterName(irComp.Type)
if !hcmContainsFilter(mgr, filterName) {
chooseFirst := i == 0
if filter, err = buildCompressorFilter(irComp, chooseFirst); err != nil {
if filter, err = buildCompressorFilter(irComp); err != nil {
return err
}
mgr.HttpFilters = append(mgr.HttpFilters, filter)
Expand All @@ -72,7 +72,7 @@ func compressorFilterName(compressorType egv1a1.CompressorType) string {
}

// buildCompressorFilter builds a compressor filter with the provided compressionType.
func buildCompressorFilter(compression *ir.Compression, chooseFirst bool) (*hcmv3.HttpFilter, error) {
func buildCompressorFilter(compression *ir.Compression) (*hcmv3.HttpFilter, error) {
var (
compressorProto *compressorv3.Compressor
extensionName string
Expand Down Expand Up @@ -105,7 +105,7 @@ func buildCompressorFilter(compression *ir.Compression, chooseFirst bool) (*hcmv
},
}

if chooseFirst {
if compression.ChooseFirst {
compressorProto.ChooseFirst = true
}

Expand Down Expand Up @@ -151,6 +151,7 @@ func (*compressor) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir
route.TypedPerFilterConfig = make(map[string]*anypb.Any)
}

compressorProto := compressorPerRouteConfig()
for _, irComp := range irRoute.Traffic.Compression {
filterName := compressorFilterName(irComp.Type)
if _, ok := perFilterCfg[filterName]; ok {
Expand All @@ -160,14 +161,18 @@ func (*compressor) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir
filterName, route)
}

compressorProto := compressorPerRouteConfig()
if compressorAny, err = proto.ToAnyWithValidation(compressorProto); err != nil {
return err
}

route.TypedPerFilterConfig[filterName] = compressorAny
}

// Ensure accept-encoding from the request to prevent double compression.
if !slices.Contains(route.RequestHeadersToRemove, "accept-encoding") {
route.RequestHeadersToRemove = append(route.RequestHeadersToRemove, "accept-encoding")
}

return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
name: envoy.filters.http.compressor.brotli
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
chooseFirst: true
compressorLibrary:
name: envoy.compression.brotli.compressor
typedConfig:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
name: httproute-1
namespace: default
name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
requestHeadersToRemove:
- accept-encoding
route:
cluster: httproute/default/httproute-1/rule/0
upgradeConfigs:
Expand Down
62 changes: 23 additions & 39 deletions test/e2e/tests/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"net"
nethttp "net/http"
"net/http/httputil"
"strings"
"testing"

"github.com/andybalholm/brotli"
Expand Down Expand Up @@ -55,8 +56,14 @@ var CompressionTest = suite.ConformanceTest{
testCompression(t, suite, egv1a1.ZstdCompressorType)
})

t.Run("HTTPRoute with brotli compression chooseFirst", func(t *testing.T) {
testCompressionChooseFirst(t, suite, egv1a1.BrotliCompressorType)
t.Run("HTTPRoute with multiple compressors chooseFirst", func(t *testing.T) {
testCompression(
t,
suite,
egv1a1.BrotliCompressorType,
egv1a1.GzipCompressorType,
egv1a1.ZstdCompressorType,
)
})

t.Run("HTTPRoute without compression", func(t *testing.T) {
Expand All @@ -77,7 +84,7 @@ var CompressionTest = suite.ConformanceTest{
Request: http.Request{
Path: "/no-compression",
Headers: map[string]string{
"Accept-encoding": "gzip",
"accept-encoding": "gzip",
},
},
Response: http.Response{
Expand All @@ -92,7 +99,7 @@ var CompressionTest = suite.ConformanceTest{
},
}

func testCompression(t *testing.T, suite *suite.ConformanceTestSuite, compressionType egv1a1.CompressorType) {
func testCompression(t *testing.T, suite *suite.ConformanceTestSuite, compressionTypes ...egv1a1.CompressorType) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "compression", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
Expand All @@ -106,52 +113,29 @@ func testCompression(t *testing.T, suite *suite.ConformanceTestSuite, compressio
}
BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "compression", Namespace: ns}, suite.ControllerName, ancestorRef)

encoding := ContentEncoding(compressionType)
expectedResponse := http.ExpectedResponse{
Request: http.Request{
Path: "/compression",
Headers: map[string]string{
"Accept-encoding": encoding,
},
},
Response: http.Response{
StatusCodes: []int{200},
Headers: map[string]string{
"content-encoding": encoding,
},
},
Namespace: ns,
}
roundTripper := &CompressionRoundTripper{Debug: suite.Debug, TimeoutConfig: suite.TimeoutConfig}
http.MakeRequestAndExpectEventuallyConsistentResponse(t, roundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
}

func testCompressionChooseFirst(t *testing.T, suite *suite.ConformanceTestSuite, compressionType egv1a1.CompressorType) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "compression", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, routeNN)

ancestorRef := gwapiv1.ParentReference{
Group: gatewayapi.GroupPtr(gwapiv1.GroupName),
Kind: gatewayapi.KindPtr(resource.KindGateway),
Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
Name: gwapiv1.ObjectName(gwNN.Name),
encodings := make([]string, len(compressionTypes))
for i, ct := range compressionTypes {
encodings[i] = ContentEncoding(ct)
}
BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "compression", Namespace: ns}, suite.ControllerName, ancestorRef)

encoding := ContentEncoding(compressionType)
acceptEncoding := strings.Join(encodings, ", ")
expectedResponse := http.ExpectedResponse{
Request: http.Request{
Path: "/compression",
Headers: map[string]string{
"Accept-encoding": "gzip, br, zstd",
"accept-encoding": acceptEncoding,
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/compression",
},
AbsentHeaders: []string{"accept-encoding"},
},
Response: http.Response{
StatusCodes: []int{200},
Headers: map[string]string{
"content-encoding": encoding,
"content-encoding": encodings[0],
},
},
Namespace: ns,
Expand Down