diff --git a/app/api_topology.go b/app/api_topology.go index 855e671fe3..305567a5dc 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -90,7 +90,9 @@ func handleWebsocket( go func(c *websocket.Conn) { for { // just discard everything the browser sends if _, _, err := c.NextReader(); err != nil { - log.Println("err:", err) + if !xfer.IsExpectedWSCloseError(err) { + log.Println("err:", err) + } close(quit) break } @@ -111,7 +113,9 @@ func handleWebsocket( previousTopo = newTopo if err := conn.SetWriteDeadline(time.Now().Add(websocketTimeout)); err != nil { - log.Println("err:", err) + if !xfer.IsExpectedWSCloseError(err) { + log.Println("err:", err) + } return } diff --git a/app/controls.go b/app/controls.go index 791d922c76..00eb43d208 100644 --- a/app/controls.go +++ b/app/controls.go @@ -79,6 +79,8 @@ func handleProbeWS(cr ControlRouter) CtxHandlerFunc { return } defer cr.Deregister(ctx, probeID, id) - codec.WaitForReadError() + if err := codec.WaitForReadError(); err != nil && !xfer.IsExpectedWSCloseError(err) { + log.Printf("Error reading from probe %s control websocket: %v", probeID, err) + } } } diff --git a/app/pipes.go b/app/pipes.go index 9f5cb71cc5..7ca90b5434 100644 --- a/app/pipes.go +++ b/app/pipes.go @@ -6,6 +6,8 @@ import ( log "github.com/Sirupsen/logrus" "github.com/gorilla/mux" "golang.org/x/net/context" + + "github.com/weaveworks/scope/common/xfer" ) // RegisterPipeRoutes registers the pipe routes @@ -41,7 +43,9 @@ func handlePipeWs(pr PipeRouter, end End) CtxHandlerFunc { defer conn.Close() log.Infof("Pipe success %s (%d)", id, end) - pipe.CopyToWebsocket(endIO, conn) + if err := pipe.CopyToWebsocket(endIO, conn); err != nil && !xfer.IsExpectedWSCloseError(err) { + log.Printf("Error copying to pipe %s (%d) websocket: %v", id, end, err) + } } } diff --git a/common/xfer/controls.go b/common/xfer/controls.go index aba3c71d4e..cfb0d1dde5 100644 --- a/common/xfer/controls.go +++ b/common/xfer/controls.go @@ -71,21 +71,21 @@ func ResponseError(err error) Response { type JSONWebsocketCodec struct { sync.Mutex conn *websocket.Conn - err chan struct{} + err chan error } // NewJSONWebsocketCodec makes a new JSONWebsocketCodec func NewJSONWebsocketCodec(conn *websocket.Conn) *JSONWebsocketCodec { return &JSONWebsocketCodec{ conn: conn, - err: make(chan struct{}), + err: make(chan error, 1), } } // WaitForReadError blocks until any read on this codec returns an error. // This is useful to know when the server has disconnected from the client. -func (j *JSONWebsocketCodec) WaitForReadError() { - <-j.err +func (j *JSONWebsocketCodec) WaitForReadError() error { + return <-j.err } // WriteRequest implements rpc.ClientCodec @@ -113,6 +113,7 @@ func (j *JSONWebsocketCodec) WriteResponse(r *rpc.Response, v interface{}) error func (j *JSONWebsocketCodec) readMessage(v interface{}) (*Message, error) { m := Message{Value: v} if err := ReadJSONfromWS(j.conn, &m); err != nil { + j.err <- err close(j.err) return nil, err } diff --git a/common/xfer/websocket.go b/common/xfer/websocket.go index 54d83883c4..1d3dd07be9 100644 --- a/common/xfer/websocket.go +++ b/common/xfer/websocket.go @@ -7,6 +7,17 @@ import ( "github.com/ugorji/go/codec" ) +// IsExpectedWSCloseError returns boolean indicating whether the error is a +// clean disconnection. +func IsExpectedWSCloseError(err error) bool { + return websocket.IsCloseError( + err, + websocket.CloseNormalClosure, + websocket.CloseGoingAway, + websocket.CloseNoStatusReceived, + ) +} + // WriteJSONtoWS writes the JSON encoding of v to the connection. func WriteJSONtoWS(c *websocket.Conn, v interface{}) error { w, err := c.NextWriter(websocket.TextMessage) diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go index 51acf242c9..a353e18565 100644 --- a/vendor/github.com/gorilla/websocket/client.go +++ b/vendor/github.com/gorilla/websocket/client.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "crypto/tls" + "encoding/base64" "errors" "io" "io/ioutil" @@ -95,13 +96,20 @@ func parseURL(s string) (*url.URL, error) { return nil, errMalformedURL } - u.Host = s - u.Opaque = "/" + if i := strings.Index(s, "?"); i >= 0 { + u.RawQuery = s[i+1:] + s = s[:i] + } + if i := strings.Index(s, "/"); i >= 0 { - u.Host = s[:i] u.Opaque = s[i:] + s = s[:i] + } else { + u.Opaque = "/" } + u.Host = s + if strings.Contains(u.Host, "@") { // Don't bother parsing user information because user information is // not allowed in websocket URIs. @@ -197,11 +205,18 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} } for k, vs := range requestHeader { - if k == "Host" { + switch { + case k == "Host": if len(vs) > 0 { req.Host = vs[0] } - } else { + case k == "Upgrade" || + k == "Connection" || + k == "Sec-Websocket-Key" || + k == "Sec-Websocket-Version" || + (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): + return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) + default: req.Header[k] = vs } } @@ -251,11 +266,19 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re } if proxyURL != nil { + connectHeader := make(http.Header) + if user := proxyURL.User; user != nil { + proxyUser := user.Username() + if proxyPassword, passwordSet := user.Password(); passwordSet { + credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) + connectHeader.Set("Proxy-Authorization", "Basic "+credential) + } + } connectReq := &http.Request{ Method: "CONNECT", URL: &url.URL{Opaque: hostPort}, Host: hostPort, - Header: make(http.Header), + Header: connectHeader, } connectReq.Write(netConn) @@ -316,10 +339,9 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re n, _ := io.ReadFull(resp.Body, buf) resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) return nil, resp, ErrBadHandshake - } else { - resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } + resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") netConn.SetDeadline(time.Time{}) diff --git a/vendor/github.com/gorilla/websocket/client_server_test.go b/vendor/github.com/gorilla/websocket/client_server_test.go index ebcba9f708..3f7345dded 100644 --- a/vendor/github.com/gorilla/websocket/client_server_test.go +++ b/vendor/github.com/gorilla/websocket/client_server_test.go @@ -7,6 +7,7 @@ package websocket import ( "crypto/tls" "crypto/x509" + "encoding/base64" "io" "io/ioutil" "net" @@ -41,9 +42,16 @@ type cstServer struct { URL string } +const ( + cstPath = "/a/b" + cstRawQuery = "x=y" + cstRequestURI = cstPath + "?" + cstRawQuery +) + func newServer(t *testing.T) *cstServer { var s cstServer s.Server = httptest.NewServer(cstHandler{t}) + s.Server.URL += cstRequestURI s.URL = makeWsProto(s.Server.URL) return &s } @@ -51,14 +59,20 @@ func newServer(t *testing.T) *cstServer { func newTLSServer(t *testing.T) *cstServer { var s cstServer s.Server = httptest.NewTLSServer(cstHandler{t}) + s.Server.URL += cstRequestURI s.URL = makeWsProto(s.Server.URL) return &s } func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - t.Logf("method %s not allowed", r.Method) - http.Error(w, "method not allowed", 405) + if r.URL.Path != cstPath { + t.Logf("path=%v, want %v", r.URL.Path, cstPath) + http.Error(w, "bad path", 400) + return + } + if r.URL.RawQuery != cstRawQuery { + t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery) + http.Error(w, "bad path", 400) return } subprotos := Subprotocols(r) @@ -162,6 +176,46 @@ func TestProxyDial(t *testing.T) { cstDialer.Proxy = http.ProxyFromEnvironment } +func TestProxyAuthorizationDial(t *testing.T) { + s := newServer(t) + defer s.Close() + + surl, _ := url.Parse(s.URL) + surl.User = url.UserPassword("username", "password") + cstDialer.Proxy = http.ProxyURL(surl) + + connect := false + origHandler := s.Server.Config.Handler + + // Capture the request Host header. + s.Server.Config.Handler = http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + proxyAuth := r.Header.Get("Proxy-Authorization") + expectedProxyAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte("username:password")) + if r.Method == "CONNECT" && proxyAuth == expectedProxyAuth { + connect = true + w.WriteHeader(200) + return + } + + if !connect { + t.Log("connect with proxy authorization not recieved") + http.Error(w, "connect with proxy authorization not recieved", 405) + return + } + origHandler.ServeHTTP(w, r) + }) + + ws, _, err := cstDialer.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) + + cstDialer.Proxy = http.ProxyFromEnvironment +} + func TestDial(t *testing.T) { s := newServer(t) defer s.Close() @@ -193,7 +247,7 @@ func TestDialTLS(t *testing.T) { d := cstDialer d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) } d.TLSClientConfig = &tls.Config{RootCAs: certs} - ws, _, err := d.Dial("wss://example.com/", nil) + ws, _, err := d.Dial("wss://example.com"+cstRequestURI, nil) if err != nil { t.Fatalf("Dial: %v", err) } @@ -268,6 +322,45 @@ func TestDialBadOrigin(t *testing.T) { } } +func TestDialBadHeader(t *testing.T) { + s := newServer(t) + defer s.Close() + + for _, k := range []string{"Upgrade", + "Connection", + "Sec-Websocket-Key", + "Sec-Websocket-Version", + "Sec-Websocket-Protocol"} { + h := http.Header{} + h.Set(k, "bad") + ws, _, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) + if err == nil { + ws.Close() + t.Errorf("Dial with header %s returned nil", k) + } + } +} + +func TestBadMethod(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ws, err := cstUpgrader.Upgrade(w, r, nil) + if err == nil { + t.Errorf("handshake succeeded, expect fail") + ws.Close() + } + })) + defer s.Close() + + resp, err := http.PostForm(s.URL, url.Values{}) + if err != nil { + t.Fatalf("PostForm returned error %v", err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusMethodNotAllowed { + t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed) + } +} + func TestHandshake(t *testing.T) { s := newServer(t) defer s.Close() diff --git a/vendor/github.com/gorilla/websocket/client_test.go b/vendor/github.com/gorilla/websocket/client_test.go index 07a9cb453e..7d2b0844fd 100644 --- a/vendor/github.com/gorilla/websocket/client_test.go +++ b/vendor/github.com/gorilla/websocket/client_test.go @@ -11,16 +11,19 @@ import ( ) var parseURLTests = []struct { - s string - u *url.URL + s string + u *url.URL + rui string }{ - {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, - {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, - {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}}, - {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}}, - {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}}, - {"ss://example.com/a/b", nil}, - {"ws://webmaster@example.com/", nil}, + {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"}, + {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"}, + {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"}, + {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"}, + {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"}, + {"ss://example.com/a/b", nil, ""}, + {"ws://webmaster@example.com/", nil, ""}, + {"wss://example.com/a/b?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b", RawQuery: "x=y"}, "/a/b?x=y"}, + {"wss://example.com?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/", RawQuery: "x=y"}, "/?x=y"}, } func TestParseURL(t *testing.T) { @@ -30,14 +33,19 @@ func TestParseURL(t *testing.T) { t.Errorf("parseURL(%q) returned error %v", tt.s, err) continue } - if tt.u == nil && err == nil { - t.Errorf("parseURL(%q) did not return error", tt.s) + if tt.u == nil { + if err == nil { + t.Errorf("parseURL(%q) did not return error", tt.s) + } continue } if !reflect.DeepEqual(u, tt.u) { - t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u) + t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u) continue } + if u.RequestURI() != tt.rui { + t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui) + } } } diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go index e8b6b3e045..eff26c6328 100644 --- a/vendor/github.com/gorilla/websocket/conn.go +++ b/vendor/github.com/gorilla/websocket/conn.go @@ -99,7 +99,66 @@ type CloseError struct { } func (e *CloseError) Error() string { - return "websocket: close " + strconv.Itoa(e.Code) + " " + e.Text + s := []byte("websocket: close ") + s = strconv.AppendInt(s, int64(e.Code), 10) + switch e.Code { + case CloseNormalClosure: + s = append(s, " (normal)"...) + case CloseGoingAway: + s = append(s, " (going away)"...) + case CloseProtocolError: + s = append(s, " (protocol error)"...) + case CloseUnsupportedData: + s = append(s, " (unsupported data)"...) + case CloseNoStatusReceived: + s = append(s, " (no status)"...) + case CloseAbnormalClosure: + s = append(s, " (abnormal closure)"...) + case CloseInvalidFramePayloadData: + s = append(s, " (invalid payload data)"...) + case ClosePolicyViolation: + s = append(s, " (policy violation)"...) + case CloseMessageTooBig: + s = append(s, " (message too big)"...) + case CloseMandatoryExtension: + s = append(s, " (mandatory extension missing)"...) + case CloseInternalServerErr: + s = append(s, " (internal server error)"...) + case CloseTLSHandshake: + s = append(s, " (TLS handshake error)"...) + } + if e.Text != "" { + s = append(s, ": "...) + s = append(s, e.Text...) + } + return string(s) +} + +// IsCloseError returns boolean indicating whether the error is a *CloseError +// with one of the specified codes. +func IsCloseError(err error, codes ...int) bool { + if e, ok := err.(*CloseError); ok { + for _, code := range codes { + if e.Code == code { + return true + } + } + } + return false +} + +// IsUnexpectedCloseError returns boolean indicating whether the error is a +// *CloseError with a code not in the list of expected codes. +func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { + if e, ok := err.(*CloseError); ok { + for _, code := range expectedCodes { + if e.Code == code { + return false + } + } + return true + } + return false } var ( @@ -155,6 +214,7 @@ type Conn struct { writeFrameType int // type of the current frame. writeSeq int // incremented to invalidate message writers. writeDeadline time.Time + isWriting bool // for best-effort concurrent write detection // Read fields readErr error @@ -168,6 +228,7 @@ type Conn struct { readMaskKey [4]byte handlePong func(string) error handlePing func(string) error + readErrCount int } func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { @@ -308,9 +369,6 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er // // There can be at most one open writer on a connection. NextWriter closes the // previous writer if the application has not already done so. -// -// The NextWriter method and the writers returned from the method cannot be -// accessed by more than one goroutine at a time. func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { if c.writeErr != nil { return nil, c.writeErr @@ -384,9 +442,22 @@ func (c *Conn) flushFrame(final bool, extra []byte) error { } } - // Write the buffers to the connection. + // Write the buffers to the connection with best-effort detection of + // concurrent writes. See the concurrency section in the package + // documentation for more info. + + if c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = true + c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra) + if !c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = false + // Setup for next frame. c.writePos = maxFrameHeaderSize c.writeFrameType = continuationFrame @@ -670,13 +741,15 @@ func (c *Conn) advanceFrame() (int, error) { return noFrame, err } case CloseMessage: - c.WriteControl(CloseMessage, []byte{}, time.Now().Add(writeWait)) + echoMessage := []byte{} closeCode := CloseNoStatusReceived closeText := "" if len(payload) >= 2 { + echoMessage = payload[:2] closeCode = int(binary.BigEndian.Uint16(payload)) closeText = string(payload[2:]) } + c.WriteControl(CloseMessage, echoMessage, time.Now().Add(writeWait)) return noFrame, &CloseError{Code: closeCode, Text: closeText} } @@ -694,8 +767,10 @@ func (c *Conn) handleProtocolError(message string) error { // There can be at most one open reader on a connection. NextReader discards // the previous message if the application has not already consumed it. // -// The NextReader method and the readers returned from the method cannot be -// accessed by more than one goroutine at a time. +// Applications must break out of the application's read loop when this method +// returns a non-nil error value. Errors returned from this method are +// 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) { c.readSeq++ @@ -711,6 +786,15 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { return frameType, messageReader{c, c.readSeq}, nil } } + + // Applications that do handle the error returned from this method spin in + // tight loop on connection failure. To help application developers detect + // this error, panic on repeated reads to the failed connection. + c.readErrCount++ + if c.readErrCount >= 1000 { + panic("repeated read on failed websocket connection") + } + return noFrame, nil, c.readErr } diff --git a/vendor/github.com/gorilla/websocket/conn_test.go b/vendor/github.com/gorilla/websocket/conn_test.go index 02f2d4b501..04c8dd8dc4 100644 --- a/vendor/github.com/gorilla/websocket/conn_test.go +++ b/vendor/github.com/gorilla/websocket/conn_test.go @@ -7,6 +7,7 @@ package websocket import ( "bufio" "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -270,3 +271,97 @@ func TestBufioReadBytes(t *testing.T) { t.Fatalf("read returnd %d bytes, want %d bytes", len(p), len(m)) } } + +var closeErrorTests = []struct { + err error + codes []int + ok bool +}{ + {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, true}, + {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, false}, + {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, true}, + {errors.New("hello"), []int{CloseNormalClosure}, false}, +} + +func TestCloseError(t *testing.T) { + for _, tt := range closeErrorTests { + ok := IsCloseError(tt.err, tt.codes...) + if ok != tt.ok { + t.Errorf("IsCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok) + } + } +} + +var unexpectedCloseErrorTests = []struct { + err error + codes []int + ok bool +}{ + {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, false}, + {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, true}, + {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, false}, + {errors.New("hello"), []int{CloseNormalClosure}, false}, +} + +func TestUnexpectedCloseErrors(t *testing.T) { + for _, tt := range unexpectedCloseErrorTests { + ok := IsUnexpectedCloseError(tt.err, tt.codes...) + if ok != tt.ok { + t.Errorf("IsUnexpectedCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok) + } + } +} + +type blockingWriter struct { + c1, c2 chan struct{} +} + +func (w blockingWriter) Write(p []byte) (int, error) { + // Allow main to continue + close(w.c1) + // Wait for panic in main + <-w.c2 + return len(p), nil +} + +func TestConcurrentWritePanic(t *testing.T) { + w := blockingWriter{make(chan struct{}), make(chan struct{})} + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + go func() { + c.WriteMessage(TextMessage, []byte{}) + }() + + // wait for goroutine to block in write. + <-w.c1 + + defer func() { + close(w.c2) + if v := recover(); v != nil { + return + } + }() + + c.WriteMessage(TextMessage, []byte{}) + t.Fatal("should not get here") +} + +type failingReader struct{} + +func (r failingReader) Read(p []byte) (int, error) { + return 0, io.EOF +} + +func TestFailedConnectionReadPanic(t *testing.T) { + c := newConn(fakeNetConn{Reader: failingReader{}, Writer: nil}, false, 1024, 1024) + + defer func() { + if v := recover(); v != nil { + return + } + }() + + for i := 0; i < 20000; i++ { + c.ReadMessage() + } + t.Fatal("should not get here") +} diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go index 72286279c2..499b03dbd9 100644 --- a/vendor/github.com/gorilla/websocket/doc.go +++ b/vendor/github.com/gorilla/websocket/doc.go @@ -46,8 +46,7 @@ // method to get an io.WriteCloser, write the message to the writer and close // the writer when done. To receive a message, call the connection NextReader // method to get an io.Reader and read until io.EOF is returned. This snippet -// snippet shows how to echo messages using the NextWriter and NextReader -// methods: +// shows how to echo messages using the NextWriter and NextReader methods: // // for { // messageType, r, err := conn.NextReader() @@ -86,14 +85,28 @@ // and pong. Call the connection WriteControl, WriteMessage or NextWriter // methods to send a control message to the peer. // -// Connections handle received ping and pong messages by invoking a callback -// function set with SetPingHandler and SetPongHandler methods. These callback -// functions can be invoked from the ReadMessage method, the NextReader method -// or from a call to the data message reader returned from NextReader. +// 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 returning an error from the -// ReadMessage method, the NextReader method or from a call to the data message -// reader returned from NextReader. +// Connections handle received close messages by sending a close message to the +// peer and returning a *CloseError from the the NextReader, ReadMessage or the +// message Read method. +// +// The application must read the connection to process ping 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: +// +// func readLoop(c *websocket.Conn) { +// for { +// if _, _, err := c.NextReader(); err != nil { +// c.Close() +// break +// } +// } +// } // // Concurrency // @@ -108,22 +121,6 @@ // The Close and WriteControl methods can be called concurrently with all other // methods. // -// Read is Required -// -// The application must read the connection to process ping 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: -// -// func readLoop(c *websocket.Conn) { -// for { -// if _, _, err := c.NextReader(); err != nil { -// c.Close() -// break -// } -// } -// } -// // Origin Considerations // // Web browsers allow Javascript applications to open a WebSocket connection to @@ -141,9 +138,9 @@ // An application can allow connections from any origin by specifying a // function that always returns true: // -// var upgrader = websocket.Upgrader{ +// var upgrader = websocket.Upgrader{ // CheckOrigin: func(r *http.Request) bool { return true }, -// } +// } // // The deprecated Upgrade function does not enforce an origin policy. It's the // application's responsibility to check the Origin header before calling diff --git a/vendor/github.com/gorilla/websocket/example_test.go b/vendor/github.com/gorilla/websocket/example_test.go new file mode 100644 index 0000000000..ed22c4aecb --- /dev/null +++ b/vendor/github.com/gorilla/websocket/example_test.go @@ -0,0 +1,40 @@ +// Copyright 2015 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_test + +import ( + "log" + "net/http" + "testing" + + "github.com/gorilla/websocket" +) + +// The websocket.IsUnexpectedCloseError function is useful for identifying +// application and protocol errors. +// +// This server application works with a client application running in the +// browser. The client application does not explicitly close the websocket. The +// only expected close message from the client has the code +// websocket.CloseGoingAway. All other other close messages are likely the +// result of an application or protocol error and are logged to aid debugging. +func ExampleIsUnexpectedCloseError(err error, c *websocket.Conn, req *http.Request) { + for { + messageType, p, err := c.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.Printf("error: %v, user-agent: %v", err, req.Header.Get("User-Agent")) + } + return + } + processMesage(messageType, p) + } +} + +func processMesage(mt int, p []byte) {} + +// TestX prevents godoc from showing this entire file in the example. Remove +// this function when a second example is added. +func TestX(t *testing.T) {} diff --git a/vendor/github.com/gorilla/websocket/examples/chat/conn.go b/vendor/github.com/gorilla/websocket/examples/chat/conn.go index ae25b75bc2..40fd38c2c9 100644 --- a/vendor/github.com/gorilla/websocket/examples/chat/conn.go +++ b/vendor/github.com/gorilla/websocket/examples/chat/conn.go @@ -51,6 +51,9 @@ func (c *connection) readPump() { for { _, message, err := c.ws.ReadMessage() if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.Printf("error: %v", err) + } break } h.broadcast <- message @@ -90,10 +93,6 @@ func (c *connection) writePump() { // serveWs handles websocket requests from the peer. func serveWs(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) diff --git a/vendor/github.com/gorilla/websocket/examples/command/main.go b/vendor/github.com/gorilla/websocket/examples/command/main.go index 73ac7e9a91..f3f022edb5 100644 --- a/vendor/github.com/gorilla/websocket/examples/command/main.go +++ b/vendor/github.com/gorilla/websocket/examples/command/main.go @@ -95,11 +95,6 @@ func internalError(ws *websocket.Conn, msg string, err error) { var upgrader = websocket.Upgrader{} func serveWs(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("upgrade:", err) diff --git a/vendor/github.com/gorilla/websocket/examples/echo/README.md b/vendor/github.com/gorilla/websocket/examples/echo/README.md index 6bb404f61a..6ad79ed76a 100644 --- a/vendor/github.com/gorilla/websocket/examples/echo/README.md +++ b/vendor/github.com/gorilla/websocket/examples/echo/README.md @@ -2,8 +2,8 @@ This example shows a simple client and server. -The server echoes messages sent to it. The client sends a message every five -seconds and prints all messages received. +The server echoes messages sent to it. The client sends a message every second +and prints all messages received. To run the example, start the server: @@ -13,3 +13,5 @@ Next, start the client: $ go run client.go +The server includes a simple web client. To use the client, open +http://127.0.0.1:8080 in the browser and follow the instructions on the page. diff --git a/vendor/github.com/gorilla/websocket/examples/echo/client.go b/vendor/github.com/gorilla/websocket/examples/echo/client.go index af6fa99d33..6578094e77 100644 --- a/vendor/github.com/gorilla/websocket/examples/echo/client.go +++ b/vendor/github.com/gorilla/websocket/examples/echo/client.go @@ -10,18 +10,23 @@ import ( "flag" "log" "net/url" + "os" + "os/signal" "time" "github.com/gorilla/websocket" ) -var addr = flag.String("addr", "localhost:8081", "http service address") +var addr = flag.String("addr", "localhost:8080", "http service address") func main() { flag.Parse() log.SetFlags(0) - u := url.URL{Scheme: "ws", Host: *addr, Path: "/"} + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} log.Printf("connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) @@ -30,26 +35,47 @@ func main() { } defer c.Close() + done := make(chan struct{}) + go func() { defer c.Close() + defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("read:", err) - break + return } log.Printf("recv: %s", message) } }() - ticker := time.NewTicker(5 * time.Second) + ticker := time.NewTicker(time.Second) defer ticker.Stop() - for t := range ticker.C { - err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) - if err != nil { - log.Println("write:", err) - break + for { + select { + case t := <-ticker.C: + err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) + if err != nil { + log.Println("write:", err) + return + } + case <-interrupt: + log.Println("interrupt") + // To cleanly close a connection, a client should send a close + // frame and wait for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Println("write close:", err) + return + } + select { + case <-done: + case <-time.After(time.Second): + } + c.Close() + return } } } diff --git a/vendor/github.com/gorilla/websocket/examples/echo/server.go b/vendor/github.com/gorilla/websocket/examples/echo/server.go index 8f18b3fbe0..a685b0974a 100644 --- a/vendor/github.com/gorilla/websocket/examples/echo/server.go +++ b/vendor/github.com/gorilla/websocket/examples/echo/server.go @@ -8,25 +8,18 @@ package main import ( "flag" + "html/template" "log" "net/http" "github.com/gorilla/websocket" ) -var addr = flag.String("addr", "localhost:8081", "http service address") +var addr = flag.String("addr", "localhost:8080", "http service address") var upgrader = websocket.Upgrader{} // use default options func echo(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.Error(w, "Not found", 404) - return - } - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } c, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Print("upgrade:", err) @@ -48,13 +41,92 @@ func echo(w http.ResponseWriter, r *http.Request) { } } +func home(w http.ResponseWriter, r *http.Request) { + homeTemplate.Execute(w, "ws://"+r.Host+"/echo") +} + func main() { flag.Parse() log.SetFlags(0) - - http.HandleFunc("/", echo) - err := http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) - } + http.HandleFunc("/echo", echo) + http.HandleFunc("/", home) + log.Fatal(http.ListenAndServe(*addr, nil)) } + +var homeTemplate = template.Must(template.New("").Parse(` + + + + + + + +
+

Click "Open" to create a connection to the server, +"Send" to send a message to the server and "Close" to close the connection. +You can change the message and send multiple times. +

+

+ + +

+ +

+
+
+
+ + +`)) diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go index e56a004933..85616c7974 100644 --- a/vendor/github.com/gorilla/websocket/server.go +++ b/vendor/github.com/gorilla/websocket/server.go @@ -92,7 +92,13 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header // The responseHeader is included in the response to the client's upgrade // request. Use the responseHeader to specify cookies (Set-Cookie) and the // application negotiated subprotocol (Sec-Websocket-Protocol). +// +// If the upgrade fails, then Upgrade replies to the client with an HTTP error +// response. func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { + if r.Method != "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" { return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13") } diff --git a/vendor/manifest b/vendor/manifest index 7a3a614f03..8bf978bc6a 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -622,7 +622,7 @@ { "importpath": "github.com/gorilla/websocket", "repository": "https://github.com/gorilla/websocket", - "revision": "527637c0f38a8035d7856bb758e56f7ee2c704a9", + "revision": "5c91b59efa232fa9a87b705d54101832c498a172", "branch": "master" }, {