@@ -55,7 +55,25 @@ const (
5555 processStartTimeHeader = "Process-Start-Time-Unix"
5656)
5757
58- var defaultEncodingOffers = []string {"gzip" , "zstd" }
58+ type Compression int
59+
60+ const (
61+ Identity Compression = iota
62+ Gzip
63+ Zstd
64+ )
65+
66+ var compressions = [... ]string {
67+ "identity" ,
68+ "gzip" ,
69+ "zstd" ,
70+ }
71+
72+ func (c Compression ) String () string {
73+ return compressions [c ]
74+ }
75+
76+ var defaultCompressionFormats = []Compression {Identity , Gzip , Zstd }
5977
6078var gzipPool = sync.Pool {
6179 New : func () interface {} {
@@ -168,46 +186,13 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
168186 } else {
169187 contentType = expfmt .Negotiate (req .Header )
170188 }
171- header := rsp .Header ()
172- header .Set (contentTypeHeader , string (contentType ))
173-
174- w := io .Writer (rsp )
175- if ! opts .DisableCompression {
176- offers := defaultEncodingOffers
177- if len (opts .EncodingOffers ) > 0 {
178- offers = opts .EncodingOffers
179- }
180- // TODO(mrueg): Replace internal/github.com/gddo once https://github.com/golang/go/issues/19307 is implemented.
181- compression := httputil .NegotiateContentEncoding (req , offers )
182- switch compression {
183- case "zstd" :
184- header .Set (contentEncodingHeader , "zstd" )
185- // TODO(mrueg): Replace klauspost/compress with stdlib implementation once https://github.com/golang/go/issues/62513 is implemented.
186- z , err := zstd .NewWriter (rsp , zstd .WithEncoderLevel (zstd .SpeedFastest ))
187- if err != nil {
188- return
189- }
190-
191- z .Reset (w )
192- defer z .Close ()
189+ rsp .Header ().Set (contentTypeHeader , string (contentType ))
193190
194- w = z
195- case "gzip" :
196- header .Set (contentEncodingHeader , "gzip" )
197- gz := gzipPool .Get ().(* gzip.Writer )
198- defer gzipPool .Put (gz )
199-
200- gz .Reset (w )
201- defer gz .Close ()
202-
203- w = gz
204- case "identity" :
205- // This means the content is not encoded.
206- default :
207- // The content encoding was not implemented yet.
208- return
191+ w , err := GetWriter (req , rsp , opts .DisableCompression , opts .OfferedCompressions )
192+ if err != nil {
193+ if opts .ErrorLog != nil {
194+ opts .ErrorLog .Println ("error getting writer" , err )
209195 }
210-
211196 }
212197
213198 enc := expfmt .NewEncoder (w , contentType )
@@ -373,12 +358,19 @@ type HandlerOpts struct {
373358 // no effect on the HTTP status code because ErrorHandling is set to
374359 // ContinueOnError.
375360 Registry prometheus.Registerer
376- // If DisableCompression is true, the handler will never compress the
377- // response, even if requested by the client.
361+ // DisableCompression disables the response encoding (compression) and
362+ // encoding negotiation. If true, the handler will
363+ // never compress the response, even if requested
364+ // by the client and the OfferedCompressions field is ignored.
378365 DisableCompression bool
379- // If DisableCompression is false, this option will allow to define the
380- // set of offered encoding algorithms.
381- EncodingOffers []string
366+ // OfferedCompressions is a set of encodings (compressions) handler will
367+ // try to offer when negotiating with the client. This defaults to zstd,
368+ // gzip and identity.
369+ // NOTE: If handler can't agree on the encodings with the client or
370+ // caller using unsupported or empty encodings in OfferedCompressions,
371+ // handler always fallbacks to no compression (identity), for
372+ // compatibility reasons. In such cases ErrorLog will be used if set.
373+ OfferedCompressions []Compression
382374 // The number of concurrent HTTP requests is limited to
383375 // MaxRequestsInFlight. Additional requests are responded to with 503
384376 // Service Unavailable and a suitable message in the body. If
@@ -426,3 +418,48 @@ func httpError(rsp http.ResponseWriter, err error) {
426418 http .StatusInternalServerError ,
427419 )
428420}
421+
422+ func GetWriter (r * http.Request , rsp http.ResponseWriter , disableCompression bool , offeredCompressions []Compression ) (io.Writer , error ) {
423+ w := io .Writer (rsp )
424+ if ! disableCompression {
425+ offers := defaultCompressionFormats
426+ if len (offeredCompressions ) > 0 {
427+ offers = offeredCompressions
428+ }
429+ var compressions []string
430+ for _ , comp := range offers {
431+ compressions = append (compressions , comp .String ())
432+ }
433+ // TODO(mrueg): Replace internal/github.com/gddo once https://github.com/golang/go/issues/19307 is implemented.
434+ compression := httputil .NegotiateContentEncoding (r , compressions )
435+ switch compression {
436+ case "zstd" :
437+ rsp .Header ().Set (contentEncodingHeader , "zstd" )
438+ // TODO(mrueg): Replace klauspost/compress with stdlib implementation once https://github.com/golang/go/issues/62513 is implemented.
439+ z , err := zstd .NewWriter (rsp , zstd .WithEncoderLevel (zstd .SpeedFastest ))
440+ if err != nil {
441+ return nil , err
442+ }
443+
444+ z .Reset (w )
445+ defer z .Close ()
446+
447+ w = z
448+ case "gzip" :
449+ rsp .Header ().Set (contentEncodingHeader , "gzip" )
450+ gz := gzipPool .Get ().(* gzip.Writer )
451+ defer gzipPool .Put (gz )
452+
453+ gz .Reset (w )
454+ defer gz .Close ()
455+
456+ w = gz
457+ case "identity" :
458+ // This means the content is not compressed.
459+ default :
460+ // The content encoding was not implemented yet.
461+ return w , fmt .Errorf ("content compression format not recognized: %s. Valid formats are: %s" , compression , defaultCompressionFormats )
462+ }
463+ }
464+ return w , nil
465+ }
0 commit comments