datastar/sdk/go/sse-compression.go

295 lines
8.0 KiB
Go

package datastar
import (
"strings"
"github.com/CAFxX/httpcompression/contrib/andybalholm/brotli"
"github.com/CAFxX/httpcompression/contrib/compress/gzip"
"github.com/CAFxX/httpcompression/contrib/compress/zlib"
"github.com/CAFxX/httpcompression/contrib/klauspost/zstd"
zstd_opts "github.com/klauspost/compress/zstd"
"github.com/CAFxX/httpcompression"
)
// CompressionStrategy indicates the strategy for selecting the compression algorithm.
type CompressionStrategy string
const (
// ClientPriority indicates that the client's preferred compression algorithm
// should be used if possible.
ClientPriority CompressionStrategy = "client_priority"
// ServerPriority indicates that the server's preferred compression algorithm
// should be used.
ServerPriority CompressionStrategy = "server_priority"
// Forced indicates that the first provided compression
// algorithm must be used regardless of client or server preferences.
Forced CompressionStrategy = "forced"
)
// Compressor pairs a [httpcompression.CompressorProvider]
// with an encoding HTTP content type.
type Compressor struct {
Encoding string
Compressor httpcompression.CompressorProvider
}
// compressionOptions holds all the data for server-sent events
// message compression configuration initiated by [CompressionOption]s.
type compressionOptions struct {
CompressionStrategy CompressionStrategy
ClientEncodings []string
Compressors []Compressor
}
// CompressionOption configures server-sent events
// message compression.
type CompressionOption func(*compressionOptions)
// GzipOption configures the Gzip compression algorithm.
type GzipOption func(*gzip.Options)
// WithGzipLevel determines the algorithm's compression level.
// Higher values result in smaller output at the cost of higher CPU usage.
//
// Choose one of the following levels:
// - [gzip.NoCompression]
// - [gzip.BestSpeed]
// - [gzip.BestCompression]
// - [gzip.DefaultCompression]
// - [gzip.HuffmanOnly]
func WithGzipLevel(level int) GzipOption {
return func(opts *gzip.Options) {
opts.Level = level
}
}
// WithGzip appends a [Gzip] compressor to the list of compressors.
//
// [Gzip]: https://en.wikipedia.org/wiki/Gzip
func WithGzip(opts ...GzipOption) CompressionOption {
return func(cfg *compressionOptions) {
// set default options
options := gzip.Options{
Level: gzip.DefaultCompression,
}
// Apply all provided options.
for _, opt := range opts {
opt(&options)
}
gzipCompressor, _ := gzip.New(options)
compressor := Compressor{
Encoding: gzip.Encoding,
Compressor: gzipCompressor,
}
cfg.Compressors = append(cfg.Compressors, compressor)
}
}
// DeflateOption configures the Deflate compression algorithm.
type DeflateOption func(*zlib.Options)
// WithDeflateLevel determines the algorithm's compression level.
// Higher values result in smaller output at the cost of higher CPU usage.
//
// Choose one of the following levels:
// - [zlib.NoCompression]
// - [zlib.BestSpeed]
// - [zlib.BestCompression]
// - [zlib.DefaultCompression]
// - [zlib.HuffmanOnly]
func WithDeflateLevel(level int) DeflateOption {
return func(opts *zlib.Options) {
opts.Level = level
}
}
// WithDeflateDictionary sets the dictionary used by the algorithm.
// This can improve compression ratio for repeated data.
func WithDeflateDictionary(dict []byte) DeflateOption {
return func(opts *zlib.Options) {
opts.Dictionary = dict
}
}
// WithDeflate appends a [Deflate] compressor to the list of compressors.
//
// [Deflate]: https://en.wikipedia.org/wiki/Deflate
func WithDeflate(opts ...DeflateOption) CompressionOption {
return func(cfg *compressionOptions) {
options := zlib.Options{
Level: zlib.DefaultCompression,
}
for _, opt := range opts {
opt(&options)
}
zlibCompressor, _ := zlib.New(options)
compressor := Compressor{
Encoding: zlib.Encoding,
Compressor: zlibCompressor,
}
cfg.Compressors = append(cfg.Compressors, compressor)
}
}
// BrotliOption configures the Brotli compression algorithm.
type BrotliOption func(*brotli.Options)
// WithBrotliLevel determines the algorithm's compression level.
// Higher values result in smaller output at the cost of higher CPU usage.
// Fastest compression level is 0. Best compression level is 11.
// Defaults to 6.
func WithBrotliLevel(level int) BrotliOption {
return func(opts *brotli.Options) {
opts.Quality = level
}
}
// WithBrotliLGWin the sliding window size for Brotli compression
// algorithm. Select a value between 10 and 24.
// Defaults to 0, indicating automatic window size selection based on compression quality.
func WithBrotliLGWin(lgwin int) BrotliOption {
return func(opts *brotli.Options) {
opts.LGWin = lgwin
}
}
// WithBrotli appends a [Brotli] compressor to the list of compressors.
//
// [Brotli]: https://en.wikipedia.org/wiki/Brotli
func WithBrotli(opts ...BrotliOption) CompressionOption {
return func(cfg *compressionOptions) {
options := brotli.Options{
Quality: brotli.DefaultCompression,
}
for _, opt := range opts {
opt(&options)
}
brotliCompressor, _ := brotli.New(options)
compressor := Compressor{
Encoding: brotli.Encoding,
Compressor: brotliCompressor,
}
cfg.Compressors = append(cfg.Compressors, compressor)
}
}
// WithZstd appends a [Zstd] compressor to the list of compressors.
//
// [Zstd]: https://en.wikipedia.org/wiki/Zstd
func WithZstd(opts ...zstd_opts.EOption) CompressionOption {
return func(cfg *compressionOptions) {
zstdCompressor, _ := zstd.New(opts...)
compressor := Compressor{
Encoding: zstd.Encoding,
Compressor: zstdCompressor,
}
cfg.Compressors = append(cfg.Compressors, compressor)
}
}
// WithClientPriority sets the compression strategy to [ClientPriority].
// The compression algorithm will be selected based on the
// client's preference from the list of included compressors.
func WithClientPriority() CompressionOption {
return func(cfg *compressionOptions) {
cfg.CompressionStrategy = ClientPriority
}
}
// WithServerPriority sets the compression strategy to [ServerPriority].
// The compression algorithm will be selected based on the
// server's preference from the list of included compressors.
func WithServerPriority() CompressionOption {
return func(cfg *compressionOptions) {
cfg.CompressionStrategy = ServerPriority
}
}
// WithForced sets the compression strategy to [Forced].
// The first compression algorithm will be selected
// from the list of included compressors.
func WithForced() CompressionOption {
return func(cfg *compressionOptions) {
cfg.CompressionStrategy = Forced
}
}
// WithCompression adds compression to server-sent event stream.
func WithCompression(opts ...CompressionOption) SSEOption {
return func(sse *ServerSentEventGenerator) {
cfg := &compressionOptions{
CompressionStrategy: ClientPriority,
ClientEncodings: parseEncodings(sse.acceptEncoding),
}
// apply options
for _, opt := range opts {
opt(cfg)
}
// set defaults
if len(cfg.Compressors) == 0 {
WithBrotli()(cfg)
WithZstd()(cfg)
WithGzip()(cfg)
WithDeflate()(cfg)
}
switch cfg.CompressionStrategy {
case ClientPriority:
for _, clientEnc := range cfg.ClientEncodings {
for _, comp := range cfg.Compressors {
if comp.Encoding == clientEnc {
sse.w = comp.Compressor.Get(sse.w)
sse.encoding = comp.Encoding
return
}
}
}
case ServerPriority:
for _, comp := range cfg.Compressors {
for _, clientEnc := range cfg.ClientEncodings {
if comp.Encoding == clientEnc {
sse.w = comp.Compressor.Get(sse.w)
sse.encoding = comp.Encoding
return
}
}
}
case Forced:
if len(cfg.Compressors) > 0 {
sse.w = cfg.Compressors[0].Compressor.Get(sse.w)
sse.encoding = cfg.Compressors[0].Encoding
}
}
}
}
func parseEncodings(header string) []string {
parts := strings.Split(header, ",")
var tokens []string
for _, part := range parts {
token := strings.SplitN(strings.TrimSpace(part), ";", 2)[0]
if token != "" {
tokens = append(tokens, token)
}
}
return tokens
}