From 2cc71d3d2f0e496f1ef8cf97d4748338d0e03a61 Mon Sep 17 00:00:00 2001 From: Sean Hinchee Date: Mon, 24 Jul 2017 21:11:09 -0500 Subject: [PATCH] vendoring websocket --- color/LICENSE.md | 20 - websocket/.gitignore | 25 + websocket/.travis.yml | 19 + websocket/AUTHORS | 8 + websocket/LICENSE | 22 + websocket/README.md | 64 + websocket/client.go | 392 ++++++ websocket/client_clone.go | 16 + websocket/client_clone_legacy.go | 38 + websocket/client_server_test.go | 512 ++++++++ websocket/client_test.go | 72 ++ websocket/compression.go | 148 +++ websocket/compression_test.go | 80 ++ websocket/conn.go | 1149 +++++++++++++++++ websocket/conn_broadcast_test.go | 134 ++ websocket/conn_read.go | 18 + websocket/conn_read_legacy.go | 21 + websocket/conn_test.go | 497 +++++++ websocket/doc.go | 179 +++ websocket/example_test.go | 46 + websocket/f.zip | Bin 0 -> 63895 bytes websocket/json.go | 60 + websocket/json_test.go | 119 ++ websocket/mask.go | 55 + websocket/mask_safe.go | 15 + websocket/mask_test.go | 73 ++ websocket/prepared.go | 103 ++ websocket/prepared_test.go | 74 ++ websocket/server.go | 292 +++++ websocket/server_test.go | 51 + websocket/util.go | 214 +++ websocket/util_test.go | 74 ++ .../examples/autobahn/README.md | 13 + .../examples/autobahn/fuzzingclient.json | 15 + .../examples/autobahn/server.go | 265 ++++ .../websocket-master/examples/chat/README.md | 102 ++ .../websocket-master/examples/chat/client.go | 137 ++ .../websocket-master/examples/chat/home.html | 98 ++ .../websocket-master/examples/chat/hub.go | 53 + .../websocket-master/examples/chat/main.go | 40 + .../examples/command/README.md | 19 + .../examples/command/home.html | 102 ++ .../websocket-master/examples/command/main.go | 193 +++ .../websocket-master/examples/echo/README.md | 17 + .../websocket-master/examples/echo/client.go | 81 ++ .../websocket-master/examples/echo/server.go | 132 ++ .../examples/filewatch/README.md | 9 + .../examples/filewatch/main.go | 193 +++ 48 files changed, 6039 insertions(+), 20 deletions(-) delete mode 100644 color/LICENSE.md create mode 100644 websocket/.gitignore create mode 100644 websocket/.travis.yml create mode 100644 websocket/AUTHORS create mode 100644 websocket/LICENSE create mode 100644 websocket/README.md create mode 100644 websocket/client.go create mode 100644 websocket/client_clone.go create mode 100644 websocket/client_clone_legacy.go create mode 100644 websocket/client_server_test.go create mode 100644 websocket/client_test.go create mode 100644 websocket/compression.go create mode 100644 websocket/compression_test.go create mode 100644 websocket/conn.go create mode 100644 websocket/conn_broadcast_test.go create mode 100644 websocket/conn_read.go create mode 100644 websocket/conn_read_legacy.go create mode 100644 websocket/conn_test.go create mode 100644 websocket/doc.go create mode 100644 websocket/example_test.go create mode 100644 websocket/f.zip create mode 100644 websocket/json.go create mode 100644 websocket/json_test.go create mode 100644 websocket/mask.go create mode 100644 websocket/mask_safe.go create mode 100644 websocket/mask_test.go create mode 100644 websocket/prepared.go create mode 100644 websocket/prepared_test.go create mode 100644 websocket/server.go create mode 100644 websocket/server_test.go create mode 100644 websocket/util.go create mode 100644 websocket/util_test.go create mode 100644 websocket/websocket-master/examples/autobahn/README.md create mode 100644 websocket/websocket-master/examples/autobahn/fuzzingclient.json create mode 100644 websocket/websocket-master/examples/autobahn/server.go create mode 100644 websocket/websocket-master/examples/chat/README.md create mode 100644 websocket/websocket-master/examples/chat/client.go create mode 100644 websocket/websocket-master/examples/chat/home.html create mode 100644 websocket/websocket-master/examples/chat/hub.go create mode 100644 websocket/websocket-master/examples/chat/main.go create mode 100644 websocket/websocket-master/examples/command/README.md create mode 100644 websocket/websocket-master/examples/command/home.html create mode 100644 websocket/websocket-master/examples/command/main.go create mode 100644 websocket/websocket-master/examples/echo/README.md create mode 100644 websocket/websocket-master/examples/echo/client.go create mode 100644 websocket/websocket-master/examples/echo/server.go create mode 100644 websocket/websocket-master/examples/filewatch/README.md create mode 100644 websocket/websocket-master/examples/filewatch/main.go diff --git a/color/LICENSE.md b/color/LICENSE.md deleted file mode 100644 index 25fdaf639dfc039992d059805e51377d3174ae87..0000000000000000000000000000000000000000 --- a/color/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Fatih Arslan - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/websocket/.gitignore b/websocket/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ac710204fa195a2a4f17fde1fa75f15ba8c2b714 --- /dev/null +++ b/websocket/.gitignore @@ -0,0 +1,25 @@ +# 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 + +.idea/ +*.iml \ No newline at end of file diff --git a/websocket/.travis.yml b/websocket/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..3d8d29cf34bef61e9fabb7031589d56fc90fe52d --- /dev/null +++ b/websocket/.travis.yml @@ -0,0 +1,19 @@ +language: go +sudo: false + +matrix: + include: + - go: 1.4 + - go: 1.5 + - go: 1.6 + - go: 1.7 + - go: 1.8 + - go: tip + allow_failures: + - go: tip + +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d .) + - go vet $(go list ./... | grep -v /vendor/) + - go test -v -race ./... diff --git a/websocket/AUTHORS b/websocket/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..b003eca0ca187243683ea444142d4465dd77b619 --- /dev/null +++ b/websocket/AUTHORS @@ -0,0 +1,8 @@ +# This is the official list of Gorilla WebSocket authors for copyright +# purposes. +# +# Please keep the list sorted. + +Gary Burd +Joachim Bauch + diff --git a/websocket/LICENSE b/websocket/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9171c972252257cf416925ddff4be6cb73973a82 --- /dev/null +++ b/websocket/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/websocket/README.md b/websocket/README.md new file mode 100644 index 0000000000000000000000000000000000000000..33c3d2be3e3d000612d906de645aaa5c7f64a62c --- /dev/null +++ b/websocket/README.md @@ -0,0 +1,64 @@ +# Gorilla WebSocket + +Gorilla WebSocket is a [Go](http://golang.org/) implementation of the +[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. + +[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) +[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) + +### Documentation + +* [API Reference](http://godoc.org/github.com/gorilla/websocket) +* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) +* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) +* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) +* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) + +### Status + +The Gorilla WebSocket package provides a complete and tested implementation of +the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The +package API is stable. + +### Installation + + go get github.com/gorilla/websocket + +### 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 +subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). + +### Gorilla WebSocket compared with other packages + + + + + + + + + + + + + + + + + + +
github.com/gorillagolang.org/x/net
RFC 6455 Features
Passes Autobahn Test SuiteYesNo
Receive fragmented messageYesNo, see note 1
Send close messageYesNo
Send pings and receive pongsYesNo
Get the type of a received data messageYesYes, see note 2
Other Features
Compression ExtensionsExperimentalNo
Read message using io.ReaderYesNo, see note 3
Write message using io.WriteCloserYesNo, see note 3
+ +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/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 + encountered. Each call to Write sends a single frame message. The Gorilla + io.Reader and io.WriteCloser operate on a single WebSocket message. + diff --git a/websocket/client.go b/websocket/client.go new file mode 100644 index 0000000000000000000000000000000000000000..43a87c753bfce1f378ba1bd7462890b18cf30b01 --- /dev/null +++ b/websocket/client.go @@ -0,0 +1,392 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "bytes" + "crypto/tls" + "encoding/base64" + "errors" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// ErrBadHandshake is returned when the server response to opening handshake is +// invalid. +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. +// The URL u specifies the host and request URI. Use requestHeader to specify +// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies +// (Cookie). Use the response.Header to get the selected subprotocol +// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). +// +// If the WebSocket handshake fails, ErrBadHandshake is returned along with a +// non-nil *http.Response so that callers can handle redirects, authentication, +// etc. +// +// Deprecated: Use Dialer instead. +func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { + d := Dialer{ + ReadBufferSize: readBufSize, + WriteBufferSize: writeBufSize, + NetDial: func(net, addr string) (net.Conn, error) { + return netConn, nil + }, + } + return d.Dial(u.String(), requestHeader) +} + +// A Dialer contains options for connecting to WebSocket server. +type Dialer struct { + // NetDial specifies the dial function for creating TCP connections. If + // NetDial is nil, net.Dial is used. + NetDial func(network, addr string) (net.Conn, error) + + // Proxy specifies a function to return a proxy for a given + // Request. If the function returns a non-nil error, the + // request is aborted with the provided error. + // If Proxy is nil or returns a nil *URL, no proxy is used. + Proxy func(*http.Request) (*url.URL, error) + + // TLSClientConfig specifies the TLS configuration to use with tls.Client. + // If nil, the default configuration is used. + TLSClientConfig *tls.Config + + // HandshakeTimeout specifies the duration for the handshake to complete. + HandshakeTimeout time.Duration + + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // size is zero, then a useful default size is used. The I/O buffer sizes + // do not limit the size of the messages that can be sent or received. + ReadBufferSize, WriteBufferSize int + + // Subprotocols specifies the client's requested subprotocols. + 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") + +// parseURL parses the URL. +// +// This function is a replacement for the standard library url.Parse function. +// In Go 1.4 and earlier, url.Parse loses information from the path. +func parseURL(s string) (*url.URL, error) { + // From the RFC: + // + // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] + // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] + var u url.URL + switch { + case strings.HasPrefix(s, "ws://"): + u.Scheme = "ws" + s = s[len("ws://"):] + case strings.HasPrefix(s, "wss://"): + u.Scheme = "wss" + s = s[len("wss://"):] + default: + return nil, errMalformedURL + } + + if i := strings.Index(s, "?"); i >= 0 { + u.RawQuery = s[i+1:] + s = s[:i] + } + + if i := strings.Index(s, "/"); i >= 0 { + 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. + return nil, errMalformedURL + } + + return &u, nil +} + +func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { + hostPort = u.Host + hostNoPort = u.Host + if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { + hostNoPort = hostNoPort[:i] + } else { + switch u.Scheme { + case "wss": + hostPort += ":443" + case "https": + hostPort += ":443" + default: + hostPort += ":80" + } + } + return hostPort, hostNoPort +} + +// DefaultDialer is a dialer with all fields set to the default zero values. +var DefaultDialer = &Dialer{ + Proxy: http.ProxyFromEnvironment, +} + +// Dial creates a new client connection. Use requestHeader to specify the +// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). +// Use the response.Header to get the selected subprotocol +// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). +// +// If the WebSocket handshake fails, ErrBadHandshake is returned along with a +// non-nil *http.Response so that callers can handle redirects, authentication, +// etcetera. The response body may not contain the entire response and does not +// need to be closed by the application. +func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { + + if d == nil { + d = &Dialer{ + Proxy: http.ProxyFromEnvironment, + } + } + + challengeKey, err := generateChallengeKey() + if err != nil { + return nil, nil, err + } + + u, err := parseURL(urlStr) + if err != nil { + return nil, nil, err + } + + switch u.Scheme { + case "ws": + u.Scheme = "http" + case "wss": + u.Scheme = "https" + default: + return nil, nil, errMalformedURL + } + + if u.User != nil { + // User name and password are not allowed in websocket URIs. + return nil, nil, errMalformedURL + } + + req := &http.Request{ + Method: "GET", + URL: u, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + 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 + // RFC examples. Although the capitalization shouldn't matter, there are + // servers that depend on it. The Header.Set method is not used because the + // method canonicalizes the header names. + req.Header["Upgrade"] = []string{"websocket"} + req.Header["Connection"] = []string{"Upgrade"} + req.Header["Sec-WebSocket-Key"] = []string{challengeKey} + req.Header["Sec-WebSocket-Version"] = []string{"13"} + if len(d.Subprotocols) > 0 { + req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} + } + for k, vs := range requestHeader { + switch { + case k == "Host": + if len(vs) > 0 { + req.Host = vs[0] + } + case k == "Upgrade" || + k == "Connection" || + k == "Sec-Websocket-Key" || + k == "Sec-Websocket-Version" || + k == "Sec-Websocket-Extensions" || + (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): + return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) + default: + req.Header[k] = vs + } + } + + if d.EnableCompression { + req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") + } + + hostPort, hostNoPort := hostPortNoPort(u) + + var proxyURL *url.URL + // Check wether the proxy method has been configured + if d.Proxy != nil { + proxyURL, err = d.Proxy(req) + } + if err != nil { + return nil, nil, err + } + + var targetHostPort string + if proxyURL != nil { + targetHostPort, _ = hostPortNoPort(proxyURL) + } else { + targetHostPort = hostPort + } + + var deadline time.Time + if d.HandshakeTimeout != 0 { + deadline = time.Now().Add(d.HandshakeTimeout) + } + + netDial := d.NetDial + if netDial == nil { + netDialer := &net.Dialer{Deadline: deadline} + netDial = netDialer.Dial + } + + netConn, err := netDial("tcp", targetHostPort) + if err != nil { + return nil, nil, err + } + + defer func() { + if netConn != nil { + netConn.Close() + } + }() + + if err := netConn.SetDeadline(deadline); err != nil { + return nil, nil, err + } + + 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: connectHeader, + } + + connectReq.Write(netConn) + + // Read response. + // Okay to use and discard buffered reader here, because + // TLS server will not speak until spoken to. + br := bufio.NewReader(netConn) + resp, err := http.ReadResponse(br, connectReq) + if err != nil { + return nil, nil, err + } + if resp.StatusCode != 200 { + f := strings.SplitN(resp.Status, " ", 2) + return nil, nil, errors.New(f[1]) + } + } + + if u.Scheme == "https" { + cfg := cloneTLSConfig(d.TLSClientConfig) + if cfg.ServerName == "" { + cfg.ServerName = hostNoPort + } + tlsConn := tls.Client(netConn, cfg) + netConn = tlsConn + if err := tlsConn.Handshake(); err != nil { + return nil, nil, err + } + if !cfg.InsecureSkipVerify { + if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { + return nil, nil, err + } + } + } + + conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) + + if err := req.Write(netConn); err != nil { + return nil, nil, err + } + + resp, err := http.ReadResponse(conn.br, req) + if err != nil { + return nil, nil, err + } + + if d.Jar != nil { + if rc := resp.Cookies(); len(rc) > 0 { + d.Jar.SetCookies(u, rc) + } + } + + if resp.StatusCode != 101 || + !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || + !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || + resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { + // Before closing the network connection on return from this + // function, slurp up some of the response to aid application + // debugging. + buf := make([]byte, 1024) + n, _ := io.ReadFull(resp.Body, buf) + resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) + return nil, resp, ErrBadHandshake + } + + for _, ext := range parseExtensions(resp.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{})) + conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") + + netConn.SetDeadline(time.Time{}) + netConn = nil // to avoid close in defer. + return conn, resp, nil +} diff --git a/websocket/client_clone.go b/websocket/client_clone.go new file mode 100644 index 0000000000000000000000000000000000000000..4f0d943723a9d094c1693892812377a51f6791bb --- /dev/null +++ b/websocket/client_clone.go @@ -0,0 +1,16 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.8 + +package websocket + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return cfg.Clone() +} diff --git a/websocket/client_clone_legacy.go b/websocket/client_clone_legacy.go new file mode 100644 index 0000000000000000000000000000000000000000..babb007fb4144ac456faeb685e24b62186c7c517 --- /dev/null +++ b/websocket/client_clone_legacy.go @@ -0,0 +1,38 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8 + +package websocket + +import "crypto/tls" + +// cloneTLSConfig clones all public fields except the fields +// SessionTicketsDisabled and SessionTicketKey. This avoids copying the +// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a +// config in active use. +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/websocket/client_server_test.go b/websocket/client_server_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7d39da6811f2e5e06eb007ca8691ea0312362e8f --- /dev/null +++ b/websocket/client_server_test.go @@ -0,0 +1,512 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "io" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "net/url" + "reflect" + "strings" + "testing" + "time" +) + +var cstUpgrader = Upgrader{ + Subprotocols: []string{"p0", "p1"}, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + EnableCompression: true, + Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { + http.Error(w, reason.Error(), status) + }, +} + +var cstDialer = Dialer{ + Subprotocols: []string{"p1", "p2"}, + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +type cstHandler struct{ *testing.T } + +type cstServer struct { + *httptest.Server + 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 +} + +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.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) + if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) { + t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols) + http.Error(w, "bad protocol", 400) + return + } + ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}}) + if err != nil { + t.Logf("Upgrade: %v", err) + return + } + defer ws.Close() + + if ws.Subprotocol() != "p1" { + t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol()) + ws.Close() + return + } + op, rd, err := ws.NextReader() + if err != nil { + t.Logf("NextReader: %v", err) + return + } + wr, err := ws.NextWriter(op) + if err != nil { + t.Logf("NextWriter: %v", err) + return + } + if _, err = io.Copy(wr, rd); err != nil { + t.Logf("NextWriter: %v", err) + return + } + if err := wr.Close(); err != nil { + t.Logf("Close: %v", err) + return + } +} + +func makeWsProto(s string) string { + return "ws" + strings.TrimPrefix(s, "http") +} + +func sendRecv(t *testing.T, ws *Conn) { + const message = "Hello World!" + if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatalf("SetWriteDeadline: %v", err) + } + if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil { + t.Fatalf("WriteMessage: %v", err) + } + if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + _, p, err := ws.ReadMessage() + if err != nil { + t.Fatalf("ReadMessage: %v", err) + } + if string(p) != message { + t.Fatalf("message=%s, want %s", p, message) + } +} + +func TestProxyDial(t *testing.T) { + + s := newServer(t) + defer s.Close() + + surl, _ := url.Parse(s.URL) + + 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) { + if r.Method == "CONNECT" { + connect = true + w.WriteHeader(200) + return + } + + if !connect { + t.Log("connect not recieved") + http.Error(w, "connect 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 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() + + ws, _, err := cstDialer.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +func TestDialCookieJar(t *testing.T) { + s := newServer(t) + defer s.Close() + + jar, _ := cookiejar.New(nil) + d := cstDialer + d.Jar = jar + + u, _ := parseURL(s.URL) + + switch u.Scheme { + case "ws": + u.Scheme = "http" + case "wss": + u.Scheme = "https" + } + + cookies := []*http.Cookie{&http.Cookie{Name: "gorilla", Value: "ws", Path: "/"}} + d.Jar.SetCookies(u, cookies) + + ws, _, err := d.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + + var gorilla string + var sessionID string + for _, c := range d.Jar.Cookies(u) { + if c.Name == "gorilla" { + gorilla = c.Value + } + + if c.Name == "sessionID" { + sessionID = c.Value + } + } + if gorilla != "ws" { + t.Error("Cookie not present in jar.") + } + + if sessionID != "1234" { + t.Error("Set-Cookie not received from the server.") + } + + sendRecv(t, ws) +} + +func TestDialTLS(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} + ws, _, err := d.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +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) { + s := newTLSServer(t) + defer s.Close() + + d := cstDialer + d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + ws, _, err := d.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +func TestDialTimeout(t *testing.T) { + s := newServer(t) + defer s.Close() + + d := cstDialer + d.HandshakeTimeout = -1 + ws, _, err := d.Dial(s.URL, nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } +} + +func TestDialBadScheme(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, _, err := cstDialer.Dial(s.Server.URL, nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } +} + +func TestDialBadOrigin(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } + if resp == nil { + t.Fatalf("resp=nil, err=%v", err) + } + if resp.StatusCode != http.StatusForbidden { + t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden) + } +} + +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() + + ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}}) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + + var sessionID string + for _, c := range resp.Cookies() { + if c.Name == "sessionID" { + sessionID = c.Value + } + } + if sessionID != "1234" { + t.Error("Set-Cookie not received from the server.") + } + + if ws.Subprotocol() != "p1" { + t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol()) + } + sendRecv(t, ws) +} + +func TestRespOnBadHandshake(t *testing.T) { + const expectedStatus = http.StatusGone + const expectedBody = "This is the response body." + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(expectedStatus) + io.WriteString(w, expectedBody) + })) + defer s.Close() + + ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } + + if resp == nil { + t.Fatalf("resp=nil, err=%v", err) + } + + if resp.StatusCode != expectedStatus { + t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus) + } + + p, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("ReadFull(resp.Body) returned error %v", err) + } + + if string(p) != expectedBody { + t.Errorf("resp.Body=%s, want %s", p, expectedBody) + } +} + +// 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() + + specifiedHost := make(chan string, 1) + origHandler := s.Server.Config.Handler + + // 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) + }) + + ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}}) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + + if gotHost := <-specifiedHost; gotHost != "testhost" { + t.Fatalf("gotHost = %q, want \"testhost\"", gotHost) + } + + sendRecv(t, ws) +} + +func TestDialCompression(t *testing.T) { + s := newServer(t) + defer s.Close() + + dialer := cstDialer + dialer.EnableCompression = true + ws, _, err := dialer.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} diff --git a/websocket/client_test.go b/websocket/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7d2b0844fdbf0d7337cf9de2de78764b4ad26537 --- /dev/null +++ b/websocket/client_test.go @@ -0,0 +1,72 @@ +// Copyright 2014 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 ( + "net/url" + "reflect" + "testing" +) + +var parseURLTests = []struct { + 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"}, "/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) { + for _, tt := range parseURLTests { + u, err := parseURL(tt.s) + if tt.u != nil && err != nil { + t.Errorf("parseURL(%q) returned error %v", tt.s, err) + continue + } + 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) = %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) + } + } +} + +var hostPortNoPortTests = []struct { + u *url.URL + hostPort, hostNoPort string +}{ + {&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"}, + {&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"}, + {&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"}, + {&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"}, +} + +func TestHostPortNoPort(t *testing.T) { + for _, tt := range hostPortNoPortTests { + hostPort, hostNoPort := hostPortNoPort(tt.u) + if hostPort != tt.hostPort { + t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort) + } + if hostNoPort != tt.hostNoPort { + t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort) + } + } +} diff --git a/websocket/compression.go b/websocket/compression.go new file mode 100644 index 0000000000000000000000000000000000000000..813ffb1e84336da415256244366e09bcd65b6765 --- /dev/null +++ b/websocket/compression.go @@ -0,0 +1,148 @@ +// 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" +) + +const ( + minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 + maxCompressionLevel = flate.BestCompression + defaultCompressionLevel = 1 +) + +var ( + flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool + flateReaderPool = sync.Pool{New: func() interface{} { + return flate.NewReader(nil) + }} +) + +func decompressNoContextTakeover(r io.Reader) io.ReadCloser { + const tail = + // Add four bytes as specified in RFC + "\x00\x00\xff\xff" + + // Add final block to squelch unexpected EOF error from flate reader. + "\x01\x00\x00\xff\xff" + + fr, _ := flateReaderPool.Get().(io.ReadCloser) + fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) + return &flateReadWrapper{fr} +} + +func isValidCompressionLevel(level int) bool { + return minCompressionLevel <= level && level <= maxCompressionLevel +} + +func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { + p := &flateWriterPools[level-minCompressionLevel] + tw := &truncWriter{w: w} + fw, _ := p.Get().(*flate.Writer) + if fw == nil { + fw, _ = flate.NewWriter(tw, level) + } else { + fw.Reset(tw) + } + return &flateWriteWrapper{fw: fw, tw: tw, p: p} +} + +// 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 flateWriteWrapper struct { + fw *flate.Writer + tw *truncWriter + p *sync.Pool +} + +func (w *flateWriteWrapper) Write(p []byte) (int, error) { + if w.fw == nil { + return 0, errWriteClosed + } + return w.fw.Write(p) +} + +func (w *flateWriteWrapper) Close() error { + if w.fw == nil { + return errWriteClosed + } + err1 := w.fw.Flush() + w.p.Put(w.fw) + w.fw = nil + if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { + return errors.New("websocket: internal error, unexpected bytes at end of flate stream") + } + err2 := w.tw.w.Close() + if err1 != nil { + return err1 + } + return err2 +} + +type flateReadWrapper struct { + fr io.ReadCloser +} + +func (r *flateReadWrapper) Read(p []byte) (int, error) { + if r.fr == nil { + return 0, io.ErrClosedPipe + } + n, err := r.fr.Read(p) + if err == io.EOF { + // Preemptively place the reader back in the pool. This helps with + // scenarios where the application does not call NextReader() soon after + // this final read. + r.Close() + } + return n, err +} + +func (r *flateReadWrapper) Close() error { + if r.fr == nil { + return io.ErrClosedPipe + } + err := r.fr.Close() + flateReaderPool.Put(r.fr) + r.fr = nil + return err +} diff --git a/websocket/compression_test.go b/websocket/compression_test.go new file mode 100644 index 0000000000000000000000000000000000000000..659cf4215fdbbd3100ce9c94f2c46dfb0a7537dd --- /dev/null +++ b/websocket/compression_test.go @@ -0,0 +1,80 @@ +package websocket + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "testing" +) + +type nopCloser struct{ io.Writer } + +func (nopCloser) Close() error { return nil } + +func TestTruncWriter(t *testing.T) { + const data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz987654321" + for n := 1; n <= 10; n++ { + var b bytes.Buffer + w := &truncWriter{w: nopCloser{&b}} + p := []byte(data) + for len(p) > 0 { + m := len(p) + if m > n { + m = n + } + w.Write(p[:m]) + p = p[m:] + } + if b.String() != data[:len(data)-len(w.p)] { + t.Errorf("%d: %q", n, b.String()) + } + } +} + +func textMessages(num int) [][]byte { + messages := make([][]byte, num) + for i := 0; i < num; i++ { + msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i) + messages[i] = []byte(msg) + } + return messages +} + +func BenchmarkWriteNoCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func BenchmarkWriteWithCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + c.enableWriteCompression = true + c.newCompressionWriter = compressNoContextTakeover + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func TestValidCompressionLevel(t *testing.T) { + c := newConn(fakeNetConn{}, false, 1024, 1024) + for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} { + if err := c.SetCompressionLevel(level); err == nil { + t.Errorf("no error for level %d", level) + } + } + for _, level := range []int{minCompressionLevel, maxCompressionLevel} { + if err := c.SetCompressionLevel(level); err != nil { + t.Errorf("error for level %d", level) + } + } +} diff --git a/websocket/conn.go b/websocket/conn.go new file mode 100644 index 0000000000000000000000000000000000000000..97e1dbacb1237677ceece110c3e9a0a45068f49f --- /dev/null +++ b/websocket/conn.go @@ -0,0 +1,1149 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "encoding/binary" + "errors" + "io" + "io/ioutil" + "math/rand" + "net" + "strconv" + "sync" + "time" + "unicode/utf8" +) + +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 + maxControlFramePayloadSize = 125 + + writeWait = time.Second + + defaultReadBufferSize = 4096 + defaultWriteBufferSize = 4096 + + continuationFrame = 0 + noFrame = -1 +) + +// Close codes defined in RFC 6455, section 11.7. +const ( + CloseNormalClosure = 1000 + CloseGoingAway = 1001 + CloseProtocolError = 1002 + CloseUnsupportedData = 1003 + CloseNoStatusReceived = 1005 + CloseAbnormalClosure = 1006 + CloseInvalidFramePayloadData = 1007 + ClosePolicyViolation = 1008 + CloseMessageTooBig = 1009 + CloseMandatoryExtension = 1010 + CloseInternalServerErr = 1011 + CloseServiceRestart = 1012 + CloseTryAgainLater = 1013 + CloseTLSHandshake = 1015 +) + +// The message types are defined in RFC 6455, section 11.8. +const ( + // TextMessage denotes a text data message. The text message payload is + // interpreted as UTF-8 encoded text data. + TextMessage = 1 + + // BinaryMessage denotes a binary data message. + BinaryMessage = 2 + + // CloseMessage denotes a close control message. The optional message + // payload contains a numeric code and text. Use the FormatCloseMessage + // function to format a close message payload. + CloseMessage = 8 + + // PingMessage denotes a ping control message. The optional message payload + // is UTF-8 encoded text. + PingMessage = 9 + + // PongMessage denotes a ping control message. The optional message payload + // is UTF-8 encoded text. + PongMessage = 10 +) + +// ErrCloseSent is returned when the application writes a message to the +// connection after sending a close message. +var ErrCloseSent = errors.New("websocket: close sent") + +// 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 { + msg string + temporary bool + timeout bool +} + +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 is defined in RFC 6455, section 11.7. + Code int + + // Text is the optional text payload. + Text string +} + +func (e *CloseError) Error() string { + 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 ( + errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: 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") +) + +func newMaskKey() [4]byte { + n := rand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + +func hideTempErr(err error) error { + if e, ok := err.(net.Error); ok && e.Temporary() { + err = &netError{msg: e.Error(), timeout: e.Timeout()} + } + return err +} + +func isControl(frameType int) bool { + return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage +} + +func isData(frameType int) bool { + return frameType == TextMessage || frameType == BinaryMessage +} + +var validReceivedCloseCodes = map[int]bool{ + // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number + + CloseNormalClosure: true, + CloseGoingAway: true, + 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 isValidReceivedCloseCode(code int) bool { + return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) +} + +// The Conn type represents a WebSocket connection. +type Conn struct { + conn net.Conn + isServer bool + subprotocol string + + // Write fields + mu chan bool // used as mutex to protect write to conn + 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 + + writeErrMu sync.Mutex + writeErr error + + enableWriteCompression bool + compressionLevel int + newCompressionWriter func(io.WriteCloser, int) io.WriteCloser + + // Read fields + reader io.ReadCloser // the current reader returned to the application + readErr error + br *bufio.Reader + readRemaining int64 // bytes remaining in current frame. + readFinal bool // true the current message has more frames. + readLength int64 // Message size. + readLimit int64 // Maximum message size. + readMaskPos int + readMaskKey [4]byte + handlePong func(string) error + handlePing func(string) error + handleClose func(int, string) error + readErrCount int + messageReader *messageReader // the current low-level reader + + readDecompress bool // whether last read frame had RSV1 set + newDecompressionReader func(io.Reader) io.ReadCloser +} + +func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { + return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) +} + +type writeHook struct { + p []byte +} + +func (wh *writeHook) Write(p []byte) (int, error) { + wh.p = p + return len(p), nil +} + +func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { + mu := make(chan bool, 1) + mu <- true + + var br *bufio.Reader + if readBufferSize == 0 && brw != nil && brw.Reader != nil { + // Reuse the supplied bufio.Reader if the buffer has a useful size. + // This code assumes that peek on a reader returns + // bufio.Reader.buf[:0]. + brw.Reader.Reset(conn) + if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { + br = brw.Reader + } + } + if br == nil { + if readBufferSize == 0 { + readBufferSize = defaultReadBufferSize + } + if readBufferSize < maxControlFramePayloadSize { + readBufferSize = maxControlFramePayloadSize + } + br = bufio.NewReaderSize(conn, readBufferSize) + } + + var writeBuf []byte + if writeBufferSize == 0 && brw != nil && brw.Writer != nil { + // Use the bufio.Writer's buffer if the buffer has a useful size. This + // code assumes that bufio.Writer.buf[:1] is passed to the + // bufio.Writer's underlying writer. + var wh writeHook + brw.Writer.Reset(&wh) + brw.Writer.WriteByte(0) + brw.Flush() + if cap(wh.p) >= maxFrameHeaderSize+256 { + writeBuf = wh.p[:cap(wh.p)] + } + } + + if writeBuf == nil { + if writeBufferSize == 0 { + writeBufferSize = defaultWriteBufferSize + } + writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) + } + + c := &Conn{ + isServer: isServer, + br: br, + conn: conn, + mu: mu, + readFinal: true, + writeBuf: writeBuf, + enableWriteCompression: true, + compressionLevel: defaultCompressionLevel, + } + c.SetCloseHandler(nil) + c.SetPingHandler(nil) + c.SetPongHandler(nil) + return c +} + +// Subprotocol returns the negotiated protocol for the connection. +func (c *Conn) Subprotocol() string { + return c.subprotocol +} + +// Close closes the underlying network connection without sending or waiting for a close frame. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// LocalAddr returns the local network address. +func (c *Conn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +// RemoteAddr returns the remote network address. +func (c *Conn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +// 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 { + <-c.mu + defer func() { c.mu <- true }() + + c.writeErrMu.Lock() + err := c.writeErr + c.writeErrMu.Unlock() + if err != nil { + return err + } + + c.conn.SetWriteDeadline(deadline) + for _, buf := range bufs { + if len(buf) > 0 { + _, err := c.conn.Write(buf) + if err != nil { + return c.writeFatal(err) + } + } + } + + if frameType == CloseMessage { + c.writeFatal(ErrCloseSent) + } + return nil +} + +// WriteControl writes a control message with the given deadline. The allowed +// message types are CloseMessage, PingMessage and PongMessage. +func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { + if !isControl(messageType) { + return errBadWriteOpCode + } + if len(data) > maxControlFramePayloadSize { + return errInvalidControlFrame + } + + b0 := byte(messageType) | finalBit + b1 := byte(len(data)) + if !c.isServer { + b1 |= maskBit + } + + buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) + buf = append(buf, b0, b1) + + if c.isServer { + buf = append(buf, data...) + } else { + key := newMaskKey() + buf = append(buf, key[:]...) + buf = append(buf, data...) + maskBytes(key, 0, buf[6:]) + } + + d := time.Hour * 1000 + if !deadline.IsZero() { + d = deadline.Sub(time.Now()) + if d < 0 { + return errWriteTimeout + } + } + + timer := time.NewTimer(d) + select { + case <-c.mu: + timer.Stop() + case <-timer.C: + return errWriteTimeout + } + defer func() { c.mu <- true }() + + c.writeErrMu.Lock() + err := c.writeErr + c.writeErrMu.Unlock() + if err != nil { + return err + } + + c.conn.SetWriteDeadline(deadline) + _, err = c.conn.Write(buf) + if err != nil { + return c.writeFatal(err) + } + if messageType == CloseMessage { + c.writeFatal(ErrCloseSent) + } + return err +} + +func (c *Conn) prepWrite(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. + 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 +// previous writer if the application has not already done so. +func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { + if err := c.prepWrite(messageType); err != nil { + return nil, err + } + + mw := &messageWriter{ + c: c, + frameType: messageType, + pos: maxFrameHeaderSize, + } + c.writer = mw + if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { + w := c.newCompressionWriter(c.writer, c.compressionLevel) + mw.compress = true + c.writer = w + } + return c.writer, nil +} + +type messageWriter struct { + 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. + if isControl(w.frameType) && + (!final || length > maxControlFramePayloadSize) { + return w.fatal(errInvalidControlFrame) + } + + b0 := byte(w.frameType) + if final { + b0 |= finalBit + } + if w.compress { + b0 |= rsv1Bit + } + w.compress = false + + b1 := byte(0) + if !c.isServer { + b1 |= maskBit + } + + // Assume that the frame starts at beginning of c.writeBuf. + framePos := 0 + if c.isServer { + // Adjust up if mask not included in the header. + framePos = 4 + } + + switch { + case length >= 65536: + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | 127 + binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) + case length > 125: + framePos += 6 + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | 126 + binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) + default: + framePos += 8 + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | byte(length) + } + + if !c.isServer { + key := newMaskKey() + 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")) + } + } + + // 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 + + err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra) + + if !c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = false + + if err != nil { + return w.fatal(err) + } + + if final { + c.writer = nil + return nil + } + + // Setup for next frame. + w.pos = maxFrameHeaderSize + w.frameType = continuationFrame + return nil +} + +func (w *messageWriter) ncopy(max int) (int, error) { + n := len(w.c.writeBuf) - w.pos + if n <= 0 { + if err := w.flushFrame(false, nil); err != nil { + return 0, err + } + n = len(w.c.writeBuf) - w.pos + } + if n > max { + n = max + } + return n, nil +} + +func (w *messageWriter) Write(p []byte) (int, error) { + if w.err != nil { + return 0, w.err + } + + if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { + // Don't buffer large messages. + err := w.flushFrame(false, p) + if err != nil { + return 0, err + } + return len(p), nil + } + + nn := len(p) + for len(p) > 0 { + n, err := w.ncopy(len(p)) + if err != nil { + return 0, err + } + copy(w.c.writeBuf[w.pos:], p[:n]) + w.pos += n + p = p[n:] + } + return nn, nil +} + +func (w *messageWriter) WriteString(p string) (int, error) { + if w.err != nil { + return 0, w.err + } + + nn := len(p) + for len(p) > 0 { + n, err := w.ncopy(len(p)) + if err != nil { + return 0, err + } + copy(w.c.writeBuf[w.pos:], p[:n]) + w.pos += n + p = p[n:] + } + return nn, nil +} + +func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { + if w.err != nil { + return 0, w.err + } + for { + if w.pos == len(w.c.writeBuf) { + err = w.flushFrame(false, nil) + if err != nil { + break + } + } + var n int + n, err = r.Read(w.c.writeBuf[w.pos:]) + w.pos += n + nn += int64(n) + if err != nil { + if err == io.EOF { + err = nil + } + break + } + } + return nn, err +} + +func (w *messageWriter) Close() error { + if w.err != nil { + return w.err + } + if err := w.flushFrame(true, nil); err != nil { + return err + } + w.err = errWriteClosed + return nil +} + +// WritePreparedMessage writes prepared message into connection. +func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error { + frameType, frameData, err := pm.frame(prepareKey{ + isServer: c.isServer, + compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType), + compressionLevel: c.compressionLevel, + }) + if err != nil { + return err + } + if c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = true + err = c.write(frameType, c.writeDeadline, frameData, nil) + if !c.isWriting { + panic("concurrent write to websocket connection") + } + c.isWriting = false + return err +} + +// WriteMessage is a helper method for getting a writer using NextWriter, +// writing the message and closing the writer. +func (c *Conn) WriteMessage(messageType int, data []byte) error { + + if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { + // Fast path with no allocations and single frame. + + if err := c.prepWrite(messageType); err != nil { + 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 { + return err + } + if _, err = w.Write(data); err != nil { + return err + } + return w.Close() +} + +// 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. +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline = t + return nil +} + +// Read methods + +func (c *Conn) advanceFrame() (int, error) { + + // 1. Skip remainder of previous frame. + + if c.readRemaining > 0 { + if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { + return noFrame, err + } + } + + // 2. Read and parse first two bytes of frame header. + + p, err := c.read(2) + if err != nil { + return noFrame, err + } + + final := p[0]&finalBit != 0 + frameType := int(p[0] & 0xf) + mask := p[1]&maskBit != 0 + c.readRemaining = int64(p[1] & 0x7f) + + c.readDecompress = false + 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 { + case CloseMessage, PingMessage, PongMessage: + if c.readRemaining > maxControlFramePayloadSize { + return noFrame, c.handleProtocolError("control frame length > 125") + } + if !final { + return noFrame, c.handleProtocolError("control frame not final") + } + case TextMessage, BinaryMessage: + if !c.readFinal { + return noFrame, c.handleProtocolError("message start before final message frame") + } + c.readFinal = final + case continuationFrame: + if c.readFinal { + return noFrame, c.handleProtocolError("continuation after final message frame") + } + c.readFinal = final + default: + return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) + } + + // 3. Read and parse frame length. + + switch c.readRemaining { + case 126: + p, err := c.read(2) + if err != nil { + return noFrame, err + } + c.readRemaining = int64(binary.BigEndian.Uint16(p)) + case 127: + p, err := c.read(8) + if err != nil { + return noFrame, err + } + c.readRemaining = int64(binary.BigEndian.Uint64(p)) + } + + // 4. Handle frame masking. + + if mask != c.isServer { + return noFrame, c.handleProtocolError("incorrect mask flag") + } + + if mask { + c.readMaskPos = 0 + p, err := c.read(len(c.readMaskKey)) + if err != nil { + return noFrame, err + } + copy(c.readMaskKey[:], p) + } + + // 5. For text and binary messages, enforce read limit and return. + + if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { + + c.readLength += c.readRemaining + if c.readLimit > 0 && c.readLength > c.readLimit { + c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) + return noFrame, ErrReadLimit + } + + return frameType, nil + } + + // 6. Read control frame payload. + + var payload []byte + if c.readRemaining > 0 { + payload, err = c.read(int(c.readRemaining)) + c.readRemaining = 0 + if err != nil { + return noFrame, err + } + if c.isServer { + maskBytes(c.readMaskKey, 0, payload) + } + } + + // 7. Process control frame payload. + + switch frameType { + case PongMessage: + if err := c.handlePong(string(payload)); err != nil { + return noFrame, err + } + case PingMessage: + if err := c.handlePing(string(payload)); err != nil { + return noFrame, err + } + case CloseMessage: + closeCode := CloseNoStatusReceived + closeText := "" + if len(payload) >= 2 { + closeCode = int(binary.BigEndian.Uint16(payload)) + if !isValidReceivedCloseCode(closeCode) { + return noFrame, c.handleProtocolError("invalid close code") + } + 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 + } + return noFrame, &CloseError{Code: closeCode, Text: closeText} + } + + return frameType, nil +} + +func (c *Conn) handleProtocolError(message string) error { + c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) + return errors.New("websocket: " + message) +} + +// NextReader returns the next data message received from the peer. The +// returned messageType is either TextMessage or BinaryMessage. +// +// There can be at most one open reader on a connection. NextReader discards +// the previous message if the application has not already consumed it. +// +// 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) { + // Close previous reader, only relevant for decompression. + if c.reader != nil { + c.reader.Close() + c.reader = nil + } + + c.messageReader = nil + c.readLength = 0 + + for c.readErr == nil { + frameType, err := c.advanceFrame() + if err != nil { + c.readErr = hideTempErr(err) + break + } + if frameType == TextMessage || frameType == BinaryMessage { + c.messageReader = &messageReader{c} + c.reader = c.messageReader + if c.readDecompress { + c.reader = c.newDecompressionReader(c.reader) + } + return frameType, c.reader, 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 +} + +type messageReader struct{ c *Conn } + +func (r *messageReader) Read(b []byte) (int, error) { + c := r.c + if c.messageReader != r { + return 0, io.EOF + } + + for c.readErr == nil { + + if c.readRemaining > 0 { + if int64(len(b)) > c.readRemaining { + b = b[:c.readRemaining] + } + n, err := c.br.Read(b) + c.readErr = hideTempErr(err) + if c.isServer { + c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) + } + c.readRemaining -= int64(n) + if c.readRemaining > 0 && c.readErr == io.EOF { + c.readErr = errUnexpectedEOF + } + return n, c.readErr + } + + if c.readFinal { + c.messageReader = nil + return 0, io.EOF + } + + frameType, err := c.advanceFrame() + switch { + case err != nil: + c.readErr = hideTempErr(err) + case frameType == TextMessage || frameType == BinaryMessage: + c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") + } + } + + err := c.readErr + if err == io.EOF && c.messageReader == r { + err = errUnexpectedEOF + } + return 0, err +} + +func (r *messageReader) Close() error { + return nil +} + +// ReadMessage is a helper method for getting a reader using NextReader and +// reading from that reader to a buffer. +func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { + var r io.Reader + messageType, r, err = c.NextReader() + if err != nil { + return messageType, nil, err + } + p, err = ioutil.ReadAll(r) + return messageType, p, err +} + +// SetReadDeadline sets the read deadline on the underlying network connection. +// After a read has timed out, the websocket connection state is corrupt and +// all future reads will return an error. A zero value for t means reads will +// not time out. +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 +// message exceeds the limit, the connection sends a close frame to the peer +// and returns ErrReadLimit to the application. +func (c *Conn) SetReadLimit(limit int64) { + 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. +// +// The application must read the connection to process close messages as +// described in the section on Control Frames above. +// +// The connection read methods return a CloseError when a close frame is +// received. Most applications should handle close messages as part of their +// normal error handling. Applications should only set a close handler when the +// application must perform some action before sending a close frame back to +// the peer. +func (c *Conn) SetCloseHandler(h func(code int, text string) error) { + if h == nil { + h = func(code int, text string) error { + 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. +// The appData argument to h is the PING frame application data. The default +// ping handler sends a pong to the peer. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. +func (c *Conn) SetPingHandler(h func(appData string) error) { + if h == nil { + h = func(message string) error { + err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) + if err == ErrCloseSent { + return nil + } else if e, ok := err.(net.Error); ok && e.Temporary() { + return nil + } + return err + } + } + 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. +// The appData argument to h is the PONG frame application data. The default +// pong handler does nothing. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. +func (c *Conn) SetPongHandler(h func(appData string) error) { + if h == nil { + h = func(string) error { return nil } + } + c.handlePong = h +} + +// UnderlyingConn returns the internal net.Conn. This can be used to further +// modifications to connection specific flags. +func (c *Conn) UnderlyingConn() net.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 +} + +// SetCompressionLevel sets the flate compression level for subsequent text and +// binary messages. This function is a noop if compression was not negotiated +// with the peer. See the compress/flate package for a description of +// compression levels. +func (c *Conn) SetCompressionLevel(level int) error { + if !isValidCompressionLevel(level) { + return errors.New("websocket: invalid compression level") + } + c.compressionLevel = level + return nil +} + +// FormatCloseMessage formats closeCode and text as a WebSocket close message. +func FormatCloseMessage(closeCode int, text string) []byte { + buf := make([]byte, 2+len(text)) + binary.BigEndian.PutUint16(buf, uint16(closeCode)) + copy(buf[2:], text) + return buf +} diff --git a/websocket/conn_broadcast_test.go b/websocket/conn_broadcast_test.go new file mode 100644 index 0000000000000000000000000000000000000000..45038e4881ff909dd0812a9deea4fec16ce5d2d4 --- /dev/null +++ b/websocket/conn_broadcast_test.go @@ -0,0 +1,134 @@ +// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package websocket + +import ( + "io" + "io/ioutil" + "sync/atomic" + "testing" +) + +// broadcastBench allows to run broadcast benchmarks. +// In every broadcast benchmark we create many connections, then send the same +// message into every connection and wait for all writes complete. This emulates +// an application where many connections listen to the same data - i.e. PUB/SUB +// scenarios with many subscribers in one channel. +type broadcastBench struct { + w io.Writer + message *broadcastMessage + closeCh chan struct{} + doneCh chan struct{} + count int32 + conns []*broadcastConn + compression bool + usePrepared bool +} + +type broadcastMessage struct { + payload []byte + prepared *PreparedMessage +} + +type broadcastConn struct { + conn *Conn + msgCh chan *broadcastMessage +} + +func newBroadcastConn(c *Conn) *broadcastConn { + return &broadcastConn{ + conn: c, + msgCh: make(chan *broadcastMessage, 1), + } +} + +func newBroadcastBench(usePrepared, compression bool) *broadcastBench { + bench := &broadcastBench{ + w: ioutil.Discard, + doneCh: make(chan struct{}), + closeCh: make(chan struct{}), + usePrepared: usePrepared, + compression: compression, + } + msg := &broadcastMessage{ + payload: textMessages(1)[0], + } + if usePrepared { + pm, _ := NewPreparedMessage(TextMessage, msg.payload) + msg.prepared = pm + } + bench.message = msg + bench.makeConns(10000) + return bench +} + +func (b *broadcastBench) makeConns(numConns int) { + conns := make([]*broadcastConn, numConns) + + for i := 0; i < numConns; i++ { + c := newConn(fakeNetConn{Reader: nil, Writer: b.w}, true, 1024, 1024) + if b.compression { + c.enableWriteCompression = true + c.newCompressionWriter = compressNoContextTakeover + } + conns[i] = newBroadcastConn(c) + go func(c *broadcastConn) { + for { + select { + case msg := <-c.msgCh: + if b.usePrepared { + c.conn.WritePreparedMessage(msg.prepared) + } else { + c.conn.WriteMessage(TextMessage, msg.payload) + } + val := atomic.AddInt32(&b.count, 1) + if val%int32(numConns) == 0 { + b.doneCh <- struct{}{} + } + case <-b.closeCh: + return + } + } + }(conns[i]) + } + b.conns = conns +} + +func (b *broadcastBench) close() { + close(b.closeCh) +} + +func (b *broadcastBench) runOnce() { + for _, c := range b.conns { + c.msgCh <- b.message + } + <-b.doneCh +} + +func BenchmarkBroadcast(b *testing.B) { + benchmarks := []struct { + name string + usePrepared bool + compression bool + }{ + {"NoCompression", false, false}, + {"WithCompression", false, true}, + {"NoCompressionPrepared", true, false}, + {"WithCompressionPrepared", true, true}, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + bench := newBroadcastBench(bm.usePrepared, bm.compression) + defer bench.close() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bench.runOnce() + } + b.ReportAllocs() + }) + } +} diff --git a/websocket/conn_read.go b/websocket/conn_read.go new file mode 100644 index 0000000000000000000000000000000000000000..1ea15059ee14b675f1ee138a0f3c9df24c727be1 --- /dev/null +++ b/websocket/conn_read.go @@ -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 +} diff --git a/websocket/conn_read_legacy.go b/websocket/conn_read_legacy.go new file mode 100644 index 0000000000000000000000000000000000000000..018541cf6cbb2d22d86df0e35ad9db30f7f63689 --- /dev/null +++ b/websocket/conn_read_legacy.go @@ -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 +} diff --git a/websocket/conn_test.go b/websocket/conn_test.go new file mode 100644 index 0000000000000000000000000000000000000000..06e9bc3f552b9e3103a35f385494e479ec50c993 --- /dev/null +++ b/websocket/conn_test.go @@ -0,0 +1,497 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "reflect" + "testing" + "testing/iotest" + "time" +) + +var _ net.Error = errWriteTimeout + +type fakeNetConn struct { + io.Reader + io.Writer +} + +func (c fakeNetConn) Close() error { return nil } +func (c fakeNetConn) LocalAddr() net.Addr { return localAddr } +func (c fakeNetConn) RemoteAddr() net.Addr { return remoteAddr } +func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } +func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil } +func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil } + +type fakeAddr int + +var ( + localAddr = fakeAddr(1) + remoteAddr = fakeAddr(2) +) + +func (a fakeAddr) Network() string { + return "net" +} + +func (a fakeAddr) String() string { + return "str" +} + +func TestFraming(t *testing.T) { + frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537} + var readChunkers = []struct { + name string + f func(io.Reader) io.Reader + }{ + {"half", iotest.HalfReader}, + {"one", iotest.OneByteReader}, + {"asis", func(r io.Reader) io.Reader { return r }}, + } + writeBuf := make([]byte, 65537) + for i := range writeBuf { + writeBuf[i] = byte(i) + } + var writers = []struct { + name string + f func(w io.Writer, n int) (int, error) + }{ + {"iocopy", func(w io.Writer, n int) (int, error) { + nn, err := io.Copy(w, bytes.NewReader(writeBuf[:n])) + return int(nn), err + }}, + {"write", func(w io.Writer, n int) (int, error) { + return w.Write(writeBuf[:n]) + }}, + {"string", func(w io.Writer, n int) (int, error) { + return io.WriteString(w, string(writeBuf[:n])) + }}, + } + + for _, compress := range []bool{false, true} { + for _, isServer := range []bool{true, false} { + for _, chunker := range readChunkers { + + var connBuf bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) + rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024) + if compress { + wc.newCompressionWriter = compressNoContextTakeover + rc.newDecompressionReader = decompressNoContextTakeover + } + for _, n := range frameSizes { + for _, writer := range writers { + name := fmt.Sprintf("z:%v, s:%v, r:%s, n:%d w:%s", compress, isServer, chunker.name, n, writer.name) + + w, err := wc.NextWriter(TextMessage) + if err != nil { + t.Errorf("%s: wc.NextWriter() returned %v", name, err) + continue + } + nn, err := writer.f(w, n) + if err != nil || nn != n { + t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err) + continue + } + err = w.Close() + if err != nil { + t.Errorf("%s: w.Close() returned %v", name, err) + continue + } + + opCode, r, err := rc.NextReader() + if err != nil || opCode != TextMessage { + t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err) + continue + } + rbuf, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("%s: ReadFull() returned rbuf, %v", name, err) + continue + } + + if len(rbuf) != n { + t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n) + continue + } + + for i, b := range rbuf { + if byte(i) != b { + t.Errorf("%s: bad byte at offset %d", name, i) + break + } + } + } + } + } + } + } +} + +func TestControl(t *testing.T) { + const message = "this is a ping/pong messsage" + for _, isServer := range []bool{true, false} { + for _, isWriteControl := range []bool{true, false} { + name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl) + var connBuf bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) + rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024) + if isWriteControl { + wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second)) + } else { + w, err := wc.NextWriter(PongMessage) + if err != nil { + t.Errorf("%s: wc.NextWriter() returned %v", name, err) + continue + } + if _, err := w.Write([]byte(message)); err != nil { + t.Errorf("%s: w.Write() returned %v", name, err) + continue + } + if err := w.Close(); err != nil { + t.Errorf("%s: w.Close() returned %v", name, err) + continue + } + var actualMessage string + rc.SetPongHandler(func(s string) error { actualMessage = s; return nil }) + rc.NextReader() + if actualMessage != message { + t.Errorf("%s: pong=%q, want %q", name, actualMessage, message) + continue + } + } + } + } +} + +func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) { + const bufSize = 512 + + expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"} + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(make([]byte, bufSize+bufSize/2)) + wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second)) + w.Close() + + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if !reflect.DeepEqual(err, expectedErr) { + t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr) + } + _, _, err = rc.NextReader() + if !reflect.DeepEqual(err, expectedErr) { + t.Fatalf("NextReader() returned %v, want %v", err, expectedErr) + } +} + +func TestEOFWithinFrame(t *testing.T) { + const bufSize = 64 + + for n := 0; ; n++ { + var b bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b}, false, 1024, 1024) + rc := newConn(fakeNetConn{Reader: &b, Writer: nil}, true, 1024, 1024) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(make([]byte, bufSize)) + w.Close() + + if n >= b.Len() { + break + } + b.Truncate(n) + + op, r, err := rc.NextReader() + if err == errUnexpectedEOF { + continue + } + if op != BinaryMessage || err != nil { + t.Fatalf("%d: NextReader() returned %d, %v", n, op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != errUnexpectedEOF { + t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF) + } + _, _, err = rc.NextReader() + if err != errUnexpectedEOF { + t.Fatalf("%d: NextReader() returned %v, want %v", n, err, errUnexpectedEOF) + } + } +} + +func TestEOFBeforeFinalFrame(t *testing.T) { + const bufSize = 512 + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(make([]byte, bufSize+bufSize/2)) + + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != errUnexpectedEOF { + t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) + } + _, _, err = rc.NextReader() + if err != errUnexpectedEOF { + t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF) + } +} + +func TestWriteAfterMessageWriterClose(t *testing.T) { + wc := newConn(fakeNetConn{Reader: nil, Writer: &bytes.Buffer{}}, false, 1024, 1024) + w, _ := wc.NextWriter(BinaryMessage) + io.WriteString(w, "hello") + if err := w.Close(); err != nil { + t.Fatalf("unxpected error closing message writer, %v", err) + } + + if _, err := io.WriteString(w, "world"); err == nil { + t.Fatalf("no error writing after close") + } + + w, _ = wc.NextWriter(BinaryMessage) + io.WriteString(w, "hello") + + // close w by getting next writer + _, err := wc.NextWriter(BinaryMessage) + if err != nil { + t.Fatalf("unexpected error getting next writer, %v", err) + } + + if _, err := io.WriteString(w, "world"); err == nil { + t.Fatalf("no error writing after close") + } +} + +func TestReadLimit(t *testing.T) { + + const readLimit = 512 + message := make([]byte, readLimit+1) + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + rc.SetReadLimit(readLimit) + + // Send message at the limit with interleaved pong. + w, _ := wc.NextWriter(BinaryMessage) + w.Write(message[:readLimit-1]) + wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second)) + w.Write(message[:1]) + w.Close() + + // Send message larger than the limit. + wc.WriteMessage(BinaryMessage, message[:readLimit+1]) + + op, _, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("1: NextReader() returned %d, %v", op, err) + } + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("2: NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != ErrReadLimit { + t.Fatalf("io.Copy() returned %v", err) + } +} + +func TestAddrs(t *testing.T) { + c := newConn(&fakeNetConn{}, true, 1024, 1024) + if c.LocalAddr() != localAddr { + t.Errorf("LocalAddr = %v, want %v", c.LocalAddr(), localAddr) + } + if c.RemoteAddr() != remoteAddr { + t.Errorf("RemoteAddr = %v, want %v", c.RemoteAddr(), remoteAddr) + } +} + +func TestUnderlyingConn(t *testing.T) { + var b1, b2 bytes.Buffer + fc := fakeNetConn{Reader: &b1, Writer: &b2} + c := newConn(fc, true, 1024, 1024) + ul := c.UnderlyingConn() + if ul != fc { + t.Fatalf("Underlying conn is not what it should be.") + } +} + +func TestBufioReadBytes(t *testing.T) { + + // Test calling bufio.ReadBytes for value longer than read buffer size. + + m := make([]byte, 512) + m[len(m)-1] = '\n' + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, len(m)+64, len(m)+64) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(m) + w.Close() + + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("NextReader() returned %d, %v", op, err) + } + + br := bufio.NewReader(r) + p, err := br.ReadBytes('\n') + if err != nil { + t.Fatalf("ReadBytes() returned %v", err) + } + if len(p) != len(m) { + 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") +} + +func TestBufioReuse(t *testing.T) { + brw := bufio.NewReadWriter(bufio.NewReader(nil), bufio.NewWriter(nil)) + c := newConnBRW(nil, false, 0, 0, brw) + + if c.br != brw.Reader { + t.Error("connection did not reuse bufio.Reader") + } + + var wh writeHook + brw.Writer.Reset(&wh) + brw.WriteByte(0) + brw.Flush() + if &c.writeBuf[0] != &wh.p[0] { + t.Error("connection did not reuse bufio.Writer") + } + + brw = bufio.NewReadWriter(bufio.NewReaderSize(nil, 0), bufio.NewWriterSize(nil, 0)) + c = newConnBRW(nil, false, 0, 0, brw) + + if c.br == brw.Reader { + t.Error("connection used bufio.Reader with small size") + } + + brw.Writer.Reset(&wh) + brw.WriteByte(0) + brw.Flush() + if &c.writeBuf[0] != &wh.p[0] { + t.Error("connection used bufio.Writer with small size") + } + +} diff --git a/websocket/doc.go b/websocket/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..f5ff0823da773f6d389000c632d9d0fc0f4a753a --- /dev/null +++ b/websocket/doc.go @@ -0,0 +1,179 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package websocket implements the WebSocket protocol defined in RFC 6455. +// +// Overview +// +// The Conn type represents a WebSocket connection. A server application calls +// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: +// +// var upgrader = websocket.Upgrader{ +// ReadBufferSize: 1024, +// WriteBufferSize: 1024, +// } +// +// func handler(w http.ResponseWriter, r *http.Request) { +// conn, err := upgrader.Upgrade(w, r, nil) +// if err != nil { +// log.Println(err) +// return +// } +// ... Use conn to send and receive messages. +// } +// +// Call the connection's WriteMessage and ReadMessage methods to send and +// receive messages as a slice of bytes. This snippet of code shows how to echo +// messages using these methods: +// +// for { +// messageType, p, err := conn.ReadMessage() +// if err != nil { +// return +// } +// if err := conn.WriteMessage(messageType, p); err != nil { +// return err +// } +// } +// +// In above snippet of code, p is a []byte and messageType is an int with value +// websocket.BinaryMessage or websocket.TextMessage. +// +// An application can also send and receive messages using the io.WriteCloser +// and io.Reader interfaces. To send a message, call the connection NextWriter +// 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 +// shows how to echo messages using the NextWriter and NextReader methods: +// +// for { +// messageType, r, err := conn.NextReader() +// if err != nil { +// return +// } +// w, err := conn.NextWriter(messageType) +// if err != nil { +// return err +// } +// if _, err := io.Copy(w, r); err != nil { +// return err +// } +// if err := w.Close(); err != nil { +// return err +// } +// } +// +// Data Messages +// +// The WebSocket protocol distinguishes between text and binary data messages. +// Text messages are interpreted as UTF-8 encoded text. The interpretation of +// binary messages is left to the application. +// +// This package uses the TextMessage and BinaryMessage integer constants to +// identify the two data message types. The ReadMessage and NextReader methods +// return the type of the received message. The messageType argument to the +// WriteMessage and NextWriter methods specifies the type of a sent message. +// +// It is the application's responsibility to ensure that text messages are +// valid UTF-8 encoded text. +// +// Control Messages +// +// The WebSocket protocol defines three types of control messages: close, ping +// and pong. Call the connection WriteControl, WriteMessage or NextWriter +// methods to send a control message to the peer. +// +// 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. +// +// Connections handle received ping and pong messages by invoking callback +// functions set with SetPingHandler and SetPongHandler methods. The callback +// 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: +// +// func readLoop(c *websocket.Conn) { +// for { +// if _, _, err := c.NextReader(); err != nil { +// c.Close() +// break +// } +// } +// } +// +// Concurrency +// +// Connections support one concurrent reader and one concurrent writer. +// +// Applications are responsible for ensuring that no more than one goroutine +// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, +// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and +// that no more than one goroutine calls the read methods (NextReader, +// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) +// concurrently. +// +// The Close and WriteControl methods can be called concurrently with all other +// methods. +// +// 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 package-level Upgrade function does not perform origin +// checking. The application is responsible for checking the Origin header +// before calling the Upgrade function. +// +// 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. +// +// var upgrader = websocket.Upgrader{ +// EnableCompression: true, +// } +// +// If compression was successfully negotiated with the connection's peer, any +// message received in compressed form will be automatically decompressed. +// All Read methods will return uncompressed bytes. +// +// Per message compression of messages written to a connection can be enabled +// or disabled by calling the corresponding Conn method: +// +// conn.EnableWriteCompression(false) +// +// 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 diff --git a/websocket/example_test.go b/websocket/example_test.go new file mode 100644 index 0000000000000000000000000000000000000000..96449eac70e0528fd46a060c478cf10273c68b17 --- /dev/null +++ b/websocket/example_test.go @@ -0,0 +1,46 @@ +// 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" +) + +var ( + c *websocket.Conn + req *http.Request +) + +// 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() { + + 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/websocket/f.zip b/websocket/f.zip new file mode 100644 index 0000000000000000000000000000000000000000..c4d9f8f465130300245c285b7e608b32a113146c GIT binary patch literal 63895 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�|mKTKIDIYTJc+>;O(+5mO|29}8Adz(K}h6bjUd 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 0; i-- { + if err := wc.WriteMessage(TextMessage, data[:i]); err != nil { + t.Fatal(err) + } + messageCount++ + } + + // Whitespace. + + if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil { + t.Fatal(err) + } + messageCount++ + + // Close. + + if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil { + t.Fatal(err) + } + + for i := 0; i < messageCount; i++ { + err := rc.ReadJSON(&v) + if err != io.ErrUnexpectedEOF { + t.Error("read", i, err) + } + } + + err = rc.ReadJSON(&v) + if _, ok := err.(*CloseError); !ok { + t.Error("final", err) + } +} + +func TestDeprecatedJSON(t *testing.T) { + var buf bytes.Buffer + c := fakeNetConn{&buf, &buf} + wc := newConn(c, true, 1024, 1024) + rc := newConn(c, false, 1024, 1024) + + var actual, expect struct { + A int + B string + } + expect.A = 1 + expect.B = "hello" + + if err := WriteJSON(wc, &expect); err != nil { + t.Fatal("write", err) + } + + if err := ReadJSON(rc, &actual); err != nil { + t.Fatal("read", err) + } + + if !reflect.DeepEqual(&actual, &expect) { + t.Fatal("equal", actual, expect) + } +} diff --git a/websocket/mask.go b/websocket/mask.go new file mode 100644 index 0000000000000000000000000000000000000000..6a88bbc74342cd64c6d342c1578fb02ee42bfd22 --- /dev/null +++ b/websocket/mask.go @@ -0,0 +1,55 @@ +// 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 !appengine + +package websocket + +import "unsafe" + +const wordSize = int(unsafe.Sizeof(uintptr(0))) + +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 +} diff --git a/websocket/mask_safe.go b/websocket/mask_safe.go new file mode 100644 index 0000000000000000000000000000000000000000..2aac060e52e7093d2dce5051d639a31560f6c4e8 --- /dev/null +++ b/websocket/mask_safe.go @@ -0,0 +1,15 @@ +// 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 appengine + +package websocket + +func maskBytes(key [4]byte, pos int, b []byte) int { + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + return pos & 3 +} diff --git a/websocket/mask_test.go b/websocket/mask_test.go new file mode 100644 index 0000000000000000000000000000000000000000..298a1e509ee637618c6a7673a44a96b0cdcce270 --- /dev/null +++ b/websocket/mask_test.go @@ -0,0 +1,73 @@ +// 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. + +// Require 1.7 for sub-bencmarks +// +build go1.7,!appengine + +package websocket + +import ( + "fmt" + "testing" +) + +func maskBytesByByte(key [4]byte, pos int, b []byte) int { + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + return pos & 3 +} + +func notzero(b []byte) int { + for i := range b { + if b[i] != 0 { + return i + } + } + return -1 +} + +func TestMaskBytes(t *testing.T) { + key := [4]byte{1, 2, 3, 4} + for size := 1; size <= 1024; size++ { + for align := 0; align < wordSize; align++ { + for pos := 0; pos < 4; pos++ { + b := make([]byte, size+align)[align:] + maskBytes(key, pos, b) + maskBytesByByte(key, pos, b) + if i := notzero(b); i >= 0 { + t.Errorf("size:%d, align:%d, pos:%d, offset:%d", size, align, pos, i) + } + } + } + } +} + +func BenchmarkMaskBytes(b *testing.B) { + for _, size := range []int{2, 4, 8, 16, 32, 512, 1024} { + b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) { + for _, align := range []int{wordSize / 2} { + b.Run(fmt.Sprintf("align-%d", align), func(b *testing.B) { + for _, fn := range []struct { + name string + fn func(key [4]byte, pos int, b []byte) int + }{ + {"byte", maskBytesByByte}, + {"word", maskBytes}, + } { + b.Run(fn.name, func(b *testing.B) { + key := newMaskKey() + data := make([]byte, size+align)[align:] + for i := 0; i < b.N; i++ { + fn.fn(key, 0, data) + } + b.SetBytes(int64(len(data))) + }) + } + }) + } + }) + } +} diff --git a/websocket/prepared.go b/websocket/prepared.go new file mode 100644 index 0000000000000000000000000000000000000000..1efffbd1ebe91160879664fe5c62d2c94be09064 --- /dev/null +++ b/websocket/prepared.go @@ -0,0 +1,103 @@ +// 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 ( + "bytes" + "net" + "sync" + "time" +) + +// PreparedMessage caches on the wire representations of a message payload. +// Use PreparedMessage to efficiently send a message payload to multiple +// connections. PreparedMessage is especially useful when compression is used +// because the CPU and memory expensive compression operation can be executed +// once for a given set of compression options. +type PreparedMessage struct { + messageType int + data []byte + err error + mu sync.Mutex + frames map[prepareKey]*preparedFrame +} + +// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. +type prepareKey struct { + isServer bool + compress bool + compressionLevel int +} + +// preparedFrame contains data in wire representation. +type preparedFrame struct { + once sync.Once + data []byte +} + +// NewPreparedMessage returns an initialized PreparedMessage. You can then send +// it to connection using WritePreparedMessage method. Valid wire +// representation will be calculated lazily only once for a set of current +// connection options. +func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { + pm := &PreparedMessage{ + messageType: messageType, + frames: make(map[prepareKey]*preparedFrame), + data: data, + } + + // Prepare a plain server frame. + _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) + if err != nil { + return nil, err + } + + // To protect against caller modifying the data argument, remember the data + // copied to the plain server frame. + pm.data = frameData[len(frameData)-len(data):] + return pm, nil +} + +func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { + pm.mu.Lock() + frame, ok := pm.frames[key] + if !ok { + frame = &preparedFrame{} + pm.frames[key] = frame + } + pm.mu.Unlock() + + var err error + frame.once.Do(func() { + // Prepare a frame using a 'fake' connection. + // TODO: Refactor code in conn.go to allow more direct construction of + // the frame. + mu := make(chan bool, 1) + mu <- true + var nc prepareConn + c := &Conn{ + conn: &nc, + mu: mu, + isServer: key.isServer, + compressionLevel: key.compressionLevel, + enableWriteCompression: true, + writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), + } + if key.compress { + c.newCompressionWriter = compressNoContextTakeover + } + err = c.WriteMessage(pm.messageType, pm.data) + frame.data = nc.buf.Bytes() + }) + return pm.messageType, frame.data, err +} + +type prepareConn struct { + buf bytes.Buffer + net.Conn +} + +func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } +func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } diff --git a/websocket/prepared_test.go b/websocket/prepared_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cf98c6c10a306ce25fb75c317fb9a3270f7f31e7 --- /dev/null +++ b/websocket/prepared_test.go @@ -0,0 +1,74 @@ +// 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 ( + "bytes" + "compress/flate" + "math/rand" + "testing" +) + +var preparedMessageTests = []struct { + messageType int + isServer bool + enableWriteCompression bool + compressionLevel int +}{ + // Server + {TextMessage, true, false, flate.BestSpeed}, + {TextMessage, true, true, flate.BestSpeed}, + {TextMessage, true, true, flate.BestCompression}, + {PingMessage, true, false, flate.BestSpeed}, + {PingMessage, true, true, flate.BestSpeed}, + + // Client + {TextMessage, false, false, flate.BestSpeed}, + {TextMessage, false, true, flate.BestSpeed}, + {TextMessage, false, true, flate.BestCompression}, + {PingMessage, false, false, flate.BestSpeed}, + {PingMessage, false, true, flate.BestSpeed}, +} + +func TestPreparedMessage(t *testing.T) { + for _, tt := range preparedMessageTests { + var data = []byte("this is a test") + var buf bytes.Buffer + c := newConn(fakeNetConn{Reader: nil, Writer: &buf}, tt.isServer, 1024, 1024) + if tt.enableWriteCompression { + c.newCompressionWriter = compressNoContextTakeover + } + c.SetCompressionLevel(tt.compressionLevel) + + // Seed random number generator for consistent frame mask. + rand.Seed(1234) + + if err := c.WriteMessage(tt.messageType, data); err != nil { + t.Fatal(err) + } + want := buf.String() + + pm, err := NewPreparedMessage(tt.messageType, data) + if err != nil { + t.Fatal(err) + } + + // Scribble on data to ensure that NewPreparedMessage takes a snapshot. + copy(data, "hello world") + + // Seed random number generator for consistent frame mask. + rand.Seed(1234) + + buf.Reset() + if err := c.WritePreparedMessage(pm); err != nil { + t.Fatal(err) + } + got := buf.String() + + if got != want { + t.Errorf("write message != prepared message for %+v", tt) + } + } +} diff --git a/websocket/server.go b/websocket/server.go new file mode 100644 index 0000000000000000000000000000000000000000..6ae97c54fec62e19abe0570d0859e743cc019350 --- /dev/null +++ b/websocket/server.go @@ -0,0 +1,292 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "errors" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// HandshakeError describes an error with the handshake from the peer. +type HandshakeError struct { + message string +} + +func (e HandshakeError) Error() string { return e.message } + +// Upgrader specifies parameters for upgrading an HTTP connection to a +// WebSocket connection. +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 + // 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. + ReadBufferSize, WriteBufferSize int + + // Subprotocols specifies the server's supported protocols in order of + // preference. If this field is set, then the Upgrade method negotiates a + // subprotocol by selecting the first match in this list with a protocol + // requested by the client. + Subprotocols []string + + // Error specifies the function for generating HTTP error responses. If Error + // is nil, then http.Error is used to generate the HTTP response. + Error func(w http.ResponseWriter, r *http.Request, status int, reason error) + + // CheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + 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) { + err := HandshakeError{reason} + if u.Error != nil { + u.Error(w, r, status, err) + } else { + w.Header().Set("Sec-Websocket-Version", "13") + http.Error(w, http.StatusText(status), status) + } + return nil, err +} + +// checkSameOrigin returns true if the origin is not set or is equal to the request host. +func checkSameOrigin(r *http.Request) bool { + origin := r.Header["Origin"] + if len(origin) == 0 { + return true + } + u, err := url.Parse(origin[0]) + if err != nil { + return false + } + return u.Host == r.Host +} + +func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { + if u.Subprotocols != nil { + clientProtocols := Subprotocols(r) + for _, serverProtocol := range u.Subprotocols { + for _, clientProtocol := range clientProtocols { + if clientProtocol == serverProtocol { + return clientProtocol + } + } + } + } else if responseHeader != nil { + return responseHeader.Get("Sec-Websocket-Protocol") + } + return "" +} + +// Upgrade upgrades the HTTP server connection to the WebSocket protocol. +// +// 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: not a websocket handshake: request method is not GET") + } + + 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, "Connection", "upgrade") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") + } + + if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") + } + + if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") + } + + checkOrigin := u.CheckOrigin + if checkOrigin == nil { + checkOrigin = checkSameOrigin + } + if !checkOrigin(r) { + return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") + } + + challengeKey := r.Header.Get("Sec-Websocket-Key") + if challengeKey == "" { + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") + } + + 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 ( + netConn net.Conn + err error + ) + + h, ok := w.(http.Hijacker) + if !ok { + return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") + } + var brw *bufio.ReadWriter + netConn, brw, err = h.Hijack() + if err != nil { + return u.returnError(w, r, http.StatusInternalServerError, err.Error()) + } + + if brw.Reader.Buffered() > 0 { + netConn.Close() + return nil, errors.New("websocket: client sent data before handshake is complete") + } + + c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw) + c.subprotocol = subprotocol + + if compress { + c.newCompressionWriter = compressNoContextTakeover + c.newDecompressionReader = decompressNoContextTakeover + } + + 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, computeAcceptKey(challengeKey)...) + p = append(p, "\r\n"...) + if c.subprotocol != "" { + p = append(p, "Sec-Websocket-Protocol: "...) + p = append(p, c.subprotocol...) + 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 { + if k == "Sec-Websocket-Protocol" { + continue + } + for _, v := range vs { + p = append(p, k...) + p = append(p, ": "...) + for i := 0; i < len(v); i++ { + b := v[i] + if b <= 31 { + // prevent response splitting. + b = ' ' + } + p = append(p, b) + } + p = append(p, "\r\n"...) + } + } + p = append(p, "\r\n"...) + + // Clear deadlines set by HTTP server. + netConn.SetDeadline(time.Time{}) + + if u.HandshakeTimeout > 0 { + netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) + } + if _, err = netConn.Write(p); err != nil { + netConn.Close() + return nil, err + } + if u.HandshakeTimeout > 0 { + netConn.SetWriteDeadline(time.Time{}) + } + + return c, nil +} + +// Upgrade upgrades the HTTP server connection to the WebSocket protocol. +// +// Deprecated: Use websocket.Upgrader instead. +// +// Upgrade does not perform origin checking. The application is responsible for +// checking the Origin header before calling Upgrade. An example implementation +// of the same origin policy check is: +// +// if req.Header.Get("Origin") != "http://"+req.Host { +// http.Error(w, "Origin not allowed", 403) +// return +// } +// +// If the endpoint supports subprotocols, then the application is responsible +// for negotiating the protocol used on the connection. Use the Subprotocols() +// function to get the subprotocols requested by the client. Use the +// Sec-Websocket-Protocol response header to specify the subprotocol selected +// by the application. +// +// The responseHeader is included in the response to the client's upgrade +// request. Use the responseHeader to specify cookies (Set-Cookie) and the +// negotiated subprotocol (Sec-Websocket-Protocol). +// +// The connection buffers IO to the underlying network connection. The +// readBufSize and writeBufSize parameters specify the size of the buffers to +// use. Messages can be larger than the buffers. +// +// If the request is not a valid WebSocket handshake, then Upgrade returns an +// error of type HandshakeError. Applications should handle this error by +// replying to the client with an HTTP error response. +func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { + u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} + u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { + // don't return errors to maintain backwards compatibility + } + u.CheckOrigin = func(r *http.Request) bool { + // allow all connections by default + return true + } + return u.Upgrade(w, r, responseHeader) +} + +// Subprotocols returns the subprotocols requested by the client in the +// Sec-Websocket-Protocol header. +func Subprotocols(r *http.Request) []string { + h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) + if h == "" { + return nil + } + protocols := strings.Split(h, ",") + for i := range protocols { + protocols[i] = strings.TrimSpace(protocols[i]) + } + 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") +} diff --git a/websocket/server_test.go b/websocket/server_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0a28141d6c6edbddd5cfcff784167b79a679fc36 --- /dev/null +++ b/websocket/server_test.go @@ -0,0 +1,51 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "net/http" + "reflect" + "testing" +) + +var subprotocolTests = []struct { + h string + protocols []string +}{ + {"", nil}, + {"foo", []string{"foo"}}, + {"foo,bar", []string{"foo", "bar"}}, + {"foo, bar", []string{"foo", "bar"}}, + {" foo, bar", []string{"foo", "bar"}}, + {" foo, bar ", []string{"foo", "bar"}}, +} + +func TestSubprotocols(t *testing.T) { + for _, st := range subprotocolTests { + r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}} + protocols := Subprotocols(&r) + if !reflect.DeepEqual(st.protocols, protocols) { + t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols) + } + } +} + +var isWebSocketUpgradeTests = []struct { + ok bool + h http.Header +}{ + {false, http.Header{"Upgrade": {"websocket"}}}, + {false, http.Header{"Connection": {"upgrade"}}}, + {true, http.Header{"Connection": {"upgRade"}, "Upgrade": {"WebSocket"}}}, +} + +func TestIsWebSocketUpgrade(t *testing.T) { + for _, tt := range isWebSocketUpgradeTests { + ok := IsWebSocketUpgrade(&http.Request{Header: tt.h}) + if tt.ok != ok { + t.Errorf("IsWebSocketUpgrade(%v) returned %v, want %v", tt.h, ok, tt.ok) + } + } +} diff --git a/websocket/util.go b/websocket/util.go new file mode 100644 index 0000000000000000000000000000000000000000..262e647bce224464a2edfef7604a47267c4152e8 --- /dev/null +++ b/websocket/util.go @@ -0,0 +1,214 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "io" + "net/http" + "strings" +) + +var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + +func computeAcceptKey(challengeKey string) string { + h := sha1.New() + h.Write([]byte(challengeKey)) + h.Write(keyGUID) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func generateChallengeKey() (string, error) { + p := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, p); err != nil { + return "", err + } + 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 + } +} + +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++ + case b == '\\': + escape = true + case b == '"': + return string(p[:j]), s[i+1:] + default: + p[j] = b + j++ + } + } + 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 +} diff --git a/websocket/util_test.go b/websocket/util_test.go new file mode 100644 index 0000000000000000000000000000000000000000..610e613c02ec01a1e639c389429f38e1827eda4b --- /dev/null +++ b/websocket/util_test.go @@ -0,0 +1,74 @@ +// Copyright 2014 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 ( + "net/http" + "reflect" + "testing" +) + +var tokenListContainsValueTests = []struct { + value string + ok bool +}{ + {"WebSocket", true}, + {"WEBSOCKET", true}, + {"websocket", true}, + {"websockets", false}, + {"x websocket", false}, + {"websocket x", false}, + {"other,websocket,more", true}, + {"other, websocket, more", true}, +} + +func TestTokenListContainsValue(t *testing.T) { + for _, tt := range tokenListContainsValueTests { + h := http.Header{"Upgrade": {tt.value}} + ok := tokenListContainsValue(h, "Upgrade", "websocket") + if ok != tt.ok { + t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok) + } + } +} + +var parseExtensionTests = []struct { + value string + extensions []map[string]string +}{ + {`foo`, []map[string]string{map[string]string{"": "foo"}}}, + {`foo, bar; baz=2`, []map[string]string{ + map[string]string{"": "foo"}, + map[string]string{"": "bar", "baz": "2"}}}, + {`foo; bar="b,a;z"`, []map[string]string{ + map[string]string{"": "foo", "bar": "b,a;z"}}}, + {`foo , bar; baz = 2`, []map[string]string{ + map[string]string{"": "foo"}, + map[string]string{"": "bar", "baz": "2"}}}, + {`foo, bar; baz=2 junk`, []map[string]string{ + map[string]string{"": "foo"}}}, + {`foo junk, bar; baz=2 junk`, nil}, + {`mux; max-channels=4; flow-control, deflate-stream`, []map[string]string{ + map[string]string{"": "mux", "max-channels": "4", "flow-control": ""}, + map[string]string{"": "deflate-stream"}}}, + {`permessage-foo; x="10"`, []map[string]string{ + map[string]string{"": "permessage-foo", "x": "10"}}}, + {`permessage-foo; use_y, permessage-foo`, []map[string]string{ + map[string]string{"": "permessage-foo", "use_y": ""}, + map[string]string{"": "permessage-foo"}}}, + {`permessage-deflate; client_max_window_bits; server_max_window_bits=10 , permessage-deflate; client_max_window_bits`, []map[string]string{ + map[string]string{"": "permessage-deflate", "client_max_window_bits": "", "server_max_window_bits": "10"}, + map[string]string{"": "permessage-deflate", "client_max_window_bits": ""}}}, +} + +func TestParseExtensions(t *testing.T) { + for _, tt := range parseExtensionTests { + h := http.Header{http.CanonicalHeaderKey("Sec-WebSocket-Extensions"): {tt.value}} + extensions := parseExtensions(h) + if !reflect.DeepEqual(extensions, tt.extensions) { + t.Errorf("parseExtensions(%q)\n = %v,\nwant %v", tt.value, extensions, tt.extensions) + } + } +} diff --git a/websocket/websocket-master/examples/autobahn/README.md b/websocket/websocket-master/examples/autobahn/README.md new file mode 100644 index 0000000000000000000000000000000000000000..075ac1530a90e873b8eb13f0545f8a19f7658b2c --- /dev/null +++ b/websocket/websocket-master/examples/autobahn/README.md @@ -0,0 +1,13 @@ +# Test Server + +This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite). + +To test the server, run + + go run server.go + +and start the client test driver + + wstest -m fuzzingclient -s fuzzingclient.json + +When the client completes, it writes a report to reports/clients/index.html. diff --git a/websocket/websocket-master/examples/autobahn/fuzzingclient.json b/websocket/websocket-master/examples/autobahn/fuzzingclient.json new file mode 100644 index 0000000000000000000000000000000000000000..aa3a0bc0a80ffe67b6affb062f459ac89395b513 --- /dev/null +++ b/websocket/websocket-master/examples/autobahn/fuzzingclient.json @@ -0,0 +1,15 @@ + +{ + "options": {"failByDrop": false}, + "outdir": "./reports/clients", + "servers": [ + {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}}, + {"agent": "ReadAllWritePreparedMessage", "url": "ws://localhost:9000/p", "options": {"version": 18}}, + {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}}, + {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}}, + {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}} + ], + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/websocket/websocket-master/examples/autobahn/server.go b/websocket/websocket-master/examples/autobahn/server.go new file mode 100644 index 0000000000000000000000000000000000000000..3db880f90175f4c33fd9d9d744871f62a76c5029 --- /dev/null +++ b/websocket/websocket-master/examples/autobahn/server.go @@ -0,0 +1,265 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Command server is a test server for the Autobahn WebSockets Test Suite. +package main + +import ( + "errors" + "flag" + "io" + "log" + "net/http" + "time" + "unicode/utf8" + + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 4096, + EnableCompression: true, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +// echoCopy echoes messages from the client using io.Copy. +func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("Upgrade:", err) + return + } + defer conn.Close() + for { + mt, r, err := conn.NextReader() + if err != nil { + if err != io.EOF { + log.Println("NextReader:", err) + } + return + } + if mt == websocket.TextMessage { + r = &validator{r: r} + } + w, err := conn.NextWriter(mt) + if err != nil { + log.Println("NextWriter:", err) + return + } + if mt == websocket.TextMessage { + r = &validator{r: r} + } + if writerOnly { + _, err = io.Copy(struct{ io.Writer }{w}, r) + } else { + _, err = io.Copy(w, r) + } + if err != nil { + if err == errInvalidUTF8 { + conn.WriteControl(websocket.CloseMessage, + websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), + time.Time{}) + } + log.Println("Copy:", err) + return + } + err = w.Close() + if err != nil { + log.Println("Close:", err) + return + } + } +} + +func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) { + echoCopy(w, r, true) +} + +func echoCopyFull(w http.ResponseWriter, r *http.Request) { + echoCopy(w, r, false) +} + +// echoReadAll echoes messages from the client by reading the entire message +// with ioutil.ReadAll. +func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("Upgrade:", err) + return + } + defer conn.Close() + for { + mt, b, err := conn.ReadMessage() + if err != nil { + if err != io.EOF { + log.Println("NextReader:", err) + } + return + } + if mt == websocket.TextMessage { + if !utf8.Valid(b) { + conn.WriteControl(websocket.CloseMessage, + websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), + time.Time{}) + log.Println("ReadAll: invalid utf8") + } + } + if writeMessage { + if !writePrepared { + err = conn.WriteMessage(mt, b) + if err != nil { + log.Println("WriteMessage:", err) + } + } else { + pm, err := websocket.NewPreparedMessage(mt, b) + if err != nil { + log.Println("NewPreparedMessage:", err) + return + } + err = conn.WritePreparedMessage(pm) + if err != nil { + log.Println("WritePreparedMessage:", err) + } + } + } else { + w, err := conn.NextWriter(mt) + if err != nil { + log.Println("NextWriter:", err) + return + } + if _, err := w.Write(b); err != nil { + log.Println("Writer:", err) + return + } + if err := w.Close(); err != nil { + log.Println("Close:", err) + return + } + } + } +} + +func echoReadAllWriter(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, false, false) +} + +func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, true, false) +} + +func echoReadAllWritePreparedMessage(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, true, true) +} + +func serveHome(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.Error(w, "Not found.", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + io.WriteString(w, "Echo Server") +} + +var addr = flag.String("addr", ":9000", "http service address") + +func main() { + flag.Parse() + http.HandleFunc("/", serveHome) + http.HandleFunc("/c", echoCopyWriterOnly) + http.HandleFunc("/f", echoCopyFull) + http.HandleFunc("/r", echoReadAllWriter) + http.HandleFunc("/m", echoReadAllWriteMessage) + http.HandleFunc("/p", echoReadAllWritePreparedMessage) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} + +type validator struct { + state int + x rune + r io.Reader +} + +var errInvalidUTF8 = errors.New("invalid utf8") + +func (r *validator) Read(p []byte) (int, error) { + n, err := r.r.Read(p) + state := r.state + x := r.x + for _, b := range p[:n] { + state, x = decode(state, x, b) + if state == utf8Reject { + break + } + } + r.state = state + r.x = x + if state == utf8Reject || (err == io.EOF && state != utf8Accept) { + return n, errInvalidUTF8 + } + return n, err +} + +// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +// +// Copyright (c) 2008-2009 Bjoern Hoehrmann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +var utf8d = [...]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df + 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef + 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8 +} + +const ( + utf8Accept = 0 + utf8Reject = 1 +) + +func decode(state int, x rune, b byte) (int, rune) { + t := utf8d[b] + if state != utf8Accept { + x = rune(b&0x3f) | (x << 6) + } else { + x = rune((0xff >> t) & b) + } + state = int(utf8d[256+state*16+int(t)]) + return state, x +} diff --git a/websocket/websocket-master/examples/chat/README.md b/websocket/websocket-master/examples/chat/README.md new file mode 100644 index 0000000000000000000000000000000000000000..47c82f908b11ef6e860ead07705d61f657365912 --- /dev/null +++ b/websocket/websocket-master/examples/chat/README.md @@ -0,0 +1,102 @@ +# Chat Example + +This application shows how to use use the +[websocket](https://github.com/gorilla/websocket) package to implement a simple +web chat application. + +## Running the example + +The example requires a working Go development environment. The [Getting +Started](http://golang.org/doc/install) page describes how to install the +development environment. + +Once you have Go up and running, you can download, build and run the example +using the following commands. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat` + $ go run *.go + +To use the chat example, open http://localhost:8080/ in your browser. + +## Server + +The server application defines two types, `Client` and `Hub`. The server +creates an instance of the `Client` type for each websocket connection. A +`Client` acts as an intermediary between the websocket connection and a single +instance of the `Hub` type. The `Hub` maintains a set of registered clients and +broadcasts messages to the clients. + +The application runs one goroutine for the `Hub` and two goroutines for each +`Client`. The goroutines communicate with each other using channels. The `Hub` +has channels for registering clients, unregistering clients and broadcasting +messages. A `Client` has a buffered channel of outbound messages. One of the +client's goroutines reads messages from this channel and writes the messages to +the websocket. The other client goroutine reads messages from the websocket and +sends them to the hub. + +### Hub + +The code for the `Hub` type is in +[hub.go](https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go). +The application's `main` function starts the hub's `run` method as a goroutine. +Clients send requests to the hub using the `register`, `unregister` and +`broadcast` channels. + +The hub registers clients by adding the client pointer as a key in the +`clients` map. The map value is always true. + +The unregister code is a little more complicated. In addition to deleting the +client pointer from the `clients` map, the hub closes the clients's `send` +channel to signal the client that no more messages will be sent to the client. + +The hub handles messages by looping over the registered clients and sending the +message to the client's `send` channel. If the client's `send` buffer is full, +then the hub assumes that the client is dead or stuck. In this case, the hub +unregisters the client and closes the websocket. + +### Client + +The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/master/examples/chat/client.go). + +The `serveWs` function is registered by the application's `main` function as +an HTTP handler. The handler upgrades the HTTP connection to the WebSocket +protocol, creates a client, registers the client with the hub and schedules the +client to be unregistered using a defer statement. + +Next, the HTTP handler starts the client's `writePump` method as a goroutine. +This method transfers messages from the client's send channel to the websocket +connection. The writer method exits when the channel is closed by the hub or +there's an error writing to the websocket connection. + +Finally, the HTTP handler calls the client's `readPump` method. This method +transfers inbound messages from the websocket to the hub. + +WebSocket connections [support one concurrent reader and one concurrent +writer](https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency). The +application ensures that these concurrency requirements are met by executing +all reads from the `readPump` goroutine and all writes from the `writePump` +goroutine. + +To improve efficiency under high load, the `writePump` function coalesces +pending chat messages in the `send` channel to a single WebSocket message. This +reduces the number of system calls and the amount of data sent over the +network. + +## Frontend + +The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/master/examples/chat/home.html). + +On document load, the script checks for websocket functionality in the browser. +If websocket functionality is available, then the script opens a connection to +the server and registers a callback to handle messages from the server. The +callback appends the message to the chat log using the appendLog function. + +To allow the user to manually scroll through the chat log without interruption +from new messages, the `appendLog` function checks the scroll position before +adding new content. If the chat log is scrolled to the bottom, then the +function scrolls new content into view after adding the content. Otherwise, the +scroll position is not changed. + +The form handler writes the user input to the websocket and clears the input +field. diff --git a/websocket/websocket-master/examples/chat/client.go b/websocket/websocket-master/examples/chat/client.go new file mode 100644 index 0000000000000000000000000000000000000000..ecfd9a7aabfd49b495c60e2f7818dff7602b0b72 --- /dev/null +++ b/websocket/websocket-master/examples/chat/client.go @@ -0,0 +1,137 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var ( + newline = []byte{'\n'} + space = []byte{' '} +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// Client is a middleman between the websocket connection and the hub. +type Client struct { + hub *Hub + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +// readPump pumps messages from the websocket connection to the hub. +// +// The application runs readPump in a per-connection goroutine. The application +// ensures that there is at most one reader on a connection by executing all +// reads from this goroutine. +func (c *Client) readPump() { + defer func() { + c.hub.unregister <- c + c.conn.Close() + }() + c.conn.SetReadLimit(maxMessageSize) + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.Printf("error: %v", err) + } + break + } + message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) + c.hub.broadcast <- message + } +} + +// writePump pumps messages from the hub to the websocket connection. +// +// A goroutine running writePump is started for each connection. The +// application ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel. + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + // Add queued chat messages to the current websocket message. + n := len(c.send) + for i := 0; i < n; i++ { + w.Write(newline) + w.Write(<-c.send) + } + + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} + client.hub.register <- client + + // Allow collection of memory referenced by the caller by doing all work in + // new goroutines. + go client.writePump() + go client.readPump() +} diff --git a/websocket/websocket-master/examples/chat/home.html b/websocket/websocket-master/examples/chat/home.html new file mode 100644 index 0000000000000000000000000000000000000000..a39a0c276b4fe9dddf7c71a7df3403b37a6a29c5 --- /dev/null +++ b/websocket/websocket-master/examples/chat/home.html @@ -0,0 +1,98 @@ + + + +Chat Example + + + + +
+
+ + +
+ + diff --git a/websocket/websocket-master/examples/chat/hub.go b/websocket/websocket-master/examples/chat/hub.go new file mode 100644 index 0000000000000000000000000000000000000000..7f07ea0790efa7285077e6bfe3dd9302f9507628 --- /dev/null +++ b/websocket/websocket-master/examples/chat/hub.go @@ -0,0 +1,53 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// hub maintains the set of active clients and broadcasts messages to the +// clients. +type Hub struct { + // Registered clients. + clients map[*Client]bool + + // Inbound messages from the clients. + broadcast chan []byte + + // Register requests from the clients. + register chan *Client + + // Unregister requests from clients. + unregister chan *Client +} + +func newHub() *Hub { + return &Hub{ + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + clients: make(map[*Client]bool), + } +} + +func (h *Hub) run() { + for { + select { + case client := <-h.register: + h.clients[client] = true + case client := <-h.unregister: + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + case message := <-h.broadcast: + for client := range h.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(h.clients, client) + } + } + } + } +} diff --git a/websocket/websocket-master/examples/chat/main.go b/websocket/websocket-master/examples/chat/main.go new file mode 100644 index 0000000000000000000000000000000000000000..74615d59c8f487cf96f63cf26c653cab3ae67bcf --- /dev/null +++ b/websocket/websocket-master/examples/chat/main.go @@ -0,0 +1,40 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "log" + "net/http" +) + +var addr = flag.String("addr", ":8080", "http service address") + +func serveHome(w http.ResponseWriter, r *http.Request) { + log.Println(r.URL) + if r.URL.Path != "/" { + http.Error(w, "Not found", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + http.ServeFile(w, r, "home.html") +} + +func main() { + flag.Parse() + hub := newHub() + go hub.run() + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + serveWs(hub, w, r) + }) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/websocket/websocket-master/examples/command/README.md b/websocket/websocket-master/examples/command/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ed6f78684402ebd8cc9de1c2f1a16ec6d4ef9ecf --- /dev/null +++ b/websocket/websocket-master/examples/command/README.md @@ -0,0 +1,19 @@ +# Command example + +This example connects a websocket connection to stdin and stdout of a command. +Received messages are written to stdin followed by a `\n`. Each line read from +standard out is sent as a message to the client. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command` + $ go run main.go + # Open http://localhost:8080/ . + +Try the following commands. + + # Echo sent messages to the output area. + $ go run main.go cat + + # Run a shell.Try sending "ls" and "cat main.go". + $ go run main.go sh + diff --git a/websocket/websocket-master/examples/command/home.html b/websocket/websocket-master/examples/command/home.html new file mode 100644 index 0000000000000000000000000000000000000000..19c46128a0ea0c701819872ec46a890622c2f5a7 --- /dev/null +++ b/websocket/websocket-master/examples/command/home.html @@ -0,0 +1,102 @@ + + + +Command Example + + + + +
+
+ + +
+ + diff --git a/websocket/websocket-master/examples/command/main.go b/websocket/websocket-master/examples/command/main.go new file mode 100644 index 0000000000000000000000000000000000000000..239c5c85cf15b90bc7e4db73937abe4f179cfb7a --- /dev/null +++ b/websocket/websocket-master/examples/command/main.go @@ -0,0 +1,193 @@ +// 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 main + +import ( + "bufio" + "flag" + "io" + "log" + "net/http" + "os" + "os/exec" + "time" + + "github.com/gorilla/websocket" +) + +var ( + addr = flag.String("addr", "127.0.0.1:8080", "http service address") + cmdPath string +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Maximum message size allowed from peer. + maxMessageSize = 8192 + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Time to wait before force close on connection. + closeGracePeriod = 10 * time.Second +) + +func pumpStdin(ws *websocket.Conn, w io.Writer) { + defer ws.Close() + ws.SetReadLimit(maxMessageSize) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, message, err := ws.ReadMessage() + if err != nil { + break + } + message = append(message, '\n') + if _, err := w.Write(message); err != nil { + break + } + } +} + +func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) { + defer func() { + }() + s := bufio.NewScanner(r) + for s.Scan() { + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { + ws.Close() + break + } + } + if s.Err() != nil { + log.Println("scan:", s.Err()) + } + close(done) + + ws.SetWriteDeadline(time.Now().Add(writeWait)) + ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + time.Sleep(closeGracePeriod) + ws.Close() +} + +func ping(ws *websocket.Conn, done chan struct{}) { + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { + log.Println("ping:", err) + } + case <-done: + return + } + } +} + +func internalError(ws *websocket.Conn, msg string, err error) { + log.Println(msg, err) + ws.WriteMessage(websocket.TextMessage, []byte("Internal server error.")) +} + +var upgrader = websocket.Upgrader{} + +func serveWs(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("upgrade:", err) + return + } + + defer ws.Close() + + outr, outw, err := os.Pipe() + if err != nil { + internalError(ws, "stdout:", err) + return + } + defer outr.Close() + defer outw.Close() + + inr, inw, err := os.Pipe() + if err != nil { + internalError(ws, "stdin:", err) + return + } + defer inr.Close() + defer inw.Close() + + proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{ + Files: []*os.File{inr, outw, outw}, + }) + if err != nil { + internalError(ws, "start:", err) + return + } + + inr.Close() + outw.Close() + + stdoutDone := make(chan struct{}) + go pumpStdout(ws, outr, stdoutDone) + go ping(ws, stdoutDone) + + pumpStdin(ws, inw) + + // Some commands will exit when stdin is closed. + inw.Close() + + // Other commands need a bonk on the head. + if err := proc.Signal(os.Interrupt); err != nil { + log.Println("inter:", err) + } + + select { + case <-stdoutDone: + case <-time.After(time.Second): + // A bigger bonk on the head. + if err := proc.Signal(os.Kill); err != nil { + log.Println("term:", err) + } + <-stdoutDone + } + + if _, err := proc.Wait(); err != nil { + log.Println("wait:", err) + } +} + +func serveHome(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 + } + http.ServeFile(w, r, "home.html") +} + +func main() { + flag.Parse() + if len(flag.Args()) < 1 { + log.Fatal("must specify at least one argument") + } + var err error + cmdPath, err = exec.LookPath(flag.Args()[0]) + if err != nil { + log.Fatal(err) + } + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", serveWs) + log.Fatal(http.ListenAndServe(*addr, nil)) +} diff --git a/websocket/websocket-master/examples/echo/README.md b/websocket/websocket-master/examples/echo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6ad79ed76aa36cd231947f576dbc2247e023df1e --- /dev/null +++ b/websocket/websocket-master/examples/echo/README.md @@ -0,0 +1,17 @@ +# Client and server example + +This example shows a simple client and server. + +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: + + $ go run server.go + +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/websocket/websocket-master/examples/echo/client.go b/websocket/websocket-master/examples/echo/client.go new file mode 100644 index 0000000000000000000000000000000000000000..6578094e7733411070b4abd69c230d65e8b5e37a --- /dev/null +++ b/websocket/websocket-master/examples/echo/client.go @@ -0,0 +1,81 @@ +// 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. + +// +build ignore + +package main + +import ( + "flag" + "log" + "net/url" + "os" + "os/signal" + "time" + + "github.com/gorilla/websocket" +) + +var addr = flag.String("addr", "localhost:8080", "http service address") + +func main() { + flag.Parse() + log.SetFlags(0) + + 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) + if err != nil { + log.Fatal("dial:", err) + } + 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) + return + } + log.Printf("recv: %s", message) + } + }() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + 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/websocket/websocket-master/examples/echo/server.go b/websocket/websocket-master/examples/echo/server.go new file mode 100644 index 0000000000000000000000000000000000000000..a685b0974ad1a43d8823a6c85343b6a06a436df9 --- /dev/null +++ b/websocket/websocket-master/examples/echo/server.go @@ -0,0 +1,132 @@ +// 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. + +// +build ignore + +package main + +import ( + "flag" + "html/template" + "log" + "net/http" + + "github.com/gorilla/websocket" +) + +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) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + for { + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", message) + err = c.WriteMessage(mt, message) + if err != nil { + log.Println("write:", err) + break + } + } +} + +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", 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/websocket/websocket-master/examples/filewatch/README.md b/websocket/websocket-master/examples/filewatch/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ca4931f3baf633dbf326ca1fdcfe22f8b1b0848b --- /dev/null +++ b/websocket/websocket-master/examples/filewatch/README.md @@ -0,0 +1,9 @@ +# File Watch example. + +This example sends a file to the browser client for display whenever the file is modified. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch` + $ go run main.go + # Open http://localhost:8080/ . + # Modify the file to see it update in the browser. diff --git a/websocket/websocket-master/examples/filewatch/main.go b/websocket/websocket-master/examples/filewatch/main.go new file mode 100644 index 0000000000000000000000000000000000000000..f5f9da5c388eaf545488960eb19781391e026d0f --- /dev/null +++ b/websocket/websocket-master/examples/filewatch/main.go @@ -0,0 +1,193 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "html/template" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write the file to the client. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the client. + pongWait = 60 * time.Second + + // Send pings to client with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Poll file for changes with this period. + filePeriod = 10 * time.Second +) + +var ( + addr = flag.String("addr", ":8080", "http service address") + homeTempl = template.Must(template.New("").Parse(homeHTML)) + filename string + upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } +) + +func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) { + fi, err := os.Stat(filename) + if err != nil { + return nil, lastMod, err + } + if !fi.ModTime().After(lastMod) { + return nil, lastMod, nil + } + p, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fi.ModTime(), err + } + return p, fi.ModTime(), nil +} + +func reader(ws *websocket.Conn) { + defer ws.Close() + ws.SetReadLimit(512) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, _, err := ws.ReadMessage() + if err != nil { + break + } + } +} + +func writer(ws *websocket.Conn, lastMod time.Time) { + lastError := "" + pingTicker := time.NewTicker(pingPeriod) + fileTicker := time.NewTicker(filePeriod) + defer func() { + pingTicker.Stop() + fileTicker.Stop() + ws.Close() + }() + for { + select { + case <-fileTicker.C: + var p []byte + var err error + + p, lastMod, err = readFileIfModified(lastMod) + + if err != nil { + if s := err.Error(); s != lastError { + lastError = s + p = []byte(lastError) + } + } else { + lastError = "" + } + + if p != nil { + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { + return + } + } + case <-pingTicker.C: + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} + +func serveWs(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + log.Println(err) + } + return + } + + var lastMod time.Time + if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil { + lastMod = time.Unix(0, n) + } + + go writer(ws, lastMod) + reader(ws) +} + +func serveHome(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 + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + p, lastMod, err := readFileIfModified(time.Time{}) + if err != nil { + p = []byte(err.Error()) + lastMod = time.Unix(0, 0) + } + var v = struct { + Host string + Data string + LastMod string + }{ + r.Host, + string(p), + strconv.FormatInt(lastMod.UnixNano(), 16), + } + homeTempl.Execute(w, &v) +} + +func main() { + flag.Parse() + if flag.NArg() != 1 { + log.Fatal("filename not specified") + } + filename = flag.Args()[0] + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", serveWs) + if err := http.ListenAndServe(*addr, nil); err != nil { + log.Fatal(err) + } +} + +const homeHTML = ` + + + WebSocket Example + + +
{{.Data}}
+ + + +`