Documentation
¶
Overview ¶
Package compress provides transparent response compression middleware for celeris.
Supported encodings are zstd, brotli, gzip, and deflate, negotiated via the Accept-Encoding request header. The server-side priority order is configurable; the default prefers zstd > brotli > gzip. Deflate is supported but opt-in only (not in the default Encodings list) because it is a legacy encoding superseded by gzip.
Basic usage with defaults (zstd > br > gzip, MinLength 256):
server.Use(compress.New())
Gzip-only with fastest compression:
server.Use(compress.New(compress.Config{
Encodings: []string{"gzip"},
GzipLevel: compress.LevelFastest,
}))
Per-encoding compression levels are configurable via Config.GzipLevel, Config.BrotliLevel, and Config.ZstdLevel. Each accepts Level constants (LevelDefault, LevelFastest, LevelBest, LevelNone) or an integer within the encoding's valid range.
Exclude specific content types and paths:
server.Use(compress.New(compress.Config{
ExcludedContentTypes: []string{"image/", "video/", "audio/", "application/octet-stream"},
SkipPaths: []string{"/health", "/metrics"},
}))
Method Filtering ¶
HEAD and OPTIONS requests bypass compression entirely (only Vary is added). This avoids unnecessary buffering for requests that do not produce response bodies.
Content-Negotiation ¶
The middleware calls celeris.Context.AcceptsEncodings with the configured encoding list. If the client does not accept any supported encoding, the response passes through uncompressed.
Status Code Range ¶
Only 2xx responses are compressed. Error responses (4xx, 5xx), redirects (3xx), and informational responses (1xx) pass through uncompressed.
Deflate (opt-in) ¶
The "deflate" encoding (raw DEFLATE, RFC 1951) is supported but not included in the default Encodings list. Add it explicitly:
server.Use(compress.New(compress.Config{
Encodings: []string{"zstd", "br", "gzip", "deflate"},
}))
Deflate is a legacy encoding. Prefer gzip, brotli, or zstd for new deployments.
Streaming Compression ¶
For endpoints that produce large or streaming responses, use NewCompressedStream to wrap a celeris.StreamWriter with on-the-fly gzip or brotli compression. This avoids buffering the entire response body but does not support the expansion guard or MinLength threshold.
Pool Strategy ¶
Writer pools eliminate per-request allocations on the hot path:
- gzip: sync.Pool of *gzip.Writer — Get, Reset, Write, Close, Put.
- brotli: sync.Pool of *brotli.Writer — same pattern.
- deflate: sync.Pool of *flate.Writer — same pattern.
- zstd: single thread-safe zstd.Encoder — EncodeAll, no pool needed.
- buffers: sync.Pool of *bytes.Buffer for gzip/brotli/deflate output.
StreamWriter Incompatibility ¶
This middleware uses celeris.Context.BufferResponse which is incompatible with celeris.Context.StreamWriter. If the downstream handler uses StreamWriter, BufferResponse returns nil and the streamed response bypasses this middleware entirely. Use Config.Skip to explicitly exclude streaming endpoints.
Response Buffering (No Streaming Compression) ¶
This middleware buffers the entire response body in memory before deciding whether and how to compress. This design enables the expansion guard (flush original if compressed >= original) and correct Content-Length headers, but means the full uncompressed body must fit in memory.
For endpoints that produce large responses (file downloads, CSV exports, streaming JSON), use Config.Skip or Config.SkipPaths to bypass compression entirely. Consider using the framework's StreamWriter for such endpoints instead.
compress.New(compress.Config{
SkipPaths: []string{"/api/export", "/files"},
Skip: func(c *celeris.Context) bool {
return strings.HasPrefix(c.Path(), "/download/")
},
})
Competing frameworks (Echo, Fiber) offer streaming compression by wrapping the response writer, but this prevents expansion detection and accurate Content-Length.
MinLength Threshold ¶
Responses smaller than Config.MinLength bytes (default 256) are not compressed. Set MinLength to 0 to compress all non-empty responses. This avoids wasting CPU on responses too small to benefit from compression.
Excluded Content Types ¶
Content types matching Config.ExcludedContentTypes prefixes are skipped. The default excludes "image/", "video/", and "audio/" since these are typically already compressed.
Vary Header ¶
The middleware appends "Accept-Encoding" to the Vary header using celeris.Context.AddHeader so that existing Vary values (e.g., Origin from CORS) are preserved. The Vary header is added on all non-skipped responses, including those that pass through uncompressed (below MinLength, excluded content type, etc.), so that caches correctly distinguish responses that vary by encoding.
Compression Expansion Guard ¶
If the compressed output is equal to or larger than the original body, the original is sent uncompressed. This prevents pathological expansion on already-compressed or incompressible data.
Ordering with ETag ¶
Compress runs OUTSIDE etag middleware. The recommended chain is:
server.Use(compress.New()) // outermost server.Use(etag.New()) // inner — ETag computed on uncompressed body
BREACH Attack Warning ¶
Compressing HTTPS responses that reflect user input (search queries, form values, URL parameters) alongside secrets (CSRF tokens, session IDs) can leak those secrets via the BREACH attack. Mitigation options:
- Exclude sensitive endpoints with Config.SkipPaths or Config.Skip
- Randomize padding on pages that mix user input and secrets
- Separate secret-bearing responses from user-controlled content
See http://breachattack.com for details. This applies to any HTTP compression layer, not just this middleware.
Response Size Measurement ¶
When metrics or OTel middleware runs before compress (the recommended ordering), response size metrics reflect uncompressed application-level sizes. See middleware/metrics and middleware/otel documentation.
Separate Sub-Module ¶
This package is a separate Go module (middleware/compress/go.mod) with its own dependency set. Import it as:
import "github.com/goceleris/celeris/middleware/compress"
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func New ¶
func New(config ...Config) celeris.HandlerFunc
New creates a compress middleware with the given config.
Example ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
// Default: zstd > brotli > gzip, MinLength 256.
// server.Use(compress.New())
_ = compress.New()
}
Output:
Example (CustomMinLength) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
_ = compress.New(compress.Config{
MinLength: 1024,
})
}
Output:
Example (Deflate) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
// Opt-in to deflate for legacy client support. Deflate is not
// included in the default Encodings list.
_ = compress.New(compress.Config{
Encodings: []string{"zstd", "br", "gzip", "deflate"},
DeflateLevel: compress.LevelDefault,
})
}
Output:
Example (ExcludedContentTypes) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
_ = compress.New(compress.Config{
ExcludedContentTypes: []string{
"image/",
"video/",
"audio/",
"application/octet-stream",
},
})
}
Output:
Example (GzipOnly) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
_ = compress.New(compress.Config{
Encodings: []string{"gzip"},
GzipLevel: compress.LevelFastest,
})
}
Output:
Example (NoCompression) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
// LevelNone stores data without compression (useful for testing).
_ = compress.New(compress.Config{
Encodings: []string{"gzip"},
GzipLevel: compress.LevelNone,
})
}
Output:
Example (Skip) ¶
package main
import (
"strings"
"github.com/goceleris/celeris"
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
// Dynamic skip: bypass compression for WebSocket upgrades and
// streaming download endpoints.
_ = compress.New(compress.Config{
Skip: func(c *celeris.Context) bool {
if c.Header("upgrade") == "websocket" {
return true
}
return strings.HasPrefix(c.Path(), "/download/")
},
})
}
Output:
Example (SkipPaths) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
_ = compress.New(compress.Config{
SkipPaths: []string{"/health", "/metrics", "/debug/pprof"},
})
}
Output:
Example (ZstdLevel) ¶
package main
import (
"github.com/goceleris/celeris/middleware/compress"
)
func main() {
// Use SpeedBestCompression (level 4) for zstd.
_ = compress.New(compress.Config{
Encodings: []string{"zstd"},
ZstdLevel: compress.LevelBest,
})
}
Output:
Types ¶
type CompressedStream ¶
type CompressedStream struct {
// contains filtered or unexported fields
}
CompressedStream wraps a celeris.StreamWriter with on-the-fly compression. The caller obtains a StreamWriter from celeris.Context.StreamWriter, then wraps it with NewCompressedStream for transparent compression.
Supported encodings: "gzip" and "br" (brotli). Zstd uses EncodeAll (batch) and is not suitable for streaming; use the buffered middleware (New) for zstd.
Unlike the buffered compress middleware, CompressedStream does not support the expansion guard or MinLength threshold — the response is compressed unconditionally.
func NewCompressedStream ¶
func NewCompressedStream(sw *celeris.StreamWriter, encoding string, opts ...StreamOption) *CompressedStream
NewCompressedStream wraps sw with compression for the given encoding. Supported encodings: "gzip", "br". Returns nil if the encoding is unsupported or sw is nil.
Optional StreamOption values configure compression levels. Without options, library defaults are used (gzip level 6, brotli level 6).
Content-Encoding and Vary headers are set automatically on CompressedStream.WriteHeader. Any Content-Length in the user headers is stripped (streaming responses use chunked transfer encoding).
func (*CompressedStream) Close ¶
func (cs *CompressedStream) Close() error
Close closes the compressor (writes final bytes/trailer) and the underlying StreamWriter. Safe to call multiple times; subsequent calls are no-ops.
func (*CompressedStream) Flush ¶
func (cs *CompressedStream) Flush() error
Flush flushes the compressor and the underlying StreamWriter.
func (*CompressedStream) Write ¶
func (cs *CompressedStream) Write(data []byte) (int, error)
Write compresses data and sends it to the underlying StreamWriter.
func (*CompressedStream) WriteHeader ¶
func (cs *CompressedStream) WriteHeader(status int, headers [][2]string) error
WriteHeader sends the status line and headers with Content-Encoding and Vary added automatically. Any Content-Length header is stripped because streaming responses use chunked transfer encoding. Must be called once before Write.
type Config ¶
type Config struct {
// Skip defines a function to skip this middleware for certain requests.
Skip func(c *celeris.Context) bool
// SkipPaths lists paths to skip from compression (exact match).
SkipPaths []string
// MinLength is the minimum response body size (in bytes) required for
// compression. Responses smaller than this are sent uncompressed.
// Default: 256 (when no Config is passed to New).
// Set to 0 to compress all non-empty responses regardless of size.
MinLength int
// Encodings lists the supported encodings in server-side priority order.
// Supported values: "zstd", "br", "gzip", "deflate".
// Default: ["zstd", "br", "gzip"] (deflate is opt-in only).
Encodings []string
// GzipLevel sets the gzip compression level.
// Default: LevelDefault (0 = library default, gzip level 6).
// Use LevelNone (-1) for store-only (no compression).
GzipLevel Level
// BrotliLevel sets the brotli compression level.
// Default: LevelDefault (0 = library default, brotli level 6).
// Use LevelNone (-1) for no compression.
BrotliLevel Level
// ZstdLevel sets the zstd compression level.
// Default: LevelDefault (0 = library default).
// Use LevelNone (-1) for no compression.
ZstdLevel Level
// DeflateLevel sets the deflate (raw DEFLATE, RFC 1951) compression level.
// Deflate is a legacy encoding; prefer gzip, brotli, or zstd for new
// deployments. Add "deflate" to Encodings to enable it (not included by
// default). Default: LevelDefault (0 = library default, flate.DefaultCompression).
DeflateLevel Level
// ExcludedContentTypes lists content-type prefixes that should not be
// compressed. Set to an empty slice to disable all content-type exclusions.
// Default: ["image/", "video/", "audio/"].
ExcludedContentTypes []string
}
Config defines the compress middleware configuration.
type Level ¶
type Level int
Level controls the compression level for a given encoding. The zero value (LevelDefault) selects the library default, which is idiomatic Go: a zero-value Config uses sensible defaults for all levels.
const ( // LevelDefault uses the library default compression level. // It is intentionally the zero value so that an unset field in Config // falls through to the library default. LevelDefault Level = 0 // LevelNone stores data without compression (gzip level 0, brotli level 0). LevelNone Level = -1 // LevelBest is a sentinel that resolves to the encoding-specific maximum: // gzip -> 9, brotli -> 11, zstd -> SpeedBestCompression (4). LevelBest Level = -2 // LevelFastest uses the fastest (lowest) compression level. LevelFastest Level = 1 )
type StreamOption ¶
type StreamOption func(*streamConfig)
StreamOption configures streaming compression.
func WithBrotliLevel ¶
func WithBrotliLevel(level Level) StreamOption
WithBrotliLevel sets the brotli compression level for streaming. Default: 6.
func WithGzipLevel ¶
func WithGzipLevel(level Level) StreamOption
WithGzipLevel sets the gzip compression level for streaming. Default: gzip.DefaultCompression (-1).