Skip to content
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
81 changes: 81 additions & 0 deletions router-tests/response_compression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (

"github.com/stretchr/testify/require"
"github.com/wundergraph/cosmo/router-tests/testenv"
"github.com/wundergraph/cosmo/router/core"
"github.com/wundergraph/cosmo/router/pkg/config"
)

const employeesIdData = `{"data":{"employees":[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":7},{"id":8},{"id":10},{"id":11},{"id":12}]}}`
Expand Down Expand Up @@ -100,3 +102,82 @@ func TestResponseCompression(t *testing.T) {
})
}
}

func TestResponseCompressionWithCustomMinSize(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
minSize config.BytesString
encoding string
responseData string
}{
{"compression enabled when response is greater than custom min size", config.BytesString(len(employeesIdData) - 1), "gzip", employeesIdData},
{"compression enabled when response is equal to custom min size", config.BytesString(len(employeesIdData)), "gzip", employeesIdData},
{"compression disabled when response is less than custom min size", config.BytesString(len(employeesIdData) + 1), "", employeesIdData},
}

for _, tc := range testCases {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel() // mark the subtest as parallel
testenv.Run(t, &testenv.Config{
RouterOptions: []core.Option{
core.WithRouterTrafficConfig(&config.RouterTrafficConfiguration{
ResponseCompressionMinSize: tc.minSize,
MaxRequestBodyBytes: 5 << 20, // 5MB
}),
},
Subgraphs: testenv.SubgraphsConfig{
Employees: testenv.SubgraphConfig{
Middleware: func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
data, err := io.ReadAll(r.Body)
require.NoError(t, err)
_, dt, _, _ := jsonparser.Get(data, "extensions", "persistedQuery")
require.Equal(t, jsonparser.NotExist, dt)
_, _ = w.Write([]byte(tc.responseData))
})
},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
headers := http.Header{
"Content-Type": []string{"application/json"},
"Accept-Encoding": []string{"gzip"},
}

data := map[string]interface{}{
"query": `query { employees { id } }`,
}
body, err := json.Marshal(data)
require.NoError(t, err)

res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", headers, bytes.NewReader(body))
require.NoError(t, err)
defer res.Body.Close()

if tc.encoding != "" {
require.Equal(t, tc.encoding, res.Header.Get("Content-Encoding"))
} else {
require.Empty(t, res.Header.Get("Content-Encoding"))
}

// Read and decompress the response
responseBody, err := io.ReadAll(res.Body)
require.NoError(t, err)

var decompressedBody []byte
if tc.encoding != "" {
decompressedBody = decompressGzip(t, bytes.NewReader(responseBody))
} else {
decompressedBody = responseBody
}

require.Contains(t, res.Header.Get("Content-Type"), "application/json")
require.JSONEq(t, tc.responseData, string(decompressedBody))
})
})
}
}
2 changes: 1 addition & 1 deletion router/core/graph_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC
}

wrapper, err := gzhttp.NewWrapper(
gzhttp.MinSize(1024*4), // 4KB
gzhttp.MinSize(int(s.routerTrafficConfig.ResponseCompressionMinSize)),
gzhttp.CompressionLevel(gzip.DefaultCompression),
gzhttp.ContentTypes(CompressibleContentTypes),
)
Expand Down
3 changes: 2 additions & 1 deletion router/core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -1802,7 +1802,8 @@ func WithDisableUsageTracking() Option {

func DefaultRouterTrafficConfig() *config.RouterTrafficConfiguration {
return &config.RouterTrafficConfiguration{
MaxRequestBodyBytes: 1000 * 1000 * 5, // 5 MB
MaxRequestBodyBytes: 1000 * 1000 * 5, // 5 MB
ResponseCompressionMinSize: 1024 * 4, // 4 KiB
}
}

Expand Down
2 changes: 2 additions & 0 deletions router/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ type RouterTrafficConfiguration struct {
MaxHeaderBytes BytesString `yaml:"max_header_bytes" envDefault:"0MiB" env:"MAX_HEADER_BYTES"`
// DecompressionEnabled is the configuration for request compression
DecompressionEnabled bool `yaml:"decompression_enabled" envDefault:"true"`
// ResponseCompressionMinSize is the minimum size of the response body in bytes to enable response compression
ResponseCompressionMinSize BytesString `yaml:"response_compression_min_size" envDefault:"4KiB" env:"RESPONSE_COMPRESSION_MIN_SIZE"`
}

type GlobalSubgraphRequestRule struct {
Expand Down
9 changes: 9 additions & 0 deletions router/pkg/config/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,15 @@
"type": "boolean",
"description": "When enabled, the router will check incoming requests for a 'Content-Encoding' header and decompress the body accordingly. Currently only gzip is supported",
"default": true
},
"response_compression_min_size": {
"type": "string",
"format": "bytes-string",
"description": "The minimum size of the response body in bytes to enable response compression. The size is specified as a string with a number and a unit, e.g. 4KiB, 10KB, 1MB",
"default": "4KiB",
"bytes": {
"minimum": "1B"
}
}
}
},
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/fixtures/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ traffic_shaping:
max_request_body_size: 5MB
max_header_bytes: 4MiB
decompression_enabled: false
response_compression_min_size: 4KiB
all: # Rules are applied to all subgraph requests.
# Subgraphs transport options
request_timeout: 60s
Expand Down
3 changes: 2 additions & 1 deletion router/pkg/config/testdata/config_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"Router": {
"MaxRequestBodyBytes": 5000000,
"MaxHeaderBytes": 0,
"DecompressionEnabled": true
"DecompressionEnabled": true,
"ResponseCompressionMinSize": 4096
},
"Subgraphs": null
},
Expand Down
3 changes: 2 additions & 1 deletion router/pkg/config/testdata/config_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@
"Router": {
"MaxRequestBodyBytes": 5000000,
"MaxHeaderBytes": 4194304,
"DecompressionEnabled": false
"DecompressionEnabled": false,
"ResponseCompressionMinSize": 4096
},
"Subgraphs": {
"products": {
Expand Down
Loading