// Copyright 2009 The Go 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" "fmt" "io" "log" "net" "net/http" "net/http/httptest" "net/url" "reflect" "runtime" "strings" "sync" "testing" "time" ) var serverAddr string var once sync.Once func echoServer(ws *Conn) { defer ws.Close() io.Copy(ws, ws) } type Count struct { S string N int } func countServer(ws *Conn) { defer ws.Close() for { var count Count err := JSON.Receive(ws, &count) if err != nil { return } count.N++ count.S = strings.Repeat(count.S, count.N) err = JSON.Send(ws, count) if err != nil { return } } } type testCtrlAndDataHandler struct { hybiFrameHandler } func (h *testCtrlAndDataHandler) WritePing(b []byte) (int, error) { h.hybiFrameHandler.conn.wio.Lock() defer h.hybiFrameHandler.conn.wio.Unlock() w, err := h.hybiFrameHandler.conn.frameWriterFactory.NewFrameWriter(PingFrame) if err != nil { return 0, err } n, err := w.Write(b) w.Close() return n, err } func ctrlAndDataServer(ws *Conn) { defer ws.Close() h := &testCtrlAndDataHandler{hybiFrameHandler: hybiFrameHandler{conn: ws}} ws.frameHandler = h go func() { for i := 0; ; i++ { var b []byte if i%2 != 0 { // with or without payload b = []byte(fmt.Sprintf("#%d-CONTROL-FRAME-FROM-SERVER", i)) } if _, err := h.WritePing(b); err != nil { break } if _, err := h.WritePong(b); err != nil { // unsolicited pong break } time.Sleep(10 * time.Millisecond) } }() b := make([]byte, 128) for { n, err := ws.Read(b) if err != nil { break } if _, err := ws.Write(b[:n]); err != nil { break } } } func subProtocolHandshake(config *Config, req *http.Request) error { for _, proto := range config.Protocol { if proto == "chat" { config.Protocol = []string{proto} return nil } } return ErrBadWebSocketProtocol } func subProtoServer(ws *Conn) { for _, proto := range ws.Config().Protocol { io.WriteString(ws, proto) } } func startServer() { http.Handle("/echo", Handler(echoServer)) http.Handle("/count", Handler(countServer)) http.Handle("/ctrldata", Handler(ctrlAndDataServer)) subproto := Server{ Handshake: subProtocolHandshake, Handler: Handler(subProtoServer), } http.Handle("/subproto", subproto) server := httptest.NewServer(nil) serverAddr = server.Listener.Addr().String() log.Print("Test WebSocket server listening on ", serverAddr) } func newConfig(t *testing.T, path string) *Config { config, _ := NewConfig(fmt.Sprintf("ws://%s%s", serverAddr, path), "http://localhost") return config } func TestEcho(t *testing.T) { once.Do(startServer) // websocket.Dial() client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } conn, err := NewClient(newConfig(t, "/echo"), client) if err != nil { t.Errorf("WebSocket handshake error: %v", err) return } msg := []byte("hello, world\n") if _, err := conn.Write(msg); err != nil { t.Errorf("Write: %v", err) } var actual_msg = make([]byte, 512) n, err := conn.Read(actual_msg) if err != nil { t.Errorf("Read: %v", err) } actual_msg = actual_msg[0:n] if !bytes.Equal(msg, actual_msg) { t.Errorf("Echo: expected %q got %q", msg, actual_msg) } conn.Close() } func TestAddr(t *testing.T) { once.Do(startServer) // websocket.Dial() client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } conn, err := NewClient(newConfig(t, "/echo"), client) if err != nil { t.Errorf("WebSocket handshake error: %v", err) return } ra := conn.RemoteAddr().String() if !strings.HasPrefix(ra, "ws://") || !strings.HasSuffix(ra, "/echo") { t.Errorf("Bad remote addr: %v", ra) } la := conn.LocalAddr().String() if !strings.HasPrefix(la, "http://") { t.Errorf("Bad local addr: %v", la) } conn.Close() } func TestCount(t *testing.T) { once.Do(startServer) // websocket.Dial() client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } conn, err := NewClient(newConfig(t, "/count"), client) if err != nil { t.Errorf("WebSocket handshake error: %v", err) return } var count Count count.S = "hello" if err := JSON.Send(conn, count); err != nil { t.Errorf("Write: %v", err) } if err := JSON.Receive(conn, &count); err != nil { t.Errorf("Read: %v", err) } if count.N != 1 { t.Errorf("count: expected %d got %d", 1, count.N) } if count.S != "hello" { t.Errorf("count: expected %q got %q", "hello", count.S) } if err := JSON.Send(conn, count); err != nil { t.Errorf("Write: %v", err) } if err := JSON.Receive(conn, &count); err != nil { t.Errorf("Read: %v", err) } if count.N != 2 { t.Errorf("count: expected %d got %d", 2, count.N) } if count.S != "hellohello" { t.Errorf("count: expected %q got %q", "hellohello", count.S) } conn.Close() } func TestWithQuery(t *testing.T) { once.Do(startServer) client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } config := newConfig(t, "/echo") config.Location, err = url.ParseRequestURI(fmt.Sprintf("ws://%s/echo?q=v", serverAddr)) if err != nil { t.Fatal("location url", err) } ws, err := NewClient(config, client) if err != nil { t.Errorf("WebSocket handshake: %v", err) return } ws.Close() } func testWithProtocol(t *testing.T, subproto []string) (string, error) { once.Do(startServer) client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } config := newConfig(t, "/subproto") config.Protocol = subproto ws, err := NewClient(config, client) if err != nil { return "", err } msg := make([]byte, 16) n, err := ws.Read(msg) if err != nil { return "", err } ws.Close() return string(msg[:n]), nil } func TestWithProtocol(t *testing.T) { proto, err := testWithProtocol(t, []string{"chat"}) if err != nil { t.Errorf("SubProto: unexpected error: %v", err) } if proto != "chat" { t.Errorf("SubProto: expected %q, got %q", "chat", proto) } } func TestWithTwoProtocol(t *testing.T) { proto, err := testWithProtocol(t, []string{"test", "chat"}) if err != nil { t.Errorf("SubProto: unexpected error: %v", err) } if proto != "chat" { t.Errorf("SubProto: expected %q, got %q", "chat", proto) } } func TestWithBadProtocol(t *testing.T) { _, err := testWithProtocol(t, []string{"test"}) if err != ErrBadStatus { t.Errorf("SubProto: expected %v, got %v", ErrBadStatus, err) } } func TestHTTP(t *testing.T) { once.Do(startServer) // If the client did not send a handshake that matches the protocol // specification, the server MUST return an HTTP response with an // appropriate error code (such as 400 Bad Request) resp, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr)) if err != nil { t.Errorf("Get: error %#v", err) return } if resp == nil { t.Error("Get: resp is null") return } if resp.StatusCode != http.StatusBadRequest { t.Errorf("Get: expected %q got %q", http.StatusBadRequest, resp.StatusCode) } } func TestTrailingSpaces(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=955 // The last runs of this create keys with trailing spaces that should not be // generated by the client. once.Do(startServer) config := newConfig(t, "/echo") for i := 0; i < 30; i++ { // body ws, err := DialConfig(config) if err != nil { t.Errorf("Dial #%d failed: %v", i, err) break } ws.Close() } } func TestDialConfigBadVersion(t *testing.T) { once.Do(startServer) config := newConfig(t, "/echo") config.Version = 1234 _, err := DialConfig(config) if dialerr, ok := err.(*DialError); ok { if dialerr.Err != ErrBadProtocolVersion { t.Errorf("dial expected err %q but got %q", ErrBadProtocolVersion, dialerr.Err) } } } func TestSmallBuffer(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=1145 // Read should be able to handle reading a fragment of a frame. once.Do(startServer) // websocket.Dial() client, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } conn, err := NewClient(newConfig(t, "/echo"), client) if err != nil { t.Errorf("WebSocket handshake error: %v", err) return } msg := []byte("hello, world\n") if _, err := conn.Write(msg); err != nil { t.Errorf("Write: %v", err) } var small_msg = make([]byte, 8) n, err := conn.Read(small_msg) if err != nil { t.Errorf("Read: %v", err) } if !bytes.Equal(msg[:len(small_msg)], small_msg) { t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg) } var second_msg = make([]byte, len(msg)) n, err = conn.Read(second_msg) if err != nil { t.Errorf("Read: %v", err) } second_msg = second_msg[0:n] if !bytes.Equal(msg[len(small_msg):], second_msg) { t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg) } conn.Close() } var parseAuthorityTests = []struct { in *url.URL out string }{ { &url.URL{ Scheme: "ws", Host: "www.google.com", }, "www.google.com:80", }, { &url.URL{ Scheme: "wss", Host: "www.google.com", }, "www.google.com:443", }, { &url.URL{ Scheme: "ws", Host: "www.google.com:80", }, "www.google.com:80", }, { &url.URL{ Scheme: "wss", Host: "www.google.com:443", }, "www.google.com:443", }, // some invalid ones for parseAuthority. parseAuthority doesn't // concern itself with the scheme unless it actually knows about it { &url.URL{ Scheme: "http", Host: "www.google.com", }, "www.google.com", }, { &url.URL{ Scheme: "http", Host: "www.google.com:80", }, "www.google.com:80", }, { &url.URL{ Scheme: "asdf", Host: "127.0.0.1", }, "127.0.0.1", }, { &url.URL{ Scheme: "asdf", Host: "www.google.com", }, "www.google.com", }, } func TestParseAuthority(t *testing.T) { for _, tt := range parseAuthorityTests { out := parseAuthority(tt.in) if out != tt.out { t.Errorf("got %v; want %v", out, tt.out) } } } type closerConn struct { net.Conn closed int // count of the number of times Close was called } func (c *closerConn) Close() error { c.closed++ return c.Conn.Close() } func TestClose(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("see golang.org/issue/11454") } once.Do(startServer) conn, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal("dialing", err) } cc := closerConn{Conn: conn} client, err := NewClient(newConfig(t, "/echo"), &cc) if err != nil { t.Fatalf("WebSocket handshake: %v", err) } // set the deadline to ten minutes ago, which will have expired by the time // client.Close sends the close status frame. conn.SetDeadline(time.Now().Add(-10 * time.Minute)) if err := client.Close(); err == nil { t.Errorf("ws.Close(): expected error, got %v", err) } if cc.closed < 1 { t.Fatalf("ws.Close(): expected underlying ws.rwc.Close to be called > 0 times, got: %v", cc.closed) } } var originTests = []struct { req *http.Request origin *url.URL }{ { req: &http.Request{ Header: http.Header{ "Origin": []string{"http://www.example.com"}, }, }, origin: &url.URL{ Scheme: "http", Host: "www.example.com", }, }, { req: &http.Request{}, }, } func TestOrigin(t *testing.T) { conf := newConfig(t, "/echo") conf.Version = ProtocolVersionHybi13 for i, tt := range originTests { origin, err := Origin(conf, tt.req) if err != nil { t.Error(err) continue } if !reflect.DeepEqual(origin, tt.origin) { t.Errorf("#%d: got origin %v; want %v", i, origin, tt.origin) continue } } } func TestCtrlAndData(t *testing.T) { once.Do(startServer) c, err := net.Dial("tcp", serverAddr) if err != nil { t.Fatal(err) } ws, err := NewClient(newConfig(t, "/ctrldata"), c) if err != nil { t.Fatal(err) } defer ws.Close() h := &testCtrlAndDataHandler{hybiFrameHandler: hybiFrameHandler{conn: ws}} ws.frameHandler = h b := make([]byte, 128) for i := 0; i < 2; i++ { data := []byte(fmt.Sprintf("#%d-DATA-FRAME-FROM-CLIENT", i)) if _, err := ws.Write(data); err != nil { t.Fatalf("#%d: %v", i, err) } var ctrl []byte if i%2 != 0 { // with or without payload ctrl = []byte(fmt.Sprintf("#%d-CONTROL-FRAME-FROM-CLIENT", i)) } if _, err := h.WritePing(ctrl); err != nil { t.Fatalf("#%d: %v", i, err) } n, err := ws.Read(b) if err != nil { t.Fatalf("#%d: %v", i, err) } if !bytes.Equal(b[:n], data) { t.Fatalf("#%d: got %v; want %v", i, b[:n], data) } } }