From 58839cefb50e56ae5b157b37e9814ae83ceee70b Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 20 Jul 2017 15:22:49 -0700 Subject: Upgrading server dependancies (#6984) --- vendor/github.com/gorilla/websocket/.travis.yml | 1 + vendor/github.com/gorilla/websocket/client.go | 36 +----- .../github.com/gorilla/websocket/client_clone.go | 16 +++ .../gorilla/websocket/client_clone_legacy.go | 38 ++++++ vendor/github.com/gorilla/websocket/compression.go | 73 ++++++++--- .../gorilla/websocket/compression_test.go | 49 +++++++ vendor/github.com/gorilla/websocket/conn.go | 144 ++++++++++++++++++--- .../gorilla/websocket/conn_broadcast_test.go | 134 +++++++++++++++++++ vendor/github.com/gorilla/websocket/conn_test.go | 32 +++++ vendor/github.com/gorilla/websocket/doc.go | 23 ++-- .../websocket/examples/autobahn/fuzzingclient.json | 1 + .../gorilla/websocket/examples/autobahn/server.go | 29 ++++- .../gorilla/websocket/examples/chat/client.go | 5 +- .../gorilla/websocket/examples/chat/home.html | 4 +- .../gorilla/websocket/examples/chat/main.go | 5 +- .../gorilla/websocket/examples/command/README.md | 2 +- .../gorilla/websocket/examples/command/home.html | 56 ++++---- .../gorilla/websocket/examples/command/main.go | 9 +- .../gorilla/websocket/examples/filewatch/main.go | 2 +- vendor/github.com/gorilla/websocket/mask.go | 12 +- vendor/github.com/gorilla/websocket/mask_safe.go | 15 +++ vendor/github.com/gorilla/websocket/mask_test.go | 2 +- vendor/github.com/gorilla/websocket/prepared.go | 103 +++++++++++++++ .../github.com/gorilla/websocket/prepared_test.go | 74 +++++++++++ vendor/github.com/gorilla/websocket/server.go | 35 +++-- 25 files changed, 752 insertions(+), 148 deletions(-) create mode 100644 vendor/github.com/gorilla/websocket/client_clone.go create mode 100644 vendor/github.com/gorilla/websocket/client_clone_legacy.go create mode 100644 vendor/github.com/gorilla/websocket/conn_broadcast_test.go create mode 100644 vendor/github.com/gorilla/websocket/mask_safe.go create mode 100644 vendor/github.com/gorilla/websocket/prepared.go create mode 100644 vendor/github.com/gorilla/websocket/prepared_test.go (limited to 'vendor/github.com/gorilla') diff --git a/vendor/github.com/gorilla/websocket/.travis.yml b/vendor/github.com/gorilla/websocket/.travis.yml index 4ea1e7a1f..3d8d29cf3 100644 --- a/vendor/github.com/gorilla/websocket/.travis.yml +++ b/vendor/github.com/gorilla/websocket/.travis.yml @@ -7,6 +7,7 @@ matrix: - go: 1.5 - go: 1.6 - go: 1.7 + - go: 1.8 - go: tip allow_failures: - go: tip diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go index 78d932877..43a87c753 100644 --- a/vendor/github.com/gorilla/websocket/client.go +++ b/vendor/github.com/gorilla/websocket/client.go @@ -66,8 +66,9 @@ type Dialer struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // Input and output buffer sizes. If the buffer size is zero, then a - // default value of 4096 is used. + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // size is zero, then a useful default size is used. The I/O buffer sizes + // do not limit the size of the messages that can be sent or received. ReadBufferSize, WriteBufferSize int // Subprotocols specifies the client's requested subprotocols. @@ -368,7 +369,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re return nil, resp, ErrBadHandshake } - for _, ext := range parseExtensions(req.Header) { + for _, ext := range parseExtensions(resp.Header) { if ext[""] != "permessage-deflate" { continue } @@ -389,32 +390,3 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re netConn = nil // to avoid close in defer. return conn, resp, nil } - -// cloneTLSConfig clones all public fields except the fields -// SessionTicketsDisabled and SessionTicketKey. This avoids copying the -// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a -// config in active use. -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - if cfg == nil { - return &tls.Config{} - } - return &tls.Config{ - Rand: cfg.Rand, - Time: cfg.Time, - Certificates: cfg.Certificates, - NameToCertificate: cfg.NameToCertificate, - GetCertificate: cfg.GetCertificate, - RootCAs: cfg.RootCAs, - NextProtos: cfg.NextProtos, - ServerName: cfg.ServerName, - ClientAuth: cfg.ClientAuth, - ClientCAs: cfg.ClientCAs, - InsecureSkipVerify: cfg.InsecureSkipVerify, - CipherSuites: cfg.CipherSuites, - PreferServerCipherSuites: cfg.PreferServerCipherSuites, - ClientSessionCache: cfg.ClientSessionCache, - MinVersion: cfg.MinVersion, - MaxVersion: cfg.MaxVersion, - CurvePreferences: cfg.CurvePreferences, - } -} diff --git a/vendor/github.com/gorilla/websocket/client_clone.go b/vendor/github.com/gorilla/websocket/client_clone.go new file mode 100644 index 000000000..4f0d94372 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/client_clone.go @@ -0,0 +1,16 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.8 + +package websocket + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return cfg.Clone() +} diff --git a/vendor/github.com/gorilla/websocket/client_clone_legacy.go b/vendor/github.com/gorilla/websocket/client_clone_legacy.go new file mode 100644 index 000000000..babb007fb --- /dev/null +++ b/vendor/github.com/gorilla/websocket/client_clone_legacy.go @@ -0,0 +1,38 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8 + +package websocket + +import "crypto/tls" + +// cloneTLSConfig clones all public fields except the fields +// SessionTicketsDisabled and SessionTicketKey. This avoids copying the +// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a +// config in active use. +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/gorilla/websocket/compression.go b/vendor/github.com/gorilla/websocket/compression.go index 72c166b2a..813ffb1e8 100644 --- a/vendor/github.com/gorilla/websocket/compression.go +++ b/vendor/github.com/gorilla/websocket/compression.go @@ -12,31 +12,45 @@ import ( "sync" ) +const ( + minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 + maxCompressionLevel = flate.BestCompression + defaultCompressionLevel = 1 +) + var ( - flateWriterPool = sync.Pool{} + flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool + flateReaderPool = sync.Pool{New: func() interface{} { + return flate.NewReader(nil) + }} ) -func decompressNoContextTakeover(r io.Reader) io.Reader { +func decompressNoContextTakeover(r io.Reader) io.ReadCloser { const tail = // Add four bytes as specified in RFC "\x00\x00\xff\xff" + // Add final block to squelch unexpected EOF error from flate reader. "\x01\x00\x00\xff\xff" - return flate.NewReader(io.MultiReader(r, strings.NewReader(tail))) + + fr, _ := flateReaderPool.Get().(io.ReadCloser) + fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) + return &flateReadWrapper{fr} } -func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) { +func isValidCompressionLevel(level int) bool { + return minCompressionLevel <= level && level <= maxCompressionLevel +} + +func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { + p := &flateWriterPools[level-minCompressionLevel] tw := &truncWriter{w: w} - i := flateWriterPool.Get() - var fw *flate.Writer - var err error - if i == nil { - fw, err = flate.NewWriter(tw, 3) + fw, _ := p.Get().(*flate.Writer) + if fw == nil { + fw, _ = flate.NewWriter(tw, level) } else { - fw = i.(*flate.Writer) fw.Reset(tw) } - return &flateWrapper{fw: fw, tw: tw}, err + return &flateWriteWrapper{fw: fw, tw: tw, p: p} } // truncWriter is an io.Writer that writes all but the last four bytes of the @@ -75,24 +89,25 @@ func (w *truncWriter) Write(p []byte) (int, error) { return n + nn, err } -type flateWrapper struct { +type flateWriteWrapper struct { fw *flate.Writer tw *truncWriter + p *sync.Pool } -func (w *flateWrapper) Write(p []byte) (int, error) { +func (w *flateWriteWrapper) Write(p []byte) (int, error) { if w.fw == nil { return 0, errWriteClosed } return w.fw.Write(p) } -func (w *flateWrapper) Close() error { +func (w *flateWriteWrapper) Close() error { if w.fw == nil { return errWriteClosed } err1 := w.fw.Flush() - flateWriterPool.Put(w.fw) + w.p.Put(w.fw) w.fw = nil if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { return errors.New("websocket: internal error, unexpected bytes at end of flate stream") @@ -103,3 +118,31 @@ func (w *flateWrapper) Close() error { } return err2 } + +type flateReadWrapper struct { + fr io.ReadCloser +} + +func (r *flateReadWrapper) Read(p []byte) (int, error) { + if r.fr == nil { + return 0, io.ErrClosedPipe + } + n, err := r.fr.Read(p) + if err == io.EOF { + // Preemptively place the reader back in the pool. This helps with + // scenarios where the application does not call NextReader() soon after + // this final read. + r.Close() + } + return n, err +} + +func (r *flateReadWrapper) Close() error { + if r.fr == nil { + return io.ErrClosedPipe + } + err := r.fr.Close() + flateReaderPool.Put(r.fr) + r.fr = nil + return err +} diff --git a/vendor/github.com/gorilla/websocket/compression_test.go b/vendor/github.com/gorilla/websocket/compression_test.go index cad70fb51..659cf4215 100644 --- a/vendor/github.com/gorilla/websocket/compression_test.go +++ b/vendor/github.com/gorilla/websocket/compression_test.go @@ -2,7 +2,9 @@ package websocket import ( "bytes" + "fmt" "io" + "io/ioutil" "testing" ) @@ -29,3 +31,50 @@ func TestTruncWriter(t *testing.T) { } } } + +func textMessages(num int) [][]byte { + messages := make([][]byte, num) + for i := 0; i < num; i++ { + msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i) + messages[i] = []byte(msg) + } + return messages +} + +func BenchmarkWriteNoCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func BenchmarkWriteWithCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + c.enableWriteCompression = true + c.newCompressionWriter = compressNoContextTakeover + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func TestValidCompressionLevel(t *testing.T) { + c := newConn(fakeNetConn{}, false, 1024, 1024) + for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} { + if err := c.SetCompressionLevel(level); err == nil { + t.Errorf("no error for level %d", level) + } + } + for _, level := range []int{minCompressionLevel, maxCompressionLevel} { + if err := c.SetCompressionLevel(level); err != nil { + t.Errorf("error for level %d", level) + } + } +} diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go index ce7f0a615..97e1dbacb 100644 --- a/vendor/github.com/gorilla/websocket/conn.go +++ b/vendor/github.com/gorilla/websocket/conn.go @@ -10,6 +10,7 @@ import ( "errors" "io" "io/ioutil" + "math/rand" "net" "strconv" "sync" @@ -180,6 +181,11 @@ var ( errInvalidControlFrame = errors.New("websocket: invalid control frame") ) +func newMaskKey() [4]byte { + n := rand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + func hideTempErr(err error) error { if e, ok := err.(net.Error); ok && e.Temporary() { err = &netError{msg: e.Error(), timeout: e.Timeout()} @@ -235,9 +241,11 @@ type Conn struct { writeErr error enableWriteCompression bool - newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error) + compressionLevel int + newCompressionWriter func(io.WriteCloser, int) io.WriteCloser // Read fields + reader io.ReadCloser // the current reader returned to the application readErr error br *bufio.Reader readRemaining int64 // bytes remaining in current frame. @@ -253,31 +261,76 @@ type Conn struct { messageReader *messageReader // the current low-level reader readDecompress bool // whether last read frame had RSV1 set - newDecompressionReader func(io.Reader) io.Reader + newDecompressionReader func(io.Reader) io.ReadCloser } func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { + return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) +} + +type writeHook struct { + p []byte +} + +func (wh *writeHook) Write(p []byte) (int, error) { + wh.p = p + return len(p), nil +} + +func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { mu := make(chan bool, 1) mu <- true - if readBufferSize == 0 { - readBufferSize = defaultReadBufferSize + var br *bufio.Reader + if readBufferSize == 0 && brw != nil && brw.Reader != nil { + // Reuse the supplied bufio.Reader if the buffer has a useful size. + // This code assumes that peek on a reader returns + // bufio.Reader.buf[:0]. + brw.Reader.Reset(conn) + if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { + br = brw.Reader + } } - if readBufferSize < maxControlFramePayloadSize { - readBufferSize = maxControlFramePayloadSize + if br == nil { + if readBufferSize == 0 { + readBufferSize = defaultReadBufferSize + } + if readBufferSize < maxControlFramePayloadSize { + readBufferSize = maxControlFramePayloadSize + } + br = bufio.NewReaderSize(conn, readBufferSize) + } + + var writeBuf []byte + if writeBufferSize == 0 && brw != nil && brw.Writer != nil { + // Use the bufio.Writer's buffer if the buffer has a useful size. This + // code assumes that bufio.Writer.buf[:1] is passed to the + // bufio.Writer's underlying writer. + var wh writeHook + brw.Writer.Reset(&wh) + brw.Writer.WriteByte(0) + brw.Flush() + if cap(wh.p) >= maxFrameHeaderSize+256 { + writeBuf = wh.p[:cap(wh.p)] + } } - if writeBufferSize == 0 { - writeBufferSize = defaultWriteBufferSize + + if writeBuf == nil { + if writeBufferSize == 0 { + writeBufferSize = defaultWriteBufferSize + } + writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) } c := &Conn{ isServer: isServer, - br: bufio.NewReaderSize(conn, readBufferSize), + br: br, conn: conn, mu: mu, readFinal: true, - writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), + writeBuf: writeBuf, enableWriteCompression: true, + compressionLevel: defaultCompressionLevel, } c.SetCloseHandler(nil) c.SetPingHandler(nil) @@ -443,11 +496,7 @@ func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { } c.writer = mw if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { - w, err := c.newCompressionWriter(c.writer) - if err != nil { - c.writer = nil - return nil, err - } + w := c.newCompressionWriter(c.writer, c.compressionLevel) mw.compress = true c.writer = w } @@ -654,12 +703,33 @@ func (w *messageWriter) Close() error { return nil } +// WritePreparedMessage writes prepared message into connection. +func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error { + frameType, frameData, err := pm.frame(prepareKey{ + isServer: c.isServer, + compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType), + compressionLevel: c.compressionLevel, + }) + if err != nil { + return err + } + if c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = true + err = c.write(frameType, c.writeDeadline, frameData, nil) + if !c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = false + return err +} + // WriteMessage is a helper method for getting a writer using NextWriter, // writing the message and closing the writer. func (c *Conn) WriteMessage(messageType int, data []byte) error { if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { - // Fast path with no allocations and single frame. if err := c.prepWrite(messageType); err != nil { @@ -855,6 +925,11 @@ func (c *Conn) handleProtocolError(message string) error { // permanent. Once this method returns a non-nil error, all subsequent calls to // this method return the same error. func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { + // Close previous reader, only relevant for decompression. + if c.reader != nil { + c.reader.Close() + c.reader = nil + } c.messageReader = nil c.readLength = 0 @@ -867,11 +942,11 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { } if frameType == TextMessage || frameType == BinaryMessage { c.messageReader = &messageReader{c} - var r io.Reader = c.messageReader + c.reader = c.messageReader if c.readDecompress { - r = c.newDecompressionReader(r) + c.reader = c.newDecompressionReader(c.reader) } - return frameType, r, nil + return frameType, c.reader, nil } } @@ -933,6 +1008,10 @@ func (r *messageReader) Read(b []byte) (int, error) { return 0, err } +func (r *messageReader) Close() error { + return nil +} + // ReadMessage is a helper method for getting a reader using NextReader and // reading from that reader to a buffer. func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { @@ -969,6 +1048,15 @@ func (c *Conn) CloseHandler() func(code int, text string) error { // The code argument to h is the received close code or CloseNoStatusReceived // if the close message is empty. The default close handler sends a close frame // back to the peer. +// +// The application must read the connection to process close messages as +// described in the section on Control Frames above. +// +// The connection read methods return a CloseError when a close frame is +// received. Most applications should handle close messages as part of their +// normal error handling. Applications should only set a close handler when the +// application must perform some action before sending a close frame back to +// the peer. func (c *Conn) SetCloseHandler(h func(code int, text string) error) { if h == nil { h = func(code int, text string) error { @@ -991,6 +1079,9 @@ func (c *Conn) PingHandler() func(appData string) error { // SetPingHandler sets the handler for ping messages received from the peer. // The appData argument to h is the PING frame application data. The default // ping handler sends a pong to the peer. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. func (c *Conn) SetPingHandler(h func(appData string) error) { if h == nil { h = func(message string) error { @@ -1014,6 +1105,9 @@ func (c *Conn) PongHandler() func(appData string) error { // SetPongHandler sets the handler for pong messages received from the peer. // The appData argument to h is the PONG frame application data. The default // pong handler does nothing. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. func (c *Conn) SetPongHandler(h func(appData string) error) { if h == nil { h = func(string) error { return nil } @@ -1034,6 +1128,18 @@ func (c *Conn) EnableWriteCompression(enable bool) { c.enableWriteCompression = enable } +// SetCompressionLevel sets the flate compression level for subsequent text and +// binary messages. This function is a noop if compression was not negotiated +// with the peer. See the compress/flate package for a description of +// compression levels. +func (c *Conn) SetCompressionLevel(level int) error { + if !isValidCompressionLevel(level) { + return errors.New("websocket: invalid compression level") + } + c.compressionLevel = level + return nil +} + // FormatCloseMessage formats closeCode and text as a WebSocket close message. func FormatCloseMessage(closeCode int, text string) []byte { buf := make([]byte, 2+len(text)) diff --git a/vendor/github.com/gorilla/websocket/conn_broadcast_test.go b/vendor/github.com/gorilla/websocket/conn_broadcast_test.go new file mode 100644 index 000000000..45038e488 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/conn_broadcast_test.go @@ -0,0 +1,134 @@ +// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package websocket + +import ( + "io" + "io/ioutil" + "sync/atomic" + "testing" +) + +// broadcastBench allows to run broadcast benchmarks. +// In every broadcast benchmark we create many connections, then send the same +// message into every connection and wait for all writes complete. This emulates +// an application where many connections listen to the same data - i.e. PUB/SUB +// scenarios with many subscribers in one channel. +type broadcastBench struct { + w io.Writer + message *broadcastMessage + closeCh chan struct{} + doneCh chan struct{} + count int32 + conns []*broadcastConn + compression bool + usePrepared bool +} + +type broadcastMessage struct { + payload []byte + prepared *PreparedMessage +} + +type broadcastConn struct { + conn *Conn + msgCh chan *broadcastMessage +} + +func newBroadcastConn(c *Conn) *broadcastConn { + return &broadcastConn{ + conn: c, + msgCh: make(chan *broadcastMessage, 1), + } +} + +func newBroadcastBench(usePrepared, compression bool) *broadcastBench { + bench := &broadcastBench{ + w: ioutil.Discard, + doneCh: make(chan struct{}), + closeCh: make(chan struct{}), + usePrepared: usePrepared, + compression: compression, + } + msg := &broadcastMessage{ + payload: textMessages(1)[0], + } + if usePrepared { + pm, _ := NewPreparedMessage(TextMessage, msg.payload) + msg.prepared = pm + } + bench.message = msg + bench.makeConns(10000) + return bench +} + +func (b *broadcastBench) makeConns(numConns int) { + conns := make([]*broadcastConn, numConns) + + for i := 0; i < numConns; i++ { + c := newConn(fakeNetConn{Reader: nil, Writer: b.w}, true, 1024, 1024) + if b.compression { + c.enableWriteCompression = true + c.newCompressionWriter = compressNoContextTakeover + } + conns[i] = newBroadcastConn(c) + go func(c *broadcastConn) { + for { + select { + case msg := <-c.msgCh: + if b.usePrepared { + c.conn.WritePreparedMessage(msg.prepared) + } else { + c.conn.WriteMessage(TextMessage, msg.payload) + } + val := atomic.AddInt32(&b.count, 1) + if val%int32(numConns) == 0 { + b.doneCh <- struct{}{} + } + case <-b.closeCh: + return + } + } + }(conns[i]) + } + b.conns = conns +} + +func (b *broadcastBench) close() { + close(b.closeCh) +} + +func (b *broadcastBench) runOnce() { + for _, c := range b.conns { + c.msgCh <- b.message + } + <-b.doneCh +} + +func BenchmarkBroadcast(b *testing.B) { + benchmarks := []struct { + name string + usePrepared bool + compression bool + }{ + {"NoCompression", false, false}, + {"WithCompression", false, true}, + {"NoCompressionPrepared", true, false}, + {"WithCompressionPrepared", true, true}, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + bench := newBroadcastBench(bm.usePrepared, bm.compression) + defer bench.close() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bench.runOnce() + } + b.ReportAllocs() + }) + } +} diff --git a/vendor/github.com/gorilla/websocket/conn_test.go b/vendor/github.com/gorilla/websocket/conn_test.go index 7431383b1..06e9bc3f5 100644 --- a/vendor/github.com/gorilla/websocket/conn_test.go +++ b/vendor/github.com/gorilla/websocket/conn_test.go @@ -463,3 +463,35 @@ func TestFailedConnectionReadPanic(t *testing.T) { } t.Fatal("should not get here") } + +func TestBufioReuse(t *testing.T) { + brw := bufio.NewReadWriter(bufio.NewReader(nil), bufio.NewWriter(nil)) + c := newConnBRW(nil, false, 0, 0, brw) + + if c.br != brw.Reader { + t.Error("connection did not reuse bufio.Reader") + } + + var wh writeHook + brw.Writer.Reset(&wh) + brw.WriteByte(0) + brw.Flush() + if &c.writeBuf[0] != &wh.p[0] { + t.Error("connection did not reuse bufio.Writer") + } + + brw = bufio.NewReadWriter(bufio.NewReaderSize(nil, 0), bufio.NewWriterSize(nil, 0)) + c = newConnBRW(nil, false, 0, 0, brw) + + if c.br == brw.Reader { + t.Error("connection used bufio.Reader with small size") + } + + brw.Writer.Reset(&wh) + brw.WriteByte(0) + brw.Flush() + if &c.writeBuf[0] != &wh.p[0] { + t.Error("connection used bufio.Writer with small size") + } + +} diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go index 610acf712..e291a952c 100644 --- a/vendor/github.com/gorilla/websocket/doc.go +++ b/vendor/github.com/gorilla/websocket/doc.go @@ -118,9 +118,10 @@ // // Applications are responsible for ensuring that no more than one goroutine // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, -// WriteJSON) concurrently and that no more than one goroutine calls the read -// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, -// SetPingHandler) concurrently. +// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and +// that no more than one goroutine calls the read methods (NextReader, +// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) +// concurrently. // // The Close and WriteControl methods can be called concurrently with all other // methods. @@ -150,19 +151,25 @@ // application's responsibility to check the Origin header before calling // Upgrade. // -// Compression [Experimental] +// Compression EXPERIMENTAL // // Per message compression extensions (RFC 7692) are experimentally supported // by this package in a limited capacity. Setting the EnableCompression option // to true in Dialer or Upgrader will attempt to negotiate per message deflate -// support. If compression was successfully negotiated with the connection's -// peer, any message received in compressed form will be automatically -// decompressed. All Read methods will return uncompressed bytes. +// support. +// +// var upgrader = websocket.Upgrader{ +// EnableCompression: true, +// } +// +// If compression was successfully negotiated with the connection's peer, any +// message received in compressed form will be automatically decompressed. +// All Read methods will return uncompressed bytes. // // Per message compression of messages written to a connection can be enabled // or disabled by calling the corresponding Conn method: // -// conn.EnableWriteCompression(true) +// conn.EnableWriteCompression(false) // // Currently this package does not support compression with "context takeover". // This means that messages must be compressed and decompressed in isolation, diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json b/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json index 27d5a5b14..aa3a0bc0a 100644 --- a/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json +++ b/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json @@ -4,6 +4,7 @@ "outdir": "./reports/clients", "servers": [ {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}}, + {"agent": "ReadAllWritePreparedMessage", "url": "ws://localhost:9000/p", "options": {"version": 18}}, {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}}, {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}}, {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}} diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go index e98563be9..3db880f90 100644 --- a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go +++ b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go @@ -85,7 +85,7 @@ func echoCopyFull(w http.ResponseWriter, r *http.Request) { // echoReadAll echoes messages from the client by reading the entire message // with ioutil.ReadAll. -func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) { +func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("Upgrade:", err) @@ -109,9 +109,21 @@ func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) { } } if writeMessage { - err = conn.WriteMessage(mt, b) - if err != nil { - log.Println("WriteMessage:", err) + if !writePrepared { + err = conn.WriteMessage(mt, b) + if err != nil { + log.Println("WriteMessage:", err) + } + } else { + pm, err := websocket.NewPreparedMessage(mt, b) + if err != nil { + log.Println("NewPreparedMessage:", err) + return + } + err = conn.WritePreparedMessage(pm) + if err != nil { + log.Println("WritePreparedMessage:", err) + } } } else { w, err := conn.NextWriter(mt) @@ -132,11 +144,15 @@ func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) { } func echoReadAllWriter(w http.ResponseWriter, r *http.Request) { - echoReadAll(w, r, false) + echoReadAll(w, r, false, false) } func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) { - echoReadAll(w, r, true) + echoReadAll(w, r, true, false) +} + +func echoReadAllWritePreparedMessage(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, true, true) } func serveHome(w http.ResponseWriter, r *http.Request) { @@ -161,6 +177,7 @@ func main() { http.HandleFunc("/f", echoCopyFull) http.HandleFunc("/r", echoReadAllWriter) http.HandleFunc("/m", echoReadAllWriteMessage) + http.HandleFunc("/p", echoReadAllWritePreparedMessage) err := http.ListenAndServe(*addr, nil) if err != nil { log.Fatal("ListenAndServe: ", err) diff --git a/vendor/github.com/gorilla/websocket/examples/chat/client.go b/vendor/github.com/gorilla/websocket/examples/chat/client.go index 26468477c..ecfd9a7aa 100644 --- a/vendor/github.com/gorilla/websocket/examples/chat/client.go +++ b/vendor/github.com/gorilla/websocket/examples/chat/client.go @@ -129,6 +129,9 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { } client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} client.hub.register <- client + + // Allow collection of memory referenced by the caller by doing all work in + // new goroutines. go client.writePump() - client.readPump() + go client.readPump() } diff --git a/vendor/github.com/gorilla/websocket/examples/chat/home.html b/vendor/github.com/gorilla/websocket/examples/chat/home.html index 7262918ec..a39a0c276 100644 --- a/vendor/github.com/gorilla/websocket/examples/chat/home.html +++ b/vendor/github.com/gorilla/websocket/examples/chat/home.html @@ -9,7 +9,7 @@ window.onload = function () { var log = document.getElementById("log"); function appendLog(item) { - var doScroll = log.scrollTop === log.scrollHeight - log.clientHeight; + var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1; log.appendChild(item); if (doScroll) { log.scrollTop = log.scrollHeight - log.clientHeight; @@ -29,7 +29,7 @@ window.onload = function () { }; if (window["WebSocket"]) { - conn = new WebSocket("ws://{{$}}/ws"); + conn = new WebSocket("ws://" + document.location.host + "/ws"); conn.onclose = function (evt) { var item = document.createElement("div"); item.innerHTML = "Connection closed."; diff --git a/vendor/github.com/gorilla/websocket/examples/chat/main.go b/vendor/github.com/gorilla/websocket/examples/chat/main.go index a865ffec5..74615d59c 100644 --- a/vendor/github.com/gorilla/websocket/examples/chat/main.go +++ b/vendor/github.com/gorilla/websocket/examples/chat/main.go @@ -8,11 +8,9 @@ import ( "flag" "log" "net/http" - "text/template" ) var addr = flag.String("addr", ":8080", "http service address") -var homeTemplate = template.Must(template.ParseFiles("home.html")) func serveHome(w http.ResponseWriter, r *http.Request) { log.Println(r.URL) @@ -24,8 +22,7 @@ func serveHome(w http.ResponseWriter, r *http.Request) { http.Error(w, "Method not allowed", 405) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - homeTemplate.Execute(w, r.Host) + http.ServeFile(w, r, "home.html") } func main() { diff --git a/vendor/github.com/gorilla/websocket/examples/command/README.md b/vendor/github.com/gorilla/websocket/examples/command/README.md index c30d3979a..ed6f78684 100644 --- a/vendor/github.com/gorilla/websocket/examples/command/README.md +++ b/vendor/github.com/gorilla/websocket/examples/command/README.md @@ -2,7 +2,7 @@ This example connects a websocket connection to stdin and stdout of a command. Received messages are written to stdin followed by a `\n`. Each line read from -from standard out is sent as a message to the client. +standard out is sent as a message to the client. $ go get github.com/gorilla/websocket $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command` diff --git a/vendor/github.com/gorilla/websocket/examples/command/home.html b/vendor/github.com/gorilla/websocket/examples/command/home.html index 72fd02b2a..19c46128a 100644 --- a/vendor/github.com/gorilla/websocket/examples/command/home.html +++ b/vendor/github.com/gorilla/websocket/examples/command/home.html @@ -2,47 +2,53 @@ Command Example -