compress

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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()
}
Example (CustomMinLength)
package main

import (
	"github.com/goceleris/celeris/middleware/compress"
)

func main() {
	_ = compress.New(compress.Config{
		MinLength: 1024,
	})
}
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,
	})
}
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",
		},
	})
}
Example (GzipOnly)
package main

import (
	"github.com/goceleris/celeris/middleware/compress"
)

func main() {
	_ = compress.New(compress.Config{
		Encodings: []string{"gzip"},
		GzipLevel: compress.LevelFastest,
	})
}
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,
	})
}
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/")
		},
	})
}
Example (SkipPaths)
package main

import (
	"github.com/goceleris/celeris/middleware/compress"
)

func main() {
	_ = compress.New(compress.Config{
		SkipPaths: []string{"/health", "/metrics", "/debug/pprof"},
	})
}
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,
	})
}

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL