Skip to content

Commit bfe9b3c

Browse files
committed
Disable compression by default
Closes #220 and #230
1 parent b453d3e commit bfe9b3c

10 files changed

+65
-36
lines changed

Diff for: README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ go get nhooyr.io/websocket
1616
- Minimal and idiomatic API
1717
- First class [context.Context](https://blog.golang.org/context) support
1818
- Fully passes the WebSocket [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
19-
- [Single dependency](https://pkg.go.dev/nhooyr.io/websocket?tab=imports)
19+
- [Zero dependencies](https://pkg.go.dev/nhooyr.io/websocket?tab=imports)
2020
- JSON and protobuf helpers in the [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb) subpackages
2121
- Zero alloc reads and writes
2222
- Concurrent writes
@@ -112,7 +112,6 @@ Advantages of nhooyr.io/websocket:
112112
- Gorilla's implementation is slower and uses [unsafe](https://golang.org/pkg/unsafe/).
113113
- Full [permessage-deflate](https://tools.ietf.org/html/rfc7692) compression extension support
114114
- Gorilla only supports no context takeover mode
115-
- We use [klauspost/compress](https://github.com/klauspost/compress) for much lower memory usage ([gorilla/websocket#203](https://github.com/gorilla/websocket/issues/203))
116115
- [CloseRead](https://pkg.go.dev/nhooyr.io/websocket#Conn.CloseRead) helper ([gorilla/websocket#492](https://github.com/gorilla/websocket/issues/492))
117116
- Actively maintained ([gorilla/websocket#370](https://github.com/gorilla/websocket/issues/370))
118117

Diff for: accept.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type AcceptOptions struct {
5151
OriginPatterns []string
5252

5353
// CompressionMode controls the compression mode.
54-
// Defaults to CompressionNoContextTakeover.
54+
// Defaults to CompressionDisabled.
5555
//
5656
// See docs on CompressionMode for details.
5757
CompressionMode CompressionMode

Diff for: accept_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ func TestAccept(t *testing.T) {
5555
r.Header.Set("Sec-WebSocket-Key", "meow123")
5656
r.Header.Set("Sec-WebSocket-Extensions", "permessage-deflate; harharhar")
5757

58-
_, err := Accept(w, r, nil)
58+
_, err := Accept(w, r, &AcceptOptions{
59+
CompressionMode: CompressionNoContextTakeover,
60+
})
5961
assert.Contains(t, err, `unsupported permessage-deflate parameter`)
6062
})
6163

Diff for: autobahn_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ func TestAutobahn(t *testing.T) {
6161
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
6262
defer cancel()
6363

64-
c, _, err := websocket.Dial(ctx, fmt.Sprintf(wstestURL+"/runCase?case=%v&agent=main", i), nil)
64+
c, _, err := websocket.Dial(ctx, fmt.Sprintf(wstestURL+"/runCase?case=%v&agent=main", i), &websocket.DialOptions{
65+
CompressionMode: websocket.CompressionContextTakeover,
66+
})
6567
assert.Success(t, err)
6668
err = wstest.EchoLoop(ctx, c)
6769
t.Logf("echoLoop: %v", err)

Diff for: compress.go

+28-17
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
package websocket
44

55
import (
6+
"compress/flate"
67
"io"
78
"net/http"
89
"sync"
9-
10-
"github.com/klauspost/compress/flate"
1110
)
1211

1312
// CompressionMode represents the modes available to the deflate extension.
1413
// See https://tools.ietf.org/html/rfc7692
15-
//
16-
// A compatibility layer is implemented for the older deflate-frame extension used
17-
// by safari. See https://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06
18-
// It will work the same in every way except that we cannot signal to the peer we
19-
// want to use no context takeover on our side, we can only signal that they should.
20-
// It is however currently disabled due to Safari bugs. See https://github.com/nhooyr/websocket/issues/218
2114
type CompressionMode int
2215

2316
const (
17+
// CompressionDisabled disables the deflate extension.
18+
//
19+
// Use this if you are using a predominantly binary protocol with very
20+
// little duplication in between messages or CPU and memory are more
21+
// important than bandwidth.
22+
//
23+
// This is the default.
24+
CompressionDisabled CompressionMode = iota
25+
2426
// CompressionNoContextTakeover grabs a new flate.Reader and flate.Writer as needed
2527
// for every message. This applies to both server and client side.
2628
//
@@ -29,23 +31,16 @@ const (
2931
// are long lived and seldom used.
3032
//
3133
// The message will only be compressed if greater than 512 bytes.
32-
CompressionNoContextTakeover CompressionMode = iota
34+
CompressionNoContextTakeover
3335

3436
// CompressionContextTakeover uses a flate.Reader and flate.Writer per connection.
3537
// This enables reusing the sliding window from previous messages.
3638
// As most WebSocket protocols are repetitive, this can be very efficient.
37-
// It carries an overhead of 8 kB for every connection compared to CompressionNoContextTakeover.
39+
// It carries an overhead of 65 kB for every connection compared to CompressionNoContextTakeover.
3840
//
3941
// If the peer negotiates NoContextTakeover on the client or server side, it will be
4042
// used instead as this is required by the RFC.
4143
CompressionContextTakeover
42-
43-
// CompressionDisabled disables the deflate extension.
44-
//
45-
// Use this if you are using a predominantly binary protocol with very
46-
// little duplication in between messages or CPU and memory are more
47-
// important than bandwidth.
48-
CompressionDisabled
4944
)
5045

5146
func (m CompressionMode) opts() *compressionOptions {
@@ -146,6 +141,22 @@ func putFlateReader(fr io.Reader) {
146141
flateReaderPool.Put(fr)
147142
}
148143

144+
var flateWriterPool sync.Pool
145+
146+
func getFlateWriter(w io.Writer) *flate.Writer {
147+
fw, ok := flateWriterPool.Get().(*flate.Writer)
148+
if !ok {
149+
fw, _ = flate.NewWriter(w, flate.BestSpeed)
150+
return fw
151+
}
152+
fw.Reset(w)
153+
return fw
154+
}
155+
156+
func putFlateWriter(w *flate.Writer) {
157+
flateWriterPool.Put(w)
158+
}
159+
149160
type slidingWindow struct {
150161
buf []byte
151162
}

Diff for: conn_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestConn(t *testing.T) {
3737
t.Parallel()
3838

3939
compressionMode := func() websocket.CompressionMode {
40-
return websocket.CompressionMode(xrand.Int(int(websocket.CompressionDisabled) + 1))
40+
return websocket.CompressionMode(xrand.Int(int(websocket.CompressionContextTakeover) + 1))
4141
}
4242

4343
for i := 0; i < 5; i++ {

Diff for: dial.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type DialOptions struct {
3535
Subprotocols []string
3636

3737
// CompressionMode controls the compression mode.
38-
// Defaults to CompressionNoContextTakeover.
38+
// Defaults to CompressionDisabled.
3939
//
4040
// See docs on CompressionMode for details.
4141
CompressionMode CompressionMode

Diff for: go.mod

-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ require (
1010
github.com/golang/protobuf v1.3.5
1111
github.com/google/go-cmp v0.4.0
1212
github.com/gorilla/websocket v1.4.1
13-
github.com/klauspost/compress v1.10.3
1413
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
1514
)

Diff for: go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
2929
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3030
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
3131
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
32-
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
33-
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
3432
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
3533
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
3634
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=

Diff for: write.go

+27-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"io"
1313
"time"
1414

15-
"github.com/klauspost/compress/flate"
15+
"compress/flate"
1616

1717
"nhooyr.io/websocket/internal/errd"
1818
)
@@ -76,8 +76,9 @@ type msgWriterState struct {
7676
opcode opcode
7777
flate bool
7878

79-
trimWriter *trimLastFourBytesWriter
80-
dict slidingWindow
79+
trimWriter *trimLastFourBytesWriter
80+
flateWriter *flate.Writer
81+
dict slidingWindow
8182
}
8283

8384
func newMsgWriterState(c *Conn) *msgWriterState {
@@ -96,7 +97,12 @@ func (mw *msgWriterState) ensureFlate() {
9697
}
9798
}
9899

99-
mw.dict.init(8192)
100+
if mw.flateWriter == nil {
101+
mw.flateWriter = getFlateWriter(mw.trimWriter)
102+
}
103+
if mw.flateContextTakeover() {
104+
mw.dict.init(32768)
105+
}
100106
mw.flate = true
101107
}
102108

@@ -153,6 +159,13 @@ func (mw *msgWriterState) reset(ctx context.Context, typ MessageType) error {
153159
return nil
154160
}
155161

162+
func (mw *msgWriterState) putFlateWriter() {
163+
if mw.flateWriter != nil {
164+
putFlateWriter(mw.flateWriter)
165+
mw.flateWriter = nil
166+
}
167+
}
168+
156169
// Write writes the given bytes to the WebSocket connection.
157170
func (mw *msgWriterState) Write(p []byte) (_ int, err error) {
158171
err = mw.writeMu.lock(mw.ctx)
@@ -177,12 +190,8 @@ func (mw *msgWriterState) Write(p []byte) (_ int, err error) {
177190
}
178191

179192
if mw.flate {
180-
err = flate.StatelessDeflate(mw.trimWriter, p, false, mw.dict.buf)
181-
if err != nil {
182-
return 0, err
183-
}
184193
mw.dict.write(p)
185-
return len(p), nil
194+
return mw.flateWriter.Write(p)
186195
}
187196

188197
return mw.write(p)
@@ -207,6 +216,14 @@ func (mw *msgWriterState) Close() (err error) {
207216
}
208217
defer mw.writeMu.unlock()
209218

219+
if mw.flate {
220+
err = mw.flateWriter.Flush()
221+
if err != nil {
222+
return fmt.Errorf("failed to flush flate: %w", err)
223+
}
224+
mw.putFlateWriter()
225+
}
226+
210227
_, err = mw.c.writeFrame(mw.ctx, true, mw.flate, mw.opcode, nil)
211228
if err != nil {
212229
return fmt.Errorf("failed to write fin frame: %w", err)
@@ -226,6 +243,7 @@ func (mw *msgWriterState) close() {
226243
}
227244

228245
mw.writeMu.forceLock()
246+
mw.putFlateWriter()
229247
mw.dict.close()
230248
}
231249

0 commit comments

Comments
 (0)