-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change allows the HTTP clients to consume and send gzip compress…
…ed response and request body. It is available for the following REST API endpoints: - GET & POST HTTP methods on /v0/data & /v1/data endpoints - POST HTTP method on /v1/compile endpoint HTTP clients can optionally: - send 'Accept-Encoding: gzip' header and expect a gzip compressed body and a Content-Encoding: gzip response header. The server will send the content encoded as gzip only after a threshold defined by server.encoding.gzip.min_length (default value is 1024). If the size is below the threshold, the body is not compressed - send 'Content-Encoding: gzip' header and a gzip compressed body and expect the server to correctly interpret the request Fixes #5310 Signed-off-by: aarnautu <[email protected]>
- Loading branch information
1 parent
dc37446
commit 727b33e
Showing
12 changed files
with
1,296 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package encoding | ||
|
||
import ( | ||
"compress/gzip" | ||
"fmt" | ||
|
||
"github.com/open-policy-agent/opa/util" | ||
) | ||
|
||
var defaultGzipMinLength = 1024 | ||
var defaultGzipCompressionLevel = gzip.BestCompression | ||
|
||
// Config represents the configuration for the Server.Encoding settings | ||
type Config struct { | ||
Gzip *Gzip `json:"gzip,omitempty"` | ||
} | ||
|
||
// Gzip represents the configuration for the Server.Encoding.Gzip settings | ||
type Gzip struct { | ||
MinLength *int `json:"min_length,omitempty"` // the minimum length of a response that will be gzipped | ||
CompressionLevel *int `json:"compression_level,omitempty"` // the compression level for gzip | ||
} | ||
|
||
// ConfigBuilder assists in the construction of the plugin configuration. | ||
type ConfigBuilder struct { | ||
raw []byte | ||
} | ||
|
||
// NewConfigBuilder returns a new ConfigBuilder to build and parse the server config | ||
func NewConfigBuilder() *ConfigBuilder { | ||
return &ConfigBuilder{} | ||
} | ||
|
||
// WithBytes sets the raw server config | ||
func (b *ConfigBuilder) WithBytes(config []byte) *ConfigBuilder { | ||
b.raw = config | ||
return b | ||
} | ||
|
||
// Parse returns a valid Config object with defaults injected. | ||
func (b *ConfigBuilder) Parse() (*Config, error) { | ||
if b.raw == nil { | ||
defaultConfig := &Config{ | ||
Gzip: &Gzip{ | ||
MinLength: &defaultGzipMinLength, | ||
CompressionLevel: &defaultGzipCompressionLevel, | ||
}, | ||
} | ||
return defaultConfig, nil | ||
} | ||
|
||
var result Config | ||
|
||
if err := util.Unmarshal(b.raw, &result); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &result, result.validateAndInjectDefaults() | ||
} | ||
|
||
func (c *Config) validateAndInjectDefaults() error { | ||
if c.Gzip == nil { | ||
c.Gzip = &Gzip{ | ||
MinLength: &defaultGzipMinLength, | ||
CompressionLevel: &defaultGzipCompressionLevel, | ||
} | ||
} | ||
if c.Gzip.MinLength == nil { | ||
c.Gzip.MinLength = &defaultGzipMinLength | ||
} | ||
|
||
if c.Gzip.CompressionLevel == nil { | ||
c.Gzip.CompressionLevel = &defaultGzipCompressionLevel | ||
} | ||
|
||
if *c.Gzip.MinLength <= 0 { | ||
return fmt.Errorf("invalid value for server.encoding.gzip.min_length field, should be a positive number") | ||
} | ||
|
||
acceptedCompressionLevels := map[int]bool{ | ||
gzip.NoCompression: true, | ||
gzip.BestSpeed: true, | ||
gzip.BestCompression: true, | ||
} | ||
_, compressionLevelAccepted := acceptedCompressionLevels[*c.Gzip.CompressionLevel] | ||
if !compressionLevelAccepted { | ||
return fmt.Errorf("invalid value for server.encoding.gzip.compression_level field, accepted values are 0, 1 or 9") | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package encoding | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestConfigValidation(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
wantErr bool | ||
}{ | ||
{ | ||
input: `{}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
input: `{"gzip": {"min_length": "not-a-number"}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip": {min_length": 42}}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
input: `{"gzip":{"min_length": "42"}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"min_length": 0}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"min_length": -10}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"random_key": 0}}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
input: `{"gzip": {"min_length": -10, "compression_level": 13}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"compression_level": "not-an-number"}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"compression_level": 1}}`, | ||
wantErr: false, | ||
}, | ||
{ | ||
input: `{"gzip":{"compression_level": 13}}`, | ||
wantErr: true, | ||
}, | ||
{ | ||
input: `{"gzip":{"min_length": 42, "compression_level": 9}}`, | ||
wantErr: false, | ||
}, | ||
} | ||
|
||
for i, test := range tests { | ||
t.Run(fmt.Sprintf("TestConfigValidation_case_%d", i), func(t *testing.T) { | ||
_, err := NewConfigBuilder().WithBytes([]byte(test.input)).Parse() | ||
if err != nil && !test.wantErr { | ||
t.Fail() | ||
} | ||
if err == nil && test.wantErr { | ||
t.Fail() | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestConfigValue(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
minLengthExpectedValue int | ||
compressionLevelExpectedValue int | ||
}{ | ||
{ | ||
input: `{}`, | ||
minLengthExpectedValue: 1024, | ||
compressionLevelExpectedValue: 9, | ||
}, | ||
{ | ||
input: `{"gzip":{"min_length": 42, "compression_level": 1}}`, | ||
minLengthExpectedValue: 42, | ||
compressionLevelExpectedValue: 1, | ||
}, | ||
} | ||
|
||
for i, test := range tests { | ||
t.Run(fmt.Sprintf("TestConfigValue_case_%d", i), func(t *testing.T) { | ||
config, err := NewConfigBuilder().WithBytes([]byte(test.input)).Parse() | ||
if err != nil { | ||
t.Fail() | ||
} | ||
if *config.Gzip.MinLength != test.minLengthExpectedValue || *config.Gzip.CompressionLevel != test.compressionLevelExpectedValue { | ||
t.Fail() | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.