updated websocket lib
This commit is contained in:
-22
@@ -1,22 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.2
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get golang.org/x/tools/cmd/vet
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet .
|
|
||||||
- go test -v -race ./...
|
|
||||||
+4
-1
@@ -3,6 +3,9 @@
|
|||||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/gorilla/websocket)
|
||||||
|
[](https://godoc.org/github.com/gorilla/websocket)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||||
@@ -43,7 +46,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
|||||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||||
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
|
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
|
||||||
<tr><td colspan="3">Other Features</tr></td>
|
<tr><td colspan="3">Other Features</tr></td>
|
||||||
<tr><td>Limit size of received message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.SetReadLimit">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=5082">No</a></td></tr>
|
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
|
||||||
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
|
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
|
||||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
+77
-7
@@ -23,6 +23,8 @@ import (
|
|||||||
// invalid.
|
// invalid.
|
||||||
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||||
|
|
||||||
|
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||||
|
|
||||||
// NewClient creates a new client connection using the given net connection.
|
// NewClient creates a new client connection using the given net connection.
|
||||||
// The URL u specifies the host and request URI. Use requestHeader to specify
|
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||||
@@ -70,6 +72,17 @@ type Dialer struct {
|
|||||||
|
|
||||||
// Subprotocols specifies the client's requested subprotocols.
|
// Subprotocols specifies the client's requested subprotocols.
|
||||||
Subprotocols []string
|
Subprotocols []string
|
||||||
|
|
||||||
|
// EnableCompression specifies if the client should attempt to negotiate
|
||||||
|
// per message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
|
|
||||||
|
// Jar specifies the cookie jar.
|
||||||
|
// If Jar is nil, cookies are not sent in requests and ignored
|
||||||
|
// in responses.
|
||||||
|
Jar http.CookieJar
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
@@ -83,7 +96,6 @@ func parseURL(s string) (*url.URL, error) {
|
|||||||
//
|
//
|
||||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
||||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
||||||
|
|
||||||
var u url.URL
|
var u url.URL
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(s, "ws://"):
|
case strings.HasPrefix(s, "ws://"):
|
||||||
@@ -193,6 +205,13 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
Host: u.Host,
|
Host: u.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the cookies present in the cookie jar of the dialer
|
||||||
|
if d.Jar != nil {
|
||||||
|
for _, cookie := range d.Jar.Cookies(u) {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the request headers using the capitalization for names and values in
|
// Set the request headers using the capitalization for names and values in
|
||||||
// RFC examples. Although the capitalization shouldn't matter, there are
|
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||||
// servers that depend on it. The Header.Set method is not used because the
|
// servers that depend on it. The Header.Set method is not used because the
|
||||||
@@ -214,6 +233,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
k == "Connection" ||
|
k == "Connection" ||
|
||||||
k == "Sec-Websocket-Key" ||
|
k == "Sec-Websocket-Key" ||
|
||||||
k == "Sec-Websocket-Version" ||
|
k == "Sec-Websocket-Version" ||
|
||||||
|
k == "Sec-Websocket-Extensions" ||
|
||||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
default:
|
default:
|
||||||
@@ -221,6 +241,10 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.EnableCompression {
|
||||||
|
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
||||||
|
}
|
||||||
|
|
||||||
hostPort, hostNoPort := hostPortNoPort(u)
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
|
||||||
var proxyURL *url.URL
|
var proxyURL *url.URL
|
||||||
@@ -298,12 +322,8 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u.Scheme == "https" {
|
if u.Scheme == "https" {
|
||||||
cfg := d.TLSClientConfig
|
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||||
if cfg == nil {
|
if cfg.ServerName == "" {
|
||||||
cfg = &tls.Config{ServerName: hostNoPort}
|
|
||||||
} else if cfg.ServerName == "" {
|
|
||||||
shallowCopy := *cfg
|
|
||||||
cfg = &shallowCopy
|
|
||||||
cfg.ServerName = hostNoPort
|
cfg.ServerName = hostNoPort
|
||||||
}
|
}
|
||||||
tlsConn := tls.Client(netConn, cfg)
|
tlsConn := tls.Client(netConn, cfg)
|
||||||
@@ -328,6 +348,13 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.Jar != nil {
|
||||||
|
if rc := resp.Cookies(); len(rc) > 0 {
|
||||||
|
d.Jar.SetCookies(u, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 101 ||
|
if resp.StatusCode != 101 ||
|
||||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||||
@@ -341,6 +368,20 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
return nil, resp, ErrBadHandshake
|
return nil, resp, ErrBadHandshake
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ext := range parseExtensions(req.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, snct := ext["server_no_context_takeover"]
|
||||||
|
_, cnct := ext["client_no_context_takeover"]
|
||||||
|
if !snct || !cnct {
|
||||||
|
return nil, resp, errInvalidCompression
|
||||||
|
}
|
||||||
|
conn.newCompressionWriter = compressNoContextTakeover
|
||||||
|
conn.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
|
||||||
@@ -348,3 +389,32 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
netConn = nil // to avoid close in defer.
|
netConn = nil // to avoid close in defer.
|
||||||
return conn, resp, nil
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flateWriterPool = sync.Pool{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func decompressNoContextTakeover(r io.Reader) io.Reader {
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) {
|
||||||
|
tw := &truncWriter{w: w}
|
||||||
|
i := flateWriterPool.Get()
|
||||||
|
var fw *flate.Writer
|
||||||
|
var err error
|
||||||
|
if i == nil {
|
||||||
|
fw, err = flate.NewWriter(tw, 3)
|
||||||
|
} else {
|
||||||
|
fw = i.(*flate.Writer)
|
||||||
|
fw.Reset(tw)
|
||||||
|
}
|
||||||
|
return &flateWrapper{fw: fw, tw: tw}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||||
|
// stream to another io.Writer.
|
||||||
|
type truncWriter struct {
|
||||||
|
w io.WriteCloser
|
||||||
|
n int
|
||||||
|
p [4]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||||
|
n := 0
|
||||||
|
|
||||||
|
// fill buffer first for simplicity.
|
||||||
|
if w.n < len(w.p) {
|
||||||
|
n = copy(w.p[w.n:], p)
|
||||||
|
p = p[n:]
|
||||||
|
w.n += n
|
||||||
|
if len(p) == 0 {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := len(p)
|
||||||
|
if m > len(w.p) {
|
||||||
|
m = len(w.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(w.p[:], w.p[m:])
|
||||||
|
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||||
|
nn, err := w.w.Write(p[:len(p)-m])
|
||||||
|
return n + nn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type flateWrapper struct {
|
||||||
|
fw *flate.Writer
|
||||||
|
tw *truncWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWrapper) Write(p []byte) (int, error) {
|
||||||
|
if w.fw == nil {
|
||||||
|
return 0, errWriteClosed
|
||||||
|
}
|
||||||
|
return w.fw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *flateWrapper) Close() error {
|
||||||
|
if w.fw == nil {
|
||||||
|
return errWriteClosed
|
||||||
|
}
|
||||||
|
err1 := w.fw.Flush()
|
||||||
|
flateWriterPool.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")
|
||||||
|
}
|
||||||
|
err2 := w.tw.w.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
+323
-195
@@ -10,18 +10,27 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Frame header byte 0 bits from Section 5.2 of RFC 6455
|
||||||
|
finalBit = 1 << 7
|
||||||
|
rsv1Bit = 1 << 6
|
||||||
|
rsv2Bit = 1 << 5
|
||||||
|
rsv3Bit = 1 << 4
|
||||||
|
|
||||||
|
// Frame header byte 1 bits from Section 5.2 of RFC 6455
|
||||||
|
maskBit = 1 << 7
|
||||||
|
|
||||||
maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
|
maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
|
||||||
maxControlFramePayloadSize = 125
|
maxControlFramePayloadSize = 125
|
||||||
finalBit = 1 << 7
|
|
||||||
maskBit = 1 << 7
|
writeWait = time.Second
|
||||||
writeWait = time.Second
|
|
||||||
|
|
||||||
defaultReadBufferSize = 4096
|
defaultReadBufferSize = 4096
|
||||||
defaultWriteBufferSize = 4096
|
defaultWriteBufferSize = 4096
|
||||||
@@ -43,6 +52,8 @@ const (
|
|||||||
CloseMessageTooBig = 1009
|
CloseMessageTooBig = 1009
|
||||||
CloseMandatoryExtension = 1010
|
CloseMandatoryExtension = 1010
|
||||||
CloseInternalServerErr = 1011
|
CloseInternalServerErr = 1011
|
||||||
|
CloseServiceRestart = 1012
|
||||||
|
CloseTryAgainLater = 1013
|
||||||
CloseTLSHandshake = 1015
|
CloseTLSHandshake = 1015
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -184,51 +195,65 @@ func isData(frameType int) bool {
|
|||||||
return frameType == TextMessage || frameType == BinaryMessage
|
return frameType == TextMessage || frameType == BinaryMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
var validReceivedCloseCodes = map[int]bool{
|
||||||
for i := range b {
|
// see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
|
||||||
b[i] ^= key[pos&3]
|
|
||||||
pos++
|
CloseNormalClosure: true,
|
||||||
}
|
CloseGoingAway: true,
|
||||||
return pos & 3
|
CloseProtocolError: true,
|
||||||
|
CloseUnsupportedData: true,
|
||||||
|
CloseNoStatusReceived: false,
|
||||||
|
CloseAbnormalClosure: false,
|
||||||
|
CloseInvalidFramePayloadData: true,
|
||||||
|
ClosePolicyViolation: true,
|
||||||
|
CloseMessageTooBig: true,
|
||||||
|
CloseMandatoryExtension: true,
|
||||||
|
CloseInternalServerErr: true,
|
||||||
|
CloseServiceRestart: true,
|
||||||
|
CloseTryAgainLater: true,
|
||||||
|
CloseTLSHandshake: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMaskKey() [4]byte {
|
func isValidReceivedCloseCode(code int) bool {
|
||||||
n := rand.Uint32()
|
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
||||||
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn represents a WebSocket connection.
|
// The Conn type represents a WebSocket connection.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
isServer bool
|
isServer bool
|
||||||
subprotocol string
|
subprotocol string
|
||||||
|
|
||||||
// Write fields
|
// Write fields
|
||||||
mu chan bool // used as mutex to protect write to conn and closeSent
|
mu chan bool // used as mutex to protect write to conn
|
||||||
closeSent bool // true if close message was sent
|
writeBuf []byte // frame is constructed in this buffer.
|
||||||
|
writeDeadline time.Time
|
||||||
|
writer io.WriteCloser // the current writer returned to the application
|
||||||
|
isWriting bool // for best-effort concurrent write detection
|
||||||
|
|
||||||
// Message writer fields.
|
writeErrMu sync.Mutex
|
||||||
writeErr error
|
writeErr error
|
||||||
writeBuf []byte // frame is constructed in this buffer.
|
|
||||||
writePos int // end of data in writeBuf.
|
enableWriteCompression bool
|
||||||
writeFrameType int // type of the current frame.
|
newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error)
|
||||||
writeSeq int // incremented to invalidate message writers.
|
|
||||||
writeDeadline time.Time
|
|
||||||
isWriting bool // for best-effort concurrent write detection
|
|
||||||
|
|
||||||
// Read fields
|
// Read fields
|
||||||
readErr error
|
readErr error
|
||||||
br *bufio.Reader
|
br *bufio.Reader
|
||||||
readRemaining int64 // bytes remaining in current frame.
|
readRemaining int64 // bytes remaining in current frame.
|
||||||
readFinal bool // true the current message has more frames.
|
readFinal bool // true the current message has more frames.
|
||||||
readSeq int // incremented to invalidate message readers.
|
|
||||||
readLength int64 // Message size.
|
readLength int64 // Message size.
|
||||||
readLimit int64 // Maximum message size.
|
readLimit int64 // Maximum message size.
|
||||||
readMaskPos int
|
readMaskPos int
|
||||||
readMaskKey [4]byte
|
readMaskKey [4]byte
|
||||||
handlePong func(string) error
|
handlePong func(string) error
|
||||||
handlePing func(string) error
|
handlePing func(string) error
|
||||||
|
handleClose func(int, string) error
|
||||||
readErrCount int
|
readErrCount int
|
||||||
|
messageReader *messageReader // the current low-level reader
|
||||||
|
|
||||||
|
readDecompress bool // whether last read frame had RSV1 set
|
||||||
|
newDecompressionReader func(io.Reader) io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
||||||
@@ -238,20 +263,23 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int)
|
|||||||
if readBufferSize == 0 {
|
if readBufferSize == 0 {
|
||||||
readBufferSize = defaultReadBufferSize
|
readBufferSize = defaultReadBufferSize
|
||||||
}
|
}
|
||||||
|
if readBufferSize < maxControlFramePayloadSize {
|
||||||
|
readBufferSize = maxControlFramePayloadSize
|
||||||
|
}
|
||||||
if writeBufferSize == 0 {
|
if writeBufferSize == 0 {
|
||||||
writeBufferSize = defaultWriteBufferSize
|
writeBufferSize = defaultWriteBufferSize
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
isServer: isServer,
|
isServer: isServer,
|
||||||
br: bufio.NewReaderSize(conn, readBufferSize),
|
br: bufio.NewReaderSize(conn, readBufferSize),
|
||||||
conn: conn,
|
conn: conn,
|
||||||
mu: mu,
|
mu: mu,
|
||||||
readFinal: true,
|
readFinal: true,
|
||||||
writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
|
writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
|
||||||
writeFrameType: noFrame,
|
enableWriteCompression: true,
|
||||||
writePos: maxFrameHeaderSize,
|
|
||||||
}
|
}
|
||||||
|
c.SetCloseHandler(nil)
|
||||||
c.SetPingHandler(nil)
|
c.SetPingHandler(nil)
|
||||||
c.SetPongHandler(nil)
|
c.SetPongHandler(nil)
|
||||||
return c
|
return c
|
||||||
@@ -279,29 +307,40 @@ func (c *Conn) RemoteAddr() net.Addr {
|
|||||||
|
|
||||||
// Write methods
|
// Write methods
|
||||||
|
|
||||||
|
func (c *Conn) writeFatal(err error) error {
|
||||||
|
err = hideTempErr(err)
|
||||||
|
c.writeErrMu.Lock()
|
||||||
|
if c.writeErr == nil {
|
||||||
|
c.writeErr = err
|
||||||
|
}
|
||||||
|
c.writeErrMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||||
<-c.mu
|
<-c.mu
|
||||||
defer func() { c.mu <- true }()
|
defer func() { c.mu <- true }()
|
||||||
|
|
||||||
if c.closeSent {
|
c.writeErrMu.Lock()
|
||||||
return ErrCloseSent
|
err := c.writeErr
|
||||||
} else if frameType == CloseMessage {
|
c.writeErrMu.Unlock()
|
||||||
c.closeSent = true
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.SetWriteDeadline(deadline)
|
c.conn.SetWriteDeadline(deadline)
|
||||||
for _, buf := range bufs {
|
for _, buf := range bufs {
|
||||||
if len(buf) > 0 {
|
if len(buf) > 0 {
|
||||||
n, err := c.conn.Write(buf)
|
_, err := c.conn.Write(buf)
|
||||||
if n != len(buf) {
|
|
||||||
// Close on partial write.
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return c.writeFatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if frameType == CloseMessage {
|
||||||
|
c.writeFatal(ErrCloseSent)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,60 +389,108 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
|||||||
}
|
}
|
||||||
defer func() { c.mu <- true }()
|
defer func() { c.mu <- true }()
|
||||||
|
|
||||||
if c.closeSent {
|
c.writeErrMu.Lock()
|
||||||
return ErrCloseSent
|
err := c.writeErr
|
||||||
} else if messageType == CloseMessage {
|
c.writeErrMu.Unlock()
|
||||||
c.closeSent = true
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.SetWriteDeadline(deadline)
|
c.conn.SetWriteDeadline(deadline)
|
||||||
n, err := c.conn.Write(buf)
|
_, err = c.conn.Write(buf)
|
||||||
if n != 0 && n != len(buf) {
|
if err != nil {
|
||||||
c.conn.Close()
|
return c.writeFatal(err)
|
||||||
}
|
}
|
||||||
return hideTempErr(err)
|
if messageType == CloseMessage {
|
||||||
|
c.writeFatal(ErrCloseSent)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextWriter returns a writer for the next message to send. The writer's
|
func (c *Conn) prepWrite(messageType int) error {
|
||||||
// Close method flushes the complete message to the network.
|
// Close previous writer if not already closed by the application. It's
|
||||||
|
// probably better to return an error in this situation, but we cannot
|
||||||
|
// change this without breaking existing applications.
|
||||||
|
if c.writer != nil {
|
||||||
|
c.writer.Close()
|
||||||
|
c.writer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isControl(messageType) && !isData(messageType) {
|
||||||
|
return errBadWriteOpCode
|
||||||
|
}
|
||||||
|
|
||||||
|
c.writeErrMu.Lock()
|
||||||
|
err := c.writeErr
|
||||||
|
c.writeErrMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextWriter returns a writer for the next message to send. The writer's Close
|
||||||
|
// method flushes the complete message to the network.
|
||||||
//
|
//
|
||||||
// There can be at most one open writer on a connection. NextWriter closes the
|
// There can be at most one open writer on a connection. NextWriter closes the
|
||||||
// previous writer if the application has not already done so.
|
// previous writer if the application has not already done so.
|
||||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||||
if c.writeErr != nil {
|
if err := c.prepWrite(messageType); err != nil {
|
||||||
return nil, c.writeErr
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.writeFrameType != noFrame {
|
mw := &messageWriter{
|
||||||
if err := c.flushFrame(true, nil); err != nil {
|
c: c,
|
||||||
|
frameType: messageType,
|
||||||
|
pos: maxFrameHeaderSize,
|
||||||
|
}
|
||||||
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
mw.compress = true
|
||||||
|
c.writer = w
|
||||||
}
|
}
|
||||||
|
return c.writer, nil
|
||||||
if !isControl(messageType) && !isData(messageType) {
|
|
||||||
return nil, errBadWriteOpCode
|
|
||||||
}
|
|
||||||
|
|
||||||
c.writeFrameType = messageType
|
|
||||||
return messageWriter{c, c.writeSeq}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) flushFrame(final bool, extra []byte) error {
|
type messageWriter struct {
|
||||||
length := c.writePos - maxFrameHeaderSize + len(extra)
|
c *Conn
|
||||||
|
compress bool // whether next call to flushFrame should set RSV1
|
||||||
|
pos int // end of data in writeBuf.
|
||||||
|
frameType int // type of the current frame.
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) fatal(err error) error {
|
||||||
|
if w.err != nil {
|
||||||
|
w.err = err
|
||||||
|
w.c.writer = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushFrame writes buffered data and extra as a frame to the network. The
|
||||||
|
// final argument indicates that this is the last frame in the message.
|
||||||
|
func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||||
|
c := w.c
|
||||||
|
length := w.pos - maxFrameHeaderSize + len(extra)
|
||||||
|
|
||||||
// Check for invalid control frames.
|
// Check for invalid control frames.
|
||||||
if isControl(c.writeFrameType) &&
|
if isControl(w.frameType) &&
|
||||||
(!final || length > maxControlFramePayloadSize) {
|
(!final || length > maxControlFramePayloadSize) {
|
||||||
c.writeSeq++
|
return w.fatal(errInvalidControlFrame)
|
||||||
c.writeFrameType = noFrame
|
|
||||||
c.writePos = maxFrameHeaderSize
|
|
||||||
return errInvalidControlFrame
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b0 := byte(c.writeFrameType)
|
b0 := byte(w.frameType)
|
||||||
if final {
|
if final {
|
||||||
b0 |= finalBit
|
b0 |= finalBit
|
||||||
}
|
}
|
||||||
|
if w.compress {
|
||||||
|
b0 |= rsv1Bit
|
||||||
|
}
|
||||||
|
w.compress = false
|
||||||
|
|
||||||
b1 := byte(0)
|
b1 := byte(0)
|
||||||
if !c.isServer {
|
if !c.isServer {
|
||||||
b1 |= maskBit
|
b1 |= maskBit
|
||||||
@@ -435,10 +522,9 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
|
|||||||
if !c.isServer {
|
if !c.isServer {
|
||||||
key := newMaskKey()
|
key := newMaskKey()
|
||||||
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
|
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
|
||||||
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:c.writePos])
|
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
|
||||||
if len(extra) > 0 {
|
if len(extra) > 0 {
|
||||||
c.writeErr = errors.New("websocket: internal error, extra used in client mode")
|
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
|
||||||
return c.writeErr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,46 +537,35 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
|
|||||||
}
|
}
|
||||||
c.isWriting = true
|
c.isWriting = true
|
||||||
|
|
||||||
c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra)
|
err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)
|
||||||
|
|
||||||
if !c.isWriting {
|
if !c.isWriting {
|
||||||
panic("concurrent write to websocket connection")
|
panic("concurrent write to websocket connection")
|
||||||
}
|
}
|
||||||
c.isWriting = false
|
c.isWriting = false
|
||||||
|
|
||||||
// Setup for next frame.
|
if err != nil {
|
||||||
c.writePos = maxFrameHeaderSize
|
return w.fatal(err)
|
||||||
c.writeFrameType = continuationFrame
|
}
|
||||||
|
|
||||||
if final {
|
if final {
|
||||||
c.writeSeq++
|
c.writer = nil
|
||||||
c.writeFrameType = noFrame
|
return nil
|
||||||
}
|
}
|
||||||
return c.writeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageWriter struct {
|
// Setup for next frame.
|
||||||
c *Conn
|
w.pos = maxFrameHeaderSize
|
||||||
seq int
|
w.frameType = continuationFrame
|
||||||
}
|
|
||||||
|
|
||||||
func (w messageWriter) err() error {
|
|
||||||
c := w.c
|
|
||||||
if c.writeSeq != w.seq {
|
|
||||||
return errWriteClosed
|
|
||||||
}
|
|
||||||
if c.writeErr != nil {
|
|
||||||
return c.writeErr
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w messageWriter) ncopy(max int) (int, error) {
|
func (w *messageWriter) ncopy(max int) (int, error) {
|
||||||
n := len(w.c.writeBuf) - w.c.writePos
|
n := len(w.c.writeBuf) - w.pos
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
if err := w.c.flushFrame(false, nil); err != nil {
|
if err := w.flushFrame(false, nil); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
n = len(w.c.writeBuf) - w.c.writePos
|
n = len(w.c.writeBuf) - w.pos
|
||||||
}
|
}
|
||||||
if n > max {
|
if n > max {
|
||||||
n = max
|
n = max
|
||||||
@@ -498,14 +573,14 @@ func (w messageWriter) ncopy(max int) (int, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w messageWriter) write(final bool, p []byte) (int, error) {
|
func (w *messageWriter) Write(p []byte) (int, error) {
|
||||||
if err := w.err(); err != nil {
|
if w.err != nil {
|
||||||
return 0, err
|
return 0, w.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
|
if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
|
||||||
// Don't buffer large messages.
|
// Don't buffer large messages.
|
||||||
err := w.c.flushFrame(final, p)
|
err := w.flushFrame(false, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -518,20 +593,16 @@ func (w messageWriter) write(final bool, p []byte) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
copy(w.c.writeBuf[w.c.writePos:], p[:n])
|
copy(w.c.writeBuf[w.pos:], p[:n])
|
||||||
w.c.writePos += n
|
w.pos += n
|
||||||
p = p[n:]
|
p = p[n:]
|
||||||
}
|
}
|
||||||
return nn, nil
|
return nn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w messageWriter) Write(p []byte) (int, error) {
|
func (w *messageWriter) WriteString(p string) (int, error) {
|
||||||
return w.write(false, p)
|
if w.err != nil {
|
||||||
}
|
return 0, w.err
|
||||||
|
|
||||||
func (w messageWriter) WriteString(p string) (int, error) {
|
|
||||||
if err := w.err(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nn := len(p)
|
nn := len(p)
|
||||||
@@ -540,27 +611,27 @@ func (w messageWriter) WriteString(p string) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
copy(w.c.writeBuf[w.c.writePos:], p[:n])
|
copy(w.c.writeBuf[w.pos:], p[:n])
|
||||||
w.c.writePos += n
|
w.pos += n
|
||||||
p = p[n:]
|
p = p[n:]
|
||||||
}
|
}
|
||||||
return nn, nil
|
return nn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
|
func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
|
||||||
if err := w.err(); err != nil {
|
if w.err != nil {
|
||||||
return 0, err
|
return 0, w.err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if w.c.writePos == len(w.c.writeBuf) {
|
if w.pos == len(w.c.writeBuf) {
|
||||||
err = w.c.flushFrame(false, nil)
|
err = w.flushFrame(false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var n int
|
var n int
|
||||||
n, err = r.Read(w.c.writeBuf[w.c.writePos:])
|
n, err = r.Read(w.c.writeBuf[w.pos:])
|
||||||
w.c.writePos += n
|
w.pos += n
|
||||||
nn += int64(n)
|
nn += int64(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -572,30 +643,43 @@ func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
|
|||||||
return nn, err
|
return nn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w messageWriter) Close() error {
|
func (w *messageWriter) Close() error {
|
||||||
if err := w.err(); err != nil {
|
if w.err != nil {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
if err := w.flushFrame(true, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return w.c.flushFrame(true, nil)
|
w.err = errWriteClosed
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMessage is a helper method for getting a writer using NextWriter,
|
// WriteMessage is a helper method for getting a writer using NextWriter,
|
||||||
// writing the message and closing the writer.
|
// writing the message and closing the writer.
|
||||||
func (c *Conn) WriteMessage(messageType int, data []byte) error {
|
func (c *Conn) WriteMessage(messageType int, data []byte) error {
|
||||||
wr, err := c.NextWriter(messageType)
|
|
||||||
|
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
|
||||||
|
|
||||||
|
// Fast path with no allocations and single frame.
|
||||||
|
|
||||||
|
if err := c.prepWrite(messageType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
|
||||||
|
n := copy(c.writeBuf[mw.pos:], data)
|
||||||
|
mw.pos += n
|
||||||
|
data = data[n:]
|
||||||
|
return mw.flushFrame(true, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := c.NextWriter(messageType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w := wr.(messageWriter)
|
if _, err = w.Write(data); err != nil {
|
||||||
if _, err := w.write(true, data); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.writeSeq == w.seq {
|
return w.Close()
|
||||||
if err := c.flushFrame(true, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteDeadline sets the write deadline on the underlying network
|
// SetWriteDeadline sets the write deadline on the underlying network
|
||||||
@@ -609,22 +693,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
|
|||||||
|
|
||||||
// Read methods
|
// Read methods
|
||||||
|
|
||||||
// readFull is like io.ReadFull except that io.EOF is never returned.
|
|
||||||
func (c *Conn) readFull(p []byte) (err error) {
|
|
||||||
var n int
|
|
||||||
for n < len(p) && err == nil {
|
|
||||||
var nn int
|
|
||||||
nn, err = c.br.Read(p[n:])
|
|
||||||
n += nn
|
|
||||||
}
|
|
||||||
if n == len(p) {
|
|
||||||
err = nil
|
|
||||||
} else if err == io.EOF {
|
|
||||||
err = errUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) advanceFrame() (int, error) {
|
func (c *Conn) advanceFrame() (int, error) {
|
||||||
|
|
||||||
// 1. Skip remainder of previous frame.
|
// 1. Skip remainder of previous frame.
|
||||||
@@ -637,19 +705,24 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
|
|
||||||
// 2. Read and parse first two bytes of frame header.
|
// 2. Read and parse first two bytes of frame header.
|
||||||
|
|
||||||
var b [8]byte
|
p, err := c.read(2)
|
||||||
if err := c.readFull(b[:2]); err != nil {
|
if err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
|
|
||||||
final := b[0]&finalBit != 0
|
final := p[0]&finalBit != 0
|
||||||
frameType := int(b[0] & 0xf)
|
frameType := int(p[0] & 0xf)
|
||||||
reserved := int((b[0] >> 4) & 0x7)
|
mask := p[1]&maskBit != 0
|
||||||
mask := b[1]&maskBit != 0
|
c.readRemaining = int64(p[1] & 0x7f)
|
||||||
c.readRemaining = int64(b[1] & 0x7f)
|
|
||||||
|
|
||||||
if reserved != 0 {
|
c.readDecompress = false
|
||||||
return noFrame, c.handleProtocolError("unexpected reserved bits " + strconv.Itoa(reserved))
|
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
|
||||||
|
c.readDecompress = true
|
||||||
|
p[0] &^= rsv1Bit
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
|
||||||
|
return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch frameType {
|
switch frameType {
|
||||||
@@ -678,15 +751,17 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
|
|
||||||
switch c.readRemaining {
|
switch c.readRemaining {
|
||||||
case 126:
|
case 126:
|
||||||
if err := c.readFull(b[:2]); err != nil {
|
p, err := c.read(2)
|
||||||
|
if err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
c.readRemaining = int64(binary.BigEndian.Uint16(b[:2]))
|
c.readRemaining = int64(binary.BigEndian.Uint16(p))
|
||||||
case 127:
|
case 127:
|
||||||
if err := c.readFull(b[:8]); err != nil {
|
p, err := c.read(8)
|
||||||
|
if err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
c.readRemaining = int64(binary.BigEndian.Uint64(b[:8]))
|
c.readRemaining = int64(binary.BigEndian.Uint64(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Handle frame masking.
|
// 4. Handle frame masking.
|
||||||
@@ -697,9 +772,11 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
|
|
||||||
if mask {
|
if mask {
|
||||||
c.readMaskPos = 0
|
c.readMaskPos = 0
|
||||||
if err := c.readFull(c.readMaskKey[:]); err != nil {
|
p, err := c.read(len(c.readMaskKey))
|
||||||
|
if err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
|
copy(c.readMaskKey[:], p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. For text and binary messages, enforce read limit and return.
|
// 5. For text and binary messages, enforce read limit and return.
|
||||||
@@ -719,9 +796,9 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
|
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if c.readRemaining > 0 {
|
if c.readRemaining > 0 {
|
||||||
payload = make([]byte, c.readRemaining)
|
payload, err = c.read(int(c.readRemaining))
|
||||||
c.readRemaining = 0
|
c.readRemaining = 0
|
||||||
if err := c.readFull(payload); err != nil {
|
if err != nil {
|
||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
if c.isServer {
|
if c.isServer {
|
||||||
@@ -741,15 +818,21 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
return noFrame, err
|
return noFrame, err
|
||||||
}
|
}
|
||||||
case CloseMessage:
|
case CloseMessage:
|
||||||
echoMessage := []byte{}
|
|
||||||
closeCode := CloseNoStatusReceived
|
closeCode := CloseNoStatusReceived
|
||||||
closeText := ""
|
closeText := ""
|
||||||
if len(payload) >= 2 {
|
if len(payload) >= 2 {
|
||||||
echoMessage = payload[:2]
|
|
||||||
closeCode = int(binary.BigEndian.Uint16(payload))
|
closeCode = int(binary.BigEndian.Uint16(payload))
|
||||||
|
if !isValidReceivedCloseCode(closeCode) {
|
||||||
|
return noFrame, c.handleProtocolError("invalid close code")
|
||||||
|
}
|
||||||
closeText = string(payload[2:])
|
closeText = string(payload[2:])
|
||||||
|
if !utf8.ValidString(closeText) {
|
||||||
|
return noFrame, c.handleProtocolError("invalid utf8 payload in close frame")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.handleClose(closeCode, closeText); err != nil {
|
||||||
|
return noFrame, err
|
||||||
}
|
}
|
||||||
c.WriteControl(CloseMessage, echoMessage, time.Now().Add(writeWait))
|
|
||||||
return noFrame, &CloseError{Code: closeCode, Text: closeText}
|
return noFrame, &CloseError{Code: closeCode, Text: closeText}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,7 +856,7 @@ func (c *Conn) handleProtocolError(message string) error {
|
|||||||
// this method return the same error.
|
// this method return the same error.
|
||||||
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
||||||
|
|
||||||
c.readSeq++
|
c.messageReader = nil
|
||||||
c.readLength = 0
|
c.readLength = 0
|
||||||
|
|
||||||
for c.readErr == nil {
|
for c.readErr == nil {
|
||||||
@@ -783,7 +866,12 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if frameType == TextMessage || frameType == BinaryMessage {
|
if frameType == TextMessage || frameType == BinaryMessage {
|
||||||
return frameType, messageReader{c, c.readSeq}, nil
|
c.messageReader = &messageReader{c}
|
||||||
|
var r io.Reader = c.messageReader
|
||||||
|
if c.readDecompress {
|
||||||
|
r = c.newDecompressionReader(r)
|
||||||
|
}
|
||||||
|
return frameType, r, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,48 +886,48 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
|||||||
return noFrame, nil, c.readErr
|
return noFrame, nil, c.readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
type messageReader struct {
|
type messageReader struct{ c *Conn }
|
||||||
c *Conn
|
|
||||||
seq int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r messageReader) Read(b []byte) (int, error) {
|
func (r *messageReader) Read(b []byte) (int, error) {
|
||||||
|
c := r.c
|
||||||
if r.seq != r.c.readSeq {
|
if c.messageReader != r {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
for r.c.readErr == nil {
|
for c.readErr == nil {
|
||||||
|
|
||||||
if r.c.readRemaining > 0 {
|
if c.readRemaining > 0 {
|
||||||
if int64(len(b)) > r.c.readRemaining {
|
if int64(len(b)) > c.readRemaining {
|
||||||
b = b[:r.c.readRemaining]
|
b = b[:c.readRemaining]
|
||||||
}
|
}
|
||||||
n, err := r.c.br.Read(b)
|
n, err := c.br.Read(b)
|
||||||
r.c.readErr = hideTempErr(err)
|
c.readErr = hideTempErr(err)
|
||||||
if r.c.isServer {
|
if c.isServer {
|
||||||
r.c.readMaskPos = maskBytes(r.c.readMaskKey, r.c.readMaskPos, b[:n])
|
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
|
||||||
}
|
}
|
||||||
r.c.readRemaining -= int64(n)
|
c.readRemaining -= int64(n)
|
||||||
return n, r.c.readErr
|
if c.readRemaining > 0 && c.readErr == io.EOF {
|
||||||
|
c.readErr = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
return n, c.readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.c.readFinal {
|
if c.readFinal {
|
||||||
r.c.readSeq++
|
c.messageReader = nil
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
frameType, err := r.c.advanceFrame()
|
frameType, err := c.advanceFrame()
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
r.c.readErr = hideTempErr(err)
|
c.readErr = hideTempErr(err)
|
||||||
case frameType == TextMessage || frameType == BinaryMessage:
|
case frameType == TextMessage || frameType == BinaryMessage:
|
||||||
r.c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
|
c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.c.readErr
|
err := c.readErr
|
||||||
if err == io.EOF && r.seq == r.c.readSeq {
|
if err == io.EOF && c.messageReader == r {
|
||||||
err = errUnexpectedEOF
|
err = errUnexpectedEOF
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -872,6 +960,34 @@ func (c *Conn) SetReadLimit(limit int64) {
|
|||||||
c.readLimit = limit
|
c.readLimit = limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseHandler returns the current close handler
|
||||||
|
func (c *Conn) CloseHandler() func(code int, text string) error {
|
||||||
|
return c.handleClose
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCloseHandler sets the handler for close messages received from the peer.
|
||||||
|
// 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.
|
||||||
|
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||||
|
if h == nil {
|
||||||
|
h = func(code int, text string) error {
|
||||||
|
message := []byte{}
|
||||||
|
if code != CloseNoStatusReceived {
|
||||||
|
message = FormatCloseMessage(code, "")
|
||||||
|
}
|
||||||
|
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.handleClose = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingHandler returns the current ping handler
|
||||||
|
func (c *Conn) PingHandler() func(appData string) error {
|
||||||
|
return c.handlePing
|
||||||
|
}
|
||||||
|
|
||||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||||
// The appData argument to h is the PING frame application data. The default
|
// The appData argument to h is the PING frame application data. The default
|
||||||
// ping handler sends a pong to the peer.
|
// ping handler sends a pong to the peer.
|
||||||
@@ -890,6 +1006,11 @@ func (c *Conn) SetPingHandler(h func(appData string) error) {
|
|||||||
c.handlePing = h
|
c.handlePing = h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PongHandler returns the current pong handler
|
||||||
|
func (c *Conn) PongHandler() func(appData string) error {
|
||||||
|
return c.handlePong
|
||||||
|
}
|
||||||
|
|
||||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||||
// The appData argument to h is the PONG frame application data. The default
|
// The appData argument to h is the PONG frame application data. The default
|
||||||
// pong handler does nothing.
|
// pong handler does nothing.
|
||||||
@@ -906,6 +1027,13 @@ func (c *Conn) UnderlyingConn() net.Conn {
|
|||||||
return c.conn
|
return c.conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableWriteCompression enables and disables write compression of
|
||||||
|
// subsequent text and binary messages. This function is a noop if
|
||||||
|
// compression was not negotiated with the peer.
|
||||||
|
func (c *Conn) EnableWriteCompression(enable bool) {
|
||||||
|
c.enableWriteCompression = enable
|
||||||
|
}
|
||||||
|
|
||||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
||||||
func FormatCloseMessage(closeCode int, text string) []byte {
|
func FormatCloseMessage(closeCode int, text string) []byte {
|
||||||
buf := make([]byte, 2+len(text))
|
buf := make([]byte, 2+len(text))
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2016 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.5
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
c.br.Discard(len(p))
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2016 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.5
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (c *Conn) read(n int) ([]byte, error) {
|
||||||
|
p, err := c.br.Peek(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = errUnexpectedEOF
|
||||||
|
}
|
||||||
|
if len(p) > 0 {
|
||||||
|
// advance over the bytes just read
|
||||||
|
io.ReadFull(c.br, p)
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
+33
-8
@@ -85,18 +85,22 @@
|
|||||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||||
// methods to send a control message to the peer.
|
// methods to send a control message to the peer.
|
||||||
//
|
//
|
||||||
// Connections handle received ping and pong messages by invoking callback
|
|
||||||
// functions set with SetPingHandler and SetPongHandler methods. The default
|
|
||||||
// ping handler sends a pong to the client. The callback functions can be
|
|
||||||
// invoked from the NextReader, ReadMessage or the message Read method.
|
|
||||||
//
|
|
||||||
// Connections handle received close messages by sending a close message to the
|
// Connections handle received close messages by sending a close message to the
|
||||||
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
||||||
// message Read method.
|
// message Read method.
|
||||||
//
|
//
|
||||||
// The application must read the connection to process ping and close messages
|
// Connections handle received ping and pong messages by invoking callback
|
||||||
// sent from the peer. If the application is not otherwise interested in
|
// functions set with SetPingHandler and SetPongHandler methods. The callback
|
||||||
// messages from the peer, then the application should start a goroutine to
|
// functions are called from the NextReader, ReadMessage and the message Read
|
||||||
|
// methods.
|
||||||
|
//
|
||||||
|
// The default ping handler sends a pong to the peer. The application's reading
|
||||||
|
// goroutine can block for a short time while the handler writes the pong data
|
||||||
|
// to the connection.
|
||||||
|
//
|
||||||
|
// The application must read the connection to process ping, pong and close
|
||||||
|
// messages sent from the peer. If the application is not otherwise interested
|
||||||
|
// in messages from the peer, then the application should start a goroutine to
|
||||||
// read and discard messages from the peer. A simple example is:
|
// read and discard messages from the peer. A simple example is:
|
||||||
//
|
//
|
||||||
// func readLoop(c *websocket.Conn) {
|
// func readLoop(c *websocket.Conn) {
|
||||||
@@ -145,4 +149,25 @@
|
|||||||
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
||||||
// application's responsibility to check the Origin header before calling
|
// application's responsibility to check the Origin header before calling
|
||||||
// Upgrade.
|
// Upgrade.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Per message compression of messages written to a connection can be enabled
|
||||||
|
// or disabled by calling the corresponding Conn method:
|
||||||
|
//
|
||||||
|
// conn.EnableWriteCompression(true)
|
||||||
|
//
|
||||||
|
// Currently this package does not support compression with "context takeover".
|
||||||
|
// This means that messages must be compressed and decompressed in isolation,
|
||||||
|
// without retaining sliding window or dictionary state across messages. For
|
||||||
|
// more details refer to RFC 7692.
|
||||||
|
//
|
||||||
|
// Use of compression is experimental and may result in decreased performance.
|
||||||
package websocket
|
package websocket
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
|
func newMaskKey() [4]byte {
|
||||||
|
n := rand.Uint32()
|
||||||
|
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||||
|
|
||||||
|
// Mask one byte at a time for small buffers.
|
||||||
|
if len(b) < 2*wordSize {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time to word boundary.
|
||||||
|
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||||
|
n = wordSize - n
|
||||||
|
for i := range b[:n] {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create aligned word size key.
|
||||||
|
var k [wordSize]byte
|
||||||
|
for i := range k {
|
||||||
|
k[i] = key[(pos+i)&3]
|
||||||
|
}
|
||||||
|
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||||
|
|
||||||
|
// Mask one word at a time.
|
||||||
|
n := (len(b) / wordSize) * wordSize
|
||||||
|
for i := 0; i < n; i += wordSize {
|
||||||
|
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask one byte at a time for remaining bytes.
|
||||||
|
b = b[n:]
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= key[pos&3]
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos & 3
|
||||||
|
}
|
||||||
+40
-1
@@ -46,6 +46,12 @@ type Upgrader struct {
|
|||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||||
// must match the host of the request.
|
// must match the host of the request.
|
||||||
CheckOrigin func(r *http.Request) bool
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
|
||||||
|
// EnableCompression specify if the server should attempt to negotiate per
|
||||||
|
// message compression (RFC 7692). Setting this value to true does not
|
||||||
|
// guarantee that compression will be supported. Currently only "no context
|
||||||
|
// takeover" modes are supported.
|
||||||
|
EnableCompression bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
||||||
@@ -53,6 +59,7 @@ func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status in
|
|||||||
if u.Error != nil {
|
if u.Error != nil {
|
||||||
u.Error(w, r, status, err)
|
u.Error(w, r, status, err)
|
||||||
} else {
|
} else {
|
||||||
|
w.Header().Set("Sec-Websocket-Version", "13")
|
||||||
http.Error(w, http.StatusText(status), status)
|
http.Error(w, http.StatusText(status), status)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -99,7 +106,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
if r.Method != "GET" {
|
if r.Method != "GET" {
|
||||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET")
|
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET")
|
||||||
}
|
}
|
||||||
if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" {
|
|
||||||
|
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||||
|
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific Sec-Websocket-Extensions headers are unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +138,18 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
|
|
||||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||||
|
|
||||||
|
// Negotiate PMCE
|
||||||
|
var compress bool
|
||||||
|
if u.EnableCompression {
|
||||||
|
for _, ext := range parseExtensions(r.Header) {
|
||||||
|
if ext[""] != "permessage-deflate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
compress = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
netConn net.Conn
|
netConn net.Conn
|
||||||
br *bufio.Reader
|
br *bufio.Reader
|
||||||
@@ -151,6 +175,11 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize)
|
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize)
|
||||||
c.subprotocol = subprotocol
|
c.subprotocol = subprotocol
|
||||||
|
|
||||||
|
if compress {
|
||||||
|
c.newCompressionWriter = compressNoContextTakeover
|
||||||
|
c.newDecompressionReader = decompressNoContextTakeover
|
||||||
|
}
|
||||||
|
|
||||||
p := c.writeBuf[:0]
|
p := c.writeBuf[:0]
|
||||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||||
p = append(p, computeAcceptKey(challengeKey)...)
|
p = append(p, computeAcceptKey(challengeKey)...)
|
||||||
@@ -160,6 +189,9 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
|||||||
p = append(p, c.subprotocol...)
|
p = append(p, c.subprotocol...)
|
||||||
p = append(p, "\r\n"...)
|
p = append(p, "\r\n"...)
|
||||||
}
|
}
|
||||||
|
if compress {
|
||||||
|
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||||
|
}
|
||||||
for k, vs := range responseHeader {
|
for k, vs := range responseHeader {
|
||||||
if k == "Sec-Websocket-Protocol" {
|
if k == "Sec-Websocket-Protocol" {
|
||||||
continue
|
continue
|
||||||
@@ -251,3 +283,10 @@ func Subprotocols(r *http.Request) []string {
|
|||||||
}
|
}
|
||||||
return protocols
|
return protocols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsWebSocketUpgrade returns true if the client requested upgrade to the
|
||||||
|
// WebSocket protocol.
|
||||||
|
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||||
|
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||||
|
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||||
|
}
|
||||||
|
|||||||
+183
-13
@@ -13,19 +13,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tokenListContainsValue returns true if the 1#token header with the given
|
|
||||||
// name contains token.
|
|
||||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
|
||||||
for _, v := range header[name] {
|
|
||||||
for _, s := range strings.Split(v, ",") {
|
|
||||||
if strings.EqualFold(value, strings.TrimSpace(s)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||||
|
|
||||||
func computeAcceptKey(challengeKey string) string {
|
func computeAcceptKey(challengeKey string) string {
|
||||||
@@ -42,3 +29,186 @@ func generateChallengeKey() (string, error) {
|
|||||||
}
|
}
|
||||||
return base64.StdEncoding.EncodeToString(p), nil
|
return base64.StdEncoding.EncodeToString(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Octet types from RFC 2616.
|
||||||
|
var octetTypes [256]byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
isTokenOctet = 1 << iota
|
||||||
|
isSpaceOctet
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// From RFC 2616
|
||||||
|
//
|
||||||
|
// OCTET = <any 8-bit sequence of data>
|
||||||
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||||
|
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||||
|
// CR = <US-ASCII CR, carriage return (13)>
|
||||||
|
// LF = <US-ASCII LF, linefeed (10)>
|
||||||
|
// SP = <US-ASCII SP, space (32)>
|
||||||
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||||
|
// <"> = <US-ASCII double-quote mark (34)>
|
||||||
|
// CRLF = CR LF
|
||||||
|
// LWS = [CRLF] 1*( SP | HT )
|
||||||
|
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||||
|
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||||
|
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||||
|
// token = 1*<any CHAR except CTLs or separators>
|
||||||
|
// qdtext = <any TEXT except <">>
|
||||||
|
|
||||||
|
for c := 0; c < 256; c++ {
|
||||||
|
var t byte
|
||||||
|
isCtl := c <= 31 || c == 127
|
||||||
|
isChar := 0 <= c && c <= 127
|
||||||
|
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||||
|
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||||
|
t |= isSpaceOctet
|
||||||
|
}
|
||||||
|
if isChar && !isCtl && !isSeparator {
|
||||||
|
t |= isTokenOctet
|
||||||
|
}
|
||||||
|
octetTypes[c] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipSpace(s string) (rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextToken(s string) (token, rest string) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:i], s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||||
|
if !strings.HasPrefix(s, "\"") {
|
||||||
|
return nextToken(s)
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '"':
|
||||||
|
return s[:i], s[i+1:]
|
||||||
|
case '\\':
|
||||||
|
p := make([]byte, len(s)-1)
|
||||||
|
j := copy(p, s[:i])
|
||||||
|
escape := true
|
||||||
|
for i = i + 1; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
switch {
|
||||||
|
case escape:
|
||||||
|
escape = false
|
||||||
|
p[j] = b
|
||||||
|
j += 1
|
||||||
|
case b == '\\':
|
||||||
|
escape = true
|
||||||
|
case b == '"':
|
||||||
|
return string(p[:j]), s[i+1:]
|
||||||
|
default:
|
||||||
|
p[j] = b
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenListContainsValue returns true if the 1#token header with the given
|
||||||
|
// name contains token.
|
||||||
|
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||||
|
headers:
|
||||||
|
for _, s := range header[name] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
if strings.EqualFold(t, value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtensiosn parses WebSocket extensions from a header.
|
||||||
|
func parseExtensions(header http.Header) []map[string]string {
|
||||||
|
|
||||||
|
// From RFC 6455:
|
||||||
|
//
|
||||||
|
// Sec-WebSocket-Extensions = extension-list
|
||||||
|
// extension-list = 1#extension
|
||||||
|
// extension = extension-token *( ";" extension-param )
|
||||||
|
// extension-token = registered-token
|
||||||
|
// registered-token = token
|
||||||
|
// extension-param = token [ "=" (token | quoted-string) ]
|
||||||
|
// ;When using the quoted-string syntax variant, the value
|
||||||
|
// ;after quoted-string unescaping MUST conform to the
|
||||||
|
// ;'token' ABNF.
|
||||||
|
|
||||||
|
var result []map[string]string
|
||||||
|
headers:
|
||||||
|
for _, s := range header["Sec-Websocket-Extensions"] {
|
||||||
|
for {
|
||||||
|
var t string
|
||||||
|
t, s = nextToken(skipSpace(s))
|
||||||
|
if t == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext := map[string]string{"": t}
|
||||||
|
for {
|
||||||
|
s = skipSpace(s)
|
||||||
|
if !strings.HasPrefix(s, ";") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var k string
|
||||||
|
k, s = nextToken(skipSpace(s[1:]))
|
||||||
|
if k == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = skipSpace(s)
|
||||||
|
var v string
|
||||||
|
if strings.HasPrefix(s, "=") {
|
||||||
|
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
|
||||||
|
s = skipSpace(s)
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' && s[0] != ';' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
ext[k] = v
|
||||||
|
}
|
||||||
|
if s != "" && s[0] != ',' {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
result = append(result, ext)
|
||||||
|
if s == "" {
|
||||||
|
continue headers
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+6
@@ -314,6 +314,12 @@
|
|||||||
"version": "v1.21.1",
|
"version": "v1.21.1",
|
||||||
"versionExact": "v1.21.1"
|
"versionExact": "v1.21.1"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "B4bk7vdV9aD7AhrtATXfgEacdqE=",
|
||||||
|
"path": "github.com/gorilla/websocket",
|
||||||
|
"revision": "3ab3a8b8831546bd18fd182c20687ca853b2bb13",
|
||||||
|
"revisionTime": "2016-12-15T22:53:35Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "8z32QKTSDusa4QQyunKE4kyYXZ8=",
|
"checksumSHA1": "8z32QKTSDusa4QQyunKE4kyYXZ8=",
|
||||||
"path": "github.com/patrickmn/go-cache",
|
"path": "github.com/patrickmn/go-cache",
|
||||||
|
|||||||
Reference in New Issue
Block a user