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