-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcontentencoding.go
150 lines (131 loc) · 3.79 KB
/
contentencoding.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Package contentencoding provides net/http compatible middleware for HTTP Content-Encoding.
// It also provides the functionality to customize the decoder.
// By default, br(brotli), gzip and zstd(zstandard) are supported.
package contentencoding
import (
"io/ioutil"
"net/http"
"strings"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/gzip"
"github.com/klauspost/compress/zstd"
)
// Decode returns net/http compatible middleware that automatically decodes body detected by Content-Encoding.
// By default, br(brotli), gzip and zstd(zstandard) are supported.
func Decode(opts ...Option) func(next http.Handler) http.Handler {
cfg := new(config)
for _, opt := range append(defaults(), opts...) {
opt(cfg)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet || r.Method == http.MethodHead {
next.ServeHTTP(w, r)
return
}
values := splitEncodingHeader(r.Header.Get("Content-Encoding"))
for i := len(values) - 1; i >= 0; i-- {
v := values[i]
switch v {
case "br":
decompressBrotli(r)
case "gzip", "x-gzip":
if err := decompressGzip(r); err != nil {
cfg.errHandler(w, r, err)
return
}
case "zstd":
if err := decompressZstd(r, cfg.dopts...); err != nil {
cfg.errHandler(w, r, err)
return
}
case "", "identity":
default:
for _, decoder := range cfg.decoders {
if v == decoder.Encoding {
if err := decoder.Handler(w, r); err != nil {
cfg.errHandler(w, r, err)
return
}
}
}
}
}
next.ServeHTTP(w, r)
})
}
}
func decompressBrotli(r *http.Request) {
r.Body = ioutil.NopCloser(brotli.NewReader(r.Body))
}
func decompressGzip(r *http.Request) error {
gr, err := gzip.NewReader(r.Body)
if err != nil {
return err
}
r.Body = gr
return nil
}
func decompressZstd(r *http.Request, opts ...zstd.DOption) error {
zr, err := zstd.NewReader(r.Body, opts...)
if err != nil {
return err
}
r.Body = ioutil.NopCloser(zr)
return nil
}
var noSpace = strings.NewReplacer(" ", "")
func splitEncodingHeader(raw string) []string {
if raw == "" {
return []string{}
}
return strings.Split(noSpace.Replace(raw), ",")
}
// Option is option for Decode.
type Option func(cfg *config)
type config struct {
errHandler ErrorHandler
decoders []*Decoder
dopts []zstd.DOption
}
// DefaultErrorHandler is ErrorHandler that will used by default.
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
// ErrorHandler is a type used to customize error handling.
type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)
// WithErrorHandler returns a Option to customize error handling.
func WithErrorHandler(eh ErrorHandler) Option {
if eh == nil {
eh = DefaultErrorHandler
}
return func(cfg *config) {
cfg.errHandler = eh
}
}
// WithDOptions returns a Option to customize zstd decoder with zstd.DOptions.
// See https://pkg.go.dev/github.com/klauspost/compress/zstd?tab=doc#DOption.
func WithDOptions(dopts ...zstd.DOption) Option {
return func(cfg *config) {
cfg.dopts = dopts
}
}
// Decoder is custom decoder for user defined Content-Encoding.
// If the Content-Encoding matches Encoding, Handler is called.
type Decoder struct {
// Encoding is a string used for Content-Encoding matching.
Encoding string
// Handler will be called when Encoding matches the Content-Encoding.
Handler func(w http.ResponseWriter, r *http.Request) error
}
// WithDecoder returns a Option to use Decode with Decoder.
func WithDecoder(decoders ...*Decoder) Option {
return func(cfg *config) {
cfg.decoders = decoders
}
}
func defaults() []Option {
return []Option{
WithErrorHandler(nil),
}
}