Showing preview only (248K chars total). Download the full file or copy to clipboard to get everything.
Repository: gorilla/websocket
Branch: main
Commit: e064f32e3674
Files: 49
Total size: 235.2 KB
Directory structure:
gitextract_bnag3gt4/
├── .circleci/
│ └── config.yml
├── .github/
│ └── release-drafter.yml
├── .gitignore
├── AUTHORS
├── LICENSE
├── README.md
├── client.go
├── client_proxy_server_test.go
├── client_server_test.go
├── client_test.go
├── compression.go
├── compression_test.go
├── conn.go
├── conn_broadcast_test.go
├── conn_test.go
├── doc.go
├── example_test.go
├── examples/
│ ├── autobahn/
│ │ ├── README.md
│ │ ├── config/
│ │ │ └── fuzzingclient.json
│ │ └── server.go
│ ├── chat/
│ │ ├── README.md
│ │ ├── client.go
│ │ ├── home.html
│ │ ├── hub.go
│ │ └── main.go
│ ├── command/
│ │ ├── README.md
│ │ ├── home.html
│ │ └── main.go
│ ├── echo/
│ │ ├── README.md
│ │ ├── client.go
│ │ └── server.go
│ └── filewatch/
│ ├── README.md
│ └── main.go
├── go.mod
├── go.sum
├── join.go
├── join_test.go
├── json.go
├── json_test.go
├── mask.go
├── mask_safe.go
├── mask_test.go
├── prepared.go
├── prepared_test.go
├── proxy.go
├── server.go
├── server_test.go
├── util.go
└── util_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2.1
jobs:
"test":
parameters:
version:
type: string
default: "latest"
golint:
type: boolean
default: true
modules:
type: boolean
default: true
goproxy:
type: string
default: ""
docker:
- image: "cimg/go:<< parameters.version >>"
working_directory: /home/circleci/project/go/src/github.com/gorilla/websocket
environment:
GO111MODULE: "on"
GOPROXY: "<< parameters.goproxy >>"
steps:
- checkout
- run:
name: "Print the Go version"
command: >
go version
- run:
name: "Fetch dependencies"
command: >
if [[ << parameters.modules >> = true ]]; then
go mod download
export GO111MODULE=on
else
go get -v ./...
fi
# Only run gofmt, vet & lint against the latest Go version
- run:
name: "Run golint"
command: >
if [ << parameters.version >> = "latest" ] && [ << parameters.golint >> = true ]; then
go get -u golang.org/x/lint/golint
golint ./...
fi
- run:
name: "Run gofmt"
command: >
if [[ << parameters.version >> = "latest" ]]; then
diff -u <(echo -n) <(gofmt -d -e .)
fi
- run:
name: "Run go vet"
command: >
if [[ << parameters.version >> = "latest" ]]; then
go vet -v ./...
fi
- run:
name: "Run go test (+ race detector)"
command: >
go test -v -race ./...
workflows:
tests:
jobs:
- test:
matrix:
parameters:
version: ["1.22", "1.21", "1.20"]
================================================
FILE: .github/release-drafter.yml
================================================
# Config for https://github.com/apps/release-drafter
template: |
<summary of changes here>
## CHANGELOG
$CHANGES
================================================
FILE: .gitignore
================================================
# 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
================================================
FILE: AUTHORS
================================================
# This is the official list of Gorilla WebSocket authors for copyright
# purposes.
#
# Please keep the list sorted.
Gary Burd <gary@beagledreams.com>
Google LLC (https://opensource.google.com/)
Joachim Bauch <mail@joachim-bauch.de>
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# Gorilla WebSocket
[](https://godoc.org/github.com/gorilla/websocket)
[](https://circleci.com/gh/gorilla/websocket)
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
### Documentation
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
* [Chat example](https://github.com/gorilla/websocket/tree/main/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/main/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/main/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/main/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](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/main/examples/autobahn).
================================================
FILE: client.go
================================================
// 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 (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httptrace"
"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.
//
// It is safe to call Dialer's methods concurrently.
type Dialer struct {
// The following custom dial functions can be set to establish
// connections to either the backend server or the proxy (if it
// exists). The scheme of the dialed entity (either backend or
// proxy) determines which custom dial function is selected:
// either NetDialTLSContext for HTTPS or NetDialContext/NetDial
// for HTTP. Since the "Proxy" function can determine the scheme
// dynamically, it can make sense to set multiple custom dial
// functions simultaneously.
//
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dialer DialContext is used.
// If "Proxy" field is also set, this function dials the proxy--not
// the backend server.
NetDial func(network, addr string) (net.Conn, error)
// NetDialContext specifies the dial function for creating TCP connections. If
// NetDialContext is nil, NetDial is used.
// If "Proxy" field is also set, this function dials the proxy--not
// the backend server.
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
// NetDialTLSContext is nil, NetDialContext is used.
// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
// TLSClientConfig is ignored.
// If "Proxy" field is also set, this function dials the proxy (and performs
// the TLS handshake with the proxy, ignoring TLSClientConfig). In this TLS proxy
// dialing case the TLSClientConfig could still be necessary for TLS to the backend server.
NetDialTLSContext func(ctx context.Context, 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.
// If NetDialTLSContext is set, Dial assumes the TLS handshake
// is done there and TLSClientConfig is ignored.
TLSClientConfig *tls.Config
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int
// WriteBufferPool is a pool of buffers for write operations. If the value
// is not set, then write buffers are allocated to the connection for the
// lifetime of the connection.
//
// A pool is most useful when the application has a modest volume of writes
// across a large number of connections.
//
// Applications should use a single pool for each unique value of
// WriteBufferSize.
WriteBufferPool BufferPool
// 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
}
// Dial creates a new client connection by calling DialContext with a background context.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
return d.DialContext(context.Background(), urlStr, requestHeader)
}
var errMalformedURL = errors.New("malformed ws or wss URL")
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 values.
var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
}
// nilDialer is dialer to use when receiver is nil.
var nilDialer = *DefaultDialer
// DialContext 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).
//
// The context will be used in the request and in the Dialer.
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
if d == nil {
d = &nilDialer
}
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
}
u, err := url.Parse(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: http.MethodGet,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
req = req.WithContext(ctx)
// 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)
case k == "Sec-Websocket-Protocol":
req.Header["Sec-WebSocket-Protocol"] = vs
default:
req.Header[k] = vs
}
}
if d.EnableCompression {
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
}
if d.HandshakeTimeout != 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
defer cancel()
}
var proxyURL *url.URL
if d.Proxy != nil {
proxyURL, err = d.Proxy(req)
if err != nil {
return nil, nil, err
}
}
netDial, err := d.netDialFn(ctx, proxyURL, u)
if err != nil {
return nil, nil, err
}
hostPort, hostNoPort := hostPortNoPort(u)
trace := httptrace.ContextClientTrace(ctx)
if trace != nil && trace.GetConn != nil {
trace.GetConn(hostPort)
}
netConn, err := netDial(ctx, "tcp", hostPort)
if err != nil {
return nil, nil, err
}
if trace != nil && trace.GotConn != nil {
trace.GotConn(httptrace.GotConnInfo{
Conn: netConn,
})
}
// Close the network connection when returning an error. The variable
// netConn is set to nil before the success return at the end of the
// function.
defer func() {
if netConn != nil {
// It's safe to ignore the error from Close() because this code is
// only executed when returning a more important error to the
// application.
_ = netConn.Close()
}
}()
// Do TLS handshake over established connection if a proxy exists.
if proxyURL != nil && u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(netConn, cfg)
netConn = tlsConn
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err := doHandshake(ctx, tlsConn, cfg)
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
}
if err != nil {
return nil, nil, err
}
}
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
if err := req.Write(netConn); err != nil {
return nil, nil, err
}
if trace != nil && trace.GotFirstResponseByte != nil {
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
trace.GotFirstResponseByte()
}
}
resp, err := http.ReadResponse(conn.br, req)
if err != nil {
if d.TLSClientConfig != nil {
for _, proto := range d.TLSClientConfig.NextProtos {
if proto != "http/1.1" {
return nil, nil, fmt.Errorf(
"websocket: protocol %q was given but is not supported;"+
"sharing tls.Config with net/http Transport can cause this error: %w",
proto, err,
)
}
}
}
return nil, nil, err
}
if d.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 {
d.Jar.SetCookies(u, rc)
}
}
if resp.StatusCode != 101 ||
!tokenListContainsValue(resp.Header, "Upgrade", "websocket") ||
!tokenListContainsValue(resp.Header, "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 = io.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 = io.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
if err := netConn.SetDeadline(time.Time{}); err != nil {
return nil, resp, err
}
// Success! Set netConn to nil to stop the deferred function above from
// closing the network connection.
netConn = nil
return conn, resp, nil
}
// Returns the dial function to establish the connection to either the backend
// server or the proxy (if it exists). If the dialed entity is HTTPS, then the
// returned dial function *also* performs the TLS handshake to the dialed entity.
// NOTE: If a proxy exists, it is possible for a second TLS handshake to be
// necessary over the established connection.
func (d *Dialer) netDialFn(ctx context.Context, proxyURL *url.URL, backendURL *url.URL) (netDialerFunc, error) {
var netDial netDialerFunc
if proxyURL != nil {
netDial = d.netDialFromURL(proxyURL)
} else {
netDial = d.netDialFromURL(backendURL)
}
// If needed, wrap the dial function to set the connection deadline.
if deadline, ok := ctx.Deadline(); ok {
netDial = netDialWithDeadline(netDial, deadline)
}
// Proxy dialing is wrapped to implement CONNECT method and possibly proxy auth.
if proxyURL != nil {
return proxyFromURL(proxyURL, netDial)
}
return netDial, nil
}
// Returns function to create the connection depending on the Dialer's
// custom dialing functions and the passed URL of entity connecting to.
func (d *Dialer) netDialFromURL(u *url.URL) netDialerFunc {
var netDial netDialerFunc
switch {
case d.NetDialContext != nil:
netDial = d.NetDialContext
case d.NetDial != nil:
netDial = func(ctx context.Context, net, addr string) (net.Conn, error) {
return d.NetDial(net, addr)
}
default:
netDial = (&net.Dialer{}).DialContext
}
// If dialed entity is HTTPS, then either use custom TLS dialing function (if exists)
// or wrap the previously computed "netDial" to use TLS config for handshake.
if u.Scheme == "https" {
if d.NetDialTLSContext != nil {
netDial = d.NetDialTLSContext
} else {
netDial = netDialWithTLSHandshake(netDial, d.TLSClientConfig, u)
}
}
return netDial
}
// Returns wrapped "netDial" function, performing TLS handshake after connecting.
func netDialWithTLSHandshake(netDial netDialerFunc, tlsConfig *tls.Config, u *url.URL) netDialerFunc {
return func(ctx context.Context, unused, addr string) (net.Conn, error) {
hostPort, hostNoPort := hostPortNoPort(u)
trace := httptrace.ContextClientTrace(ctx)
if trace != nil && trace.GetConn != nil {
trace.GetConn(hostPort)
}
// Creates TCP connection to addr using passed "netDial" function.
conn, err := netDial(ctx, "tcp", addr)
if err != nil {
return nil, err
}
cfg := cloneTLSConfig(tlsConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(conn, cfg)
// Do the TLS handshake using TLSConfig over the wrapped connection.
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err = doHandshake(ctx, tlsConn, cfg)
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
}
if err != nil {
tlsConn.Close()
return nil, err
}
return tlsConn, nil
}
}
// Returns wrapped "netDial" function, setting passed deadline.
func netDialWithDeadline(netDial netDialerFunc, deadline time.Time) netDialerFunc {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
c, err := netDial(ctx, network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.HandshakeContext(ctx); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}
================================================
FILE: client_proxy_server_test.go
================================================
// Copyright 2025 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"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync/atomic"
"testing"
)
// These test cases use a websocket client (Dialer)/proxy/websocket server (Upgrader)
// to validate the cases where a proxy is an intermediary between a websocket client
// and server. The test cases usually 1) create a websocket server which echoes any
// data received back to the client, 2) a basic duplex streaming proxy, and 3) a
// websocket client which sends random data to the server through the proxy,
// validating any subsequent data received is the same as the data sent. The various
// permutations include the proxy and backend schemes (HTTP or HTTPS), as well as
// the custom dial functions (e.g NetDialContext, NetDial) set on the Dialer.
const (
subprotocolV1 = "subprotocol-version-1"
subprotocolV2 = "subprotocol-version-2"
)
// Permutation 1
//
// Backend: HTTP
// Proxy: HTTP
func TestHTTPProxyAndBackend(t *testing.T) {
websocketTLS := false
proxyTLS := false
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Dial the websocket server through the proxy server.
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 2
//
// Backend: HTTP
// Proxy: HTTP
// DialFn: NetDial (dials proxy)
func TestHTTPProxyWithNetDial(t *testing.T) {
websocketTLS := false
proxyTLS := false
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Dial the websocket server through the proxy server.
var netDialCalled atomic.Int64
dialer := Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
netDialCalled.Add(1)
return (&net.Dialer{}).DialContext(context.Background(), network, addr)
},
Proxy: http.ProxyURL(proxyServerURL),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), netDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 3
//
// Backend: HTTP
// Proxy: HTTP
// DialFn: NetDialContext (dials proxy)
func TestHTTPProxyWithNetDialContext(t *testing.T) {
websocketTLS := false
proxyTLS := false
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Dial the websocket server through the proxy server.
var netDialCalled atomic.Int64
dialer := Dialer{
NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netDialCalled.Add(1)
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
Proxy: http.ProxyURL(proxyServerURL),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), netDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 4
//
// Backend: HTTPS
// Proxy: HTTP
// DialFn: NetDialTLSConfig (set but *ignored*)
// TLS Config: set (used for backend TLS)
func TestHTTPProxyWithHTTPSBackend(t *testing.T) {
websocketTLS := true
proxyTLS := false
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
var netDialTLSCalled atomic.Int64
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
// This function should be ignored, because an HTTP proxy exists
// and the backend TLS handshake should use TLSClientConfig.
NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netDialTLSCalled.Add(1)
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
// Used for the backend server TLS handshake.
TLSClientConfig: tlsConfig(websocketTLS, proxyTLS),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if numTLSDials := netDialTLSCalled.Load(); numTLSDials > 0 {
t.Errorf("NetDialTLS should have been ignored")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 5
//
// Backend: HTTPS
// Proxy: HTTPS
// TLS Config: set (used for both proxy and backend TLS)
func TestHTTPSProxyAndBackend(t *testing.T) {
websocketTLS := true
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(websocketTLS, proxyTLS),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 6
//
// Backend: HTTPS
// Proxy: HTTPS
// DialFn: NetDial (used to dial proxy)
// TLS Config: set (used for both proxy and backend TLS)
func TestHTTPSProxyUsingNetDial(t *testing.T) {
websocketTLS := true
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
var netDialCalled atomic.Int64
dialer := Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
netDialCalled.Add(1)
return (&net.Dialer{}).DialContext(context.Background(), network, addr)
},
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(websocketTLS, proxyTLS),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), netDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 7
//
// Backend: HTTPS
// Proxy: HTTPS
// DialFn: NetDialContext (used to dial proxy)
// TLS Config: set (used for both proxy and backend TLS)
func TestHTTPSProxyUsingNetDialContext(t *testing.T) {
websocketTLS := true
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
var netDialCalled atomic.Int64
dialer := Dialer{
NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netDialCalled.Add(1)
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(websocketTLS, proxyTLS),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), netDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 8
//
// Backend: HTTPS
// Proxy: HTTPS
// DialFn: NetDialTLSContext (used for proxy TLS)
// TLS Config: set (used for backend TLS)
func TestHTTPSProxyUsingNetDialTLSContext(t *testing.T) {
websocketTLS := true
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Configure the proxy dialing function which dials the proxy and
// performs the TLS handshake.
var proxyDialCalled atomic.Int64
proxyCerts := x509.NewCertPool()
proxyCerts.AppendCertsFromPEM(proxyServerCert)
proxyTLSConfig := &tls.Config{RootCAs: proxyCerts}
proxyDial := func(ctx context.Context, network, addr string) (net.Conn, error) {
proxyDialCalled.Add(1)
return tls.Dial(network, addr, proxyTLSConfig)
}
// Configure the backend webscocket TLS configuration (handshake occurs
// over the previously created proxy connection).
websocketCerts := x509.NewCertPool()
websocketCerts.AppendCertsFromPEM(websocketServerCert)
websocketTLSConfig := &tls.Config{RootCAs: websocketCerts}
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
// Dial and TLS handshake function to proxy.
NetDialTLSContext: proxyDial,
// Used for second TLS handshake to backend server over previously
// established proxy connection.
TLSClientConfig: websocketTLSConfig,
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), proxyDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 9
//
// Backend: HTTP
// Proxy: HTTPS
// TLS Config: set (used for proxy TLS)
func TestHTTPSProxyHTTPBackend(t *testing.T) {
websocketTLS := false
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(websocketTLS, proxyTLS),
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
// Permutation 10
//
// Backend: HTTP
// Proxy: HTTPS
// DialFn: NetDialTLSContext (used for proxy TLS)
// TLS Config: set (ignored)
func TestHTTPSProxyUsingNetDialTLSContextWithHTTPBackend(t *testing.T) {
websocketTLS := false
proxyTLS := true
// Start the websocket server, which echoes data back to sender.
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Start the proxy server.
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
var proxyDialCalled atomic.Int64
dialer := Dialer{
NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
proxyDialCalled.Add(1)
return tls.Dial(network, addr, tlsConfig(websocketTLS, proxyTLS))
},
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: &tls.Config{}, // Misconfigured, but ignored.
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
// Send, receive, and validate random data over websocket connection.
sendReceiveData(t, wsClient)
if e, a := int64(1), proxyDialCalled.Load(); e != a {
t.Errorf("netDial not called")
}
// Validate the proxy server was called.
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy not called")
}
}
func TestTLSValidationErrors(t *testing.T) {
// Both websocket and proxy servers are started with TLS.
websocketTLS := true
proxyTLS := true
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
proxyServer, proxyServerURL, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Dialer without proxy CA cert fails TLS verification.
tlsError := "tls: failed to verify certificate"
dialer := Dialer{
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(true, false),
Subprotocols: []string{subprotocolV1},
}
_, _, err = dialer.Dial(websocketURL.String(), nil)
if err == nil {
t.Errorf("expected proxy TLS verification error did not arrive")
} else if !strings.Contains(err.Error(), tlsError) {
t.Errorf("expected proxy TLS error (%s), got (%s)", err.Error(), tlsError)
}
// Validate the proxy handler was *NOT* called (because proxy
// server TLS validation failed).
if e, a := int64(0), proxyServer.numCalls(); e != a {
t.Errorf("proxy should not have been called")
}
// Dialer without websocket CA cert fails TLS verification.
dialer = Dialer{
Proxy: http.ProxyURL(proxyServerURL),
TLSClientConfig: tlsConfig(false, true),
Subprotocols: []string{subprotocolV1},
}
_, _, err = dialer.Dial(websocketURL.String(), nil)
if err == nil {
t.Errorf("expected websocket TLS verification error did not arrive")
} else if !strings.Contains(err.Error(), tlsError) {
t.Errorf("expected websocket TLS error (%s), got (%s)", err.Error(), tlsError)
}
// Validate the proxy server *was* called (but subsequent
// websocket server failed TLS validation).
if e, a := int64(1), proxyServer.numCalls(); e != a {
t.Errorf("proxy have been called")
}
}
func TestProxyFnErrorIsPropagated(t *testing.T) {
websocketServer, websocketURL, err := newWebsocketServer(false)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
// Create a Dialer where Proxy function always returns an error.
proxyURLError := errors.New("proxy URL generation error")
dialer := Dialer{
Proxy: func(r *http.Request) (*url.URL, error) {
return nil, proxyURLError
},
Subprotocols: []string{subprotocolV1},
}
// Proxy URL generation error should halt request and be propagated.
_, _, err = dialer.Dial(websocketURL.String(), nil)
if err == nil {
t.Fatalf("expected websocket dial error, received none")
} else if !errors.Is(proxyURLError, err) {
t.Fatalf("expected error (%s), got (%s)", proxyURLError, err)
}
}
func TestProxyFnNilMeansNoProxy(t *testing.T) {
// Both websocket and proxy servers are started.
websocketTLS := false
proxyTLS := false
websocketServer, websocketURL, err := newWebsocketServer(websocketTLS)
defer websocketServer.Close()
if err != nil {
t.Fatalf("error starting websocket server: %v", err)
}
proxyServer, _, err := newProxyServer(proxyTLS)
defer proxyServer.Close()
if err != nil {
t.Fatalf("error starting proxy server: %v", err)
}
// Dialer created with Proxy URL generation function returning nil
// proxy URL, which continues with backend server connection without
// proxying.
dialer := Dialer{
Proxy: func(r *http.Request) (*url.URL, error) {
return nil, nil
},
Subprotocols: []string{subprotocolV1},
}
wsClient, _, err := dialer.Dial(websocketURL.String(), nil)
if err != nil {
t.Fatalf("websocket dial error: %v", err)
}
sendReceiveData(t, wsClient)
// Validate the proxy handler was *NOT* called (because proxy
// URL generation returned nil).
if e, a := int64(0), proxyServer.numCalls(); e != a {
t.Errorf("proxy should not have been called")
}
}
// "counter" interface can be implemented by a server to keep track
// of the number of times a handler was called, as well as "Close".
type counter interface {
increment()
numCalls() int64
closer
}
type closer interface {
Close()
}
// testServer implements "counter" interface.
type testServer struct {
server *httptest.Server
numHandled atomic.Int64
}
func (ts *testServer) numCalls() int64 {
return ts.numHandled.Load()
}
func (ts *testServer) increment() {
ts.numHandled.Add(1)
}
func (ts *testServer) Close() {
if ts.server != nil {
ts.server.Close()
}
}
// websocketEchoHandler upgrades the connection associated with the request, and
// echoes binary messages read off the websocket connection back to the client.
var websocketEchoHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
upgrader := Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Accepting all requests
},
Subprotocols: []string{
subprotocolV1,
subprotocolV2,
},
}
wsConn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
defer wsConn.Close()
for {
writer, err := wsConn.NextWriter(BinaryMessage)
if err != nil {
break
}
messageType, reader, err := wsConn.NextReader()
if err != nil {
break
}
if messageType != BinaryMessage {
http.Error(w, "websocket reader not binary message type",
http.StatusInternalServerError)
}
_, err = io.Copy(writer, reader)
if err != nil {
http.Error(w, "websocket server io copy error",
http.StatusInternalServerError)
}
}
})
// Returns a test backend websocket server as well as the URL pointing
// to the server, or an error if one occurred. Sets up a TLS endpoint
// on the server if the passed "tlsServer" is true.
// func newWebsocketServer(tlsServer bool) (*httptest.Server, *url.URL, error) {
func newWebsocketServer(tlsServer bool) (closer, *url.URL, error) {
// Start the websocket server, which echoes data back to sender.
websocketServer := httptest.NewUnstartedServer(websocketEchoHandler)
if tlsServer {
websocketKeyPair, err := tls.X509KeyPair(websocketServerCert, websocketServerKey)
if err != nil {
return nil, nil, err
}
websocketServer.TLS = &tls.Config{
Certificates: []tls.Certificate{websocketKeyPair},
}
websocketServer.StartTLS()
} else {
websocketServer.Start()
}
websocketURL, err := url.Parse(websocketServer.URL)
if err != nil {
return nil, nil, err
}
if tlsServer {
websocketURL.Scheme = "wss"
} else {
websocketURL.Scheme = "ws"
}
return websocketServer, websocketURL, nil
}
// proxyHandler creates a full duplex streaming connection between the client
// (hijacking the http request connection), and an "upstream" dialed connection
// to the "Host". Creates two goroutines to copy between connections in each direction.
var proxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Validate the CONNECT method.
if req.Method != http.MethodConnect {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Dial upstream server.
upstream, err := (&net.Dialer{}).DialContext(req.Context(), "tcp", req.URL.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer upstream.Close()
// Return 200 OK to client.
w.WriteHeader(http.StatusOK)
// Hijack client connection.
client, _, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer client.Close()
// Create duplex streaming between client and upstream connections.
done := make(chan struct{}, 2)
go func() {
_, _ = io.Copy(upstream, client)
done <- struct{}{}
}()
go func() {
_, _ = io.Copy(client, upstream)
done <- struct{}{}
}()
<-done
})
// Returns a new test HTTP server, as well as the URL to that server, or
// an error if one occurred. numProxyCalls keeps track of the number of
// times the proxy handler was called with this server.
func newProxyServer(tlsServer bool) (counter, *url.URL, error) {
// Start the proxy server, keeping track of how many times the handler is called.
ts := &testServer{}
proxyServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ts.increment()
proxyHandler.ServeHTTP(w, req)
}))
if tlsServer {
proxyKeyPair, err := tls.X509KeyPair(proxyServerCert, proxyServerKey)
if err != nil {
return nil, nil, err
}
proxyServer.TLS = &tls.Config{
Certificates: []tls.Certificate{proxyKeyPair},
}
proxyServer.StartTLS()
} else {
proxyServer.Start()
}
proxyURL, err := url.Parse(proxyServer.URL)
if err != nil {
return nil, nil, err
}
return ts, proxyURL, nil
}
// Returns the TLS config with the RootCAs cert pool set. If
// neither websocket nor proxy server uses TLS, returns nil.
func tlsConfig(websocketTLS bool, proxyTLS bool) *tls.Config {
if !websocketTLS && !proxyTLS {
return nil
}
certPool := x509.NewCertPool()
tlsConfig := &tls.Config{
RootCAs: certPool,
}
if websocketTLS {
tlsConfig.RootCAs.AppendCertsFromPEM(websocketServerCert)
}
if proxyTLS {
tlsConfig.RootCAs.AppendCertsFromPEM(proxyServerCert)
}
return tlsConfig
}
// Sends, receives, and validates random data sent and received
// over the passed websocket connection.
const randomDataSize = 128 * 1024
func sendReceiveData(t *testing.T, wsConn *Conn) {
// Create the random data.
randomData := make([]byte, randomDataSize)
if _, err := rand.Read(randomData); err != nil {
t.Errorf("unexpected error reading random data: %v", err)
}
// Send the random data.
err := wsConn.WriteMessage(BinaryMessage, randomData)
if err != nil {
t.Errorf("websocket write error: %v", err)
}
// Read from the websocket connection, and validate the
// read data is the same as the previously sent data.
_, received, err := wsConn.ReadMessage()
if !bytes.Equal(randomData, received) {
t.Errorf("unexpected data received: %d bytes sent, %d bytes received",
len(received), len(randomData))
}
}
// proxyServerCert was generated from crypto/tls/generate_cert.go with the following command:
//
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
//
// proxyServerCert is a self-signed.
var proxyServerCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C
JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L
SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g
dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE
Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz
bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD
AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R
BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY
XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z
DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s
8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT
MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1
D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU=
-----END CERTIFICATE-----`)
// proxyServerKey is the private key for proxyServerCert.
var proxyServerKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ
LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5
nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9
H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi
84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp
gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc
kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk
bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3
EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB
LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23
uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2
NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441
VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ
frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY
5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+
9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx
uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/
p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ
jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX
yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID
iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2
LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP
4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ
9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2
KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q=
-----END RSA PRIVATE KEY-----
`)
// websocketServerCert is self-signed.
var websocketServerCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIQYSN1VY/favsLUo+B7gJ5tTANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEApBlintjkL1fO1Sk2pzNvl862CtTwU7/Jy6EZqWzI17wEbPn4sbSD
bHhfDlPl2nmw3hVkc6LNK+eqzm2GX/ai4tgMiaH7kyyNit1K3g7y7GISMf9poWIa
POJhid2wmhKHbEtHECSdQ5c/jEN1UVzB4go5LO7MEEVo9kyQ+yBqS6gISyFmfaT4
qOsPJBir33bBpptSend1JSXaRTXqRa1p+oudw2ILa4U7KfuKK3emp21m5/HYAuSf
CV4WqqDoDiBPMpsQ0kPEPugWZKFeF3qanmqFFvptYx+zJbOznWYY2D3idWsvcg6q
VLPEB19oXaVBV0HXPFtObm5m1jCpl8FI1wIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBQcSkjqA9rgos1daegNj49BpRCA0jAuBgNVHREEJzAlggtleGFtcGxlLmNv
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAnk9i
9rogNTi9B1pn+Fbk3WALKdEjv/uyePsTnwdyvswVbeYbQweU9TrhYT2+eXbMA5kY
7TaQm46idRqxCKMgc3Ip3DADJdm8cJX9p2ExU4fKdkPc1KD/J+4QHHx1W2Ml5S2o
foOo6j1F0UdZP/rBj0UumEZp32qW+4DhVV/QQjUB8J0gaDC7yZBMdyMIeClR0RqE
YfZdCJbQHqtTwBXN+imQUHPGmksYkRDpFRvw/4crpcMIE04mVVd99nOpFCQnK61t
9US1y17VW1lYpkqlCS+rkcAtor4Z5naSf9/oLGCxEAwyW0pwHGO6MXtMxvB/JD20
hJdlz1I7wlSfF4MiRQ==
-----END CERTIFICATE-----`)
// websocketServerKey is the private key for websocketServerCert.
var websocketServerKey = []byte(`-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkGWKe2OQvV87V
KTanM2+XzrYK1PBTv8nLoRmpbMjXvARs+fixtINseF8OU+XaebDeFWRzos0r56rO
bYZf9qLi2AyJofuTLI2K3UreDvLsYhIx/2mhYho84mGJ3bCaEodsS0cQJJ1Dlz+M
Q3VRXMHiCjks7swQRWj2TJD7IGpLqAhLIWZ9pPio6w8kGKvfdsGmm1J6d3UlJdpF
NepFrWn6i53DYgtrhTsp+4ord6anbWbn8dgC5J8JXhaqoOgOIE8ymxDSQ8Q+6BZk
oV4XepqeaoUW+m1jH7Mls7OdZhjYPeJ1ay9yDqpUs8QHX2hdpUFXQdc8W05ubmbW
MKmXwUjXAgMBAAECggEAE6BkTDgH//rnkP/Ej/Y17Zkv6qxnMLe/4evwZB7PsrBu
cxOUAWUOpvA1UO215bh87+2XvcDbUISnyC1kpKDyAGGeC5llER2DXE11VokWgtvZ
Q0OXavw5w83A+WVGFFdiUmXP0l10CxEm7OwQjFz6D21GQ1qC65tG9NZZghTxbFTe
iZKqgWqyHsaAWLOuDQbj1FTEBMFrY8f9RbclSh0luPZnzGc4BVI/t34jKPZBpH2N
NCkr8aB7MMHGhrNZFHAu/KAvq8UBrDTX+O8ERMwcwQWB4nne2+GOTN0MdcAUc72i
GryzIa8TgO+TpQOYoZ4NPnzFrsa+m3G2Tug3vbt62QKBgQDOPfM4/5/x/h/ggxQn
aRvEOC+8ldeqEOS1VTGiuDKJMWXrNkG+d+AsxfNP4k0QVNrpEAZSYcf0gnS9Odcl
luEsi/yPZDDnPg/cS+Z3336VKsggly7BWFs1Ct/9I+ZfSCl88TkVpIfeCBC34XEb
0mFUq/RdLqXj/mVLbBfr+H8cEwKBgQDLsJUm8lkWFAPJ8UMto8xeUMGk44VukYwx
+oI6KhplFntiI0C1Dd9wrxyCjySlJcc0NFt6IPN84d7pI9LQSbiKXQ1jMvsBzd4G
EMtG8SHpIY/mMU+KzWLHYVFS0FA4PvXXvPRNLOXas7hbALZdLshVKd7aDlkQAb5C
KWFHeIFwrQKBgA8r5Xl67HQrwoKMge4IQF+l1nUj/LJo/boNI1KaBDWtaZbs7dcq
EFaa1TQ6LHsYEuZ0JFLpGIF3G0lUOOxt9fCF97VApIxON3J4LuMAkNo+RGyJUoos
isETJLkFbAv0TgD/6bga21fM9hXgwqZOSpSk9ZvpM5DbBO6QbA4SwJ77AoGAX7h1
/z14XAW/2hDE7xfAnLn6plA9jj5b0cjVlhvfF44/IVlLuUnxrPS9wyUdpXZhbMkG
DBicFB3ZMVqiYTuju3ILLojwqGJkahlOTeJXe0VIaHbX2HS4bNXw76fxat07jsy/
Sd1Fj0dR5YIqMRQhFNR+Y57Gf90x2cm0a2/X9GkCgYANawYx9bNfcX0HMVG7vktK
6/80omnoBM0JUxA+V7DxS8kr9Cj2Y/kcS+VHb4yyoSkDgnsSdnCr1ZTctcj828MJ
8AUwskAtEjPkHRXEgRRnEl2oJGD1TT5iwBNnuPAQDXwzkGCRYBnlfZNbILbOoSUz
m+VDcqT5XzcRADa/TLlEXA==
-----END PRIVATE KEY-----
`)
================================================
FILE: client_server_test.go
================================================
// 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"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/http/httptrace"
"net/url"
"reflect"
"strings"
"sync"
"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,
HandshakeTimeout: 30 * time.Second,
}
type cstHandler struct {
*testing.T
s *cstServer
}
type cstServer struct {
URL string
Server *httptest.Server
wg sync.WaitGroup
}
const (
cstPath = "/a/b"
cstRawQuery = "x=y"
cstRequestURI = cstPath + "?" + cstRawQuery
)
func (s *cstServer) Close() {
s.Server.Close()
// Wait for handler functions to complete.
s.wg.Wait()
}
func newServer(t *testing.T) *cstServer {
var s cstServer
s.Server = httptest.NewServer(cstHandler{T: t, s: &s})
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: t, s: &s})
s.Server.URL += cstRequestURI
s.URL = makeWsProto(s.Server.URL)
return &s
}
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Because tests wait for a response from a server, we are guaranteed that
// the wait group count is incremented before the test waits on the group
// in the call to (*cstServer).Close().
t.s.wg.Add(1)
defer t.s.wg.Done()
if r.URL.Path != cstPath {
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
http.Error(w, "bad path", http.StatusBadRequest)
return
}
if r.URL.RawQuery != cstRawQuery {
t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery)
http.Error(w, "bad path", http.StatusBadRequest)
return
}
subprotos := Subprotocols(r)
if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) {
t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols)
http.Error(w, "bad protocol", http.StatusBadRequest)
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.Server.URL)
cstDialer := cstDialer // make local copy for modification on next line.
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 == http.MethodConnect {
connect = true
w.WriteHeader(http.StatusOK)
return
}
if !connect {
t.Log("connect not received")
http.Error(w, "connect not received", http.StatusMethodNotAllowed)
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)
}
func TestProxyAuthorizationDial(t *testing.T) {
s := newServer(t)
defer s.Close()
surl, _ := url.Parse(s.Server.URL)
surl.User = url.UserPassword("username", "password")
cstDialer := cstDialer // make local copy for modification on next line.
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 == http.MethodConnect && proxyAuth == expectedProxyAuth {
connect = true
w.WriteHeader(http.StatusOK)
return
}
if !connect {
t.Log("connect with proxy authorization not received")
http.Error(w, "connect with proxy authorization not received", http.StatusMethodNotAllowed)
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)
}
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, _ := url.Parse(s.URL)
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
}
cookies := []*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 rootCAs(t *testing.T, s *httptest.Server) *x509.CertPool {
certs := x509.NewCertPool()
for _, c := range s.TLS.Certificates {
roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
if err != nil {
t.Fatalf("error parsing server's root cert: %v", err)
}
for _, root := range roots {
certs.AddCert(root)
}
}
return certs
}
func TestDialTLS(t *testing.T) {
s := newTLSServer(t)
defer s.Close()
d := cstDialer
d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)}
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")
}
}
// requireDeadlineNetConn fails the current test when Read or Write are called
// with no deadline.
type requireDeadlineNetConn struct {
t *testing.T
c net.Conn
readDeadlineIsSet bool
writeDeadlineIsSet bool
}
func (c *requireDeadlineNetConn) SetDeadline(t time.Time) error {
c.writeDeadlineIsSet = !t.Equal(time.Time{})
c.readDeadlineIsSet = c.writeDeadlineIsSet
return c.c.SetDeadline(t)
}
func (c *requireDeadlineNetConn) SetReadDeadline(t time.Time) error {
c.readDeadlineIsSet = !t.Equal(time.Time{})
return c.c.SetDeadline(t)
}
func (c *requireDeadlineNetConn) SetWriteDeadline(t time.Time) error {
c.writeDeadlineIsSet = !t.Equal(time.Time{})
return c.c.SetDeadline(t)
}
func (c *requireDeadlineNetConn) Write(p []byte) (int, error) {
if !c.writeDeadlineIsSet {
c.t.Fatalf("write with no deadline")
}
return c.c.Write(p)
}
func (c *requireDeadlineNetConn) Read(p []byte) (int, error) {
if !c.readDeadlineIsSet {
c.t.Fatalf("read with no deadline")
}
return c.c.Read(p)
}
func (c *requireDeadlineNetConn) Close() error { return c.c.Close() }
func (c *requireDeadlineNetConn) LocalAddr() net.Addr { return c.c.LocalAddr() }
func (c *requireDeadlineNetConn) RemoteAddr() net.Addr { return c.c.RemoteAddr() }
func TestHandshakeTimeout(t *testing.T) {
s := newServer(t)
defer s.Close()
d := cstDialer
d.NetDial = func(n, a string) (net.Conn, error) {
c, err := net.Dial(n, a)
return &requireDeadlineNetConn{c: c, t: t}, err
}
ws, _, err := d.Dial(s.URL, nil)
if err != nil {
t.Fatal("Dial:", err)
}
ws.Close()
}
func TestHandshakeTimeoutInContext(t *testing.T) {
s := newServer(t)
defer s.Close()
d := cstDialer
d.HandshakeTimeout = 0
d.NetDialContext = func(ctx context.Context, n, a string) (net.Conn, error) {
netDialer := &net.Dialer{}
c, err := netDialer.DialContext(ctx, n, a)
return &requireDeadlineNetConn{c: c, t: t}, err
}
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
ws, _, err := d.DialContext(ctx, s.URL, nil)
if err != nil {
t.Fatal("Dial:", err)
}
ws.Close()
}
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()
req, err := http.NewRequest(http.MethodPost, s.URL, strings.NewReader(""))
if err != nil {
t.Fatalf("NewRequest returned error %v", err)
}
req.Header.Set("Connection", "upgrade")
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Sec-Websocket-Version", "13")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Do returned error %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed)
}
}
func TestNoUpgrade(t *testing.T) {
t.Parallel()
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()
req, err := http.NewRequest(http.MethodGet, s.URL, strings.NewReader(""))
if err != nil {
t.Fatalf("NewRequest returned error %v", err)
}
req.Header.Set("Connection", "upgrade")
req.Header.Set("Sec-Websocket-Version", "13")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Do returned error %v", err)
}
resp.Body.Close()
if u := resp.Header.Get("Upgrade"); u != "websocket" {
t.Errorf("Upgrade response header is %q, want %q", u, "websocket")
}
if resp.StatusCode != http.StatusUpgradeRequired {
t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusUpgradeRequired)
}
}
func TestDialExtraTokensInRespHeaders(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
challengeKey := r.Header.Get("Sec-Websocket-Key")
w.Header().Set("Upgrade", "foo, websocket")
w.Header().Set("Connection", "upgrade, keep-alive")
w.Header().Set("Sec-Websocket-Accept", computeAcceptKey(challengeKey))
w.WriteHeader(101)
}))
defer s.Close()
ws, _, err := cstDialer.Dial(makeWsProto(s.URL), nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer ws.Close()
}
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 := io.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)
}
}
type testLogWriter struct {
t *testing.T
}
func (w testLogWriter) Write(p []byte) (int, error) {
w.t.Logf("%s", p)
return len(p), nil
}
// TestHost tests handling of host names and confirms that it matches net/http.
func TestHost(t *testing.T) {
upgrader := Upgrader{}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if IsWebSocketUpgrade(r) {
c, err := upgrader.Upgrade(w, r, http.Header{"X-Test-Host": {r.Host}})
if err != nil {
t.Fatal(err)
}
c.Close()
} else {
w.Header().Set("X-Test-Host", r.Host)
}
})
server := httptest.NewServer(handler)
defer server.Close()
tlsServer := httptest.NewTLSServer(handler)
defer tlsServer.Close()
addrs := map[*httptest.Server]string{server: server.Listener.Addr().String(), tlsServer: tlsServer.Listener.Addr().String()}
wsProtos := map[*httptest.Server]string{server: "ws://", tlsServer: "wss://"}
httpProtos := map[*httptest.Server]string{server: "http://", tlsServer: "https://"}
// Avoid log noise from net/http server by logging to testing.T
server.Config.ErrorLog = log.New(testLogWriter{t}, "", 0)
tlsServer.Config.ErrorLog = server.Config.ErrorLog
cas := rootCAs(t, tlsServer)
tests := []struct {
fail bool // true if dial / get should fail
server *httptest.Server // server to use
url string // host for request URI
header string // optional request host header
tls string // optional host for tls ServerName
wantAddr string // expected host for dial
wantHeader string // expected request header on server
insecureSkipVerify bool
}{
{
server: server,
url: addrs[server],
wantAddr: addrs[server],
wantHeader: addrs[server],
},
{
server: tlsServer,
url: addrs[tlsServer],
wantAddr: addrs[tlsServer],
wantHeader: addrs[tlsServer],
},
{
server: server,
url: addrs[server],
header: "badhost.com",
wantAddr: addrs[server],
wantHeader: "badhost.com",
},
{
server: tlsServer,
url: addrs[tlsServer],
header: "badhost.com",
wantAddr: addrs[tlsServer],
wantHeader: "badhost.com",
},
{
server: server,
url: "example.com",
header: "badhost.com",
wantAddr: "example.com:80",
wantHeader: "badhost.com",
},
{
server: tlsServer,
url: "example.com",
header: "badhost.com",
wantAddr: "example.com:443",
wantHeader: "badhost.com",
},
{
server: server,
url: "badhost.com",
header: "example.com",
wantAddr: "badhost.com:80",
wantHeader: "example.com",
},
{
fail: true,
server: tlsServer,
url: "badhost.com",
header: "example.com",
wantAddr: "badhost.com:443",
},
{
server: tlsServer,
url: "badhost.com",
insecureSkipVerify: true,
wantAddr: "badhost.com:443",
wantHeader: "badhost.com",
},
{
server: tlsServer,
url: "badhost.com",
tls: "example.com",
wantAddr: "badhost.com:443",
wantHeader: "badhost.com",
},
}
for i, tt := range tests {
tls := &tls.Config{
RootCAs: cas,
ServerName: tt.tls,
InsecureSkipVerify: tt.insecureSkipVerify,
}
var gotAddr string
dialer := Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
gotAddr = addr
return net.Dial(network, addrs[tt.server])
},
TLSClientConfig: tls,
}
// Test websocket dial
h := http.Header{}
if tt.header != "" {
h.Set("Host", tt.header)
}
c, resp, err := dialer.Dial(wsProtos[tt.server]+tt.url+"/", h)
if err == nil {
c.Close()
}
check := func(protos map[*httptest.Server]string) {
name := fmt.Sprintf("%d: %s%s/ header[Host]=%q, tls.ServerName=%q", i+1, protos[tt.server], tt.url, tt.header, tt.tls)
if gotAddr != tt.wantAddr {
t.Errorf("%s: got addr %s, want %s", name, gotAddr, tt.wantAddr)
}
switch {
case tt.fail && err == nil:
t.Errorf("%s: unexpected success", name)
case !tt.fail && err != nil:
t.Errorf("%s: unexpected error %v", name, err)
case !tt.fail && err == nil:
if gotHost := resp.Header.Get("X-Test-Host"); gotHost != tt.wantHeader {
t.Errorf("%s: got host %s, want %s", name, gotHost, tt.wantHeader)
}
}
}
check(wsProtos)
// Confirm that net/http has same result
transport := &http.Transport{
Dial: dialer.NetDial,
TLSClientConfig: dialer.TLSClientConfig,
}
req, _ := http.NewRequest(http.MethodGet, httpProtos[tt.server]+tt.url+"/", nil)
if tt.header != "" {
req.Host = tt.header
}
client := &http.Client{Transport: transport}
resp, err = client.Do(req)
if err == nil {
resp.Body.Close()
}
transport.CloseIdleConnections()
check(httpProtos)
}
}
func TestDialCompression(t *testing.T) {
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)
}
func TestSocksProxyDial(t *testing.T) {
s := newServer(t)
defer s.Close()
proxyListener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen failed: %v", err)
}
defer proxyListener.Close()
go func() {
c1, err := proxyListener.Accept()
if err != nil {
t.Errorf("proxy accept failed: %v", err)
return
}
defer c1.Close()
_ = c1.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 32)
if _, err := io.ReadFull(c1, buf[:3]); err != nil {
t.Errorf("read failed: %v", err)
return
}
if want := []byte{5, 1, 0}; !bytes.Equal(want, buf[:len(want)]) {
t.Errorf("read %x, want %x", buf[:len(want)], want)
}
if _, err := c1.Write([]byte{5, 0}); err != nil {
t.Errorf("write failed: %v", err)
return
}
if _, err := io.ReadFull(c1, buf[:10]); err != nil {
t.Errorf("read failed: %v", err)
return
}
if want := []byte{5, 1, 0, 1}; !bytes.Equal(want, buf[:len(want)]) {
t.Errorf("read %x, want %x", buf[:len(want)], want)
return
}
buf[1] = 0
if _, err := c1.Write(buf[:10]); err != nil {
t.Errorf("write failed: %v", err)
return
}
ip := net.IP(buf[4:8])
port := binary.BigEndian.Uint16(buf[8:10])
c2, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: int(port)})
if err != nil {
t.Errorf("dial failed; %v", err)
return
}
defer c2.Close()
done := make(chan struct{})
go func() {
_, _ = io.Copy(c1, c2)
close(done)
}()
_, _ = io.Copy(c2, c1)
<-done
}()
purl, err := url.Parse("socks5://" + proxyListener.Addr().String())
if err != nil {
t.Fatalf("parse failed: %v", err)
}
cstDialer := cstDialer // make local copy for modification on next line.
cstDialer.Proxy = http.ProxyURL(purl)
ws, _, err := cstDialer.Dial(s.URL, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer ws.Close()
sendRecv(t, ws)
}
func TestTracingDialWithContext(t *testing.T) {
var headersWrote, requestWrote, getConn, gotConn, connectDone, gotFirstResponseByte bool
trace := &httptrace.ClientTrace{
WroteHeaders: func() {
headersWrote = true
},
WroteRequest: func(httptrace.WroteRequestInfo) {
requestWrote = true
},
GetConn: func(hostPort string) {
getConn = true
},
GotConn: func(info httptrace.GotConnInfo) {
gotConn = true
},
ConnectDone: func(network, addr string, err error) {
connectDone = true
},
GotFirstResponseByte: func() {
gotFirstResponseByte = true
},
}
ctx := httptrace.WithClientTrace(context.Background(), trace)
s := newTLSServer(t)
defer s.Close()
d := cstDialer
d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)}
ws, _, err := d.DialContext(ctx, s.URL, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
if !headersWrote {
t.Fatal("Headers was not written")
}
if !requestWrote {
t.Fatal("Request was not written")
}
if !getConn {
t.Fatal("getConn was not called")
}
if !gotConn {
t.Fatal("gotConn was not called")
}
if !connectDone {
t.Fatal("connectDone was not called")
}
if !gotFirstResponseByte {
t.Fatal("GotFirstResponseByte was not called")
}
defer ws.Close()
sendRecv(t, ws)
}
func TestEmptyTracingDialWithContext(t *testing.T) {
trace := &httptrace.ClientTrace{}
ctx := httptrace.WithClientTrace(context.Background(), trace)
s := newTLSServer(t)
defer s.Close()
d := cstDialer
d.TLSClientConfig = &tls.Config{RootCAs: rootCAs(t, s.Server)}
ws, _, err := d.DialContext(ctx, s.URL, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer ws.Close()
sendRecv(t, ws)
}
// TestNetDialConnect tests selection of dial method between NetDial, NetDialContext, NetDialTLS or NetDialTLSContext
func TestNetDialConnect(t *testing.T) {
upgrader := Upgrader{}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if IsWebSocketUpgrade(r) {
c, err := upgrader.Upgrade(w, r, http.Header{"X-Test-Host": {r.Host}})
if err != nil {
t.Fatal(err)
}
c.Close()
} else {
w.Header().Set("X-Test-Host", r.Host)
}
})
server := httptest.NewServer(handler)
defer server.Close()
tlsServer := httptest.NewTLSServer(handler)
defer tlsServer.Close()
testUrls := map[*httptest.Server]string{
server: "ws://" + server.Listener.Addr().String() + "/",
tlsServer: "wss://" + tlsServer.Listener.Addr().String() + "/",
}
cas := rootCAs(t, tlsServer)
tlsConfig := &tls.Config{
RootCAs: cas,
ServerName: "example.com",
InsecureSkipVerify: false,
}
tests := []struct {
name string
server *httptest.Server // server to use
netDial func(network, addr string) (net.Conn, error)
netDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
netDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
tlsClientConfig *tls.Config
}{
{
name: "HTTP server, all NetDial* defined, shall use NetDialContext",
server: server,
netDial: func(network, addr string) (net.Conn, error) {
return nil, errors.New("NetDial should not be called")
},
netDialContext: func(_ context.Context, network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
netDialTLSContext: func(_ context.Context, network, addr string) (net.Conn, error) {
return nil, errors.New("NetDialTLSContext should not be called")
},
tlsClientConfig: nil,
},
{
name: "HTTP server, all NetDial* undefined",
server: server,
netDial: nil,
netDialContext: nil,
netDialTLSContext: nil,
tlsClientConfig: nil,
},
{
name: "HTTP server, NetDialContext undefined, shall fallback to NetDial",
server: server,
netDial: func(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
netDialContext: nil,
netDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return nil, errors.New("NetDialTLSContext should not be called")
},
tlsClientConfig: nil,
},
{
name: "HTTPS server, all NetDial* defined, shall use NetDialTLSContext",
server: tlsServer,
netDial: func(network, addr string) (net.Conn, error) {
return nil, errors.New("NetDial should not be called")
},
netDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return nil, errors.New("NetDialContext should not be called")
},
netDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netConn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
tlsConn := tls.Client(netConn, tlsConfig)
err = tlsConn.Handshake()
if err != nil {
return nil, err
}
return tlsConn, nil
},
tlsClientConfig: nil,
},
{
name: "HTTPS server, NetDialTLSContext undefined, shall fallback to NetDialContext and do handshake",
server: tlsServer,
netDial: func(network, addr string) (net.Conn, error) {
return nil, errors.New("NetDial should not be called")
},
netDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
netDialTLSContext: nil,
tlsClientConfig: tlsConfig,
},
{
name: "HTTPS server, NetDialTLSContext and NetDialContext undefined, shall fallback to NetDial and do handshake",
server: tlsServer,
netDial: func(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
netDialContext: nil,
netDialTLSContext: nil,
tlsClientConfig: tlsConfig,
},
{
name: "HTTPS server, all NetDial* undefined",
server: tlsServer,
netDial: nil,
netDialContext: nil,
netDialTLSContext: nil,
tlsClientConfig: tlsConfig,
},
{
name: "HTTPS server, all NetDialTLSContext defined, dummy TlsClientConfig defined, shall not do handshake",
server: tlsServer,
netDial: func(network, addr string) (net.Conn, error) {
return nil, errors.New("NetDial should not be called")
},
netDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return nil, errors.New("NetDialContext should not be called")
},
netDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netConn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
tlsConn := tls.Client(netConn, tlsConfig)
err = tlsConn.Handshake()
if err != nil {
return nil, err
}
return tlsConn, nil
},
tlsClientConfig: &tls.Config{
RootCAs: nil,
ServerName: "badserver.com",
InsecureSkipVerify: false,
},
},
}
for _, tc := range tests {
dialer := Dialer{
NetDial: tc.netDial,
NetDialContext: tc.netDialContext,
NetDialTLSContext: tc.netDialTLSContext,
TLSClientConfig: tc.tlsClientConfig,
}
// Test websocket dial
c, _, err := dialer.Dial(testUrls[tc.server], nil)
if err != nil {
t.Errorf("FAILED %s, err: %s", tc.name, err.Error())
} else {
c.Close()
}
}
}
func TestNextProtos(t *testing.T) {
ts := httptest.NewUnstartedServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
)
ts.EnableHTTP2 = true
ts.StartTLS()
defer ts.Close()
d := Dialer{
TLSClientConfig: ts.Client().Transport.(*http.Transport).TLSClientConfig,
}
r, err := ts.Client().Get(ts.URL)
if err != nil {
t.Fatalf("Get: %v", err)
}
r.Body.Close()
// Asserts that Dialer.TLSClientConfig.NextProtos contains "h2"
// after the Client.Get call from net/http above.
var containsHTTP2 bool = false
for _, proto := range d.TLSClientConfig.NextProtos {
if proto == "h2" {
containsHTTP2 = true
}
}
if !containsHTTP2 {
t.Fatalf("Dialer.TLSClientConfig.NextProtos does not contain \"h2\"")
}
_, _, err = d.Dial(makeWsProto(ts.URL), nil)
if err == nil {
t.Fatalf("Dial succeeded, expect fail ")
}
}
type dataBeforeHandshakeResponseWriter struct {
http.ResponseWriter
}
type dataBeforeHandshakeConnection struct {
net.Conn
io.Reader
}
func (c *dataBeforeHandshakeConnection) Read(p []byte) (int, error) {
return c.Reader.Read(p)
}
func (w dataBeforeHandshakeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// Example single-frame masked text message from section 5.7 of the RFC.
message := []byte{0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58}
n := len(message) / 2
c, rw, err := http.NewResponseController(w.ResponseWriter).Hijack()
if rw != nil {
// Load first part of message into bufio.Reader. If the websocket
// connection reads more than n bytes from the bufio.Reader, then the
// test will fail with an unexpected EOF error.
rw.Reader.Reset(bytes.NewReader(message[:n]))
rw.Reader.Peek(n)
}
if c != nil {
// Inject second part of message before data read from the network connection.
c = &dataBeforeHandshakeConnection{
Conn: c,
Reader: io.MultiReader(bytes.NewReader(message[n:]), c),
}
}
return c, rw, err
}
func TestDataReceivedBeforeHandshake(t *testing.T) {
s := newServer(t)
defer s.Close()
origHandler := s.Server.Config.Handler
s.Server.Config.Handler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
origHandler.ServeHTTP(dataBeforeHandshakeResponseWriter{w}, r)
})
for _, readBufferSize := range []int{0, 1024} {
t.Run(fmt.Sprintf("ReadBufferSize=%d", readBufferSize), func(t *testing.T) {
dialer := cstDialer
dialer.ReadBufferSize = readBufferSize
ws, _, err := cstDialer.Dial(s.URL, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer ws.Close()
_, m, err := ws.ReadMessage()
if err != nil || string(m) != "Hello" {
t.Fatalf("ReadMessage() = %q, %v, want \"Hello\", nil", m, err)
}
})
}
}
================================================
FILE: client_test.go
================================================
// 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"
"testing"
)
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)
}
}
}
================================================
FILE: compression.go
================================================
// 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)
mr := io.MultiReader(r, strings.NewReader(tail))
if err := fr.(flate.Resetter).Reset(mr, nil); err != nil {
// Reset never fails, but handle error in case that changes.
fr = flate.NewReader(mr)
}
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
}
================================================
FILE: compression_test.go
================================================
package websocket
import (
"bytes"
"fmt"
"io"
"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 := io.Discard
c := newTestConn(nil, w, false)
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 := io.Discard
c := newTestConn(nil, w, false)
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 := newTestConn(nil, nil, false)
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)
}
}
}
================================================
FILE: conn.go
================================================
// 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"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"net"
"strconv"
"strings"
"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 pong 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 a close message.
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")
)
// maskRand is an io.Reader for generating mask bytes. The reader is initialized
// to crypto/rand Reader. Tests swap the reader to a math/rand reader for
// reproducible results.
var maskRand = rand.Reader
// newMaskKey returns a new 32 bit value for masking client frames.
func newMaskKey() [4]byte {
var k [4]byte
_, _ = io.ReadFull(maskRand, k[:])
return k
}
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)
}
// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this
// interface. The type of the value stored in a pool is not specified.
type BufferPool interface {
// Get gets a value from the pool or returns nil if the pool is empty.
Get() interface{}
// Put adds a value to the pool.
Put(interface{})
}
// writePoolData is the type added to the write buffer pool. This wrapper is
// used to prevent applications from peeking at and depending on the values
// added to the pool.
type writePoolData struct{ buf []byte }
// The Conn type represents a WebSocket connection.
type Conn struct {
conn net.Conn
isServer bool
subprotocol string
// Write fields
mu chan struct{} // used as mutex to protect write to conn
writeBuf []byte // frame is constructed in this buffer.
writePool BufferPool
writeBufSize int
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
// bytes remaining in current frame.
// set setReadRemaining to safely update this value and prevent overflow
readRemaining int64
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, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn {
if br == nil {
if readBufferSize == 0 {
readBufferSize = defaultReadBufferSize
} else if readBufferSize < maxControlFramePayloadSize {
// must be large enough for control frame
readBufferSize = maxControlFramePayloadSize
}
br = bufio.NewReaderSize(conn, readBufferSize)
}
if writeBufferSize <= 0 {
writeBufferSize = defaultWriteBufferSize
}
writeBufferSize += maxFrameHeaderSize
if writeBuf == nil && writeBufferPool == nil {
writeBuf = make([]byte, writeBufferSize)
}
mu := make(chan struct{}, 1)
mu <- struct{}{}
c := &Conn{
isServer: isServer,
br: br,
conn: conn,
mu: mu,
readFinal: true,
writeBuf: writeBuf,
writePool: writeBufferPool,
writeBufSize: writeBufferSize,
enableWriteCompression: true,
compressionLevel: defaultCompressionLevel,
}
c.SetCloseHandler(nil)
c.SetPingHandler(nil)
c.SetPongHandler(nil)
return c
}
// setReadRemaining tracks the number of bytes remaining on the connection. If n
// overflows, an ErrReadLimit is returned.
func (c *Conn) setReadRemaining(n int64) error {
if n < 0 {
return ErrReadLimit
}
c.readRemaining = n
return nil
}
// 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 message.
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 {
c.writeErrMu.Lock()
if c.writeErr == nil {
c.writeErr = err
}
c.writeErrMu.Unlock()
return err
}
func (c *Conn) read(n int) ([]byte, error) {
p, err := c.br.Peek(n)
if err == io.EOF {
err = errUnexpectedEOF
}
// Discard is guaranteed to succeed because the number of bytes to discard
// is less than or equal to the number of bytes buffered.
_, _ = c.br.Discard(len(p))
return p, err
}
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
<-c.mu
defer func() { c.mu <- struct{}{} }()
c.writeErrMu.Lock()
err := c.writeErr
c.writeErrMu.Unlock()
if err != nil {
return err
}
if err := c.conn.SetWriteDeadline(deadline); err != nil {
return c.writeFatal(err)
}
if len(buf1) == 0 {
_, err = c.conn.Write(buf0)
} else {
err = c.writeBufs(buf0, buf1)
}
if err != nil {
return c.writeFatal(err)
}
if frameType == CloseMessage {
_ = c.writeFatal(ErrCloseSent)
}
return nil
}
func (c *Conn) writeBufs(bufs ...[]byte) error {
b := net.Buffers(bufs)
_, err := b.WriteTo(c.conn)
return err
}
// 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:])
}
if deadline.IsZero() {
// No timeout for zero time.
<-c.mu
} else {
d := time.Until(deadline)
if d < 0 {
return errWriteTimeout
}
select {
case <-c.mu:
default:
timer := time.NewTimer(d)
select {
case <-c.mu:
timer.Stop()
case <-timer.C:
return errWriteTimeout
}
}
}
defer func() { c.mu <- struct{}{} }()
c.writeErrMu.Lock()
err := c.writeErr
c.writeErrMu.Unlock()
if err != nil {
return err
}
if err := c.conn.SetWriteDeadline(deadline); err != nil {
return c.writeFatal(err)
}
if _, err = c.conn.Write(buf); err != nil {
return c.writeFatal(err)
}
if messageType == CloseMessage {
_ = c.writeFatal(ErrCloseSent)
}
return err
}
// beginMessage prepares a connection and message writer for a new message.
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
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()
if err != nil {
return err
}
mw.c = c
mw.frameType = messageType
mw.pos = maxFrameHeaderSize
if c.writeBuf == nil {
wpd, ok := c.writePool.Get().(writePoolData)
if ok {
c.writeBuf = wpd.buf
} else {
c.writeBuf = make([]byte, c.writeBufSize)
}
}
return nil
}
// 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.
//
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
// PongMessage) are supported.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return nil, err
}
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) endMessage(err error) error {
if w.err != nil {
return err
}
c := w.c
w.err = err
c.writer = nil
if c.writePool != nil {
c.writePool.Put(writePoolData{buf: c.writeBuf})
c.writeBuf = nil
}
return err
}
// 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.endMessage(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 w.endMessage(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.endMessage(err)
}
if final {
_ = w.endMessage(errWriteClosed)
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
}
return w.flushFrame(true, 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.
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return err
}
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(io.Discard, c.br, c.readRemaining); err != nil {
return noFrame, err
}
}
// 2. Read and parse first two bytes of frame header.
// To aid debugging, collect and report all errors in the first two bytes
// of the header.
var errors []string
p, err := c.read(2)
if err != nil {
return noFrame, err
}
frameType := int(p[0] & 0xf)
final := p[0]&finalBit != 0
rsv1 := p[0]&rsv1Bit != 0
rsv2 := p[0]&rsv2Bit != 0
rsv3 := p[0]&rsv3Bit != 0
mask := p[1]&maskBit != 0
_ = c.setReadRemaining(int64(p[1] & 0x7f)) // will not fail because argument is >= 0
c.readDecompress = false
if rsv1 {
if c.newDecompressionReader != nil {
c.readDecompress = true
} else {
errors = append(errors, "RSV1 set")
}
}
if rsv2 {
errors = append(errors, "RSV2 set")
}
if rsv3 {
errors = append(errors, "RSV3 set")
}
switch frameType {
case CloseMessage, PingMessage, PongMessage:
if c.readRemaining > maxControlFramePayloadSize {
errors = append(errors, "len > 125 for control")
}
if !final {
errors = append(errors, "FIN not set on control")
}
case TextMessage, BinaryMessage:
if !c.readFinal {
errors = append(errors, "data before FIN")
}
c.readFinal = final
case continuationFrame:
if c.readFinal {
errors = append(errors, "continuation after FIN")
}
c.readFinal = final
default:
errors = append(errors, "bad opcode "+strconv.Itoa(frameType))
}
if mask != c.isServer {
errors = append(errors, "bad MASK")
}
if len(errors) > 0 {
return noFrame, c.handleProtocolError(strings.Join(errors, ", "))
}
// 3. Read and parse frame length as per
// https://tools.ietf.org/html/rfc6455#section-5.2
//
// The length of the "Payload data", in bytes: if 0-125, that is the payload
// length.
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
// integer are the payload length.
// - If 127, the following 8 bytes interpreted as
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
// payload length. Multibyte length quantities are expressed in network byte
// order.
switch c.readRemaining {
case 126:
p, err := c.read(2)
if err != nil {
return noFrame, err
}
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
return noFrame, err
}
case 127:
p, err := c.read(8)
if err != nil {
return noFrame, err
}
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
return noFrame, err
}
}
// 4. Handle frame masking.
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
// Don't allow readLength to overflow in the presence of a large readRemaining
// counter.
if c.readLength < 0 {
return noFrame, ErrReadLimit
}
if c.readLimit > 0 && c.readLength > c.readLimit {
// Make a best effort to send a close message describing the problem.
_ = 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.setReadRemaining(0) // will not fail because argument is >= 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("bad close code " + strconv.Itoa(closeCode))
}
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 {
data := FormatCloseMessage(CloseProtocolError, message)
if len(data) > maxControlFramePayloadSize {
data = data[:maxControlFramePayloadSize]
}
// Make a best effor to send a close message describing the problem.
_ = c.WriteControl(CloseMessage, data, 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 = 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 = err
if c.isServer {
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
}
rem := c.readRemaining
rem -= int64(n)
_ = c.setReadRemaining(rem) // rem is guaranteed to be >= 0
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 = 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 = io.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 in bytes for a message read from the peer. If a
// message exceeds the limit, the connection sends a close message to the peer
// and returns ErrReadLimit to the application.
func (c *Conn) SetReadLimit(limit int64) {
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
// message back to the peer.
//
// The handler function is called from the NextReader, ReadMessage and message
// reader Read methods. The application must read the connection to process
// close messages as described in the section on Control Messages above.
//
// The connection read methods return a CloseError when a close message 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 message 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 := FormatCloseMessage(code, "")
// Make a best effor to send the close message.
_ = 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 message application data. The default
// ping handler sends a pong to the peer.
//
// The handler function is called from the NextReader, ReadMessage and message
// reader Read methods. The application must read the connection to process
// ping messages as described in the section on Control Messages above.
func (c *Conn) SetPingHandler(h func(appData string) error) {
if h == nil {
h = func(message string) error {
// Make a best effort to send the pong message.
_ = c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
return nil
}
}
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 message application data. The default
// pong handler does nothing.
//
// The handler function is called from the NextReader, ReadMessage and message
// reader Read methods. The application must read the connection to process
// pong messages as described in the section on Control Messages above.
func (c *Conn) SetPongHandler(h func(appData string) error) {
if h == nil {
h = func(string) error { return nil }
}
c.handlePong = h
}
// NetConn returns the underlying connection that is wrapped by c.
// Note that writing to or reading from this connection directly will corrupt the
// WebSocket connection.
func (c *Conn) NetConn() net.Conn {
return c.conn
}
// UnderlyingConn returns the internal net.Conn. This can be used to further
// modifications to connection specific flags.
// Deprecated: Use the NetConn method.
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.
// An empty message is returned for code CloseNoStatusReceived.
func FormatCloseMessage(closeCode int, text string) []byte {
if closeCode == CloseNoStatusReceived {
// Return empty message because it's illegal to send
// CloseNoStatusReceived. Return non-nil value in case application
// checks for nil.
return []byte{}
}
buf := make([]byte, 2+len(text))
binary.BigEndian.PutUint16(buf, uint16(closeCode))
copy(buf[2:], text)
return buf
}
================================================
FILE: conn_broadcast_test.go
================================================
// 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 (
"io"
"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
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: io.Discard,
doneCh: make(chan struct{}),
closeCh: make(chan struct{}),
usePrepared: usePrepared,
compression: compression,
}
bench.makeConns(10000)
return bench
}
func (b *broadcastBench) makeConns(numConns int) {
conns := make([]*broadcastConn, numConns)
for i := 0; i < numConns; i++ {
c := newTestConn(nil, b.w, true)
if b.compression {
c.enableWriteCompression = true
c.newCompressionWriter = compressNoContextTakeover
}
conns[i] = newBroadcastConn(c)
go func(c *broadcastConn) {
for {
select {
case msg := <-c.msgCh:
if msg.prepared != nil {
_ = 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) broadcastOnce(msg *broadcastMessage) {
for _, c := range b.conns {
c.msgCh <- msg
}
<-b.doneCh
}
func BenchmarkBroadcast(b *testing.B) {
benchmarks := []struct {
name string
usePrepared bool
compression bool
}{
{"NoCompression", false, false},
{"Compression", false, true},
{"NoCompressionPrepared", true, false},
{"CompressionPrepared", true, true},
}
payload := textMessages(1)[0]
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++ {
message := &broadcastMessage{
payload: payload,
}
if bench.usePrepared {
pm, _ := NewPreparedMessage(TextMessage, message.payload)
message.prepared = pm
}
bench.broadcastOnce(message)
}
b.ReportAllocs()
})
}
}
================================================
FILE: conn_test.go
================================================
// 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"
"net"
"reflect"
"sync"
"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"
}
// newTestConn creates a connection backed by a fake network connection using
// default values for buffering.
func newTestConn(r io.Reader, w io.Writer, isServer bool) *Conn {
return newConn(fakeNetConn{Reader: r, Writer: w}, isServer, 1024, 1024, nil, nil, nil)
}
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 := newTestConn(nil, &connBuf, isServer)
rc := newTestConn(chunker.f(&connBuf), nil, !isServer)
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
}
t.Logf("frame size: %d", n)
rbuf, err := io.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 TestWriteControlDeadline(t *testing.T) {
t.Parallel()
message := []byte("hello")
var connBuf bytes.Buffer
c := newTestConn(nil, &connBuf, true)
if err := c.WriteControl(PongMessage, message, time.Time{}); err != nil {
t.Errorf("WriteControl(..., zero deadline) = %v, want nil", err)
}
if err := c.WriteControl(PongMessage, message, time.Now().Add(time.Second)); err != nil {
t.Errorf("WriteControl(..., future deadline) = %v, want nil", err)
}
if err := c.WriteControl(PongMessage, message, time.Now().Add(-time.Second)); err == nil {
t.Errorf("WriteControl(..., past deadline) = nil, want timeout error")
}
}
func TestConcurrencyWriteControl(t *testing.T) {
const message = "this is a ping/pong messsage"
loop := 10
workers := 10
for i := 0; i < loop; i++ {
var connBuf bytes.Buffer
wg := sync.WaitGroup{}
wc := newTestConn(nil, &connBuf, true)
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if err := wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second)); err != nil {
t.Errorf("concurrently wc.WriteControl() returned %v", err)
}
}()
}
wg.Wait()
wc.Close()
}
}
func TestControl(t *testing.T) {
const message = "this is a ping/pong message"
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 := newTestConn(nil, &connBuf, isServer)
rc := newTestConn(&connBuf, nil, !isServer)
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
}
}
}
}
}
// simpleBufferPool is an implementation of BufferPool for TestWriteBufferPool.
type simpleBufferPool struct {
v interface{}
}
func (p *simpleBufferPool) Get() interface{} {
v := p.v
p.v = nil
return v
}
func (p *simpleBufferPool) Put(v interface{}) {
p.v = v
}
func TestWriteBufferPool(t *testing.T) {
const message = "Now is the time for all good people to come to the aid of the party."
var buf bytes.Buffer
var pool simpleBufferPool
rc := newTestConn(&buf, nil, false)
// Specify writeBufferSize smaller than message size to ensure that pooling
// works with fragmented messages.
wc := newConn(fakeNetConn{Writer: &buf}, true, 1024, len(message)-1, &pool, nil, nil)
if wc.writeBuf != nil {
t.Fatal("writeBuf not nil after create")
}
// Part 1: test NextWriter/Write/Close
w, err := wc.NextWriter(TextMessage)
if err != nil {
t.Fatalf("wc.NextWriter() returned %v", err)
}
if wc.writeBuf == nil {
t.Fatal("writeBuf is nil after NextWriter")
}
writeBufAddr := &wc.writeBuf[0]
if _, err := io.WriteString(w, message); err != nil {
t.Fatalf("io.WriteString(w, message) returned %v", err)
}
if err := w.Close(); err != nil {
t.Fatalf("w.Close() returned %v", err)
}
if wc.writeBuf != nil {
t.Fatal("writeBuf not nil after w.Close()")
}
if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr {
t.Fatal("writeBuf not returned to pool")
}
opCode, p, err := rc.ReadMessage()
if opCode != TextMessage || err != nil {
t.Fatalf("ReadMessage() returned %d, p, %v", opCode, err)
}
if s := string(p); s != message {
t.Fatalf("message is %s, want %s", s, message)
}
// Part 2: Test WriteMessage.
if err := wc.WriteMessage(TextMessage, []byte(message)); err != nil {
t.Fatalf("wc.WriteMessage() returned %v", err)
}
if wc.writeBuf != nil {
t.Fatal("writeBuf not nil after wc.WriteMessage()")
}
if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr {
t.Fatal("writeBuf not returned to pool after WriteMessage")
}
opCode, p, err = rc.ReadMessage()
if opCode != TextMessage || err != nil {
t.Fatalf("ReadMessage() returned %d, p, %v", opCode, err)
}
if s := string(p); s != message {
t.Fatalf("message is %s, want %s", s, message)
}
}
// TestWriteBufferPoolSync ensures that *sync.Pool works as a buffer pool.
func TestWriteBufferPoolSync(t *testing.T) {
var buf bytes.Buffer
var pool sync.Pool
wc := newConn(fakeNetConn{Writer: &buf}, true, 1024, 1024, &pool, nil, nil)
rc := newTestConn(&buf, nil, false)
const message = "Hello World!"
for i := 0; i < 3; i++ {
if err := wc.WriteMessage(TextMessage, []byte(message)); err != nil {
t.Fatalf("wc.WriteMessage() returned %v", err)
}
opCode, p, err := rc.ReadMessage()
if opCode != TextMessage || err != nil {
t.Fatalf("ReadMessage() returned %d, p, %v", opCode, err)
}
if s := string(p); s != message {
t.Fatalf("message is %s, want %s", s, message)
}
}
}
// errorWriter is an io.Writer than returns an error on all writes.
type errorWriter struct{}
func (ew errorWriter) Write(p []byte) (int, error) { return 0, errors.New("error") }
// TestWriteBufferPoolError ensures that buffer is returned to pool after error
// on write.
func TestWriteBufferPoolError(t *testing.T) {
// Part 1: Test NextWriter/Write/Close
var pool simpleBufferPool
wc := newConn(fakeNetConn{Writer: errorWriter{}}, true, 1024, 1024, &pool, nil, nil)
w, err := wc.NextWriter(TextMessage)
if err != nil {
t.Fatalf("wc.NextWriter() returned %v", err)
}
if wc.writeBuf == nil {
t.Fatal("writeBuf is nil after NextWriter")
}
writeBufAddr := &wc.writeBuf[0]
if _, err := io.WriteString(w, "Hello"); err != nil {
t.Fatalf("io.WriteString(w, message) returned %v", err)
}
if err := w.Close(); err == nil {
t.Fatalf("w.Close() did not return error")
}
if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr {
t.Fatal("writeBuf not returned to pool")
}
// Part 2: Test WriteMessage
wc = newConn(fakeNetConn{Writer: errorWriter{}}, true, 1024, 1024, &pool, nil, nil)
if err := wc.WriteMessage(TextMessage, []byte("Hello")); err == nil {
t.Fatalf("wc.WriteMessage did not return error")
}
if wpd, ok := pool.v.(writePoolData); !ok || len(wpd.buf) == 0 || &wpd.buf[0] != writeBufAddr {
t.Fatal("writeBuf not returned to pool")
}
}
func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) {
const bufSize = 512
expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
var b1, b2 bytes.Buffer
wc := newConn(&fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize, nil, nil, nil)
rc := newTestConn(&b1, &b2, true)
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(io.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 := newTestConn(nil, &b, false)
rc := newTestConn(&b, nil, true)
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(io.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{Writer: &b1}, false, 1024, bufSize, nil, nil, nil)
rc := newTestConn(&b1, &b2, true)
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(io.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 := newTestConn(nil, &bytes.Buffer{}, false)
w, _ := wc.NextWriter(BinaryMessage)
_, _ = io.WriteString(w, "hello")
if err := w.Close(); err != nil {
t.Fatalf("unexpected 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) {
t.Run("Test ReadLimit is enforced", func(t *testing.T) {
const readLimit = 512
message := make([]byte, readLimit+1)
var b1, b2 bytes.Buffer
wc := newConn(&fakeNetConn{Writer: &b1}, false, 1024, readLimit-2, nil, nil, nil)
rc := newTestConn(&b1, &b2, true)
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(io.Discard, r)
if err != ErrReadLimit {
t.Fatalf("io.Copy() returned %v", err)
}
})
t.Run("Test that ReadLimit cannot be overflowed", func(t *testing.T) {
const readLimit = 1
var b1, b2 bytes.Buffer
rc := newTestConn(&b1, &b2, true)
rc.SetReadLimit(readLimit)
// First, send a non-final binary message
b1.Write([]byte("\x02\x81"))
// Mask key
b1.Write([]byte("\x00\x00\x00\x00"))
// First payload
b1.Write([]byte("A"))
// Next, send a negative-length, non-final continuation frame
b1.Write([]byte("\x00\xFF\x80\x00\x00\x00\x00\x00\x00\x00"))
// Mask key
b1.Write([]byte("\x00\x00\x00\x00"))
// Next, send a too long, final continuation frame
b1.Write([]byte("\x80\xFF\x00\x00\x00\x00\x00\x00\x00\x05"))
// Mask key
b1.Write([]byte("\x00\x00\x00\x00"))
// Too-long payload
b1.Write([]byte("BCDEF"))
op, r, err := rc.NextReader()
if op != BinaryMessage || err != nil {
t.Fatalf("1: NextReader() returned %d, %v", op, err)
}
var buf [10]byte
var read int
n, err := r.Read(buf[:])
if err != nil && err != ErrReadLimit {
t.Fatalf("unexpected error testing read limit: %v", err)
}
read += n
n, err = r.Read(buf[:])
if err != nil && err != ErrReadLimit {
t.Fatalf("unexpected error testing read limit: %v", err)
}
read += n
if err == nil && read > readLimit {
t.Fatalf("read limit exceeded: limit %d, read %d", readLimit, read)
}
})
}
func TestAddrs(t *testing.T) {
c := newTestConn(nil, nil, true)
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 TestDeprecatedUnderlyingConn(t *testing.T) {
var b1, b2 bytes.Buffer
fc := fakeNetConn{Reader: &b1, Writer: &b2}
c := newConn(fc, true, 1024, 1024, nil, nil, nil)
ul := c.UnderlyingConn()
if ul != fc {
t.Fatalf("Underlying conn is not what it should be.")
}
}
func TestNetConn(t *testing.T) {
var b1, b2 bytes.Buffer
fc := fakeNetConn{Reader: &b1, Writer: &b2}
c := newConn(fc, true, 1024, 1024, nil, nil, nil)
ul := c.NetConn()
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{Writer: &b1}, false, len(m)+64, len(m)+64, nil, nil, nil)
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64, nil, nil, nil)
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 returned %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 := newTestConn(nil, w, false)
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 := newTestConn(failingReader{}, nil, false)
defer func() {
if v := recover(); v != nil {
return
}
}()
for i := 0; i < 20000; i++ {
_, _, _ = c.ReadMessage()
}
t.Fatal("should not get here")
}
================================================
FILE: doc.go
================================================
// 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 {
// log.Println(err)
// return
// }
// if err := conn.WriteMessage(messageType, p); err != nil {
// log.Println(err)
// return
// }
// }
//
// 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 calling the handler function
// set with the SetCloseHandler method and by returning a *CloseError from the
// NextReader, ReadMessage or the message Read method. The default close
// handler sends a close message to the peer.
//
// Connections handle received ping messages by calling the handler function
// set with the SetPingHandler method. The default ping handler sends a pong
// message to the peer.
//
// Connections handle received pong messages by calling the handler function
// set with the SetPongHandler method. The default pong handler does nothing.
// If an application sends ping messages, then the application should set a
// pong handler to receive the corresponding pong.
//
// The control message handler functions are called from the NextReader,
// ReadMessage and message reader Read methods. The default close and ping
// handlers can block these methods for a short time when the handler writes to
// the connection.
//
// The application must read the connection to process close, ping and pong
// 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 the Origin host is
// not equal to the Host request header.
//
// 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.
//
// Buffers
//
// Connections buffer network input and output to reduce the number
// of system calls when reading or writing messages.
//
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
// Section 5 for a discussion of message framing. A WebSocket frame header is
// written to the network each time a write buffer is flushed to the network.
// Decreasing the size of the write buffer can increase the amount of framing
// overhead on the connection.
//
// The buffer sizes in bytes are specified by the ReadBufferSize and
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
// buffers created by the HTTP server when a buffer size field is set to zero.
// The HTTP server buffers have a size of 4096 at the time of this writing.
//
// The buffer sizes do not limit the size of a message that can be read or
// written by a connection.
//
// Buffers are held for the lifetime of the connection by default. If the
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
// write buffer only when writing a message.
//
// Applications should tune the buffer sizes to balance memory use and
// performance. Increasing the buffer size uses more memory, but can reduce the
// number of system calls to read or write the network. In the case of writing,
// increasing the buffer size can reduce the number of frame headers written to
// the network.
//
// Some guidelines for setting buffer parameters are:
//
// Limit the buffer sizes to the maximum expected message size. Buffers larger
// than the largest message do not provide any benefit.
//
// Depending on the distribution of message sizes, setting the buffer size to
// a value less than the maximum expected message size can greatly reduce memory
// use with a small impact on performance. Here's an example: If 99% of the
// messages are smaller than 256 bytes and the maximum message size is 512
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
// than a buffer size of 512 bytes. The memory savings is 50%.
//
// A write buffer pool is useful when the application has a modest number
// writes over a large number of connections. when buffers are pooled, a larger
// buffer size has a reduced impact on total memory use and has the benefit of
// reducing system calls and frame overhead.
//
// 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
================================================
FILE: example_test.go
================================================
// 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 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
}
processMessage(messageType, p)
}
}
func processMessage(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) {}
================================================
FILE: examples/autobahn/README.md
================================================
# Test Server
This package contains a server for the [Autobahn WebSockets Test Suite](https://github.com/crossbario/autobahn-testsuite).
To test the server, run
go run server.go
and start the client test driver
mkdir -p reports
docker run -it --rm \
-v ${PWD}/config:/config \
-v ${PWD}/reports:/reports \
crossbario/autobahn-testsuite \
wstest -m fuzzingclient -s /config/fuzzingclient.json
When the client completes, it writes a report to reports/index.html.
================================================
FILE: examples/autobahn/config/fuzzingclient.json
================================================
{
"cases": ["*"],
"exclude-cases": [],
"exclude-agent-cases": {},
"outdir": "/reports",
"options": {"failByDrop": false},
"servers": [
{
"agent": "ReadAllWriteMessage",
"url": "ws://host.docker.internal:9000/m"
},
{
"agent": "ReadAllWritePreparedMessage",
"url": "ws://host.docker.internal:9000/p"
},
{
"agent": "CopyFull",
"url": "ws://host.docker.internal:9000/f"
},
{
"agent": "ReadAllWrite",
"url": "ws://host.docker.internal:9000/r"
},
{
"agent": "CopyWriterOnly",
"url": "ws://host.docker.internal:9000/c"
}
]
}
================================================
FILE: examples/autobahn/server.go
================================================
// 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 io.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.", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.WriteString(w, "<html><body>Echo Server</body></html>")
}
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 <bjoern@hoehrmann.de>
//
// 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
}
================================================
FILE: examples/chat/README.md
================================================
# Chat Example
This application shows how to 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/main/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/main/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/main/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.
================================================
FILE: examples/chat/client.go
================================================
// 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, websocket.CloseAbnormalClosure) {
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, nil); 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()
}
================================================
FILE: examples/chat/home.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chat Example</title>
<script type="text/javascript">
window.onload = function () {
var conn;
var msg = document.getElementById("msg");
var log = document.getElementById("log");
function appendLog(item) {
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
log.appendChild(item);
if (doScroll) {
log.scrollTop = log.scrollHeight - log.clientHeight;
}
}
document.getElementById("form").onsubmit = function () {
if (!conn) {
return false;
}
if (!msg.value) {
return false;
}
conn.send(msg.value);
msg.value = "";
return false;
};
if (window["WebSocket"]) {
conn = new WebSocket("ws://" + document.location.host + "/ws");
conn.onclose = function (evt) {
var item = document.createElement("div");
item.innerHTML = "<b>Connection closed.</b>";
appendLog(item);
};
conn.onmessage = function (evt) {
var messages = evt.data.split('\n');
for (var i = 0; i < messages.length; i++) {
var item = document.createElement("div");
item.innerText = messages[i];
appendLog(item);
}
};
} else {
var item = document.createElement("div");
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
appendLog(item);
}
};
</script>
<style type="text/css">
html {
overflow: hidden;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background: gray;
}
#log {
background: white;
margin: 0;
padding: 0.5em 0.5em 0.5em 0.5em;
position: absolute;
top: 0.5em;
left: 0.5em;
right: 0.5em;
bottom: 3em;
overflow: auto;
}
#form {
padding: 0 0.5em 0 0.5em;
margin: 0;
position: absolute;
bottom: 1em;
left: 0px;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="Send" />
<input type="text" id="msg" size="64" autofocus />
</form>
</body>
</html>
================================================
FILE: examples/chat/hub.go
================================================
// 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)
}
}
}
}
}
================================================
FILE: examples/chat/main.go
================================================
// 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", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
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)
}
}
================================================
FILE: examples/command/README.md
================================================
# 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 <command and arguments to run>
# 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
================================================
FILE: examples/command/home.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>Command Example</title>
<script type="text/javascript">
window.onload = function () {
var conn;
var msg = document.getElementById("msg");
var log = document.getElementById("log");
function appendLog(item) {
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
log.appendChild(item);
if (doScroll) {
log.scrollTop = log.scrollHeight - log.clientHeight;
}
}
document.getElementById("form").onsubmit = function () {
if (!conn) {
return false;
}
if (!msg.value) {
return false;
}
conn.send(msg.value);
msg.value = "";
return false;
};
if (window["WebSocket"]) {
conn = new WebSocket("ws://" + document.location.host + "/ws");
conn.onclose = function (evt) {
var item = document.createElement("div");
item.innerHTML = "<b>Connection closed.</b>";
appendLog(item);
};
conn.onmessage = function (evt) {
var messages = evt.data.split('\n');
for (var i = 0; i < messages.length; i++) {
var item = document.createElement("div");
item.innerText = messages[i];
appendLog(item);
}
};
} else {
var item = document.createElement("div");
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
appendLog(item);
}
};
</script>
<style type="text/css">
html {
overflow: hidden;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background: gray;
}
#log {
background: white;
margin: 0;
padding: 0.5em 0.5em 0.5em 0.5em;
position: absolute;
top: 0.5em;
left: 0.5em;
right: 0.5em;
bottom: 3em;
overflow: auto;
}
#log pre {
margin: 0;
}
#form {
padding: 0 0.5em 0 0.5em;
margin: 0;
position: absolute;
bottom: 1em;
left: 0px;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="Send" />
<input type="text" id="msg" size="64"/>
</form>
</body>
</html>
================================================
FILE: examples/command/main.go
================================================
// 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", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
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))
}
================================================
FILE: examples/echo/README.md
================================================
# 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.
================================================
FILE: examples/echo/client.go
================================================
// 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.
//go:build ignore
// +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 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 <-done:
return
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")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) 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):
}
return
}
}
}
================================================
FILE: examples/echo/server.go
================================================
// 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.
//go:build ignore
// +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(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
output.scroll(0, output.scrollHeight);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>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.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output" style="max-height: 70vh;overflow-y: scroll;"></div>
</td></tr></table>
</body>
</html>
`))
================================================
FILE: examples/filewatch/README.md
================================================
# 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 <name of file to watch>
# Open http://localhost:8080/ .
# Modify the file to see it update in the browser.
================================================
FILE: examples/filewatch/main.go
================================================
// 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"
"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 := os.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", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-T
gitextract_bnag3gt4/ ├── .circleci/ │ └── config.yml ├── .github/ │ └── release-drafter.yml ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── client.go ├── client_proxy_server_test.go ├── client_server_test.go ├── client_test.go ├── compression.go ├── compression_test.go ├── conn.go ├── conn_broadcast_test.go ├── conn_test.go ├── doc.go ├── example_test.go ├── examples/ │ ├── autobahn/ │ │ ├── README.md │ │ ├── config/ │ │ │ └── fuzzingclient.json │ │ └── server.go │ ├── chat/ │ │ ├── README.md │ │ ├── client.go │ │ ├── home.html │ │ ├── hub.go │ │ └── main.go │ ├── command/ │ │ ├── README.md │ │ ├── home.html │ │ └── main.go │ ├── echo/ │ │ ├── README.md │ │ ├── client.go │ │ └── server.go │ └── filewatch/ │ ├── README.md │ └── main.go ├── go.mod ├── go.sum ├── join.go ├── join_test.go ├── json.go ├── json_test.go ├── mask.go ├── mask_safe.go ├── mask_test.go ├── prepared.go ├── prepared_test.go ├── proxy.go ├── server.go ├── server_test.go ├── util.go └── util_test.go
SYMBOL INDEX (373 symbols across 32 files)
FILE: client.go
function NewClient (line 39) | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, ...
type Dialer (line 53) | type Dialer struct
method Dial (line 131) | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn...
method DialContext (line 175) | func (d *Dialer) DialContext(ctx context.Context, urlStr string, reque...
method netDialFn (line 406) | func (d *Dialer) netDialFn(ctx context.Context, proxyURL *url.URL, bac...
method netDialFromURL (line 426) | func (d *Dialer) netDialFromURL(u *url.URL) netDialerFunc {
function hostPortNoPort (line 137) | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
function netDialWithTLSHandshake (line 451) | func netDialWithTLSHandshake(netDial netDialerFunc, tlsConfig *tls.Confi...
function netDialWithDeadline (line 485) | func netDialWithDeadline(netDial netDialerFunc, deadline time.Time) netD...
function cloneTLSConfig (line 500) | func cloneTLSConfig(cfg *tls.Config) *tls.Config {
function doHandshake (line 507) | func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config...
FILE: client_proxy_server_test.go
constant subprotocolV1 (line 34) | subprotocolV1 = "subprotocol-version-1"
constant subprotocolV2 (line 35) | subprotocolV2 = "subprotocol-version-2"
function TestHTTPProxyAndBackend (line 42) | func TestHTTPProxyAndBackend(t *testing.T) {
function TestHTTPProxyWithNetDial (line 79) | func TestHTTPProxyWithNetDial(t *testing.T) {
function TestHTTPProxyWithNetDialContext (line 124) | func TestHTTPProxyWithNetDialContext(t *testing.T) {
function TestHTTPProxyWithHTTPSBackend (line 170) | func TestHTTPProxyWithHTTPSBackend(t *testing.T) {
function TestHTTPSProxyAndBackend (line 218) | func TestHTTPSProxyAndBackend(t *testing.T) {
function TestHTTPSProxyUsingNetDial (line 256) | func TestHTTPSProxyUsingNetDial(t *testing.T) {
function TestHTTPSProxyUsingNetDialContext (line 302) | func TestHTTPSProxyUsingNetDialContext(t *testing.T) {
function TestHTTPSProxyUsingNetDialTLSContext (line 348) | func TestHTTPSProxyUsingNetDialTLSContext(t *testing.T) {
function TestHTTPSProxyHTTPBackend (line 407) | func TestHTTPSProxyHTTPBackend(t *testing.T) {
function TestHTTPSProxyUsingNetDialTLSContextWithHTTPBackend (line 445) | func TestHTTPSProxyUsingNetDialTLSContextWithHTTPBackend(t *testing.T) {
function TestTLSValidationErrors (line 485) | func TestTLSValidationErrors(t *testing.T) {
function TestProxyFnErrorIsPropagated (line 536) | func TestProxyFnErrorIsPropagated(t *testing.T) {
function TestProxyFnNilMeansNoProxy (line 559) | func TestProxyFnNilMeansNoProxy(t *testing.T) {
type counter (line 596) | type counter interface
type closer (line 602) | type closer interface
type testServer (line 607) | type testServer struct
method numCalls (line 612) | func (ts *testServer) numCalls() int64 {
method increment (line 616) | func (ts *testServer) increment() {
method Close (line 620) | func (ts *testServer) Close() {
function newWebsocketServer (line 668) | func newWebsocketServer(tlsServer bool) (closer, *url.URL, error) {
function newProxyServer (line 736) | func newProxyServer(tlsServer bool) (counter, *url.URL, error) {
function tlsConfig (line 764) | func tlsConfig(websocketTLS bool, proxyTLS bool) *tls.Config {
constant randomDataSize (line 783) | randomDataSize = 128 * 1024
function sendReceiveData (line 785) | func sendReceiveData(t *testing.T, wsConn *Conn) {
FILE: client_server_test.go
type cstHandler (line 49) | type cstHandler struct
method ServeHTTP (line 88) | func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
type cstServer (line 54) | type cstServer struct
method Close (line 66) | func (s *cstServer) Close() {
constant cstPath (line 61) | cstPath = "/a/b"
constant cstRawQuery (line 62) | cstRawQuery = "x=y"
constant cstRequestURI (line 63) | cstRequestURI = cstPath + "?" + cstRawQuery
function newServer (line 72) | func newServer(t *testing.T) *cstServer {
function newTLSServer (line 80) | func newTLSServer(t *testing.T) *cstServer {
function makeWsProto (line 143) | func makeWsProto(s string) string {
function sendRecv (line 147) | func sendRecv(t *testing.T, ws *Conn) {
function TestProxyDial (line 167) | func TestProxyDial(t *testing.T) {
function TestProxyAuthorizationDial (line 205) | func TestProxyAuthorizationDial(t *testing.T) {
function TestDial (line 245) | func TestDial(t *testing.T) {
function TestDialCookieJar (line 257) | func TestDialCookieJar(t *testing.T) {
function rootCAs (line 305) | func rootCAs(t *testing.T, s *httptest.Server) *x509.CertPool {
function TestDialTLS (line 319) | func TestDialTLS(t *testing.T) {
function TestDialTimeout (line 333) | func TestDialTimeout(t *testing.T) {
type requireDeadlineNetConn (line 348) | type requireDeadlineNetConn struct
method SetDeadline (line 355) | func (c *requireDeadlineNetConn) SetDeadline(t time.Time) error {
method SetReadDeadline (line 361) | func (c *requireDeadlineNetConn) SetReadDeadline(t time.Time) error {
method SetWriteDeadline (line 366) | func (c *requireDeadlineNetConn) SetWriteDeadline(t time.Time) error {
method Write (line 371) | func (c *requireDeadlineNetConn) Write(p []byte) (int, error) {
method Read (line 378) | func (c *requireDeadlineNetConn) Read(p []byte) (int, error) {
method Close (line 385) | func (c *requireDeadlineNetConn) Close() error { return c.c.Cl...
method LocalAddr (line 386) | func (c *requireDeadlineNetConn) LocalAddr() net.Addr { return c.c.Lo...
method RemoteAddr (line 387) | func (c *requireDeadlineNetConn) RemoteAddr() net.Addr { return c.c.Re...
function TestHandshakeTimeout (line 389) | func TestHandshakeTimeout(t *testing.T) {
function TestHandshakeTimeoutInContext (line 405) | func TestHandshakeTimeoutInContext(t *testing.T) {
function TestDialBadScheme (line 426) | func TestDialBadScheme(t *testing.T) {
function TestDialBadOrigin (line 437) | func TestDialBadOrigin(t *testing.T) {
function TestDialBadHeader (line 454) | func TestDialBadHeader(t *testing.T) {
function TestBadMethod (line 473) | func TestBadMethod(t *testing.T) {
function TestNoUpgrade (line 501) | func TestNoUpgrade(t *testing.T) {
function TestDialExtraTokensInRespHeaders (line 532) | func TestDialExtraTokensInRespHeaders(t *testing.T) {
function TestHandshake (line 549) | func TestHandshake(t *testing.T) {
function TestRespOnBadHandshake (line 575) | func TestRespOnBadHandshake(t *testing.T) {
type testLogWriter (line 609) | type testLogWriter struct
method Write (line 613) | func (w testLogWriter) Write(p []byte) (int, error) {
function TestHost (line 619) | func TestHost(t *testing.T) {
function TestDialCompression (line 800) | func TestDialCompression(t *testing.T) {
function TestSocksProxyDial (line 814) | func TestSocksProxyDial(t *testing.T) {
function TestTracingDialWithContext (line 893) | func TestTracingDialWithContext(t *testing.T) {
function TestEmptyTracingDialWithContext (line 952) | func TestEmptyTracingDialWithContext(t *testing.T) {
function TestNetDialConnect (line 973) | func TestNetDialConnect(t *testing.T) {
function TestNextProtos (line 1148) | func TestNextProtos(t *testing.T) {
type dataBeforeHandshakeResponseWriter (line 1184) | type dataBeforeHandshakeResponseWriter struct
method Hijack (line 1197) | func (w dataBeforeHandshakeResponseWriter) Hijack() (net.Conn, *bufio....
type dataBeforeHandshakeConnection (line 1188) | type dataBeforeHandshakeConnection struct
method Read (line 1193) | func (c *dataBeforeHandshakeConnection) Read(p []byte) (int, error) {
function TestDataReceivedBeforeHandshake (line 1220) | func TestDataReceivedBeforeHandshake(t *testing.T) {
FILE: client_test.go
function TestHostPortNoPort (line 22) | func TestHostPortNoPort(t *testing.T) {
FILE: compression.go
constant minCompressionLevel (line 16) | minCompressionLevel = -2
constant maxCompressionLevel (line 17) | maxCompressionLevel = flate.BestCompression
constant defaultCompressionLevel (line 18) | defaultCompressionLevel = 1
function decompressNoContextTakeover (line 28) | func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
function isValidCompressionLevel (line 44) | func isValidCompressionLevel(level int) bool {
function compressNoContextTakeover (line 48) | func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteClos...
type truncWriter (line 62) | type truncWriter struct
method Write (line 68) | func (w *truncWriter) Write(p []byte) (int, error) {
type flateWriteWrapper (line 96) | type flateWriteWrapper struct
method Write (line 102) | func (w *flateWriteWrapper) Write(p []byte) (int, error) {
method Close (line 109) | func (w *flateWriteWrapper) Close() error {
type flateReadWrapper (line 126) | type flateReadWrapper struct
method Read (line 130) | func (r *flateReadWrapper) Read(p []byte) (int, error) {
method Close (line 144) | func (r *flateReadWrapper) Close() error {
FILE: compression_test.go
type nopCloser (line 10) | type nopCloser struct
method Close (line 12) | func (nopCloser) Close() error { return nil }
function TestTruncWriter (line 14) | func TestTruncWriter(t *testing.T) {
function textMessages (line 34) | func textMessages(num int) [][]byte {
function BenchmarkWriteNoCompression (line 43) | func BenchmarkWriteNoCompression(b *testing.B) {
function BenchmarkWriteWithCompression (line 54) | func BenchmarkWriteWithCompression(b *testing.B) {
function TestValidCompressionLevel (line 67) | func TestValidCompressionLevel(t *testing.T) {
FILE: conn.go
constant finalBit (line 23) | finalBit = 1 << 7
constant rsv1Bit (line 24) | rsv1Bit = 1 << 6
constant rsv2Bit (line 25) | rsv2Bit = 1 << 5
constant rsv3Bit (line 26) | rsv3Bit = 1 << 4
constant maskBit (line 29) | maskBit = 1 << 7
constant maxFrameHeaderSize (line 31) | maxFrameHeaderSize = 2 + 8 + 4
constant maxControlFramePayloadSize (line 32) | maxControlFramePayloadSize = 125
constant writeWait (line 34) | writeWait = time.Second
constant defaultReadBufferSize (line 36) | defaultReadBufferSize = 4096
constant defaultWriteBufferSize (line 37) | defaultWriteBufferSize = 4096
constant continuationFrame (line 39) | continuationFrame = 0
constant noFrame (line 40) | noFrame = -1
constant CloseNormalClosure (line 45) | CloseNormalClosure = 1000
constant CloseGoingAway (line 46) | CloseGoingAway = 1001
constant CloseProtocolError (line 47) | CloseProtocolError = 1002
constant CloseUnsupportedData (line 48) | CloseUnsupportedData = 1003
constant CloseNoStatusReceived (line 49) | CloseNoStatusReceived = 1005
constant CloseAbnormalClosure (line 50) | CloseAbnormalClosure = 1006
constant CloseInvalidFramePayloadData (line 51) | CloseInvalidFramePayloadData = 1007
constant ClosePolicyViolation (line 52) | ClosePolicyViolation = 1008
constant CloseMessageTooBig (line 53) | CloseMessageTooBig = 1009
constant CloseMandatoryExtension (line 54) | CloseMandatoryExtension = 1010
constant CloseInternalServerErr (line 55) | CloseInternalServerErr = 1011
constant CloseServiceRestart (line 56) | CloseServiceRestart = 1012
constant CloseTryAgainLater (line 57) | CloseTryAgainLater = 1013
constant CloseTLSHandshake (line 58) | CloseTLSHandshake = 1015
constant TextMessage (line 65) | TextMessage = 1
constant BinaryMessage (line 68) | BinaryMessage = 2
constant CloseMessage (line 73) | CloseMessage = 8
constant PingMessage (line 77) | PingMessage = 9
constant PongMessage (line 81) | PongMessage = 10
type netError (line 93) | type netError struct
method Error (line 99) | func (e *netError) Error() string { return e.msg }
method Temporary (line 100) | func (e *netError) Temporary() bool { return e.temporary }
method Timeout (line 101) | func (e *netError) Timeout() bool { return e.timeout }
type CloseError (line 104) | type CloseError struct
method Error (line 112) | func (e *CloseError) Error() string {
function IsCloseError (line 150) | func IsCloseError(err error, codes ...int) bool {
function IsUnexpectedCloseError (line 163) | func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
function newMaskKey (line 189) | func newMaskKey() [4]byte {
function isControl (line 195) | func isControl(frameType int) bool {
function isData (line 199) | func isData(frameType int) bool {
function isValidReceivedCloseCode (line 222) | func isValidReceivedCloseCode(code int) bool {
type BufferPool (line 228) | type BufferPool interface
type writePoolData (line 238) | type writePoolData struct
type Conn (line 241) | type Conn struct
method setReadRemaining (line 327) | func (c *Conn) setReadRemaining(n int64) error {
method Subprotocol (line 337) | func (c *Conn) Subprotocol() string {
method Close (line 343) | func (c *Conn) Close() error {
method LocalAddr (line 348) | func (c *Conn) LocalAddr() net.Addr {
method RemoteAddr (line 353) | func (c *Conn) RemoteAddr() net.Addr {
method writeFatal (line 359) | func (c *Conn) writeFatal(err error) error {
method read (line 368) | func (c *Conn) read(n int) ([]byte, error) {
method write (line 379) | func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []b...
method writeBufs (line 407) | func (c *Conn) writeBufs(bufs ...[]byte) error {
method WriteControl (line 415) | func (c *Conn) WriteControl(messageType int, data []byte, deadline tim...
method beginMessage (line 484) | func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
method NextWriter (line 527) | func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
method WritePreparedMessage (line 744) | func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error {
method WriteMessage (line 767) | func (c *Conn) WriteMessage(messageType int, data []byte) error {
method SetWriteDeadline (line 796) | func (c *Conn) SetWriteDeadline(t time.Time) error {
method advanceFrame (line 803) | func (c *Conn) advanceFrame() (int, error) {
method handleProtocolError (line 989) | func (c *Conn) handleProtocolError(message string) error {
method NextReader (line 1009) | func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
method ReadMessage (line 1102) | func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
method SetReadDeadline (line 1116) | func (c *Conn) SetReadDeadline(t time.Time) error {
method SetReadLimit (line 1123) | func (c *Conn) SetReadLimit(limit int64) {
method CloseHandler (line 1128) | func (c *Conn) CloseHandler() func(code int, text string) error {
method SetCloseHandler (line 1146) | func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
method PingHandler (line 1159) | func (c *Conn) PingHandler() func(appData string) error {
method SetPingHandler (line 1170) | func (c *Conn) SetPingHandler(h func(appData string) error) {
method PongHandler (line 1182) | func (c *Conn) PongHandler() func(appData string) error {
method SetPongHandler (line 1193) | func (c *Conn) SetPongHandler(h func(appData string) error) {
method NetConn (line 1203) | func (c *Conn) NetConn() net.Conn {
method UnderlyingConn (line 1210) | func (c *Conn) UnderlyingConn() net.Conn {
method EnableWriteCompression (line 1217) | func (c *Conn) EnableWriteCompression(enable bool) {
method SetCompressionLevel (line 1225) | func (c *Conn) SetCompressionLevel(level int) error {
function newConn (line 284) | func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSi...
type messageWriter (line 541) | type messageWriter struct
method endMessage (line 549) | func (w *messageWriter) endMessage(err error) error {
method flushFrame (line 565) | func (w *messageWriter) flushFrame(final bool, extra []byte) error {
method ncopy (line 652) | func (w *messageWriter) ncopy(max int) (int, error) {
method Write (line 666) | func (w *messageWriter) Write(p []byte) (int, error) {
method WriteString (line 693) | func (w *messageWriter) WriteString(p string) (int, error) {
method ReadFrom (line 711) | func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
method Close (line 736) | func (w *messageWriter) Close() error {
type messageReader (line 1047) | type messageReader struct
method Read (line 1049) | func (r *messageReader) Read(b []byte) (int, error) {
method Close (line 1096) | func (r *messageReader) Close() error {
function FormatCloseMessage (line 1235) | func FormatCloseMessage(closeCode int, text string) []byte {
FILE: conn_broadcast_test.go
type broadcastBench (line 18) | type broadcastBench struct
method makeConns (line 57) | func (b *broadcastBench) makeConns(numConns int) {
method close (line 89) | func (b *broadcastBench) close() {
method broadcastOnce (line 93) | func (b *broadcastBench) broadcastOnce(msg *broadcastMessage) {
type broadcastMessage (line 28) | type broadcastMessage struct
type broadcastConn (line 33) | type broadcastConn struct
function newBroadcastConn (line 38) | func newBroadcastConn(c *Conn) *broadcastConn {
function newBroadcastBench (line 45) | func newBroadcastBench(usePrepared, compression bool) *broadcastBench {
function BenchmarkBroadcast (line 100) | func BenchmarkBroadcast(b *testing.B) {
FILE: conn_test.go
type fakeNetConn (line 23) | type fakeNetConn struct
method Close (line 28) | func (c fakeNetConn) Close() error { return nil }
method LocalAddr (line 29) | func (c fakeNetConn) LocalAddr() net.Addr { return loca...
method RemoteAddr (line 30) | func (c fakeNetConn) RemoteAddr() net.Addr { return remo...
method SetDeadline (line 31) | func (c fakeNetConn) SetDeadline(t time.Time) error { return nil }
method SetReadDeadline (line 32) | func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil }
method SetWriteDeadline (line 33) | func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil }
type fakeAddr (line 35) | type fakeAddr
method Network (line 42) | func (a fakeAddr) Network() string {
method String (line 46) | func (a fakeAddr) String() string {
function newTestConn (line 52) | func newTestConn(r io.Reader, w io.Writer, isServer bool) *Conn {
function TestFraming (line 56) | func TestFraming(t *testing.T) {
function TestWriteControlDeadline (line 151) | func TestWriteControlDeadline(t *testing.T) {
function TestConcurrencyWriteControl (line 167) | func TestConcurrencyWriteControl(t *testing.T) {
function TestControl (line 192) | func TestControl(t *testing.T) {
type simpleBufferPool (line 229) | type simpleBufferPool struct
method Get (line 233) | func (p *simpleBufferPool) Get() interface{} {
method Put (line 239) | func (p *simpleBufferPool) Put(v interface{}) {
function TestWriteBufferPool (line 243) | func TestWriteBufferPool(t *testing.T) {
function TestWriteBufferPoolSync (line 321) | func TestWriteBufferPoolSync(t *testing.T) {
type errorWriter (line 343) | type errorWriter struct
method Write (line 345) | func (ew errorWriter) Write(p []byte) (int, error) { return 0, errors....
function TestWriteBufferPoolError (line 349) | func TestWriteBufferPoolError(t *testing.T) {
function TestCloseFrameBeforeFinalMessageFrame (line 392) | func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) {
function TestEOFWithinFrame (line 420) | func TestEOFWithinFrame(t *testing.T) {
function TestEOFBeforeFinalFrame (line 455) | func TestEOFBeforeFinalFrame(t *testing.T) {
function TestWriteAfterMessageWriterClose (line 479) | func TestWriteAfterMessageWriterClose(t *testing.T) {
function TestReadLimit (line 505) | func TestReadLimit(t *testing.T) {
function TestAddrs (line 595) | func TestAddrs(t *testing.T) {
function TestDeprecatedUnderlyingConn (line 605) | func TestDeprecatedUnderlyingConn(t *testing.T) {
function TestNetConn (line 615) | func TestNetConn(t *testing.T) {
function TestBufioReadBytes (line 625) | func TestBufioReadBytes(t *testing.T) {
function TestCloseError (line 665) | func TestCloseError(t *testing.T) {
function TestUnexpectedCloseErrors (line 685) | func TestUnexpectedCloseErrors(t *testing.T) {
type blockingWriter (line 694) | type blockingWriter struct
method Write (line 698) | func (w blockingWriter) Write(p []byte) (int, error) {
function TestConcurrentWritePanic (line 706) | func TestConcurrentWritePanic(t *testing.T) {
type failingReader (line 727) | type failingReader struct
method Read (line 729) | func (r failingReader) Read(p []byte) (int, error) {
function TestFailedConnectionReadPanic (line 733) | func TestFailedConnectionReadPanic(t *testing.T) {
FILE: example_test.go
function ExampleIsUnexpectedCloseError (line 28) | func ExampleIsUnexpectedCloseError() {
function processMessage (line 41) | func processMessage(mt int, p []byte) {}
function TestX (line 45) | func TestX(t *testing.T) {}
FILE: examples/autobahn/server.go
function echoCopy (line 30) | func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) {
function echoCopyWriterOnly (line 78) | func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) {
function echoCopyFull (line 82) | func echoCopyFull(w http.ResponseWriter, r *http.Request) {
function echoReadAll (line 88) | func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, w...
function echoReadAllWriter (line 146) | func echoReadAllWriter(w http.ResponseWriter, r *http.Request) {
function echoReadAllWriteMessage (line 150) | func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) {
function echoReadAllWritePreparedMessage (line 154) | func echoReadAllWritePreparedMessage(w http.ResponseWriter, r *http.Requ...
function serveHome (line 158) | func serveHome(w http.ResponseWriter, r *http.Request) {
function main (line 173) | func main() {
type validator (line 187) | type validator struct
method Read (line 195) | func (r *validator) Read(p []byte) (int, error) {
constant utf8Accept (line 252) | utf8Accept = 0
constant utf8Reject (line 253) | utf8Reject = 1
function decode (line 256) | func decode(state int, x rune, b byte) (int, rune) {
FILE: examples/chat/client.go
constant writeWait (line 18) | writeWait = 10 * time.Second
constant pongWait (line 21) | pongWait = 60 * time.Second
constant pingPeriod (line 24) | pingPeriod = (pongWait * 9) / 10
constant maxMessageSize (line 27) | maxMessageSize = 512
type Client (line 41) | type Client struct
method readPump (line 56) | func (c *Client) readPump() {
method writePump (line 82) | func (c *Client) writePump() {
function serveWs (line 124) | func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
FILE: examples/chat/hub.go
type Hub (line 9) | type Hub struct
method run (line 32) | func (h *Hub) run() {
function newHub (line 23) | func newHub() *Hub {
FILE: examples/chat/main.go
function serveHome (line 15) | func serveHome(w http.ResponseWriter, r *http.Request) {
function main (line 28) | func main() {
FILE: examples/command/main.go
constant writeWait (line 27) | writeWait = 10 * time.Second
constant maxMessageSize (line 30) | maxMessageSize = 8192
constant pongWait (line 33) | pongWait = 60 * time.Second
constant pingPeriod (line 36) | pingPeriod = (pongWait * 9) / 10
constant closeGracePeriod (line 39) | closeGracePeriod = 10 * time.Second
function pumpStdin (line 42) | func pumpStdin(ws *websocket.Conn, w io.Writer) {
function pumpStdout (line 59) | func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
function ping (line 81) | func ping(ws *websocket.Conn, done chan struct{}) {
function internalError (line 96) | func internalError(ws *websocket.Conn, msg string, err error) {
function serveWs (line 103) | func serveWs(w http.ResponseWriter, r *http.Request) {
function serveHome (line 168) | func serveHome(w http.ResponseWriter, r *http.Request) {
function main (line 180) | func main() {
FILE: examples/echo/client.go
function main (line 23) | func main() {
FILE: examples/echo/server.go
function echo (line 23) | func echo(w http.ResponseWriter, r *http.Request) {
function home (line 45) | func home(w http.ResponseWriter, r *http.Request) {
function main (line 49) | func main() {
FILE: examples/filewatch/main.go
constant writeWait (line 21) | writeWait = 10 * time.Second
constant pongWait (line 24) | pongWait = 60 * time.Second
constant pingPeriod (line 27) | pingPeriod = (pongWait * 9) / 10
constant filePeriod (line 30) | filePeriod = 10 * time.Second
function readFileIfModified (line 43) | func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
function reader (line 58) | func reader(ws *websocket.Conn) {
function writer (line 71) | func writer(ws *websocket.Conn, lastMod time.Time) {
function serveWs (line 112) | func serveWs(w http.ResponseWriter, r *http.Request) {
function serveHome (line 130) | func serveHome(w http.ResponseWriter, r *http.Request) {
function main (line 157) | func main() {
constant homeHTML (line 170) | homeHTML = `<!DOCTYPE html>
FILE: join.go
function JoinMessages (line 15) | func JoinMessages(c *Conn, term string) io.Reader {
type joinReader (line 19) | type joinReader struct
method Read (line 25) | func (r *joinReader) Read(p []byte) (int, error) {
FILE: join_test.go
function TestJoinMessages (line 14) | func TestJoinMessages(t *testing.T) {
FILE: json.go
function WriteJSON (line 15) | func WriteJSON(c *Conn, v interface{}) error {
method WriteJSON (line 23) | func (c *Conn) WriteJSON(v interface{}) error {
function ReadJSON (line 40) | func ReadJSON(c *Conn, v interface{}) error {
method ReadJSON (line 49) | func (c *Conn) ReadJSON(v interface{}) error {
FILE: json_test.go
function TestJSON (line 15) | func TestJSON(t *testing.T) {
function TestPartialJSONRead (line 40) | func TestPartialJSONRead(t *testing.T) {
function TestDeprecatedJSON (line 93) | func TestDeprecatedJSON(t *testing.T) {
FILE: mask.go
constant wordSize (line 12) | wordSize = int(unsafe.Sizeof(uintptr(0)))
function maskBytes (line 14) | func maskBytes(key [4]byte, pos int, b []byte) int {
FILE: mask_safe.go
function maskBytes (line 10) | func maskBytes(key [4]byte, pos int, b []byte) int {
FILE: mask_test.go
function maskBytesByByte (line 14) | func maskBytesByByte(key [4]byte, pos int, b []byte) int {
function notzero (line 22) | func notzero(b []byte) int {
function TestMaskBytes (line 31) | func TestMaskBytes(t *testing.T) {
function BenchmarkMaskBytes (line 47) | func BenchmarkMaskBytes(b *testing.B) {
FILE: prepared.go
type PreparedMessage (line 19) | type PreparedMessage struct
method frame (line 62) | func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
type prepareKey (line 27) | type prepareKey struct
type preparedFrame (line 34) | type preparedFrame struct
function NewPreparedMessage (line 43) | func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage,...
type prepareConn (line 96) | type prepareConn struct
method Write (line 101) | func (pc *prepareConn) Write(p []byte) (int, error) { return pc...
method SetWriteDeadline (line 102) | func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
FILE: prepared_test.go
function TestPreparedMessage (line 35) | func TestPreparedMessage(t *testing.T) {
FILE: proxy.go
type netDialerFunc (line 21) | type netDialerFunc
method Dial (line 23) | func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
method DialContext (line 27) | func (fn netDialerFunc) DialContext(ctx context.Context, network, addr...
function proxyFromURL (line 31) | func proxyFromURL(proxyURL *url.URL, forwardDial netDialerFunc) (netDial...
type httpProxyDialer (line 47) | type httpProxyDialer struct
method DialContext (line 52) | func (hpd *httpProxyDialer) DialContext(ctx context.Context, network s...
FILE: server.go
type HandshakeError (line 17) | type HandshakeError struct
method Error (line 21) | func (e HandshakeError) Error() string { return e.message }
type Upgrader (line 27) | type Upgrader struct
method returnError (line 76) | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request,...
method selectSubprotocol (line 100) | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader h...
method Upgrade (line 124) | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, res...
function checkSameOrigin (line 88) | func checkSameOrigin(r *http.Request) bool {
function Upgrade (line 316) | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http...
function Subprotocols (line 330) | func Subprotocols(r *http.Request) []string {
function IsWebSocketUpgrade (line 344) | func IsWebSocketUpgrade(r *http.Request) bool {
type brNetConn (line 349) | type brNetConn struct
method Read (line 354) | func (b *brNetConn) Read(p []byte) (n int, err error) {
method NetConn (line 370) | func (b *brNetConn) NetConn() net.Conn {
FILE: server_test.go
function TestSubprotocols (line 31) | func TestSubprotocols(t *testing.T) {
function TestIsWebSocketUpgrade (line 50) | func TestIsWebSocketUpgrade(t *testing.T) {
function TestSubProtocolSelection (line 59) | func TestSubProtocolSelection(t *testing.T) {
function TestCheckSameOrigin (line 98) | func TestCheckSameOrigin(t *testing.T) {
type reuseTestResponseWriter (line 107) | type reuseTestResponseWriter struct
method Hijack (line 112) | func (resp *reuseTestResponseWriter) Hijack() (net.Conn, *bufio.ReadWr...
function xTestBufioReuse (line 124) | func xTestBufioReuse(t *testing.T) {
function TestHijack_NotSupported (line 153) | func TestHijack_NotSupported(t *testing.T) {
FILE: util.go
function computeAcceptKey (line 19) | func computeAcceptKey(challengeKey string) string {
function generateChallengeKey (line 26) | func generateChallengeKey() (string, error) {
function skipSpace (line 117) | func skipSpace(s string) (rest string) {
function nextToken (line 129) | func nextToken(s string) (token, rest string) {
function nextTokenOrQuoted (line 141) | func nextTokenOrQuoted(s string) (value string, rest string) {
function equalASCIIFold (line 178) | func equalASCIIFold(s, t string) bool {
function tokenListContainsValue (line 202) | func tokenListContainsValue(header http.Header, name string, value strin...
function parseExtensions (line 228) | func parseExtensions(header http.Header) []map[string]string {
function isValidChallengeKey (line 286) | func isValidChallengeKey(s string) bool {
FILE: util_test.go
function TestEqualASCIIFold (line 23) | func TestEqualASCIIFold(t *testing.T) {
function TestTokenListContainsValue (line 46) | func TestTokenListContainsValue(t *testing.T) {
function TestIsValidChallengeKey (line 66) | func TestIsValidChallengeKey(t *testing.T) {
function TestParseExtensions (line 107) | func TestParseExtensions(t *testing.T) {
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (264K chars).
[
{
"path": ".circleci/config.yml",
"chars": 1821,
"preview": "version: 2.1\n\njobs:\n \"test\":\n parameters:\n version:\n type: string\n default: \"latest\"\n golint"
},
{
"path": ".github/release-drafter.yml",
"chars": 125,
"preview": "# Config for https://github.com/apps/release-drafter\ntemplate: |\n \n <summary of changes here>\n \n ## CHANGELOG\n $CHA"
},
{
"path": ".gitignore",
"chars": 266,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": "AUTHORS",
"chars": 234,
"preview": "# This is the official list of Gorilla WebSocket authors for copyright\n# purposes.\n#\n# Please keep the list sorted.\n\nGar"
},
{
"path": "LICENSE",
"chars": 1312,
"preview": "Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.\n\nRedistribution and use in source and binary form"
},
{
"path": "README.md",
"chars": 1366,
"preview": "# Gorilla WebSocket\n\n[](https://godoc.org/github.com/"
},
{
"path": "client.go",
"chars": 16517,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "client_proxy_server_test.go",
"chars": 32069,
"preview": "// Copyright 2025 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "client_server_test.go",
"chars": 31350,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "client_test.go",
"chars": 1072,
"preview": "// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "compression.go",
"chars": 3319,
"preview": "// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "compression_test.go",
"chars": 1852,
"preview": "package websocket\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n)\n\ntype nopCloser struct{ io.Writer }\n\nfunc (nopCloser) Clo"
},
{
"path": "conn.go",
"chars": 33580,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "conn_broadcast_test.go",
"chars": 3054,
"preview": "// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "conn_test.go",
"chars": 19999,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "doc.go",
"chars": 9585,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "example_test.go",
"chars": 1309,
"preview": "// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/autobahn/README.md",
"chars": 511,
"preview": "# Test Server\n\nThis package contains a server for the [Autobahn WebSockets Test Suite](https://github.com/crossbario/aut"
},
{
"path": "examples/autobahn/config/fuzzingclient.json",
"chars": 632,
"preview": "{\n \"cases\": [\"*\"],\n \"exclude-cases\": [],\n \"exclude-agent-cases\": {},\n \"outdir\": \"/reports\",\n \"options\": {\"failByDro"
},
{
"path": "examples/autobahn/server.go",
"chars": 7750,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/chat/README.md",
"chars": 4704,
"preview": "# Chat Example\n\nThis application shows how to use the\n[websocket](https://github.com/gorilla/websocket) package to imple"
},
{
"path": "examples/chat/client.go",
"chars": 3425,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/chat/home.html",
"chars": 2244,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<title>Chat Example</title>\n<script type=\"text/javascript\">\nwindow.onload = func"
},
{
"path": "examples/chat/hub.go",
"chars": 1148,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/chat/main.go",
"chars": 893,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/command/README.md",
"chars": 616,
"preview": "# Command example\n\nThis example connects a websocket connection to stdin and stdout of a command.\nReceived messages are "
},
{
"path": "examples/command/home.html",
"chars": 2263,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<title>Command Example</title>\n<script type=\"text/javascript\">\nwindow.onload = f"
},
{
"path": "examples/command/main.go",
"chars": 4176,
"preview": "// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/echo/README.md",
"chars": 446,
"preview": "# Client and server example\n\nThis example shows a simple client and server.\n\nThe server echoes messages sent to it. The "
},
{
"path": "examples/echo/client.go",
"chars": 1689,
"preview": "// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/echo/server.go",
"chars": 3089,
"preview": "// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "examples/filewatch/README.md",
"chars": 374,
"preview": "# File Watch example.\n\nThis example sends a file to the browser client for display whenever the file is modified.\n\n $"
},
{
"path": "examples/filewatch/main.go",
"chars": 4358,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "go.mod",
"chars": 135,
"preview": "module github.com/gorilla/websocket\n\ngo 1.20\n\nretract (\n v1.5.2 // tag accidentally overwritten\n)\n\nrequire golang.org"
},
{
"path": "go.sum",
"chars": 153,
"preview": "golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=\ngolang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh"
},
{
"path": "join.go",
"chars": 913,
"preview": "// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "join_test.go",
"chars": 1108,
"preview": "// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "json.go",
"chars": 1521,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "json_test.go",
"chars": 2097,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "mask.go",
"chars": 1176,
"preview": "// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of\n// this source code is governed by a BSD-s"
},
{
"path": "mask_safe.go",
"chars": 360,
"preview": "// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of\n// this source code is governed by a BSD-s"
},
{
"path": "mask_test.go",
"chars": 1593,
"preview": "// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of\n// this source code is governed by a BSD-s"
},
{
"path": "prepared.go",
"chars": 2969,
"preview": "// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "prepared_test.go",
"chars": 2082,
"preview": "// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "proxy.go",
"chars": 2960,
"preview": "// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "server.go",
"chars": 12369,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "server_test.go",
"chars": 4986,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "util.go",
"chars": 5894,
"preview": "// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
},
{
"path": "util_test.go",
"chars": 3390,
"preview": "// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved.\n// Use of this source code is governed by a BSD-st"
}
]
About this extraction
This page contains the full source code of the gorilla/websocket GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (235.2 KB), approximately 70.3k tokens, and a symbol index with 373 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.