Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/bench.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ jobs:
go-version: "stable"

- uses: actions/checkout@v4
with:
# need to fetch all history to check out arbitrary commits for
# baseline benchmarks (if neceesssary)
fetch-depth: 0

- name: restore previous baseline results
id: baseline-restore
Expand Down
2 changes: 1 addition & 1 deletion examples/benchserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,5 @@ func getListenAddr() string {
if port := os.Getenv("PORT"); port != "" {
return ":" + port
}
return "127.0.0.1:8080"
return "127.0.0.1:9001"
}
5 changes: 5 additions & 0 deletions websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,8 @@ func (ws *Websocket) resetWriteDeadline() {
panic(fmt.Sprintf("websocket: failed to set write deadline: %s", err))
}
}

// ClientKey returns the client key for a connection.
func (ws *Websocket) ClientKey() ClientKey {
return ws.clientKey
}
105 changes: 69 additions & 36 deletions websocket_autobahn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package websocket_test
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
Expand All @@ -34,6 +36,7 @@ var defaultExcludedTestCases = []string{
func TestWebSocketServer(t *testing.T) {
t.Parallel()

// TODO: document AUTOBAHN_* env vars that control test functionality
if os.Getenv("AUTOBAHN_TESTS") == "" {
t.Skipf("set AUTOBAHN_TESTS=1 to run autobahn integration tests")
}
Expand All @@ -48,32 +51,36 @@ func TestWebSocketServer(t *testing.T) {
hooks = newTestHooks(t)
}

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ws, err := websocket.Accept(w, r, websocket.Options{
Hooks: hooks,
// long ReadTimeout because some autobahn test cases (e.g. 5.19)
// sleep up to 1 second between frames
ReadTimeout: 5000 * time.Millisecond,
WriteTimeout: 500 * time.Millisecond,
// some autobahn test cases send large frames, so we need to
// support large fragments and messages
MaxFragmentSize: 1024 * 1024 * 16,
MaxMessageSize: 1024 * 1024 * 16,
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ws.Serve(r.Context(), websocket.EchoHandler)
}))
defer srv.Close()
targetURL := os.Getenv("AUTOBAHN_TARGET")
if targetURL == "" {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ws, err := websocket.Accept(w, r, websocket.Options{
Hooks: hooks,
// long ReadTimeout because some autobahn test cases (e.g. 5.19)
// sleep up to 1 second between frames
ReadTimeout: 5000 * time.Millisecond,
WriteTimeout: 500 * time.Millisecond,
// some autobahn test cases send large frames, so we need to
// support large fragments and messages
MaxFragmentSize: 1024 * 1024 * 16,
MaxMessageSize: 1024 * 1024 * 16,
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ws.Serve(r.Context(), websocket.EchoHandler)
}))
defer srv.Close()
targetURL = srv.URL
} else {
t.Logf("running autobahn against external target: %s", targetURL)
}

testDir := newTestDir(t)
t.Logf("test dir: %s", testDir)

targetURL := newAutobahnTargetURL(t, srv)
targetURL = newAutobahnTargetURL(t, targetURL)
t.Logf("target url: %s", targetURL)

autobahnCfg := map[string]any{
"servers": []map[string]string{
{
Expand Down Expand Up @@ -131,30 +138,56 @@ func TestWebSocketServer(t *testing.T) {
}
}

// newAutobahnTargetURL returns the URL that the autobahn test suite should use
// to connect to the given httptest server.
// newAutobahnTargetURL returns the URL that the autobahn test client should
// use to connect to the given target URL, which may be an ephemeral httptest
// server URL listening on localhost and a random port or some external URL
// provided by the AUTOBAHN_TARGET env var.
//
// Note that the autobahn client will be running inside a container using
// --net=host, so localhost inside the container *should* map to localhost
// outside the container.
//
// On Macs, the docker engine is running inside an implicit VM, so even with
// --net=host, we need to use the special hostname to escape the VM.
// On macOS, however, we must use a special "host.docker.internal" hostname for
// localhost addrs, because otherwise localhost inside the container will
// resolve to localhost on the implicit guest VM where docker is running rather
// than localhost on the actual macOS host machine.
//
// See the Docker Desktop docs[1] for more information. This same special
// hostname seems to work across Docker Desktop for Mac, OrbStack, and Colima.
//
// [1]: https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host
func newAutobahnTargetURL(t *testing.T, srv *httptest.Server) string {
func newAutobahnTargetURL(t *testing.T, targetURL string) string {
t.Helper()
u, err := url.Parse(srv.URL)
if matched, _ := regexp.MatchString("^https?://", targetURL); !matched {
targetURL = "http://" + targetURL
}
u, err := url.Parse(targetURL)
assert.NilError(t, err)

var host string
switch runtime.GOOS {
case "darwin":
host = "host.docker.internal"
default:
host = "127.0.0.1"
u.Scheme = "ws"
if runtime.GOOS == "darwin" && isLocalhost(u.Hostname()) {
host := "host.docker.internal"
_, port, _ := net.SplitHostPort(u.Host)
if port != "" {
host = net.JoinHostPort(host, port)
}
u.Host = host
}
return u.String()
}

return fmt.Sprintf("ws://%s:%s/websocket/echo", host, u.Port())
func isLocalhost(ipAddr string) bool {
ipAddr = strings.ToLower(ipAddr)
for _, addr := range []string{
"localhost",
"127.0.0.1",
"::1",
"0:0:0:0:0:0:0:1",
} {
if ipAddr == addr {
return true
}
}
return false
}

func runCmd(t *testing.T, cmd *exec.Cmd) {
Expand Down
33 changes: 17 additions & 16 deletions websocket_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ func TestDefaults(t *testing.T) {
t.Parallel()

var (
wrapedConn net.Conn
key = ClientKey("test-client-key")
opts = Options{}
conn = New(wrapedConn, key, opts)
conn net.Conn
key = ClientKey("test-client-key")
opts = Options{}
ws = New(conn, key, opts)
)

assert.Equal(t, conn.maxFragmentSize, DefaultMaxFragmentSize, "incorrect max fragment size")
assert.Equal(t, conn.maxMessageSize, DefaultMaxMessageSize, "incorrect max message size")
assert.Equal(t, conn.readTimeout, 0, "incorrect read timeout")
assert.Equal(t, conn.writeTimeout, 0, "incorrect write timeout")
assert.Equal(t, conn.server, true, "incorrect server value")
assert.Equal(t, conn.hooks.OnClose != nil, true, "OnClose hook is nil")
assert.Equal(t, conn.hooks.OnReadError != nil, true, "OnReadError hook is nil")
assert.Equal(t, conn.hooks.OnReadFrame != nil, true, "OnReadFrame hook is nil")
assert.Equal(t, conn.hooks.OnReadMessage != nil, true, "OnReadMessage hook is nil")
assert.Equal(t, conn.hooks.OnWriteError != nil, true, "OnWriteError hook is nil")
assert.Equal(t, conn.hooks.OnWriteFrame != nil, true, "OnWriteFrame hook is nil")
assert.Equal(t, conn.hooks.OnWriteMessage != nil, true, "OnWriteMessage hook is nil")
assert.Equal(t, ws.ClientKey(), key, "incorrect client key")
assert.Equal(t, ws.maxFragmentSize, DefaultMaxFragmentSize, "incorrect max fragment size")
assert.Equal(t, ws.maxMessageSize, DefaultMaxMessageSize, "incorrect max message size")
assert.Equal(t, ws.readTimeout, 0, "incorrect read timeout")
assert.Equal(t, ws.writeTimeout, 0, "incorrect write timeout")
assert.Equal(t, ws.server, true, "incorrect server value")
assert.Equal(t, ws.hooks.OnClose != nil, true, "OnClose hook is nil")
assert.Equal(t, ws.hooks.OnReadError != nil, true, "OnReadError hook is nil")
assert.Equal(t, ws.hooks.OnReadFrame != nil, true, "OnReadFrame hook is nil")
assert.Equal(t, ws.hooks.OnReadMessage != nil, true, "OnReadMessage hook is nil")
assert.Equal(t, ws.hooks.OnWriteError != nil, true, "OnWriteError hook is nil")
assert.Equal(t, ws.hooks.OnWriteFrame != nil, true, "OnWriteFrame hook is nil")
assert.Equal(t, ws.hooks.OnWriteMessage != nil, true, "OnWriteMessage hook is nil")
}
Loading