Skip to content

Commit c527088

Browse files
Add lazy marshaling to handler.go.
1 parent 94424fa commit c527088

File tree

2 files changed

+85
-40
lines changed

2 files changed

+85
-40
lines changed

pkg/handler/handler.go

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
jsoniter "github.com/json-iterator/go"
3434
"github.com/munnerz/goautoneg"
3535
"gopkg.in/yaml.v2"
36+
klog "k8s.io/klog/v2"
3637
"k8s.io/kube-openapi/pkg/builder"
3738
"k8s.io/kube-openapi/pkg/common"
3839
"k8s.io/kube-openapi/pkg/validation/spec"
@@ -55,13 +56,26 @@ type OpenAPIService struct {
5556

5657
lastModified time.Time
5758

58-
specBytes []byte
59-
specPb []byte
60-
specPbGz []byte
59+
jsonCache cache
60+
protoCache cache
61+
}
62+
63+
type cache struct {
64+
BuildCache func() ([]byte, error)
65+
once sync.Once
66+
bytes []byte
67+
etag string
68+
err error
69+
}
6170

62-
specBytesETag string
63-
specPbETag string
64-
specPbGzETag string
71+
func (c *cache) Get() ([]byte, string, error) {
72+
c.once.Do(func() {
73+
c.bytes, c.err = c.BuildCache()
74+
if c.err != nil {
75+
c.etag = computeETag(c.bytes)
76+
}
77+
})
78+
return c.bytes, c.etag, c.err
6579
}
6680

6781
func init() {
@@ -83,50 +97,44 @@ func NewOpenAPIService(spec *spec.Swagger) (*OpenAPIService, error) {
8397
return o, nil
8498
}
8599

86-
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time) {
87-
o.rwMutex.RLock()
88-
defer o.rwMutex.RUnlock()
89-
return o.specBytes, o.specBytesETag, o.lastModified
90-
}
91-
92-
func (o *OpenAPIService) getSwaggerPbBytes() ([]byte, string, time.Time) {
100+
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time, error) {
93101
o.rwMutex.RLock()
94102
defer o.rwMutex.RUnlock()
95-
return o.specPb, o.specPbETag, o.lastModified
103+
specBytes, etag, err := o.jsonCache.Get()
104+
if err != nil {
105+
return nil, "", time.Time{}, err
106+
}
107+
return specBytes, etag, o.lastModified, nil
96108
}
97109

98-
func (o *OpenAPIService) getSwaggerPbGzBytes() ([]byte, string, time.Time) {
110+
func (o *OpenAPIService) getSwaggerPbBytes() ([]byte, string, time.Time, error) {
99111
o.rwMutex.RLock()
100112
defer o.rwMutex.RUnlock()
101-
return o.specPbGz, o.specPbGzETag, o.lastModified
113+
specPb, etag, err := o.protoCache.Get()
114+
if err != nil {
115+
return nil, "", time.Time{}, err
116+
}
117+
return specPb, etag, o.lastModified, nil
102118
}
103119

104120
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
105-
specBytes, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(openapiSpec)
106-
if err != nil {
107-
return err
121+
o.rwMutex.Lock()
122+
defer o.rwMutex.Unlock()
123+
o.jsonCache = cache{
124+
BuildCache: func() ([]byte, error) {
125+
return jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(openapiSpec)
126+
},
108127
}
109-
specPb, err := ToProtoBinary(specBytes)
110-
if err != nil {
111-
return err
128+
o.protoCache = cache{
129+
BuildCache: func() ([]byte, error) {
130+
json, _, err := o.jsonCache.Get()
131+
if err != nil {
132+
return nil, err
133+
}
134+
return ToProtoBinary(json)
135+
},
112136
}
113-
specPbGz := toGzip(specPb)
114-
115-
specBytesETag := computeETag(specBytes)
116-
specPbETag := computeETag(specPb)
117-
specPbGzETag := computeETag(specPbGz)
118-
119137
lastModified := time.Now()
120-
121-
o.rwMutex.Lock()
122-
defer o.rwMutex.Unlock()
123-
124-
o.specBytes = specBytes
125-
o.specPb = specPb
126-
o.specPbGz = specPbGz
127-
o.specBytesETag = specBytesETag
128-
o.specPbETag = specPbETag
129-
o.specPbGzETag = specPbGzETag
130138
o.lastModified = lastModified
131139

132140
return nil
@@ -206,7 +214,7 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
206214
accepted := []struct {
207215
Type string
208216
SubType string
209-
GetDataAndETag func() ([]byte, string, time.Time)
217+
GetDataAndETag func() ([]byte, string, time.Time, error)
210218
}{
211219
{"application", "json", o.getSwaggerBytes},
212220
{"application", "[email protected]+protobuf", o.getSwaggerPbBytes},
@@ -230,7 +238,12 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
230238
}
231239

232240
// serve the first matching media type in the sorted clause list
233-
data, etag, lastModified := accepts.GetDataAndETag()
241+
data, etag, lastModified, err := accepts.GetDataAndETag()
242+
if err != nil {
243+
klog.Errorf("Error in OpenAPI handler: %s", err)
244+
w.WriteHeader(http.StatusInternalServerError)
245+
return
246+
}
234247
w.Header().Set("Etag", etag)
235248
// ServeContent will take care of caching using eTag.
236249
http.ServeContent(w, r, servePath, lastModified, bytes.NewReader(data))

pkg/handler/handler_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handler
22

33
import (
4+
"errors"
45
"io/ioutil"
56
"math"
67
"net/http"
@@ -177,3 +178,34 @@ func TestToProtoBinary(t *testing.T) {
177178
}
178179
// TODO: add some kind of roundtrip test here
179180
}
181+
182+
func TestCache(t *testing.T) {
183+
calledCount := 0
184+
expectedBytes := []byte("ABC")
185+
cacheObj := cache{
186+
BuildCache: func() ([]byte, error) {
187+
calledCount++
188+
return expectedBytes, nil
189+
},
190+
}
191+
bytes, _, _ := cacheObj.Get()
192+
if string(bytes) != string(expectedBytes) {
193+
t.Fatalf("got value of %q from cache (expected %q)", bytes, expectedBytes)
194+
}
195+
cacheObj.Get()
196+
if calledCount != 1 {
197+
t.Fatalf("expected BuildCache to be called once (called %d times)", calledCount)
198+
}
199+
}
200+
201+
func TestCacheError(t *testing.T) {
202+
cacheObj := cache{
203+
BuildCache: func() ([]byte, error) {
204+
return nil, errors.New("cache error")
205+
},
206+
}
207+
_, _, err := cacheObj.Get()
208+
if err == nil {
209+
t.Fatalf("expected non-nil err from cache.Get()")
210+
}
211+
}

0 commit comments

Comments
 (0)