From 87f6f6a22ebfbc3f89b9ccdc7fddd1b914c095f9 Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Sun, 12 Oct 2014 09:34:51 -0700 Subject: [PATCH 1/8] Add documentation about origin policy. --- doc.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc.go b/doc.go index efde3dc..798de9c 100644 --- a/doc.go +++ b/doc.go @@ -117,4 +117,29 @@ // } // } // } +// +// Origin Considerations +// +// Web browsers allow Javascript applications to open a WebSocket connection to +// any host. It's up to the server to enforce an origin policy using the Origin +// request header sent by the browser. +// +// The Upgrader calls the function specified in the CheckOrigin field to check +// the origin. If the CheckOrigin function returns false, then the Upgrade +// method fails the WebSocket handshake with HTTP status 403. +// +// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail +// the handshake if the Origin request header is present and not equal to the +// Host request header. +// +// An application can allow connections from any origin by specifying a +// function that always returns true: +// +// var upgrader = websocket.Upgrader{ +// CheckOrigin: func(r *http.Request) bool { return true }, +// } +// +// The deprecated Upgrade function does enforce an origin policy. It's the +// application's responsibility to check the Origin header before calling +// Upgrade. package websocket From 02eec998dac2a937d8f615f99b43c602d1017e03 Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Fri, 31 Oct 2014 14:51:45 -0700 Subject: [PATCH 2/8] Improve documentation. --- doc.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc.go b/doc.go index 798de9c..0d2bd91 100644 --- a/doc.go +++ b/doc.go @@ -97,10 +97,13 @@ // // Concurrency // -// A Conn supports a single concurrent caller to the write methods (NextWriter, -// SetWriteDeadline, WriteMessage) and a single concurrent caller to the read -// methods (NextReader, SetReadDeadline, ReadMessage). The Close and -// WriteControl methods can be called concurrently with all other methods. +// Connections do not support concurrent calls to the write methods +// (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read +// methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do +// support a concurrent reader and writer. +// +// The Close and WriteControl methods can be called concurrently with all other +// methods. // // Read is Required // @@ -139,7 +142,7 @@ // CheckOrigin: func(r *http.Request) bool { return true }, // } // -// The deprecated Upgrade function does 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 // Upgrade. package websocket From 47f93dfaed0afa5b5fefe94620365a69f55d910a Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Fri, 31 Oct 2014 14:52:20 -0700 Subject: [PATCH 3/8] Improve errors. - Use new closeError type for reporting close frames to the application. - Use closeError with code 1006 when the peer closes connection without sending a close frame. The error io.ErrUnexpectedEOF was used previously. This change helps developers distinguish abnormal closure and an unexpected EOF in the JSON parser. --- conn.go | 38 +++++++++++++++++++++++++------------- conn_test.go | 14 +++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/conn.go b/conn.go index 2701142..73c64a4 100644 --- a/conn.go +++ b/conn.go @@ -70,18 +70,30 @@ var ( ErrReadLimit = errors.New("websocket: read limit exceeded") ) -type websocketError struct { +// netError satisfies the net Error interface. +type netError struct { msg string temporary bool timeout bool } -func (e *websocketError) Error() string { return e.msg } -func (e *websocketError) Temporary() bool { return e.temporary } -func (e *websocketError) Timeout() bool { return e.timeout } +func (e *netError) Error() string { return e.msg } +func (e *netError) Temporary() bool { return e.temporary } +func (e *netError) Timeout() bool { return e.timeout } + +// closeError represents close frame. +type closeError struct { + code int + text string +} + +func (e *closeError) Error() string { + return "websocket: close " + strconv.Itoa(e.code) + " " + e.text +} var ( - errWriteTimeout = &websocketError{msg: "websocket: write timeout", timeout: true} + errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true} + errUnexpectedEOF = &closeError{code: CloseAbnormalClosure, text: io.ErrUnexpectedEOF.Error()} errBadWriteOpCode = errors.New("websocket: bad write message type") errWriteClosed = errors.New("websocket: write closed") errInvalidControlFrame = errors.New("websocket: invalid control frame") @@ -527,7 +539,7 @@ func (c *Conn) readFull(p []byte) (err error) { if n == len(p) { err = nil } else if err == io.EOF { - err = io.ErrUnexpectedEOF + err = errUnexpectedEOF } return } @@ -649,17 +661,17 @@ func (c *Conn) advanceFrame() (int, error) { } case CloseMessage: c.WriteControl(CloseMessage, []byte{}, time.Now().Add(writeWait)) - if len(payload) < 2 { - return noFrame, io.EOF + closeCode := CloseNoStatusReceived + closeText := "" + if len(payload) >= 2 { + closeCode = int(binary.BigEndian.Uint16(payload)) + closeText = string(payload[2:]) } - closeCode := binary.BigEndian.Uint16(payload) switch closeCode { case CloseNormalClosure, CloseGoingAway: return noFrame, io.EOF default: - return noFrame, errors.New("websocket: close " + - strconv.Itoa(int(closeCode)) + " " + - string(payload[2:])) + return noFrame, &closeError{code: closeCode, text: closeText} } } @@ -739,7 +751,7 @@ func (r messageReader) Read(b []byte) (int, error) { err := r.c.readErr if err == io.EOF && r.seq == r.c.readSeq { - err = io.ErrUnexpectedEOF + err = errUnexpectedEOF } return 0, err } diff --git a/conn_test.go b/conn_test.go index 632725a..1f1197e 100644 --- a/conn_test.go +++ b/conn_test.go @@ -152,7 +152,7 @@ func TestCloseBeforeFinalFrame(t *testing.T) { w, _ := wc.NextWriter(BinaryMessage) w.Write(make([]byte, bufSize+bufSize/2)) - wc.WriteControl(CloseMessage, []byte{}, time.Now().Add(10*time.Second)) + wc.WriteControl(CloseMessage, FormatCloseMessage(CloseNormalClosure, ""), time.Now().Add(10*time.Second)) w.Close() op, r, err := rc.NextReader() @@ -160,8 +160,8 @@ func TestCloseBeforeFinalFrame(t *testing.T) { t.Fatalf("NextReader() returned %d, %v", op, err) } _, err = io.Copy(ioutil.Discard, r) - if err != io.ErrUnexpectedEOF { - t.Fatalf("io.Copy() returned %v, want %v", err, io.ErrUnexpectedEOF) + if err != errUnexpectedEOF { + t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) } _, _, err = rc.NextReader() if err != io.EOF { @@ -184,12 +184,12 @@ func TestEOFBeforeFinalFrame(t *testing.T) { t.Fatalf("NextReader() returned %d, %v", op, err) } _, err = io.Copy(ioutil.Discard, r) - if err != io.ErrUnexpectedEOF { - t.Fatalf("io.Copy() returned %v, want %v", err, io.ErrUnexpectedEOF) + if err != errUnexpectedEOF { + t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) } _, _, err = rc.NextReader() - if err != io.ErrUnexpectedEOF { - t.Fatalf("NextReader() returned %v, want %v", err, io.ErrUnexpectedEOF) + if err != errUnexpectedEOF { + t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF) } } From d9b6ff71d6dac80d62940406b31f8d18ae421731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Ol=C3=A1h?= Date: Mon, 3 Nov 2014 16:02:20 +0100 Subject: [PATCH 4/8] fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ca3ca8..030f25e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,6 @@ Notes: function. 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. Read returns when the input buffer is full or a frame boundary is - encountered, Each call to Write sends a single frame message. The Gorilla + encountered. Each call to Write sends a single frame message. The Gorilla io.Reader and io.WriteCloser operate on a single WebSocket message. From 6af932933af5c52b246e39cb3f02d469cad00cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Ol=C3=A1h?= Date: Mon, 3 Nov 2014 16:48:34 +0100 Subject: [PATCH 5/8] fix typo in conn.go --- conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conn.go b/conn.go index 73c64a4..dc7d111 100644 --- a/conn.go +++ b/conn.go @@ -520,7 +520,7 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error { // SetWriteDeadline sets the write deadline on the underlying network // connection. After a write has timed out, the websocket state is corrupt and // all future writes will return an error. A zero value for t means writes will -// not time out +// not time out. func (c *Conn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t return nil From 7d2ea39ebc0c0e8335bc48963a83146ee1200adb Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Wed, 5 Nov 2014 12:26:52 -0800 Subject: [PATCH 6/8] Cleanup buffer size calculations. --- client.go | 12 +----------- conn.go | 54 ++++++++++++++++++++++++++++++------------------------ server.go | 15 +-------------- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/client.go b/client.go index 3b5cac4..c25d24f 100644 --- a/client.go +++ b/client.go @@ -215,16 +215,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re } } - readBufferSize := d.ReadBufferSize - if readBufferSize == 0 { - readBufferSize = 4096 - } - - writeBufferSize := d.WriteBufferSize - if writeBufferSize == 0 { - writeBufferSize = 4096 - } - if len(d.Subprotocols) > 0 { h := http.Header{} for k, v := range requestHeader { @@ -234,7 +224,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re requestHeader = h } - conn, resp, err := NewClient(netConn, u, requestHeader, readBufferSize, writeBufferSize) + conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize) if err != nil { return nil, resp, err } diff --git a/conn.go b/conn.go index dc7d111..005b6d8 100644 --- a/conn.go +++ b/conn.go @@ -16,6 +16,20 @@ import ( "time" ) +const ( + maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask + maxControlFramePayloadSize = 125 + finalBit = 1 << 7 + maskBit = 1 << 7 + writeWait = time.Second + + defaultReadBufferSize = 4096 + defaultWriteBufferSize = 4096 + + continuationFrame = 0 + noFrame = -1 +) + // Close codes defined in RFC 6455, section 11.7. const ( CloseNormalClosure = 1000 @@ -55,20 +69,13 @@ const ( PongMessage = 10 ) -var ( - continuationFrame = 0 - noFrame = -1 -) +// ErrCloseSent is returned when the application writes a message to the +// connection after sending a close message. +var ErrCloseSent = errors.New("websocket: close sent") -var ( - // ErrCloseSent is returned when the application writes a message to the - // connection after sending a close message. - ErrCloseSent = errors.New("websocket: close sent") - - // ErrReadLimit is returned when reading a message that is larger than the - // read limit set for the connection. - ErrReadLimit = errors.New("websocket: read limit exceeded") -) +// ErrReadLimit is returned when reading a message that is larger than the +// read limit set for the connection. +var ErrReadLimit = errors.New("websocket: read limit exceeded") // netError satisfies the net Error interface. type netError struct { @@ -99,14 +106,6 @@ var ( errInvalidControlFrame = errors.New("websocket: invalid control frame") ) -const ( - maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask - maxControlFramePayloadSize = 125 - finalBit = 1 << 7 - maskBit = 1 << 7 - writeWait = time.Second -) - func hideTempErr(err error) error { if e, ok := err.(net.Error); ok && e.Temporary() { err = struct{ error }{err} @@ -167,17 +166,24 @@ type Conn struct { handlePing func(string) error } -func newConn(conn net.Conn, isServer bool, readBufSize, writeBufSize int) *Conn { +func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { mu := make(chan bool, 1) mu <- true + if readBufferSize == 0 { + readBufferSize = defaultReadBufferSize + } + if writeBufferSize == 0 { + writeBufferSize = defaultWriteBufferSize + } + c := &Conn{ isServer: isServer, - br: bufio.NewReaderSize(conn, readBufSize), + br: bufio.NewReaderSize(conn, readBufferSize), conn: conn, mu: mu, readFinal: true, - writeBuf: make([]byte, writeBufSize+maxFrameHeaderSize), + writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), writeFrameType: noFrame, writePos: maxFrameHeaderSize, } diff --git a/server.go b/server.go index c24c410..349e5b9 100644 --- a/server.go +++ b/server.go @@ -21,11 +21,6 @@ type HandshakeError struct { func (e HandshakeError) Error() string { return e.message } -const ( - defaultReadBufferSize = 4096 - defaultWriteBufferSize = 4096 -) - // Upgrader specifies parameters for upgrading an HTTP connection to a // WebSocket connection. type Upgrader struct { @@ -147,15 +142,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade return nil, errors.New("websocket: client sent data before handshake is complete") } - readBufSize := u.ReadBufferSize - if readBufSize == 0 { - readBufSize = defaultReadBufferSize - } - writeBufSize := u.WriteBufferSize - if writeBufSize == 0 { - writeBufSize = defaultWriteBufferSize - } - c := newConn(netConn, true, readBufSize, writeBufSize) + c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize) c.subprotocol = subprotocol p := c.writeBuf[:0] From ea78a26f8048532764e0a020ee901572dc7f6046 Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Thu, 6 Nov 2014 16:56:58 -0800 Subject: [PATCH 7/8] Don't hide Timeout on errors from underying net conn. --- conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conn.go b/conn.go index 005b6d8..86c35e5 100644 --- a/conn.go +++ b/conn.go @@ -108,7 +108,7 @@ var ( func hideTempErr(err error) error { if e, ok := err.(net.Error); ok && e.Temporary() { - err = struct{ error }{err} + err = &netError{msg: e.Error(), timeout: e.Timeout()} } return err } From f761cdb666383d5db7a8bea9ff8c9db35332b4a0 Mon Sep 17 00:00:00 2001 From: Gary Burd Date: Mon, 10 Nov 2014 09:56:46 -0800 Subject: [PATCH 8/8] Update README to use new path for Go sub-repos. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 030f25e..9ad75a0 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn - - + + @@ -50,7 +50,7 @@ Notes: 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 2. The application can get the type of a received data message by implementing - a [Codec marshal](http://godoc.org/code.google.com/p/go.net/websocket#Codec.Marshal) + a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) function. 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. Read returns when the input buffer is full or a frame boundary is
gorillago.netgithub.com/gorillagolang.org/x/net
RFC 6455 Features