@marcel91200 / caddy Mirror · read-only / commits / 7a630f29
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 13:30 UTC

encode: make zstd checksum configurable (#7586)

* http: make zstd checksum configurable * disable_checksum

Sam Ottenhoff committed Mar 28, 2026 at 15:07 UTC 7a630f29103e0141ab293eb8dd76eb4f38bd4d96
3 files changed +94 -27
caddytest/integration/caddyfile_adapt/encode_options.caddyfiletest
+6 -2
@@ -18,7 +18,9 @@ encode gzip zstd {
18
19 # Long way with a block for each encoding
20 encode {
21 - zstd
21 + zstd {
22 + disable_checksum
23 + }
24 gzip 5
25 }
26
@@ -71,7 +73,9 @@ encode
73 "gzip": {
74 "level": 5
75 },
74 - "zstd": {}
76 + "zstd": {
77 + "checksum": false
78 + }
79 },
80 "handler": "encode",
81 "prefer": [
modules/caddyhttp/encode/caddyfile.go
+4 -1
@@ -41,7 +41,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
41 //
42 // encode [<matcher>] <formats...> {
43 // gzip [<level>]
44 -// zstd
44 +// zstd [<level>] {
45 +// level <level>
46 +// disable_checksum
47 +// }
48 // minimum_length <length>
49 // # response matcher block
50 // match {
modules/caddyhttp/encode/zstd/zstd.go
+84 -24
@@ -33,6 +33,10 @@ type Zstd struct {
33 // The compression level. Accepted values: fastest, better, best, default.
34 Level string `json:"level,omitempty"`
35
36 + // Whether to include the optional 4-byte zstd frame checksum trailer.
37 + // If unset, the upstream zstd library default is preserved.
38 + Checksum *bool `json:"checksum,omitempty"`
39 +
40 // Compression level refer to type constants value from zstd.SpeedFastest to zstd.SpeedBestCompression
41 level zstd.EncoderLevel
42 }
@@ -48,19 +52,48 @@ func (Zstd) CaddyModule() caddy.ModuleInfo {
52 // UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
53 func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
54 d.Next() // consume option name
51 - if !d.NextArg() {
52 - return nil
55 + args := d.RemainingArgs()
56 + switch len(args) {
57 + case 0:
58 + case 1:
59 + if _, err := parseEncoderLevel(args[0]); err != nil {
60 + return d.Err(err.Error())
61 + }
62 + z.Level = args[0]
63 + default:
64 + return d.ArgErr()
65 }
54 - levelStr := d.Val()
55 - if ok, _ := zstd.EncoderLevelFromString(levelStr); !ok {
56 - return d.Errf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
57 - zstd.SpeedFastest,
58 - zstd.SpeedBetterCompression,
59 - zstd.SpeedBestCompression,
60 - zstd.SpeedDefault,
61 - )
66 +
67 + for nesting := d.Nesting(); d.NextBlock(nesting); {
68 + switch d.Val() {
69 + case "level":
70 + args := d.RemainingArgs()
71 + if len(args) != 1 {
72 + return d.ArgErr()
73 + }
74 + if z.Level != "" {
75 + return d.Err("compression level already specified")
76 + }
77 + if _, err := parseEncoderLevel(args[0]); err != nil {
78 + return d.Err(err.Error())
79 + }
80 + z.Level = args[0]
81 +
82 + case "disable_checksum":
83 + if d.NextArg() {
84 + return d.ArgErr()
85 + }
86 + if z.Checksum != nil {
87 + return d.Err("checksum already specified")
88 + }
89 + disabled := false
90 + z.Checksum = &disabled
91 +
92 + default:
93 + return d.Errf("unknown subdirective '%s'", d.Val())
94 + }
95 }
63 - z.Level = levelStr
96 +
97 return nil
98 }
99
@@ -69,15 +102,11 @@ func (z *Zstd) Provision(ctx caddy.Context) error {
102 if z.Level == "" {
103 z.Level = zstd.SpeedDefault.String()
104 }
72 - var ok bool
73 - if ok, z.level = zstd.EncoderLevelFromString(z.Level); !ok {
74 - return fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
75 - zstd.SpeedFastest,
76 - zstd.SpeedDefault,
77 - zstd.SpeedBetterCompression,
78 - zstd.SpeedBestCompression,
79 - )
105 + level, err := parseEncoderLevel(z.Level)
106 + if err != nil {
107 + return err
108 }
109 + z.level = level
110 return nil
111 }
112
@@ -90,14 +119,45 @@ func (z Zstd) NewEncoder() encode.Encoder {
119 // The default of 8MB for the window is
120 // too large for many clients, so we limit
121 // it to 128K to lighten their load.
93 - writer, _ := zstd.NewWriter(
94 - nil,
95 - zstd.WithWindowSize(128<<10),
122 + writer, _ := zstd.NewWriter(nil, z.writerOptions(128<<10)...)
123 + return writer
124 +}
125 +
126 +func (z Zstd) writerOptions(windowSize int) []zstd.EOption {
127 + opts := []zstd.EOption{
128 + zstd.WithWindowSize(windowSize),
129 zstd.WithEncoderConcurrency(1),
130 zstd.WithZeroFrames(true),
98 - zstd.WithEncoderLevel(z.level),
131 + zstd.WithEncoderLevel(z.encoderLevel()),
132 + }
133 + if z.Checksum != nil {
134 + opts = append(opts, zstd.WithEncoderCRC(*z.Checksum))
135 + }
136 + return opts
137 +}
138 +
139 +func (z Zstd) encoderLevel() zstd.EncoderLevel {
140 + if z.level != 0 {
141 + return z.level
142 + }
143 + if z.Level != "" {
144 + if level, err := parseEncoderLevel(z.Level); err == nil {
145 + return level
146 + }
147 + }
148 + return zstd.SpeedDefault
149 +}
150 +
151 +func parseEncoderLevel(level string) (zstd.EncoderLevel, error) {
152 + if ok, encLevel := zstd.EncoderLevelFromString(level); ok {
153 + return encLevel, nil
154 + }
155 + return 0, fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
156 + zstd.SpeedFastest,
157 + zstd.SpeedBetterCompression,
158 + zstd.SpeedBestCompression,
159 + zstd.SpeedDefault,
160 )
100 - return writer
161 }
162
163 // Interface guards