Skip to content
This repository was archived by the owner on Oct 3, 2022. It is now read-only.

Commit 3e8cde2

Browse files
committed
fix http writes without deflate and clean up APIs
1 parent 84e4086 commit 3e8cde2

File tree

8 files changed

+88
-113
lines changed

8 files changed

+88
-113
lines changed

cache.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ func (c *Cache) NewFrontend(get Getter) *Frontend {
9393

9494
// Get or create a new record in the cache.
9595
// fresh=true, if record is freshly created and requires population.
96-
func (c *Cache) getRecord(loc recordLocation) (rec *record, fresh bool) {
96+
func (c *Cache) getRecord(loc recordLocation) (rec *Record, fresh bool) {
9797
c.mu.Lock()
9898
defer c.mu.Unlock()
9999

100100
recWithMeta, ok := c.record(loc)
101101
if !ok {
102102
recWithMeta = recordWithMeta{
103103
node: c.lruList.Prepend(loc),
104-
rec: new(record),
104+
rec: new(Record),
105105
}
106106
recWithMeta.rec.semaphore.Init() // Block all reads until population
107107
} else {
@@ -148,7 +148,7 @@ func (c *Cache) record(loc recordLocation) (recordWithMeta, bool) {
148148
}
149149

150150
// Set record used memory
151-
func (c *Cache) setUsedMemory(src *record, loc recordLocation, memoryUsed int) {
151+
func (c *Cache) setUsedMemory(src *Record, loc recordLocation, memoryUsed int) {
152152
c.mu.Lock()
153153
defer c.mu.Unlock()
154154

components.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package recache
22

33
import (
4+
"bytes"
5+
"compress/flate"
46
"crypto/sha1"
57
"io"
68
)
@@ -12,6 +14,7 @@ type component interface {
1214
Size() int
1315
Hash() [sha1.Size]byte
1416
GetFrameDescriptor() frameDescriptor
17+
Decompress() io.Reader
1518
}
1619

1720
// Common part of both buffer and reference components
@@ -36,9 +39,7 @@ func (b buffer) WriteTo(w io.Writer) (int64, error) {
3639
}
3740

3841
func (b buffer) NewReader() io.Reader {
39-
return &bufferReader{
40-
buffer: b,
41-
}
42+
return bytes.NewReader(b.data)
4243
}
4344

4445
func (b buffer) Size() int {
@@ -49,28 +50,15 @@ func (b buffer) GetFrameDescriptor() frameDescriptor {
4950
return b.frameDescriptor
5051
}
5152

52-
// Adapter for reading data from component w/o mutating it
53-
type bufferReader struct {
54-
off int
55-
buffer
56-
}
57-
58-
func (r *bufferReader) Read(p []byte) (n int, err error) {
59-
if len(p) == 0 {
60-
return
61-
}
62-
if r.off >= len(r.data) {
63-
return 0, io.EOF
64-
}
65-
n = copy(p, r.data[r.off:])
66-
r.off += n
67-
return
53+
// Read component as decompressed stream
54+
func (b buffer) Decompress() io.Reader {
55+
return flate.NewReader(b.NewReader())
6856
}
6957

7058
// Reference to another record
7159
type recordReference struct {
7260
componentCommon
73-
*record
61+
*Record
7462
}
7563

7664
func (r recordReference) Size() int {

frontend.go

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

33
import (
4-
"compress/flate"
54
"crypto/sha1"
65
"encoding/base64"
76
"encoding/binary"
@@ -26,36 +25,6 @@ type Key interface{}
2625
// Getter must be thread-safe.
2726
type Getter func(Key, *RecordWriter) error
2827

29-
// Readable stream with support for io.WriterTo and conversion to
30-
// io.Reader interfaces
31-
type Streamer interface {
32-
// Can be called safely from multiple goroutines
33-
io.WriterTo
34-
35-
// Create a new io.Reader for this stream.
36-
// Multiple instances of such an io.Reader can exist and be read
37-
// concurrently.
38-
NewReader() io.Reader
39-
40-
// Convenience method for efficiently decoding stream contents as JSON into
41-
// the destination variable.
42-
//
43-
// dst: pointer to destination variable
44-
DecodeJSON(dst interface{}) error
45-
46-
// Create a new io.ReadCloser for the Decompressped content of this stream.
47-
//
48-
// It is the caller's responsibility to call Close on the io.ReadCloser
49-
// when finished reading.
50-
Decompress() io.Reader
51-
52-
// Return SHA1 hash of the content
53-
SHA1() [sha1.Size]byte
54-
55-
// Return strong etag of content
56-
ETag() string
57-
}
58-
5928
// A frontend for accessing the cache contents
6029
type Frontend struct {
6130
id int
@@ -64,7 +33,7 @@ type Frontend struct {
6433
}
6534

6635
// Populates a record using the registered Getter
67-
func (f *Frontend) populate(k Key, rec *record) (err error) {
36+
func (f *Frontend) populate(k Key, rec *Record) (err error) {
6837
rw := RecordWriter{
6938
cache: f.cache.id,
7039
frontend: f.id,
@@ -125,7 +94,7 @@ func (f *Frontend) populate(k Key, rec *record) (err error) {
12594
}
12695

12796
// Get a record by key and block until it has been generated
128-
func (f *Frontend) getGeneratedRecord(k Key) (rec *record, err error) {
97+
func (f *Frontend) getGeneratedRecord(k Key) (rec *Record, err error) {
12998
loc := recordLocation{f.id, k}
13099
rec, fresh := f.cache.getRecord(loc)
131100
if fresh {
@@ -151,19 +120,15 @@ func (f *Frontend) getGeneratedRecord(k Key) (rec *record, err error) {
151120
return
152121
}
153122

154-
// Retrieve or generate data by key and return a consumable result Stream
155-
func (f *Frontend) Get(k Key) (s Streamer, err error) {
156-
rec, err := f.getGeneratedRecord(k)
157-
if err != nil {
158-
return
159-
}
160-
s = recordDecoder{rec}
161-
return
123+
// Retrieve or generate data by key and return cache Record
124+
func (f *Frontend) Get(k Key) (*Record, error) {
125+
return f.getGeneratedRecord(k)
162126
}
163127

164128
// Retrieve or generate data by key and write it to w.
165129
// Writes ETag to w and returns 304 on ETag match without writing data.
166-
// Sets "Content-Encoding" header to "deflate".
130+
// Sets "Content-Encoding" header to "deflate", if client support deflate
131+
// compressions
167132
func (f *Frontend) WriteHTTP(k Key, w http.ResponseWriter, r *http.Request,
168133
) (n int64, err error) {
169134
rec, err := f.getGeneratedRecord(k)
@@ -180,14 +145,14 @@ func (f *Frontend) WriteHTTP(k Key, w http.ResponseWriter, r *http.Request,
180145
if !supportsDeflate {
181146
// Different eTag to maintain strong eTag byte-equivalence guarantee by
182147
// differing it from the compressed eTag.
183-
eTag = eTag[:len(eTag)-2] + `-uc"`
148+
eTag = rec.ETagDecompressed()
184149
}
185150
if r.Header.Get("If-None-Match") == eTag {
186151
w.WriteHeader(304)
187152
return
188153
}
189154
h := w.Header()
190-
h.Set("ETag", rec.eTag)
155+
h.Set("ETag", eTag)
191156

192157
if supportsDeflate {
193158
// If client accepts deflate compression use efficient deflate stream
@@ -242,15 +207,9 @@ func (f *Frontend) WriteHTTP(k Key, w http.ResponseWriter, r *http.Request,
242207
}
243208
n += 6
244209
} else {
245-
// Streaming decompression for clients that don't accept deflate
210+
// Streaming decompression for clients that don't support deflate
246211
// compression
247-
248-
dr := flate.NewReader(rec.NewReader())
249-
n, err = io.Copy(w, dr)
250-
if err != nil {
251-
return
252-
}
253-
err = dr.Close()
212+
n, err = io.Copy(w, rec.Decompress())
254213
}
255214

256215
return

frontend_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func TestWriteHTTP(t *testing.T) {
173173
for i := 0; i < 3; i++ {
174174
c := cases[i]
175175
c.name += " no deflate"
176-
c.useDeflate = true
176+
c.useDeflate = false
177177
cases = append(cases, c)
178178
}
179179

@@ -204,7 +204,13 @@ func TestWriteHTTP(t *testing.T) {
204204
if err != nil {
205205
t.Fatal(err)
206206
}
207-
assertEquals(t, s.ETag(), etag)
207+
var stdETag string
208+
if c.useDeflate {
209+
stdETag = s.ETag()
210+
} else {
211+
stdETag = s.ETagDecompressed()
212+
}
213+
assertEquals(t, stdETag, etag)
208214

209215
body := rec.Body
210216
if c.useDeflate {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/bakape/recache/v5
1+
module github.com/bakape/recache/v6
22

33
go 1.13

record.go

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ type recordWithMeta struct {
4141
// lock on the cache mutex held.
4242
//
4343
// Record must be a pointer, because it contains mutexes.
44-
rec *record
44+
rec *Record
4545
}
4646

4747
// Data storage unit in the cache. Linked to a single Key on a Frontend.
48-
type record struct {
48+
type Record struct {
4949
semaphore semaphore
5050

5151
// Contained data and metainformation
@@ -68,7 +68,8 @@ type componentNode struct {
6868
next *componentNode
6969
}
7070

71-
func (r *record) WriteTo(w io.Writer) (n int64, err error) {
71+
// Implements io.WrWriteTo
72+
func (r *Record) WriteTo(w io.Writer) (n int64, err error) {
7273
for c, m := &r.data, int64(0); c != nil; c = c.next {
7374
m, err = c.WriteTo(w)
7475
if err != nil {
@@ -79,19 +80,51 @@ func (r *record) WriteTo(w io.Writer) (n int64, err error) {
7980
return
8081
}
8182

82-
func (r *record) NewReader() io.Reader {
83+
// Create a new io.Reader for this stream.
84+
// Multiple instances of such an io.Reader can exist and be read
85+
// concurrently.
86+
func (r *Record) NewReader() io.Reader {
8387
return &recordReader{
84-
next: r.data.next,
8588
current: r.data.NewReader(),
86-
record: r,
89+
next: r.data.next,
8790
}
8891
}
8992

93+
// Create a new io.ReadCloser for the Decompressped content of this stream.
94+
//
95+
// It is the caller's responsibility to call Close on the io.ReadCloser
96+
// when finished reading.
97+
func (r *Record) Decompress() io.Reader {
98+
return eofCaster{flate.NewReader(r.NewReader())}
99+
}
100+
101+
// Convenience method for efficiently decoding stream contents as JSON into
102+
// the destination variable.
103+
//
104+
// dst: pointer to destination variable
105+
func (r *Record) DecodeJSON(dst interface{}) (err error) {
106+
return json.NewDecoder(r.Decompress()).Decode(dst)
107+
}
108+
109+
// Return SHA1 hash of the content
110+
func (r *Record) SHA1() [sha1.Size]byte {
111+
return r.hash
112+
}
113+
114+
// Return strong ETag of content, if served as a compressed stream
115+
func (r *Record) ETag() string {
116+
return r.eTag
117+
}
118+
119+
// Return strong ETag of content, if served as a decompressed stream
120+
func (r *Record) ETagDecompressed() string {
121+
return r.eTag[:len(r.eTag)-1] + `-uc"`
122+
}
123+
90124
// Adapter for reading data from record w/o mutating it
91125
type recordReader struct {
92126
current io.Reader
93127
next *componentNode
94-
*record
95128
}
96129

97130
func (r *recordReader) Read(p []byte) (n int, err error) {
@@ -116,23 +149,16 @@ func (r *recordReader) Read(p []byte) (n int, err error) {
116149
return
117150
}
118151

119-
// Adapter, that enables decoding the record as JSON
120-
type recordDecoder struct {
121-
*record
122-
}
123-
124-
func (r recordDecoder) DecodeJSON(dst interface{}) (err error) {
125-
return json.NewDecoder(r.Decompress()).Decode(dst)
126-
}
127-
128-
func (r recordDecoder) Decompress() io.Reader {
129-
return flate.NewReader(r.NewReader())
130-
}
131-
132-
func (r recordDecoder) SHA1() [sha1.Size]byte {
133-
return r.record.hash
152+
// Suppresses unexpected EOF errors resulting as a consequence of flate using
153+
// bufio
154+
type eofCaster struct {
155+
io.Reader
134156
}
135157

136-
func (r recordDecoder) ETag() string {
137-
return r.eTag
158+
func (e eofCaster) Read(p []byte) (n int, err error) {
159+
n, err = e.Reader.Read(p)
160+
if err == io.ErrUnexpectedEOF {
161+
err = io.EOF
162+
}
163+
return
138164
}

utils_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func assertEquals(t *testing.T, res, std interface{}) {
2121
}
2222
}
2323

24-
func decodeJSON(t *testing.T, src Streamer, dst interface{}) {
24+
func decodeJSON(t *testing.T, src *Record, dst interface{}) {
2525
t.Helper()
2626

2727
err := src.DecodeJSON(&dst)
@@ -30,7 +30,7 @@ func decodeJSON(t *testing.T, src Streamer, dst interface{}) {
3030
}
3131
}
3232

33-
func assertJsonStringEquals(t *testing.T, src Streamer, std string) {
33+
func assertJsonStringEquals(t *testing.T, src *Record, std string) {
3434
t.Helper()
3535

3636
var res string

0 commit comments

Comments
 (0)