From cdd40f587d40a45cf3ccd9dde1175b7a67d51a87 Mon Sep 17 00:00:00 2001 From: Steven Scott <42449819+stevenscott89@users.noreply.github.com> Date: Mon, 24 Sep 2018 16:10:46 -0700 Subject: [PATCH 1/6] Add comprehensive host test (#429) Add table driven test for handling of host in request URL, request header and TLS server name. In addition to testing various uses of host names, this test also confirms that host names are handled the same as the net/http client. The new table driven test replaces TestDialTLS, TestDialTLSNoverify, TestDialTLSBadCert and TestHostHeader. Eliminate duplicated code for constructing root CA. --- client_server_test.go | 273 ++++++++++++++++++++++++++++++------------ 1 file changed, 194 insertions(+), 79 deletions(-) diff --git a/client_server_test.go b/client_server_test.go index 564fbbe..8c6d012 100644 --- a/client_server_test.go +++ b/client_server_test.go @@ -11,8 +11,10 @@ import ( "crypto/x509" "encoding/base64" "encoding/binary" + "fmt" "io" "io/ioutil" + "log" "net" "net/http" "net/http/cookiejar" @@ -42,17 +44,12 @@ var cstDialer = Dialer{ HandshakeTimeout: 30 * time.Second, } -var cstDialerWithoutHandshakeTimeout = Dialer{ - Subprotocols: []string{"p1", "p2"}, - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - type cstHandler struct{ *testing.T } type cstServer struct { *httptest.Server URL string + t *testing.T } const ( @@ -288,10 +285,7 @@ func TestDialCookieJar(t *testing.T) { sendRecv(t, ws) } -func TestDialTLS(t *testing.T) { - s := newTLSServer(t) - defer s.Close() - +func rootCAs(t *testing.T, s *httptest.Server) *x509.CertPool { certs := x509.NewCertPool() for _, c := range s.TLS.Certificates { roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) @@ -302,35 +296,15 @@ func TestDialTLS(t *testing.T) { certs.AddCert(root) } } - - d := cstDialer - d.TLSClientConfig = &tls.Config{RootCAs: certs} - ws, _, err := d.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) + return certs } -func xTestDialTLSBadCert(t *testing.T) { - // This test is deactivated because of noisy logging from the net/http package. - s := newTLSServer(t) - defer s.Close() - - ws, _, err := cstDialer.Dial(s.URL, nil) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } -} - -func TestDialTLSNoVerify(t *testing.T) { +func TestDialTLS(t *testing.T) { s := newTLSServer(t) defer s.Close() d := cstDialer - d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)} ws, _, err := d.Dial(s.URL, nil) if err != nil { t.Fatalf("Dial: %v", err) @@ -415,7 +389,8 @@ func TestHandshakeTimeoutInContext(t *testing.T) { s := newServer(t) defer s.Close() - d := cstDialerWithoutHandshakeTimeout + d := cstDialer + d.HandshakeTimeout = 0 d.NetDialContext = func(ctx context.Context, n, a string) (net.Conn, error) { netDialer := &net.Dialer{} c, err := netDialer.DialContext(ctx, n, a) @@ -566,33 +541,195 @@ func TestRespOnBadHandshake(t *testing.T) { } } -// TestHostHeader confirms that the host header provided in the call to Dial is -// sent to the server. -func TestHostHeader(t *testing.T) { - s := newServer(t) - defer s.Close() +type testLogWriter struct { + t *testing.T +} - specifiedHost := make(chan string, 1) - origHandler := s.Server.Config.Handler +func (w testLogWriter) Write(p []byte) (int, error) { + w.t.Logf("%s", p) + return len(p), nil +} - // Capture the request Host header. - s.Server.Config.Handler = http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - specifiedHost <- r.Host - origHandler.ServeHTTP(w, r) - }) +// TestHost tests handling of host names and confirms that it matches net/http. +func TestHost(t *testing.T) { - ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}}) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() + upgrader := Upgrader{} + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if IsWebSocketUpgrade(r) { + c, err := upgrader.Upgrade(w, r, http.Header{"X-Test-Host": {r.Host}}) + if err != nil { + t.Fatal(err) + } + c.Close() + } else { + w.Header().Set("X-Test-Host", r.Host) + } + }) - if gotHost := <-specifiedHost; gotHost != "testhost" { - t.Fatalf("gotHost = %q, want \"testhost\"", gotHost) + server := httptest.NewServer(handler) + defer server.Close() + + tlsServer := httptest.NewTLSServer(handler) + defer tlsServer.Close() + + addrs := map[*httptest.Server]string{server: server.Listener.Addr().String(), tlsServer: tlsServer.Listener.Addr().String()} + wsProtos := map[*httptest.Server]string{server: "ws://", tlsServer: "wss://"} + httpProtos := map[*httptest.Server]string{server: "http://", tlsServer: "https://"} + + // Avoid log noise from net/http server by logging to testing.T + server.Config.ErrorLog = log.New(testLogWriter{t}, "", 0) + tlsServer.Config.ErrorLog = server.Config.ErrorLog + + cas := rootCAs(t, tlsServer) + + tests := []struct { + fail bool // true if dial / get should fail + server *httptest.Server // server to use + url string // host for request URI + header string // optional request host header + tls string // optiona host for tls ServerName + wantAddr string // expected host for dial + wantHeader string // expected request header on server + insecureSkipVerify bool + }{ + { + server: server, + url: addrs[server], + wantAddr: addrs[server], + wantHeader: addrs[server], + }, + { + server: tlsServer, + url: addrs[tlsServer], + wantAddr: addrs[tlsServer], + wantHeader: addrs[tlsServer], + }, + + { + server: server, + url: addrs[server], + header: "badhost.com", + wantAddr: addrs[server], + wantHeader: "badhost.com", + }, + { + server: tlsServer, + url: addrs[tlsServer], + header: "badhost.com", + wantAddr: addrs[tlsServer], + wantHeader: "badhost.com", + }, + + { + server: server, + url: "example.com", + header: "badhost.com", + wantAddr: "example.com:80", + wantHeader: "badhost.com", + }, + { + server: tlsServer, + url: "example.com", + header: "badhost.com", + wantAddr: "example.com:443", + wantHeader: "badhost.com", + }, + + { + server: server, + url: "badhost.com", + header: "example.com", + wantAddr: "badhost.com:80", + wantHeader: "example.com", + }, + { + fail: true, + server: tlsServer, + url: "badhost.com", + header: "example.com", + wantAddr: "badhost.com:443", + }, + { + server: tlsServer, + url: "badhost.com", + insecureSkipVerify: true, + wantAddr: "badhost.com:443", + wantHeader: "badhost.com", + }, + { + server: tlsServer, + url: "badhost.com", + tls: "example.com", + wantAddr: "badhost.com:443", + wantHeader: "badhost.com", + }, } - sendRecv(t, ws) + for i, tt := range tests { + + tls := &tls.Config{ + RootCAs: cas, + ServerName: tt.tls, + InsecureSkipVerify: tt.insecureSkipVerify, + } + + var gotAddr string + dialer := Dialer{ + NetDial: func(network, addr string) (net.Conn, error) { + gotAddr = addr + return net.Dial(network, addrs[tt.server]) + }, + TLSClientConfig: tls, + } + + // Test websocket dial + + h := http.Header{} + if tt.header != "" { + h.Set("Host", tt.header) + } + c, resp, err := dialer.Dial(wsProtos[tt.server]+tt.url+"/", h) + if err == nil { + c.Close() + } + + check := func(protos map[*httptest.Server]string) { + name := fmt.Sprintf("%d: %s%s/ header[Host]=%q, tls.ServerName=%q", i+1, protos[tt.server], tt.url, tt.header, tt.tls) + if gotAddr != tt.wantAddr { + t.Errorf("%s: got addr %s, want %s", name, gotAddr, tt.wantAddr) + } + switch { + case tt.fail && err == nil: + t.Errorf("%s: unexpected success", name) + case !tt.fail && err != nil: + t.Errorf("%s: unexpected error %v", name, err) + case !tt.fail && err == nil: + if gotHost := resp.Header.Get("X-Test-Host"); gotHost != tt.wantHeader { + t.Errorf("%s: got host %s, want %s", name, gotHost, tt.wantHeader) + } + } + } + + check(wsProtos) + + // Confirm that net/http has same result + + transport := &http.Transport{ + Dial: dialer.NetDial, + TLSClientConfig: dialer.TLSClientConfig, + } + req, _ := http.NewRequest("GET", httpProtos[tt.server]+tt.url+"/", nil) + if tt.header != "" { + req.Host = tt.header + } + client := &http.Client{Transport: transport} + resp, err = client.Do(req) + if err == nil { + resp.Body.Close() + } + transport.CloseIdleConnections() + check(httpProtos) + } } func TestDialCompression(t *testing.T) { @@ -716,19 +853,8 @@ func TestTracingDialWithContext(t *testing.T) { s := newTLSServer(t) defer s.Close() - certs := x509.NewCertPool() - for _, c := range s.TLS.Certificates { - roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) - if err != nil { - t.Fatalf("error parsing server's root cert: %v", err) - } - for _, root := range roots { - certs.AddCert(root) - } - } - d := cstDialer - d.TLSClientConfig = &tls.Config{RootCAs: certs} + d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)} ws, _, err := d.DialContext(ctx, s.URL, nil) if err != nil { @@ -766,19 +892,8 @@ func TestEmptyTracingDialWithContext(t *testing.T) { s := newTLSServer(t) defer s.Close() - certs := x509.NewCertPool() - for _, c := range s.TLS.Certificates { - roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) - if err != nil { - t.Fatalf("error parsing server's root cert: %v", err) - } - for _, root := range roots { - certs.AddCert(root) - } - } - d := cstDialer - d.TLSClientConfig = &tls.Config{RootCAs: certs} + d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)} ws, _, err := d.DialContext(ctx, s.URL, nil) if err != nil { From 3130e8d3f1986de7dc3fd86255f4ce9618677508 Mon Sep 17 00:00:00 2001 From: Steven Scott <42449819+stevenscott89@users.noreply.github.com> Date: Mon, 24 Sep 2018 16:26:12 -0700 Subject: [PATCH 2/6] Return write buffer to pool on write error (#427) Fix bug where connection did not return the write buffer to the pool after a write error. Add test for the same. Rename messsageWriter.fatal method to endMessage and consolidate all message cleanup code there. This ensures that the buffer is returned to pool on all code paths. Rename Conn.prepMessage to beginMessage for symmetry with endMessage. Move some duplicated code at calls to prepMessage to beginMessage. Bonus improvement: Adjust message and buffer size in TestWriteBufferPool to test that pool works with fragmented messages. --- conn.go | 47 ++++++++++++++++++++-------------------- conn_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/conn.go b/conn.go index d2a21c1..7eb05cf 100644 --- a/conn.go +++ b/conn.go @@ -451,7 +451,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er return err } -func (c *Conn) prepWrite(messageType int) error { +// beginMessage prepares a connection and message writer for a new message. +func (c *Conn) beginMessage(mw *messageWriter, messageType int) error { // 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. @@ -471,6 +472,10 @@ func (c *Conn) prepWrite(messageType int) error { return err } + mw.c = c + mw.frameType = messageType + mw.pos = maxFrameHeaderSize + if c.writeBuf == nil { wpd, ok := c.writePool.Get().(writePoolData) if ok { @@ -491,16 +496,11 @@ func (c *Conn) prepWrite(messageType int) error { // All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and // PongMessage) are supported. func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, messageType); err != nil { return nil, err } - - mw := &messageWriter{ - c: c, - frameType: messageType, - pos: maxFrameHeaderSize, - } - c.writer = mw + c.writer = &mw if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { w := c.newCompressionWriter(c.writer, c.compressionLevel) mw.compress = true @@ -517,10 +517,16 @@ type messageWriter struct { err error } -func (w *messageWriter) fatal(err error) error { +func (w *messageWriter) endMessage(err error) error { if w.err != nil { - w.err = err - w.c.writer = nil + return err + } + c := w.c + w.err = err + c.writer = nil + if c.writePool != nil { + c.writePool.Put(writePoolData{buf: c.writeBuf}) + c.writeBuf = nil } return err } @@ -534,7 +540,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { // Check for invalid control frames. if isControl(w.frameType) && (!final || length > maxControlFramePayloadSize) { - return w.fatal(errInvalidControlFrame) + return w.endMessage(errInvalidControlFrame) } b0 := byte(w.frameType) @@ -579,7 +585,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) if len(extra) > 0 { - return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) + return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))) } } @@ -600,15 +606,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { c.isWriting = false if err != nil { - return w.fatal(err) + return w.endMessage(err) } if final { - c.writer = nil - if c.writePool != nil { - c.writePool.Put(writePoolData{buf: c.writeBuf}) - c.writeBuf = nil - } + w.endMessage(errWriteClosed) return nil } @@ -709,7 +711,6 @@ func (w *messageWriter) Close() error { if err := w.flushFrame(true, nil); err != nil { return err } - w.err = errWriteClosed return nil } @@ -742,10 +743,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error { if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { // Fast path with no allocations and single frame. - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, 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:] diff --git a/conn_test.go b/conn_test.go index 5ca0673..ad906b1 100644 --- a/conn_test.go +++ b/conn_test.go @@ -196,11 +196,16 @@ func (p *simpleBufferPool) Put(v interface{}) { } func TestWriteBufferPool(t *testing.T) { + const message = "Now is the time for all good people to come to the aid of the party." + var buf bytes.Buffer var pool simpleBufferPool - wc := newConn(fakeNetConn{Writer: &buf}, true, 1024, 1024, &pool, nil, nil) rc := newTestConn(&buf, nil, false) + // Specify writeBufferSize smaller than message size to ensure that pooling + // works with fragmented messages. + wc := newConn(fakeNetConn{Writer: &buf}, true, 1024, len(message)-1, &pool, nil, nil) + if wc.writeBuf != nil { t.Fatal("writeBuf not nil after create") } @@ -218,8 +223,6 @@ func TestWriteBufferPool(t *testing.T) { writeBufAddr := &wc.writeBuf[0] - const message = "Hello World!" - if _, err := io.WriteString(w, message); err != nil { t.Fatalf("io.WriteString(w, message) returned %v", err) } @@ -269,6 +272,7 @@ func TestWriteBufferPool(t *testing.T) { } } +// TestWriteBufferPoolSync ensures that *sync.Pool works as a buffer pool. func TestWriteBufferPoolSync(t *testing.T) { var buf bytes.Buffer var pool sync.Pool @@ -290,6 +294,56 @@ func TestWriteBufferPoolSync(t *testing.T) { } } +// errorWriter is an io.Writer than returns an error on all writes. +type errorWriter struct{} + +func (ew errorWriter) Write(p []byte) (int, error) { return 0, errors.New("Error!") } + +// TestWriteBufferPoolError ensures that buffer is returned to pool after error +// on write. +func TestWriteBufferPoolError(t *testing.T) { + + // Part 1: Test NextWriter/Write/Close + + var pool simpleBufferPool + wc := newConn(fakeNetConn{Writer: errorWriter{}}, true, 1024, 1024, &pool, nil, nil) + + w, err := wc.NextWriter(TextMessage) + if err != nil { + t.Fatalf("wc.NextWriter() returned %v", err) + } + + if wc.writeBuf == nil { + t.Fatal("writeBuf is nil after NextWriter") + } + + writeBufAddr := &wc.writeBuf[0] + + if _, err := io.WriteString(w, "Hello"); err != nil { + t.Fatalf("io.WriteString(w, message) returned %v", err) + } + + if err := w.Close(); err == nil { + t.Fatalf("w.Close() did not return error") + } + + if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr { + t.Fatal("writeBuf not returned to pool") + } + + // Part 2: Test WriteMessage + + wc = newConn(fakeNetConn{Writer: errorWriter{}}, true, 1024, 1024, &pool, nil, nil) + + if err := wc.WriteMessage(TextMessage, []byte("Hello")); err == nil { + t.Fatalf("wc.WriteMessage did not return error") + } + + if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr { + t.Fatal("writeBuf not returned to pool") + } +} + func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) { const bufSize = 512 From a51a35ae3232254685f26a0b6d995ca0e81e2248 Mon Sep 17 00:00:00 2001 From: Steven Scott <42449819+stevenscott89@users.noreply.github.com> Date: Sat, 6 Oct 2018 11:35:33 -0400 Subject: [PATCH 3/6] Improve header parsing code Because the net/http server removes \r\n from multi-line header values, there's no need to to check for \r or \n when skipping whitespace in headers (see https://godoc.org/net/textproto#Reader.ReadMIMEHeader). Given this fact, the whitespace test can be simplified to b == ' ' || b == '\t'. There's no need for the isSpaceOctet bit field in octetTypes. The isTokenOctet bit field is the only bit field remaining after the removal of isSpaceOctet. Simplify the code by replacing the isTokenOctet bit test in octetTypes with an array of booleans called isTokenOctet. Declare isTokenOctet as a composite literal instead of constructing it at runtime. Add documentation to core functions for parsing HTTP headers. --- util.go | 132 ++++++++++++++++++++++++++++++++++----------------- util_test.go | 1 + 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/util.go b/util.go index 354001e..7bf2f66 100644 --- a/util.go +++ b/util.go @@ -31,68 +31,113 @@ func generateChallengeKey() (string, error) { 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 = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - 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 - } +// Token octets per RFC 2616. +var isTokenOctet = [256]bool{ + '!': true, + '#': true, + '$': true, + '%': true, + '&': true, + '\'': true, + '*': true, + '+': true, + '-': true, + '.': true, + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '5': true, + '6': true, + '7': true, + '8': true, + '9': true, + 'A': true, + 'B': true, + 'C': true, + 'D': true, + 'E': true, + 'F': true, + 'G': true, + 'H': true, + 'I': true, + 'J': true, + 'K': true, + 'L': true, + 'M': true, + 'N': true, + 'O': true, + 'P': true, + 'Q': true, + 'R': true, + 'S': true, + 'T': true, + 'U': true, + 'W': true, + 'V': true, + 'X': true, + 'Y': true, + 'Z': true, + '^': true, + '_': true, + '`': true, + 'a': true, + 'b': true, + 'c': true, + 'd': true, + 'e': true, + 'f': true, + 'g': true, + 'h': true, + 'i': true, + 'j': true, + 'k': true, + 'l': true, + 'm': true, + 'n': true, + 'o': true, + 'p': true, + 'q': true, + 'r': true, + 's': true, + 't': true, + 'u': true, + 'v': true, + 'w': true, + 'x': true, + 'y': true, + 'z': true, + '|': true, + '~': true, } +// skipSpace returns a slice of the string s with all leading RFC 2616 linear +// whitespace removed. func skipSpace(s string) (rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpaceOctet == 0 { + if b := s[i]; b != ' ' && b != '\t' { break } } return s[i:] } +// nextToken returns the leading RFC 2616 token of s and the string following +// the token. func nextToken(s string) (token, rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isTokenOctet == 0 { + if !isTokenOctet[s[i]] { break } } return s[:i], s[i:] } +// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616 +// and the string following the token or quoted string. func nextTokenOrQuoted(s string) (value string, rest string) { if !strings.HasPrefix(s, "\"") { return nextToken(s) @@ -128,7 +173,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) { return "", "" } -// equalASCIIFold returns true if s is equal to t with ASCII case folding. +// equalASCIIFold returns true if s is equal to t with ASCII case folding as +// defined in RFC 4790. func equalASCIIFold(s, t string) bool { for s != "" && t != "" { sr, size := utf8.DecodeRuneInString(s) diff --git a/util_test.go b/util_test.go index 6e15965..af710ba 100644 --- a/util_test.go +++ b/util_test.go @@ -17,6 +17,7 @@ var equalASCIIFoldTests = []struct { {"WebSocket", "websocket", true}, {"websocket", "WebSocket", true}, {"Öyster", "öyster", false}, + {"WebSocket", "WetSocket", false}, } func TestEqualASCIIFold(t *testing.T) { From 76e4896901ef89d253d69b528d3701dd146fe2ae Mon Sep 17 00:00:00 2001 From: "Jeff R. Allen" Date: Fri, 12 Oct 2018 04:01:31 +0200 Subject: [PATCH 4/6] Fix formatting problem in the docs. (#435) Sorry for the dumbest PR ever, but this tiny addition of a period changes the formatting of this sentence from a header to a regular paragraph in godoc. --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 2e32fd5..0778119 100644 --- a/client.go +++ b/client.go @@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer // Use the response.Header to get the selected subprotocol // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). // -// The context will be used in the request and in the Dialer +// The context will be used in the request and in the Dialer. // // If the WebSocket handshake fails, ErrBadHandshake is returned along with a // non-nil *http.Response so that callers can handle redirects, authentication, From 483fb8d7c32fcb4b5636cd293a92e3935932e2f4 Mon Sep 17 00:00:00 2001 From: kanozec <30459655+kanozec@users.noreply.github.com> Date: Tue, 30 Oct 2018 22:45:53 +0800 Subject: [PATCH 5/6] Add "in bytes" to sizes in documentation --- client.go | 2 +- conn.go | 2 +- server.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 0778119..962c06a 100644 --- a/client.go +++ b/client.go @@ -70,7 +70,7 @@ type Dialer struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then a useful default size is used. The I/O buffer sizes // do not limit the size of the messages that can be sent or received. ReadBufferSize, WriteBufferSize int diff --git a/conn.go b/conn.go index 7eb05cf..3848ab4 100644 --- a/conn.go +++ b/conn.go @@ -1042,7 +1042,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } -// SetReadLimit sets the maximum size for a message read from the peer. If a +// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a // message exceeds the limit, the connection sends a close message to the peer // and returns ErrReadLimit to the application. func (c *Conn) SetReadLimit(limit int64) { diff --git a/server.go b/server.go index a761824..3d4480a 100644 --- a/server.go +++ b/server.go @@ -27,7 +27,7 @@ type Upgrader struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then buffers allocated by the HTTP server are used. The // I/O buffer sizes do not limit the size of the messages that can be sent // or received. From 95ba29eb981bbb27d92e1f70bf8a1949452d926b Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Thu, 6 Dec 2018 12:32:39 +0530 Subject: [PATCH 6/6] Updated autobahn test suite URL Fixes #436 --- README.md | 2 +- examples/autobahn/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 20e391f..2469694 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ package API is stable. ### Protocol Compliance The Gorilla WebSocket package passes the server tests in the [Autobahn Test -Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn +Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). ### Gorilla WebSocket compared with other packages diff --git a/examples/autobahn/README.md b/examples/autobahn/README.md index 075ac15..dde8525 100644 --- a/examples/autobahn/README.md +++ b/examples/autobahn/README.md @@ -1,6 +1,6 @@ # Test Server -This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite). +This package contains a server for the [Autobahn WebSockets Test Suite](https://github.com/crossbario/autobahn-testsuite). To test the server, run