mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Add gzip support to API handlers (#38675)
**Related issue:** Resolves #37944 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] QA'd all new/changed functionality manually ## New Fleet configuration settings - [x] Setting(s) is/are explicitly excluded from GitOps (it's a server configuration)
This commit is contained in:
parent
be3079b4fd
commit
3a0b72a329
6 changed files with 99 additions and 3 deletions
1
changes/37944-gzip-responses
Normal file
1
changes/37944-gzip-responses
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Add `gzip_responses` server configuration option that allows the server to gzip API responses when the client indicates support through the `Accept-Encoding: gzip` request header.
|
||||
2
go.mod
2
go.mod
|
|
@ -89,7 +89,7 @@ require (
|
|||
github.com/igm/sockjs-go/v3 v3.0.2
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/josephspurrier/goversioninfo v1.4.0
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/compress v1.18.3
|
||||
github.com/kolide/launcher v1.0.12
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/macadmins/osquery-extension v1.2.7
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -562,8 +562,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
|
|||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kolide/kit v0.0.0-20221107170827-fb85e3d59eab h1:KVR7cs+oPyy85i+8t1ZaNSy1bymCy5FuWyt51pdrXu4=
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ type ServerConfig struct {
|
|||
CleanupDistTargetsAge time.Duration `yaml:"cleanup_dist_targets_age"`
|
||||
MaxInstallerSizeBytes int64 `yaml:"max_installer_size"`
|
||||
TrustedProxies string `yaml:"trusted_proxies"`
|
||||
GzipResponses bool `yaml:"gzip_responses"`
|
||||
}
|
||||
|
||||
func (s *ServerConfig) DefaultHTTPServer(ctx context.Context, handler http.Handler) *http.Server {
|
||||
|
|
@ -1213,6 +1214,7 @@ func (man Manager) addConfigs() {
|
|||
man.addConfigByteSize("server.max_installer_size", installersize.Human(installersize.DefaultMaxInstallerSize), "Maximum size in bytes for software installer uploads (e.g. 10GiB, 500MB, 1G)")
|
||||
man.addConfigString("server.trusted_proxies", "",
|
||||
"Trusted proxy configuration for client IP extraction: 'none' (RemoteAddr only), a header name (e.g., 'True-Client-IP'), a hop count (e.g., '2'), or comma-separated IP/CIDR ranges")
|
||||
man.addConfigBool("server.gzip_responses", false, "Enable gzip-compressed responses for supported clients")
|
||||
|
||||
// Hide the sandbox flag as we don't want it to be discoverable for users for now
|
||||
man.hideConfig("server.sandbox_enabled")
|
||||
|
|
@ -1683,6 +1685,7 @@ func (man Manager) LoadConfig() FleetConfig {
|
|||
CleanupDistTargetsAge: man.getConfigDuration("server.cleanup_dist_targets_age"),
|
||||
MaxInstallerSizeBytes: man.getConfigByteSize("server.max_installer_size"),
|
||||
TrustedProxies: man.getConfigString("server.trusted_proxies"),
|
||||
GzipResponses: man.getConfigBool("server.gzip_responses"),
|
||||
},
|
||||
Auth: AuthConfig{
|
||||
BcryptCost: man.getConfigInt("auth.bcrypt_cost"),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
|
||||
eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/publicip"
|
||||
|
|
@ -139,6 +141,12 @@ func MakeHandler(
|
|||
}
|
||||
}
|
||||
|
||||
if config.Server.GzipResponses {
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return gzhttp.GzipHandler(h)
|
||||
})
|
||||
}
|
||||
|
||||
// Add middleware to extract the client IP and set it in the request context.
|
||||
r.Use(func(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
|
@ -10,8 +11,12 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
kitlog "github.com/go-kit/log"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
|
@ -327,3 +332,82 @@ func mockRouteHandler(route *mux.Route, status int) (verb, path string, err erro
|
|||
route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) })
|
||||
return meths[0], path, nil
|
||||
}
|
||||
|
||||
func TestGzipResponses(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return &fleet.AppConfig{}, nil
|
||||
}
|
||||
|
||||
testRoute := func(r *mux.Router, opts []kithttp.ServerOption) {
|
||||
r.Handle("/api/test-gzip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// Write enough data to trigger gzip (default threshold is 1500 bytes)
|
||||
data := make([]byte, 2000)
|
||||
for i := range data {
|
||||
data[i] = 'a'
|
||||
}
|
||||
_, err := w.Write(data)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
}
|
||||
|
||||
t.Run("Enabled", func(t *testing.T) {
|
||||
cfg := config.TestConfig()
|
||||
cfg.Server.GzipResponses = true
|
||||
|
||||
_, server := RunServerForTestsWithDS(t, ds, &TestServerOpts{
|
||||
FleetConfig: &cfg,
|
||||
FeatureRoutes: []endpointer.HandlerRoutesFunc{testRoute},
|
||||
SkipCreateTestUsers: true,
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
t.Run("WithAcceptEncoding", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", server.URL+"/api/test-gzip", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
resp, err := fleethttp.NewClient().Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
require.Equal(t, "gzip", resp.Header.Get("Content-Encoding"), "Expected gzip Content-Encoding when enabled")
|
||||
})
|
||||
|
||||
t.Run("WithoutAcceptEncoding", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", server.URL+"/api/test-gzip", nil)
|
||||
require.NoError(t, err)
|
||||
// Do NOT set Accept-Encoding header
|
||||
transport := fleethttp.NewTransport()
|
||||
transport.DisableCompression = true // Prevents automatic addition of Accept-Encoding: gzip
|
||||
client := fleethttp.NewClient()
|
||||
client.Transport = transport
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
require.Empty(t, resp.Header.Get("Content-Encoding"), "Expected no gzip Content-Encoding when Accept-Encoding not set")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Disabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := config.TestConfig()
|
||||
cfg.Server.GzipResponses = false
|
||||
_, server := RunServerForTestsWithDS(t, ds, &TestServerOpts{
|
||||
FleetConfig: &cfg,
|
||||
FeatureRoutes: []endpointer.HandlerRoutesFunc{testRoute},
|
||||
SkipCreateTestUsers: true,
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", server.URL+"/api/test-gzip", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
resp, err := fleethttp.NewClient().Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
require.Empty(t, resp.Header.Get("Content-Encoding"), "Expected no gzip Content-Encoding when disabled")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue