Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to disable compression for thruster by env variable #56

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,26 @@ configuration. But if you need to customize its behavior, there are a few
environment variables that you can set.

| Variable Name | Description | Default Value |
|-----------------------|---------------------------------------------------------|---------------|
| `TLS_DOMAIN` | Comma-separated list of domain names to use for TLS provisioning. If not set, TLS will be disabled. | None |
| `TARGET_PORT` | The port that your Puma server should run on. Thruster will set `PORT` to this value when starting your server. | 3000 |
| `CACHE_SIZE` | The size of the HTTP cache in bytes. | 64MB |
| `MAX_CACHE_ITEM_SIZE` | The maximum size of a single item in the HTTP cache in bytes. | 1MB |
| `X_SENDFILE_ENABLED` | Whether to enable X-Sendfile support. Set to `0` or `false` to disable. | Enabled |
| `MAX_REQUEST_BODY` | The maximum size of a request body in bytes. Requests larger than this size will be refused; `0` means no maximum size is enforced. | `0` |
| `STORAGE_PATH` | The path to store Thruster's internal state. Provisioned TLS certificates will be stored here, so that they will not need to be requested every time your application is started. | `./storage/thruster` |
| `BAD_GATEWAY_PAGE` | Path to an HTML file to serve when the backend server returns a 502 Bad Gateway error. If there is no file at the specific path, Thruster will serve an empty 502 response instead. Because Thruster boots very quickly, a custom page can be a useful way to show that your application is starting up. | `./public/502.html` |
| `HTTP_PORT` | The port to listen on for HTTP traffic. | 80 |
| `HTTPS_PORT` | The port to listen on for HTTPS traffic. | 443 |
| `HTTP_IDLE_TIMEOUT` | The maximum time in seconds that a client can be idle before the connection is closed. | 60 |
| `HTTP_READ_TIMEOUT` | The maximum time in seconds that a client can take to send the request headers and body. | 30 |
| `HTTP_WRITE_TIMEOUT` | The maximum time in seconds during which the client must read the response. | 30 |
| `ACME_DIRECTORY` | The URL of the ACME directory to use for TLS certificate provisioning. | `https://acme-v02.api.letsencrypt.org/directory` (Let's Encrypt production) |
| `EAB_KID` | The EAB key identifier to use when provisioning TLS certificates, if required. | None |
| `EAB_HMAC_KEY` | The Base64-encoded EAB HMAC key to use when provisioning TLS certificates, if required. | None |
| `FORWARD_HEADERS` | Whether to forward X-Forwarded-* headers from the client. | Disabled when running with TLS; enabled otherwise |
| `DEBUG` | Set to `1` or `true` to enable debug logging. | Disabled |
|-----------------------------|---------------------------------------------------------|---------------|
| `TLS_DOMAIN` | Comma-separated list of domain names to use for TLS provisioning. If not set, TLS will be disabled. | None |
| `TARGET_PORT` | The port that your Puma server should run on. Thruster will set `PORT` to this value when starting your server. | 3000 |
| `CACHE_SIZE` | The size of the HTTP cache in bytes. | 64MB |
| `MAX_CACHE_ITEM_SIZE` | The maximum size of a single item in the HTTP cache in bytes. | 1MB |
| `GZIP_COMPRESSION_ENABLED` | Whether to enable gzip compression for static assets. Set to `0` or `false` to disable. | Enabled |
| `X_SENDFILE_ENABLED` | Whether to enable X-Sendfile support. Set to `0` or `false` to disable. | Enabled |
| `MAX_REQUEST_BODY` | The maximum size of a request body in bytes. Requests larger than this size will be refused; `0` means no maximum size is enforced. | `0` |
| `STORAGE_PATH` | The path to store Thruster's internal state. Provisioned TLS certificates will be stored here, so that they will not need to be requested every time your application is started. | `./storage/thruster` |
| `BAD_GATEWAY_PAGE` | Path to an HTML file to serve when the backend server returns a 502 Bad Gateway error. If there is no file at the specific path, Thruster will serve an empty 502 response instead. Because Thruster boots very quickly, a custom page can be a useful way to show that your application is starting up. | `./public/502.html` |
| `HTTP_PORT` | The port to listen on for HTTP traffic. | 80 |
| `HTTPS_PORT` | The port to listen on for HTTPS traffic. | 443 |
| `HTTP_IDLE_TIMEOUT` | The maximum time in seconds that a client can be idle before the connection is closed. | 60 |
| `HTTP_READ_TIMEOUT` | The maximum time in seconds that a client can take to send the request headers and body. | 30 |
| `HTTP_WRITE_TIMEOUT` | The maximum time in seconds during which the client must read the response. | 30 |
| `ACME_DIRECTORY` | The URL of the ACME directory to use for TLS certificate provisioning. | `https://acme-v02.api.letsencrypt.org/directory` (Let's Encrypt production) |
| `EAB_KID` | The EAB key identifier to use when provisioning TLS certificates, if required. | None |
| `EAB_HMAC_KEY` | The Base64-encoded EAB HMAC key to use when provisioning TLS certificates, if required. | None |
| `FORWARD_HEADERS` | Whether to forward X-Forwarded-* headers from the client. | Disabled when running with TLS; enabled otherwise |
| `DEBUG` | Set to `1` or `true` to enable debug logging. | Disabled |

To prevent naming clashes with your application's own environment variables,
Thruster's environment variables can optionally be prefixed with `THRUSTER_`.
Expand Down
18 changes: 10 additions & 8 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ type Config struct {
UpstreamCommand string
UpstreamArgs []string

CacheSizeBytes int
MaxCacheItemSizeBytes int
XSendfileEnabled bool
MaxRequestBody int
CacheSizeBytes int
MaxCacheItemSizeBytes int
XSendfileEnabled bool
GzipCompressionEnabled bool
MaxRequestBody int

TLSDomains []string
ACMEDirectoryURL string
Expand Down Expand Up @@ -79,10 +80,11 @@ func NewConfig() (*Config, error) {
UpstreamCommand: os.Args[1],
UpstreamArgs: os.Args[2:],

CacheSizeBytes: getEnvInt("CACHE_SIZE", defaultCacheSize),
MaxCacheItemSizeBytes: getEnvInt("MAX_CACHE_ITEM_SIZE", defaultMaxCacheItemSizeBytes),
XSendfileEnabled: getEnvBool("X_SENDFILE_ENABLED", true),
MaxRequestBody: getEnvInt("MAX_REQUEST_BODY", defaultMaxRequestBody),
CacheSizeBytes: getEnvInt("CACHE_SIZE", defaultCacheSize),
MaxCacheItemSizeBytes: getEnvInt("MAX_CACHE_ITEM_SIZE", defaultMaxCacheItemSizeBytes),
XSendfileEnabled: getEnvBool("X_SENDFILE_ENABLED", true),
GzipCompressionEnabled: getEnvBool("GZIP_COMPRESSION_ENABLED", true),
MaxRequestBody: getEnvInt("MAX_REQUEST_BODY", defaultMaxRequestBody),

TLSDomains: getEnvStrings("TLS_DOMAIN", []string{}),
ACMEDirectoryURL: getEnvString("ACME_DIRECTORY", defaultACMEDirectoryURL),
Expand Down
2 changes: 2 additions & 0 deletions internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func TestConfig_override_defaults_with_env_vars(t *testing.T) {
usingEnvVar(t, "CACHE_SIZE", "256")
usingEnvVar(t, "HTTP_READ_TIMEOUT", "5")
usingEnvVar(t, "X_SENDFILE_ENABLED", "0")
usingEnvVar(t, "GZIP_COMPRESSION_ENABLED", "0")
usingEnvVar(t, "DEBUG", "1")
usingEnvVar(t, "ACME_DIRECTORY", "https://acme-staging-v02.api.letsencrypt.org/directory")

Expand All @@ -123,6 +124,7 @@ func TestConfig_override_defaults_with_env_vars(t *testing.T) {
assert.Equal(t, 256, c.CacheSizeBytes)
assert.Equal(t, 5*time.Second, c.HttpReadTimeout)
assert.Equal(t, false, c.XSendfileEnabled)
assert.Equal(t, false, c.GzipCompressionEnabled)
assert.Equal(t, slog.LevelDebug, c.LogLevel)
assert.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", c.ACMEDirectoryURL)
}
Expand Down
5 changes: 4 additions & 1 deletion internal/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ type HandlerOptions struct {
maxRequestBody int
targetUrl *url.URL
xSendfileEnabled bool
gzipCompressionEnabled bool
forwardHeaders bool
}

func NewHandler(options HandlerOptions) http.Handler {
handler := NewProxyHandler(options.targetUrl, options.badGatewayPage, options.forwardHeaders)
handler = NewCacheHandler(options.cache, options.maxCacheableResponseBody, handler)
handler = NewSendfileHandler(options.xSendfileEnabled, handler)
handler = gzhttp.GzipHandler(handler)
if options.gzipCompressionEnabled {
handler = gzhttp.GzipHandler(handler)
}

if options.maxRequestBody > 0 {
handler = http.MaxBytesHandler(handler, int64(options.maxRequestBody))
Expand Down
25 changes: 25 additions & 0 deletions internal/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ func TestHandlerGzipCompression_when_proxying(t *testing.T) {
assert.Less(t, transferredSize, fixtureLength("loremipsum.txt"))
}

func TestNotHandlerGzipCompression_when_disabled(t *testing.T) {
fixtureLength := strconv.FormatInt(fixtureLength("loremipsum.txt"), 10)

upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", fixtureLength)
w.Write(fixtureContent("loremipsum.txt"))
}))
defer upstream.Close()

options := handlerOptions(upstream.URL)
options.gzipCompressionEnabled = false
h := NewHandler(options)

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "gzip")
h.ServeHTTP(w, r)

assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Header().Get("Content-Type"), "text/plain")
assert.Empty(t, w.Header().Get("Content-Encoding"))
assert.Equal(t, fixtureLength, w.Header().Get("Content-Length"))
}

func TestHandlerGzipCompression_is_not_applied_when_not_requested(t *testing.T) {
fixtureLength := strconv.FormatInt(fixtureLength("loremipsum.txt"), 10)

Expand Down Expand Up @@ -248,6 +272,7 @@ func handlerOptions(targetUrl string) HandlerOptions {
cache: NewMemoryCache(defaultCacheSize, defaultMaxCacheItemSizeBytes),
targetUrl: url,
xSendfileEnabled: true,
gzipCompressionEnabled: true,
maxCacheableResponseBody: 1024,
badGatewayPage: "",
forwardHeaders: true,
Expand Down
1 change: 1 addition & 0 deletions internal/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (s *Service) Run() int {
cache: s.cache(),
targetUrl: s.targetUrl(),
xSendfileEnabled: s.config.XSendfileEnabled,
gzipCompressionEnabled: s.config.GzipCompressionEnabled,
maxCacheableResponseBody: s.config.MaxCacheItemSizeBytes,
maxRequestBody: s.config.MaxRequestBody,
badGatewayPage: s.config.BadGatewayPage,
Expand Down