mirror of
https://github.com/rancher/steve.git
synced 2025-09-22 03:47:26 +00:00
170 lines
3.8 KiB
Go
170 lines
3.8 KiB
Go
package db
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/gob"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
func init() {
|
|
// necessary in order to gob/ungob unstructured.Unstructured objects
|
|
gob.Register(map[string]any{})
|
|
gob.Register([]any{})
|
|
}
|
|
|
|
type encoding interface {
|
|
// Encode serializes an object into the provided writer
|
|
Encode(io.Writer, any) error
|
|
// Decode reads from a provided reader and deserializes into an object
|
|
Decode(io.Reader, any) error
|
|
}
|
|
|
|
type gobEncoding struct {
|
|
writeLock, readLock sync.Mutex
|
|
writeBuf, readBuf bytes.Buffer
|
|
encoder *gob.Encoder
|
|
decoder *gob.Decoder
|
|
seenTypes map[reflect.Type]struct{}
|
|
}
|
|
|
|
func (g *gobEncoding) Encode(w io.Writer, obj any) error {
|
|
g.writeLock.Lock()
|
|
defer g.writeLock.Unlock()
|
|
|
|
if g.encoder == nil {
|
|
g.encoder = gob.NewEncoder(&g.writeBuf)
|
|
}
|
|
|
|
g.writeBuf.Reset()
|
|
if err := g.encoder.Encode(obj); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := g.registerTypeIfNeeded(obj); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := g.writeBuf.WriteTo(w)
|
|
return err
|
|
}
|
|
|
|
// registerTypeIfNeeded prevents future decoding errors by running Decode right after the first Encode for an object type
|
|
// This is needed when reusing a gob.Encoder, as it assumes the receiving end of those messages is always the same gob.Decoder.
|
|
// Due to this assumption, it applies some optimizations, like avoiding sending complete type's information (needed for decoding) if it already did it before.
|
|
// This means the first object for each type encoded by a gob.Encoder will have a bigger payload, but more importantly,
|
|
// the decoding will fail if this is not the first object being decoded later. This function forces that to prevent this from happening.
|
|
func (g *gobEncoding) registerTypeIfNeeded(obj any) error {
|
|
if g.seenTypes == nil {
|
|
g.seenTypes = make(map[reflect.Type]struct{})
|
|
}
|
|
|
|
typ := reflect.TypeOf(obj)
|
|
if _, ok := g.seenTypes[typ]; !ok {
|
|
g.seenTypes[typ] = struct{}{}
|
|
// Consume the current write buffer and re-generate it (will produce a smaller version)
|
|
newObj := reflect.New(typ).Interface()
|
|
if err := g.Decode(bytes.NewReader(g.writeBuf.Bytes()), newObj); err != nil {
|
|
return fmt.Errorf("could not decode %T: %w", obj, err)
|
|
}
|
|
|
|
g.writeBuf.Reset()
|
|
return g.encoder.Encode(obj)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *gobEncoding) Decode(r io.Reader, into any) error {
|
|
g.readLock.Lock()
|
|
defer g.readLock.Unlock()
|
|
|
|
if g.decoder == nil {
|
|
g.decoder = gob.NewDecoder(&g.readBuf)
|
|
}
|
|
|
|
g.readBuf.Reset()
|
|
if _, err := g.readBuf.ReadFrom(r); err != nil {
|
|
return err
|
|
}
|
|
return g.decoder.Decode(into)
|
|
}
|
|
|
|
type jsonEncoding struct {
|
|
indentLevel int
|
|
}
|
|
|
|
func (j jsonEncoding) Encode(w io.Writer, obj any) error {
|
|
enc := json.NewEncoder(w)
|
|
if j.indentLevel > 0 {
|
|
enc.SetIndent("", strings.Repeat(" ", j.indentLevel))
|
|
}
|
|
|
|
if err := enc.Encode(obj); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (j jsonEncoding) Decode(r io.Reader, into any) error {
|
|
return json.NewDecoder(r).Decode(into)
|
|
}
|
|
|
|
type gzipEncoding struct {
|
|
encoding
|
|
writers sync.Pool
|
|
readers sync.Pool
|
|
}
|
|
|
|
func gzipped(wrapped encoding) *gzipEncoding {
|
|
gz := gzipEncoding{
|
|
encoding: wrapped,
|
|
}
|
|
return &gz
|
|
}
|
|
|
|
func (gz *gzipEncoding) Encode(w io.Writer, obj any) error {
|
|
gzw, ok := gz.writers.Get().(*gzip.Writer)
|
|
if !ok {
|
|
gzw = gzip.NewWriter(io.Discard)
|
|
}
|
|
gzw.Reset(w)
|
|
defer func() {
|
|
gzw.Reset(nil)
|
|
gz.writers.Put(gzw)
|
|
}()
|
|
|
|
if err := gz.encoding.Encode(gzw, obj); err != nil {
|
|
return err
|
|
}
|
|
return gzw.Close()
|
|
}
|
|
|
|
func (gz *gzipEncoding) Decode(r io.Reader, into any) error {
|
|
gzr, ok := gz.readers.Get().(*gzip.Reader)
|
|
if ok {
|
|
if err := gzr.Reset(r); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
var err error
|
|
gzr, err = gzip.NewReader(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
defer func() {
|
|
gzr.Close()
|
|
gz.readers.Put(gzr)
|
|
}()
|
|
|
|
if err := gz.encoding.Decode(gzr, into); err != nil {
|
|
return err
|
|
}
|
|
return gzr.Close()
|
|
}
|