Files
teleport/lib/kube/proxy/compression.go
T
Tiago Silva 6ce42b4ac1 Add minor improvements to lib/kube/proxy (#21885)
This PR resolves the comments pointed out by @jakule during the review
of #21846.
2023-02-16 13:49:20 +00:00

118 lines
3.4 KiB
Go

// Copyright 2023 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"compress/gzip"
"io"
"net/http"
"sync"
"github.com/gravitational/trace"
)
const (
// contentEncodingHeader is the HTTP header used to specify the
// content encoding of the response.
contentEncodingHeader = "Content-Encoding"
// contentEncodingGZIP is the value for the Content-Encoding header when
// the response is compressed with gzip.
contentEncodingGZIP = "gzip"
// defaultGzipContentEncodingLevel is set to 1 which uses least CPU compared to higher levels, yet offers
// similar compression ratios (off by at most 1.5x, but typically within 1.1x-1.3x). For further details see -
// https://github.com/kubernetes/kubernetes/issues/112296
defaultGzipContentEncodingLevel = 1
)
var gzipPool = &sync.Pool{
New: func() any {
gw, err := gzip.NewWriterLevel(nil, defaultGzipContentEncodingLevel)
if err != nil {
// This should never happen.
panic(err)
}
return gw
},
}
type (
// compressionFunc is a function that decompresses data.
decompressionFunc func(dst io.Writer, src io.Reader) error
// compressionFunc is a function that returns a WriteCloser that compresses data
// written to it into the provided io.Writer.
compressionFunc func(dst io.Writer) io.WriteCloser
)
// getResponseCompressorDecompressor returns a compression and decompression function based on the
// Content-Encoding header.
func getResponseCompressorDecompressor(headers http.Header) (compressor compressionFunc, decompressor decompressionFunc, err error) {
encoding := headers.Get(contentEncodingHeader)
switch encoding {
case contentEncodingGZIP:
compressor = func(dst io.Writer) io.WriteCloser {
gzw := gzipPool.Get().(*gzip.Writer)
gzw.Reset(dst)
return &gzipWrapper{gzw}
}
decompressor = func(dst io.Writer, src io.Reader) error {
gzr, err := gzip.NewReader(src)
if err != nil {
return trace.Wrap(err)
}
defer gzr.Close()
_, err = io.Copy(dst, gzr)
return trace.Wrap(err)
}
return
case "":
compressor = func(dst io.Writer) io.WriteCloser {
return &nopCloserWrapper{dst}
}
decompressor = func(dst io.Writer, src io.Reader) error {
_, err := io.Copy(dst, src)
return trace.Wrap(err)
}
return
default:
return nil, nil, trace.BadParameter("unknown encoding %q", encoding)
}
}
// gzipWrapper wraps a gzip.Writer to implement io.WriteCloser.
// When Close is called, the underlying gzip.Writer is returned to the pool.
type gzipWrapper struct {
*gzip.Writer
}
// Close closes the underlying writter and returns it to the pool.
func (w *gzipWrapper) Close() error {
err := w.Writer.Close()
w.Writer.Reset(nil)
gzipPool.Put(w.Writer)
w.Writer = nil
return trace.Wrap(err)
}
// nopCloserWrapper wraps an io.Writer to implement io.WriteCloser.
type nopCloserWrapper struct {
io.Writer
}
// Close has no action on the underlying writer.
func (*nopCloserWrapper) Close() error {
return nil
}