Skip to content

Commit b0dc455

Browse files
committed
Change default and add API for compression level
Change the default compression level to 1. This level is faster and uses less memory. Add Conn.SetCompressionLevel API to allow applications to tune compression on a per message basis.
1 parent bb547c6 commit b0dc455

File tree

4 files changed

+59
-15
lines changed

4 files changed

+59
-15
lines changed

compression.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import (
1212
"sync"
1313
)
1414

15+
const (
16+
minCompressionLevel = flate.HuffmanOnly
17+
maxCompressionLevel = flate.BestCompression
18+
defaultCompressionLevel = 1
19+
)
20+
1521
var (
16-
flateWriterPool = sync.Pool{New: func() interface{} {
17-
fw, _ := flate.NewWriter(nil, 3)
18-
return fw
19-
}}
20-
flateReaderPool = sync.Pool{New: func() interface{} {
22+
flateWriterPools [maxCompressionLevel - minCompressionLevel]sync.Pool
23+
flateReaderPool = sync.Pool{New: func() interface{} {
2124
return flate.NewReader(nil)
2225
}}
2326
)
@@ -34,11 +37,20 @@ func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
3437
return &flateReadWrapper{fr}
3538
}
3639

37-
func compressNoContextTakeover(w io.WriteCloser) io.WriteCloser {
40+
func isValidCompressionLevel(level int) bool {
41+
return minCompressionLevel <= level && level <= maxCompressionLevel
42+
}
43+
44+
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
45+
p := &flateWriterPools[level-minCompressionLevel]
3846
tw := &truncWriter{w: w}
39-
fw, _ := flateWriterPool.Get().(*flate.Writer)
40-
fw.Reset(tw)
41-
return &flateWriteWrapper{fw: fw, tw: tw}
47+
fw, _ := p.Get().(*flate.Writer)
48+
if fw == nil {
49+
fw, _ = flate.NewWriter(tw, level)
50+
} else {
51+
fw.Reset(tw)
52+
}
53+
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
4254
}
4355

4456
// truncWriter is an io.Writer that writes all but the last four bytes of the
@@ -80,6 +92,7 @@ func (w *truncWriter) Write(p []byte) (int, error) {
8092
type flateWriteWrapper struct {
8193
fw *flate.Writer
8294
tw *truncWriter
95+
p *sync.Pool
8396
}
8497

8598
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
@@ -94,7 +107,7 @@ func (w *flateWriteWrapper) Close() error {
94107
return errWriteClosed
95108
}
96109
err1 := w.fw.Flush()
97-
flateWriterPool.Put(w.fw)
110+
w.p.Put(w.fw)
98111
w.fw = nil
99112
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
100113
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")

compression_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,17 @@ func BenchmarkWriteWithCompression(b *testing.B) {
6464
}
6565
b.ReportAllocs()
6666
}
67+
68+
func TestValidCompressionLevel(t *testing.T) {
69+
c := newConn(fakeNetConn{}, false, 1024, 1024)
70+
for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} {
71+
if err := c.SetCompressionLevel(level); err == nil {
72+
t.Errorf("no error for level %d", level)
73+
}
74+
}
75+
for _, level := range []int{minCompressionLevel, maxCompressionLevel} {
76+
if err := c.SetCompressionLevel(level); err != nil {
77+
t.Errorf("error for level %d", level)
78+
}
79+
}
80+
}

conn.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ type Conn struct {
241241
writeErr error
242242

243243
enableWriteCompression bool
244-
newCompressionWriter func(io.WriteCloser) io.WriteCloser
244+
compressionLevel int
245+
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
245246

246247
// Read fields
247248
reader io.ReadCloser // the current reader returned to the application
@@ -285,6 +286,7 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int)
285286
readFinal: true,
286287
writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
287288
enableWriteCompression: true,
289+
compressionLevel: defaultCompressionLevel,
288290
}
289291
c.SetCloseHandler(nil)
290292
c.SetPingHandler(nil)
@@ -450,7 +452,7 @@ func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
450452
}
451453
c.writer = mw
452454
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
453-
w := c.newCompressionWriter(c.writer)
455+
w := c.newCompressionWriter(c.writer, c.compressionLevel)
454456
mw.compress = true
455457
c.writer = w
456458
}
@@ -1061,6 +1063,20 @@ func (c *Conn) EnableWriteCompression(enable bool) {
10611063
c.enableWriteCompression = enable
10621064
}
10631065

1066+
// SetCompressionLevel sets the flate compression level for subsequent text and
1067+
// binary messages. This function is a noop if compression was not negotiated
1068+
// with the peer. Valid levels range from -2 to 9. Level -1 uses the default
1069+
// compression level. Level -2 uses Huffman compression only, Level 0 does not
1070+
// attempt any compression. Levels 1 through 9 range from best speed to best
1071+
// compression.
1072+
func (c *Conn) SetCompressionLevel(level int) error {
1073+
if !isValidCompressionLevel(level) {
1074+
return errors.New("websocket: invalid compression level")
1075+
}
1076+
c.compressionLevel = level
1077+
return nil
1078+
}
1079+
10641080
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
10651081
func FormatCloseMessage(closeCode int, text string) []byte {
10661082
buf := make([]byte, 2+len(text))

doc.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@
118118
//
119119
// Applications are responsible for ensuring that no more than one goroutine
120120
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
121-
// WriteJSON) concurrently and that no more than one goroutine calls the read
122-
// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler,
123-
// SetPingHandler) concurrently.
121+
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
122+
// that no more than one goroutine calls the read methods (NextReader,
123+
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
124+
// concurrently.
124125
//
125126
// The Close and WriteControl methods can be called concurrently with all other
126127
// methods.

0 commit comments

Comments
 (0)