Skip to content

Commit 4918725

Browse files
authored
Merge pull request #143 from nhooyr/refactor
Restructure library
2 parents 7b4bd30 + 5c656ed commit 4918725

23 files changed

+1547
-1651
lines changed

websocket.go renamed to conn.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (c *Conn) close(err error) {
138138
// closeErr.
139139
c.closer.Close()
140140

141-
// See comment in dial.go
141+
// See comment on bufioReaderPool in handshake.go
142142
if c.client {
143143
// By acquiring the locks, we ensure no goroutine will touch the bufio reader or writer
144144
// and we can safely return them.

websocket_test.go renamed to conn_test.go

+369
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import (
1212
"io"
1313
"io/ioutil"
1414
"math/rand"
15+
"net"
1516
"net/http"
1617
"net/http/cookiejar"
1718
"net/http/httptest"
1819
"net/url"
20+
"os"
21+
"os/exec"
1922
"reflect"
2023
"strconv"
2124
"strings"
@@ -1962,3 +1965,369 @@ func assertReadMessage(ctx context.Context, c *websocket.Conn, typ websocket.Mes
19621965
}
19631966
return assertEqualf(p, actP, "unexpected frame %v payload", actTyp)
19641967
}
1968+
1969+
func BenchmarkConn(b *testing.B) {
1970+
sizes := []int{
1971+
2,
1972+
16,
1973+
32,
1974+
512,
1975+
4096,
1976+
16384,
1977+
}
1978+
1979+
b.Run("write", func(b *testing.B) {
1980+
for _, size := range sizes {
1981+
b.Run(strconv.Itoa(size), func(b *testing.B) {
1982+
b.Run("stream", func(b *testing.B) {
1983+
benchConn(b, false, true, size)
1984+
})
1985+
b.Run("buffer", func(b *testing.B) {
1986+
benchConn(b, false, false, size)
1987+
})
1988+
})
1989+
}
1990+
})
1991+
1992+
b.Run("echo", func(b *testing.B) {
1993+
for _, size := range sizes {
1994+
b.Run(strconv.Itoa(size), func(b *testing.B) {
1995+
benchConn(b, true, true, size)
1996+
})
1997+
}
1998+
})
1999+
}
2000+
2001+
func benchConn(b *testing.B, echo, stream bool, size int) {
2002+
s, closeFn := testServer(b, func(w http.ResponseWriter, r *http.Request) error {
2003+
c, err := websocket.Accept(w, r, nil)
2004+
if err != nil {
2005+
return err
2006+
}
2007+
if echo {
2008+
wsecho.Loop(r.Context(), c)
2009+
} else {
2010+
discardLoop(r.Context(), c)
2011+
}
2012+
return nil
2013+
}, false)
2014+
defer closeFn()
2015+
2016+
wsURL := strings.Replace(s.URL, "http", "ws", 1)
2017+
2018+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
2019+
defer cancel()
2020+
2021+
c, _, err := websocket.Dial(ctx, wsURL, nil)
2022+
if err != nil {
2023+
b.Fatal(err)
2024+
}
2025+
defer c.Close(websocket.StatusInternalError, "")
2026+
2027+
msg := []byte(strings.Repeat("2", size))
2028+
readBuf := make([]byte, len(msg))
2029+
b.SetBytes(int64(len(msg)))
2030+
b.ReportAllocs()
2031+
b.ResetTimer()
2032+
for i := 0; i < b.N; i++ {
2033+
if stream {
2034+
w, err := c.Writer(ctx, websocket.MessageText)
2035+
if err != nil {
2036+
b.Fatal(err)
2037+
}
2038+
2039+
_, err = w.Write(msg)
2040+
if err != nil {
2041+
b.Fatal(err)
2042+
}
2043+
2044+
err = w.Close()
2045+
if err != nil {
2046+
b.Fatal(err)
2047+
}
2048+
} else {
2049+
err = c.Write(ctx, websocket.MessageText, msg)
2050+
if err != nil {
2051+
b.Fatal(err)
2052+
}
2053+
}
2054+
2055+
if echo {
2056+
_, r, err := c.Reader(ctx)
2057+
if err != nil {
2058+
b.Fatal(err)
2059+
}
2060+
2061+
_, err = io.ReadFull(r, readBuf)
2062+
if err != nil {
2063+
b.Fatal(err)
2064+
}
2065+
}
2066+
}
2067+
b.StopTimer()
2068+
2069+
c.Close(websocket.StatusNormalClosure, "")
2070+
}
2071+
2072+
func discardLoop(ctx context.Context, c *websocket.Conn) {
2073+
defer c.Close(websocket.StatusInternalError, "")
2074+
2075+
ctx, cancel := context.WithTimeout(ctx, time.Minute)
2076+
defer cancel()
2077+
2078+
b := make([]byte, 32768)
2079+
echo := func() error {
2080+
_, r, err := c.Reader(ctx)
2081+
if err != nil {
2082+
return err
2083+
}
2084+
2085+
_, err = io.CopyBuffer(ioutil.Discard, r, b)
2086+
if err != nil {
2087+
return err
2088+
}
2089+
return nil
2090+
}
2091+
2092+
for {
2093+
err := echo()
2094+
if err != nil {
2095+
return
2096+
}
2097+
}
2098+
}
2099+
2100+
func TestAutobahnPython(t *testing.T) {
2101+
// This test contains the old autobahn test suite tests that use the
2102+
// python binary. The approach is clunky and slow so new tests
2103+
// have been written in pure Go in websocket_test.go.
2104+
// These have been kept for correctness purposes and are occasionally ran.
2105+
if os.Getenv("AUTOBAHN_PYTHON") == "" {
2106+
t.Skip("Set $AUTOBAHN_PYTHON to run tests against the python autobahn test suite")
2107+
}
2108+
2109+
t.Run("server", testServerAutobahnPython)
2110+
t.Run("client", testClientAutobahnPython)
2111+
}
2112+
2113+
// https://github.com/crossbario/autobahn-python/tree/master/wstest
2114+
func testServerAutobahnPython(t *testing.T) {
2115+
t.Parallel()
2116+
2117+
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2118+
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
2119+
Subprotocols: []string{"echo"},
2120+
})
2121+
if err != nil {
2122+
t.Logf("server handshake failed: %+v", err)
2123+
return
2124+
}
2125+
wsecho.Loop(r.Context(), c)
2126+
}))
2127+
defer s.Close()
2128+
2129+
spec := map[string]interface{}{
2130+
"outdir": "ci/out/wstestServerReports",
2131+
"servers": []interface{}{
2132+
map[string]interface{}{
2133+
"agent": "main",
2134+
"url": strings.Replace(s.URL, "http", "ws", 1),
2135+
},
2136+
},
2137+
"cases": []string{"*"},
2138+
// We skip the UTF-8 handling tests as there isn't any reason to reject invalid UTF-8, just
2139+
// more performance overhead. 7.5.1 is the same.
2140+
// 12.* and 13.* as we do not support compression.
2141+
"exclude-cases": []string{"6.*", "7.5.1", "12.*", "13.*"},
2142+
}
2143+
specFile, err := ioutil.TempFile("", "websocketFuzzingClient.json")
2144+
if err != nil {
2145+
t.Fatalf("failed to create temp file for fuzzingclient.json: %v", err)
2146+
}
2147+
defer specFile.Close()
2148+
2149+
e := json.NewEncoder(specFile)
2150+
e.SetIndent("", "\t")
2151+
err = e.Encode(spec)
2152+
if err != nil {
2153+
t.Fatalf("failed to write spec: %v", err)
2154+
}
2155+
2156+
err = specFile.Close()
2157+
if err != nil {
2158+
t.Fatalf("failed to close file: %v", err)
2159+
}
2160+
2161+
ctx := context.Background()
2162+
ctx, cancel := context.WithTimeout(ctx, time.Minute*10)
2163+
defer cancel()
2164+
2165+
args := []string{"--mode", "fuzzingclient", "--spec", specFile.Name()}
2166+
wstest := exec.CommandContext(ctx, "wstest", args...)
2167+
out, err := wstest.CombinedOutput()
2168+
if err != nil {
2169+
t.Fatalf("failed to run wstest: %v\nout:\n%s", err, out)
2170+
}
2171+
2172+
checkWSTestIndex(t, "./ci/out/wstestServerReports/index.json")
2173+
}
2174+
2175+
func unusedListenAddr() (string, error) {
2176+
l, err := net.Listen("tcp", "localhost:0")
2177+
if err != nil {
2178+
return "", err
2179+
}
2180+
l.Close()
2181+
return l.Addr().String(), nil
2182+
}
2183+
2184+
// https://github.com/crossbario/autobahn-python/blob/master/wstest/testee_client_aio.py
2185+
func testClientAutobahnPython(t *testing.T) {
2186+
t.Parallel()
2187+
2188+
if os.Getenv("AUTOBAHN_PYTHON") == "" {
2189+
t.Skip("Set $AUTOBAHN_PYTHON to test against the python autobahn test suite")
2190+
}
2191+
2192+
serverAddr, err := unusedListenAddr()
2193+
if err != nil {
2194+
t.Fatalf("failed to get unused listen addr for wstest: %v", err)
2195+
}
2196+
2197+
wsServerURL := "ws://" + serverAddr
2198+
2199+
spec := map[string]interface{}{
2200+
"url": wsServerURL,
2201+
"outdir": "ci/out/wstestClientReports",
2202+
"cases": []string{"*"},
2203+
// See TestAutobahnServer for the reasons why we exclude these.
2204+
"exclude-cases": []string{"6.*", "7.5.1", "12.*", "13.*"},
2205+
}
2206+
specFile, err := ioutil.TempFile("", "websocketFuzzingServer.json")
2207+
if err != nil {
2208+
t.Fatalf("failed to create temp file for fuzzingserver.json: %v", err)
2209+
}
2210+
defer specFile.Close()
2211+
2212+
e := json.NewEncoder(specFile)
2213+
e.SetIndent("", "\t")
2214+
err = e.Encode(spec)
2215+
if err != nil {
2216+
t.Fatalf("failed to write spec: %v", err)
2217+
}
2218+
2219+
err = specFile.Close()
2220+
if err != nil {
2221+
t.Fatalf("failed to close file: %v", err)
2222+
}
2223+
2224+
ctx := context.Background()
2225+
ctx, cancel := context.WithTimeout(ctx, time.Minute*10)
2226+
defer cancel()
2227+
2228+
args := []string{"--mode", "fuzzingserver", "--spec", specFile.Name(),
2229+
// Disables some server that runs as part of fuzzingserver mode.
2230+
// See https://github.com/crossbario/autobahn-testsuite/blob/058db3a36b7c3a1edf68c282307c6b899ca4857f/autobahntestsuite/autobahntestsuite/wstest.py#L124
2231+
"--webport=0",
2232+
}
2233+
wstest := exec.CommandContext(ctx, "wstest", args...)
2234+
err = wstest.Start()
2235+
if err != nil {
2236+
t.Fatal(err)
2237+
}
2238+
defer func() {
2239+
err := wstest.Process.Kill()
2240+
if err != nil {
2241+
t.Error(err)
2242+
}
2243+
}()
2244+
2245+
// Let it come up.
2246+
time.Sleep(time.Second * 5)
2247+
2248+
var cases int
2249+
func() {
2250+
c, _, err := websocket.Dial(ctx, wsServerURL+"/getCaseCount", nil)
2251+
if err != nil {
2252+
t.Fatal(err)
2253+
}
2254+
defer c.Close(websocket.StatusInternalError, "")
2255+
2256+
_, r, err := c.Reader(ctx)
2257+
if err != nil {
2258+
t.Fatal(err)
2259+
}
2260+
b, err := ioutil.ReadAll(r)
2261+
if err != nil {
2262+
t.Fatal(err)
2263+
}
2264+
cases, err = strconv.Atoi(string(b))
2265+
if err != nil {
2266+
t.Fatal(err)
2267+
}
2268+
2269+
c.Close(websocket.StatusNormalClosure, "")
2270+
}()
2271+
2272+
for i := 1; i <= cases; i++ {
2273+
func() {
2274+
ctx, cancel := context.WithTimeout(ctx, time.Second*45)
2275+
defer cancel()
2276+
2277+
c, _, err := websocket.Dial(ctx, fmt.Sprintf(wsServerURL+"/runCase?case=%v&agent=main", i), nil)
2278+
if err != nil {
2279+
t.Fatal(err)
2280+
}
2281+
wsecho.Loop(ctx, c)
2282+
}()
2283+
}
2284+
2285+
c, _, err := websocket.Dial(ctx, fmt.Sprintf(wsServerURL+"/updateReports?agent=main"), nil)
2286+
if err != nil {
2287+
t.Fatal(err)
2288+
}
2289+
c.Close(websocket.StatusNormalClosure, "")
2290+
2291+
checkWSTestIndex(t, "./ci/out/wstestClientReports/index.json")
2292+
}
2293+
2294+
func checkWSTestIndex(t *testing.T, path string) {
2295+
wstestOut, err := ioutil.ReadFile(path)
2296+
if err != nil {
2297+
t.Fatalf("failed to read index.json: %v", err)
2298+
}
2299+
2300+
var indexJSON map[string]map[string]struct {
2301+
Behavior string `json:"behavior"`
2302+
BehaviorClose string `json:"behaviorClose"`
2303+
}
2304+
err = json.Unmarshal(wstestOut, &indexJSON)
2305+
if err != nil {
2306+
t.Fatalf("failed to unmarshal index.json: %v", err)
2307+
}
2308+
2309+
var failed bool
2310+
for _, tests := range indexJSON {
2311+
for test, result := range tests {
2312+
switch result.Behavior {
2313+
case "OK", "NON-STRICT", "INFORMATIONAL":
2314+
default:
2315+
failed = true
2316+
t.Errorf("test %v failed", test)
2317+
}
2318+
switch result.BehaviorClose {
2319+
case "OK", "INFORMATIONAL":
2320+
default:
2321+
failed = true
2322+
t.Errorf("bad close behaviour for test %v", test)
2323+
}
2324+
}
2325+
}
2326+
2327+
if failed {
2328+
path = strings.Replace(path, ".json", ".html", 1)
2329+
if os.Getenv("CI") == "" {
2330+
t.Errorf("wstest found failure, see %q (output as an artifact in CI)", path)
2331+
}
2332+
}
2333+
}

0 commit comments

Comments
 (0)