mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
This PR resolves the comments pointed out by @jakule during the review of #21846.
118 lines
3.4 KiB
Go
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
|
|
}
|