diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:11:55 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:11:55 +0200 |
commit | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (patch) | |
tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /src/pkg/websocket | |
parent | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (diff) | |
download | golang-80f18fc933cf3f3e829c5455a1023d69f7b86e52.tar.gz |
Imported Upstream version 60
Diffstat (limited to 'src/pkg/websocket')
-rw-r--r-- | src/pkg/websocket/Makefile | 9 | ||||
-rw-r--r-- | src/pkg/websocket/client.go | 323 | ||||
-rw-r--r-- | src/pkg/websocket/server.go | 220 | ||||
-rw-r--r-- | src/pkg/websocket/websocket.go | 192 | ||||
-rw-r--r-- | src/pkg/websocket/websocket_test.go | 270 |
5 files changed, 0 insertions, 1014 deletions
diff --git a/src/pkg/websocket/Makefile b/src/pkg/websocket/Makefile deleted file mode 100644 index 6d3c9cbd1..000000000 --- a/src/pkg/websocket/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -include ../../Make.inc - -TARG=websocket -GOFILES=\ - client.go\ - server.go\ - websocket.go\ - -include ../../Make.pkg diff --git a/src/pkg/websocket/client.go b/src/pkg/websocket/client.go deleted file mode 100644 index f066a1832..000000000 --- a/src/pkg/websocket/client.go +++ /dev/null @@ -1,323 +0,0 @@ -// 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 ( - "bufio" - "bytes" - "container/vector" - "crypto/tls" - "fmt" - "http" - "io" - "net" - "os" - "rand" - "strings" -) - -type ProtocolError struct { - ErrorString string -} - -func (err *ProtocolError) String() string { return string(err.ErrorString) } - -var ( - ErrBadScheme = &ProtocolError{"bad scheme"} - ErrBadStatus = &ProtocolError{"bad status"} - ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} - ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} - ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} - ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} - ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"} - secKeyRandomChars [0x30 - 0x21 + 0x7F - 0x3A]byte -) - -type DialError struct { - URL string - Protocol string - Origin string - Error os.Error -} - -func (e *DialError) String() string { - return "websocket.Dial " + e.URL + ": " + e.Error.String() -} - -func init() { - i := 0 - for ch := byte(0x21); ch < 0x30; ch++ { - secKeyRandomChars[i] = ch - i++ - } - for ch := byte(0x3a); ch < 0x7F; ch++ { - secKeyRandomChars[i] = ch - i++ - } -} - -type handshaker func(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) os.Error - -// newClient creates a new Web Socket client connection. -func newClient(resourceName, host, origin, location, protocol string, rwc io.ReadWriteCloser, handshake handshaker) (ws *Conn, err os.Error) { - br := bufio.NewReader(rwc) - bw := bufio.NewWriter(rwc) - err = handshake(resourceName, host, origin, location, protocol, br, bw) - if err != nil { - return - } - buf := bufio.NewReadWriter(br, bw) - ws = newConn(origin, location, protocol, buf, rwc) - return -} - -/* -Dial opens a new client connection to a Web Socket. - -A trivial example client: - - package main - - import ( - "websocket" - "strings" - ) - - func main() { - ws, err := websocket.Dial("ws://localhost/ws", "", "http://localhost/"); - if err != nil { - panic("Dial: " + err.String()) - } - if _, err := ws.Write([]byte("hello, world!\n")); err != nil { - panic("Write: " + err.String()) - } - var msg = make([]byte, 512); - if n, err := ws.Read(msg); err != nil { - panic("Read: " + err.String()) - } - // use msg[0:n] - } -*/ -func Dial(url, protocol, origin string) (ws *Conn, err os.Error) { - var client net.Conn - - parsedUrl, err := http.ParseURL(url) - if err != nil { - goto Error - } - - switch parsedUrl.Scheme { - case "ws": - client, err = net.Dial("tcp", parsedUrl.Host) - - case "wss": - client, err = tls.Dial("tcp", parsedUrl.Host, nil) - - default: - err = ErrBadScheme - } - if err != nil { - goto Error - } - - ws, err = newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url, protocol, client, handshake) - if err != nil { - goto Error - } - return - -Error: - return nil, &DialError{url, protocol, origin, err} -} - -/* -Generates handshake key as described in 4.1 Opening handshake step 16 to 22. -cf. http://www.whatwg.org/specs/web-socket-protocol/ -*/ -func generateKeyNumber() (key string, number uint32) { - // 16. Let /spaces_n/ be a random integer from 1 to 12 inclusive. - spaces := rand.Intn(12) + 1 - - // 17. Let /max_n/ be the largest integer not greater than - // 4,294,967,295 divided by /spaces_n/ - max := int(4294967295 / uint32(spaces)) - - // 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive. - number = uint32(rand.Intn(max + 1)) - - // 19. Let /product_n/ be the result of multiplying /number_n/ and - // /spaces_n/ together. - product := number * uint32(spaces) - - // 20. Let /key_n/ be a string consisting of /product_n/, expressed - // in base ten using the numerals in the range U+0030 DIGIT ZERO (0) - // to U+0039 DIGIT NINE (9). - key = fmt.Sprintf("%d", product) - - // 21. Insert between one and twelve random characters from the ranges - // U+0021 to U+002F and U+003A to U+007E into /key_n/ at random - // positions. - n := rand.Intn(12) + 1 - for i := 0; i < n; i++ { - pos := rand.Intn(len(key)) + 1 - ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))] - key = key[0:pos] + string(ch) + key[pos:] - } - - // 22. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random - // positions other than the start or end of the string. - for i := 0; i < spaces; i++ { - pos := rand.Intn(len(key)-1) + 1 - key = key[0:pos] + " " + key[pos:] - } - - return -} - -/* -Generates handshake key_3 as described in 4.1 Opening handshake step 26. -cf. http://www.whatwg.org/specs/web-socket-protocol/ -*/ -func generateKey3() (key []byte) { - // 26. Let /key3/ be a string consisting of eight random bytes (or - // equivalently, a random 64 bit integer encoded in big-endian order). - key = make([]byte, 8) - for i := 0; i < 8; i++ { - key[i] = byte(rand.Intn(256)) - } - return -} - -/* -Web Socket protocol handshake based on -http://www.whatwg.org/specs/web-socket-protocol/ -(draft of http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) -*/ -func handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) { - // 4.1. Opening handshake. - // Step 5. send a request line. - bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n") - - // Step 6-14. push request headers in fields. - var fields vector.StringVector - fields.Push("Upgrade: WebSocket\r\n") - fields.Push("Connection: Upgrade\r\n") - fields.Push("Host: " + host + "\r\n") - fields.Push("Origin: " + origin + "\r\n") - if protocol != "" { - fields.Push("Sec-WebSocket-Protocol: " + protocol + "\r\n") - } - // TODO(ukai): Step 15. send cookie if any. - - // Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields. - key1, number1 := generateKeyNumber() - key2, number2 := generateKeyNumber() - fields.Push("Sec-WebSocket-Key1: " + key1 + "\r\n") - fields.Push("Sec-WebSocket-Key2: " + key2 + "\r\n") - - // Step 24. shuffle fields and send them out. - for i := 1; i < len(fields); i++ { - j := rand.Intn(i) - fields[i], fields[j] = fields[j], fields[i] - } - for i := 0; i < len(fields); i++ { - bw.WriteString(fields[i]) - } - // Step 25. send CRLF. - bw.WriteString("\r\n") - - // Step 26. generate 8 bytes random key. - key3 := generateKey3() - // Step 27. send it out. - bw.Write(key3) - if err = bw.Flush(); err != nil { - return - } - - // Step 28-29, 32-40. read response from server. - resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) - if err != nil { - return err - } - // Step 30. check response code is 101. - if resp.StatusCode != 101 { - return ErrBadStatus - } - - // Step 41. check websocket headers. - if resp.Header.Get("Upgrade") != "WebSocket" || - strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { - return ErrBadUpgrade - } - - if resp.Header.Get("Sec-Websocket-Origin") != origin { - return ErrBadWebSocketOrigin - } - - if resp.Header.Get("Sec-Websocket-Location") != location { - return ErrBadWebSocketLocation - } - - if protocol != "" && resp.Header.Get("Sec-Websocket-Protocol") != protocol { - return ErrBadWebSocketProtocol - } - - // Step 42-43. get expected data from challenge data. - expected, err := getChallengeResponse(number1, number2, key3) - if err != nil { - return err - } - - // Step 44. read 16 bytes from server. - reply := make([]byte, 16) - if _, err = io.ReadFull(br, reply); err != nil { - return err - } - - // Step 45. check the reply equals to expected data. - if !bytes.Equal(expected, reply) { - return ErrChallengeResponse - } - // WebSocket connection is established. - return -} - -/* -Handshake described in (soon obsolete) -draft-hixie-thewebsocket-protocol-75. -*/ -func draft75handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) { - bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n") - bw.WriteString("Upgrade: WebSocket\r\n") - bw.WriteString("Connection: Upgrade\r\n") - bw.WriteString("Host: " + host + "\r\n") - bw.WriteString("Origin: " + origin + "\r\n") - if protocol != "" { - bw.WriteString("WebSocket-Protocol: " + protocol + "\r\n") - } - bw.WriteString("\r\n") - bw.Flush() - resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) - if err != nil { - return - } - if resp.Status != "101 Web Socket Protocol Handshake" { - return ErrBadStatus - } - if resp.Header.Get("Upgrade") != "WebSocket" || - resp.Header.Get("Connection") != "Upgrade" { - return ErrBadUpgrade - } - if resp.Header.Get("Websocket-Origin") != origin { - return ErrBadWebSocketOrigin - } - if resp.Header.Get("Websocket-Location") != location { - return ErrBadWebSocketLocation - } - if protocol != "" && resp.Header.Get("Websocket-Protocol") != protocol { - return ErrBadWebSocketProtocol - } - return -} diff --git a/src/pkg/websocket/server.go b/src/pkg/websocket/server.go deleted file mode 100644 index 165cbffee..000000000 --- a/src/pkg/websocket/server.go +++ /dev/null @@ -1,220 +0,0 @@ -// 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 ( - "http" - "io" - "strings" -) - -/* -Handler is an interface to a WebSocket. - -A trivial example server: - - package main - - import ( - "http" - "io" - "websocket" - ) - - // Echo the data received on the Web Socket. - func EchoServer(ws *websocket.Conn) { - io.Copy(ws, ws); - } - - func main() { - http.Handle("/echo", websocket.Handler(EchoServer)); - err := http.ListenAndServe(":12345", nil); - if err != nil { - panic("ListenAndServe: " + err.String()) - } - } -*/ -type Handler func(*Conn) - -/* -Gets key number from Sec-WebSocket-Key<n>: field as described -in 5.2 Sending the server's opening handshake, 4. -*/ -func getKeyNumber(s string) (r uint32) { - // 4. Let /key-number_n/ be the digits (characters in the range - // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_1/, - // interpreted as a base ten integer, ignoring all other characters - // in /key_n/. - r = 0 - for i := 0; i < len(s); i++ { - if s[i] >= '0' && s[i] <= '9' { - r = r*10 + uint32(s[i]) - '0' - } - } - return -} - -// ServeHTTP implements the http.Handler interface for a Web Socket -func (f Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - rwc, buf, err := w.(http.Hijacker).Hijack() - if err != nil { - panic("Hijack failed: " + err.String()) - return - } - // The server should abort the WebSocket connection if it finds - // the client did not send a handshake that matches with protocol - // specification. - defer rwc.Close() - - if req.Method != "GET" { - return - } - // HTTP version can be safely ignored. - - if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || - strings.ToLower(req.Header.Get("Connection")) != "upgrade" { - return - } - - // TODO(ukai): check Host - origin := req.Header.Get("Origin") - if origin == "" { - return - } - - key1 := req.Header.Get("Sec-Websocket-Key1") - if key1 == "" { - return - } - key2 := req.Header.Get("Sec-Websocket-Key2") - if key2 == "" { - return - } - key3 := make([]byte, 8) - if _, err := io.ReadFull(buf, key3); err != nil { - return - } - - var location string - if req.TLS != nil { - location = "wss://" + req.Host + req.URL.RawPath - } else { - location = "ws://" + req.Host + req.URL.RawPath - } - - // Step 4. get key number in Sec-WebSocket-Key<n> fields. - keyNumber1 := getKeyNumber(key1) - keyNumber2 := getKeyNumber(key2) - - // Step 5. get number of spaces in Sec-WebSocket-Key<n> fields. - space1 := uint32(strings.Count(key1, " ")) - space2 := uint32(strings.Count(key2, " ")) - if space1 == 0 || space2 == 0 { - return - } - - // Step 6. key number must be an integral multiple of spaces. - if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 { - return - } - - // Step 7. let part be key number divided by spaces. - part1 := keyNumber1 / space1 - part2 := keyNumber2 / space2 - - // Step 8. let challenge be concatenation of part1, part2 and key3. - // Step 9. get MD5 fingerprint of challenge. - response, err := getChallengeResponse(part1, part2, key3) - if err != nil { - return - } - - // Step 10. send response status line. - buf.WriteString("HTTP/1.1 101 WebSocket Protocol Handshake\r\n") - // Step 11. send response headers. - buf.WriteString("Upgrade: WebSocket\r\n") - buf.WriteString("Connection: Upgrade\r\n") - buf.WriteString("Sec-WebSocket-Location: " + location + "\r\n") - buf.WriteString("Sec-WebSocket-Origin: " + origin + "\r\n") - protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) - if protocol != "" { - buf.WriteString("Sec-WebSocket-Protocol: " + protocol + "\r\n") - } - // Step 12. send CRLF. - buf.WriteString("\r\n") - // Step 13. send response data. - buf.Write(response) - if err := buf.Flush(); err != nil { - return - } - ws := newConn(origin, location, protocol, buf, rwc) - ws.Request = req - f(ws) -} - - -/* -Draft75Handler is an interface to a WebSocket based on the -(soon obsolete) draft-hixie-thewebsocketprotocol-75. -*/ -type Draft75Handler func(*Conn) - -// ServeHTTP implements the http.Handler interface for a Web Socket. -func (f Draft75Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if req.Method != "GET" || req.Proto != "HTTP/1.1" { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "Unexpected request") - return - } - if req.Header.Get("Upgrade") != "WebSocket" { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "missing Upgrade: WebSocket header") - return - } - if req.Header.Get("Connection") != "Upgrade" { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "missing Connection: Upgrade header") - return - } - origin := strings.TrimSpace(req.Header.Get("Origin")) - if origin == "" { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "missing Origin header") - return - } - - rwc, buf, err := w.(http.Hijacker).Hijack() - if err != nil { - panic("Hijack failed: " + err.String()) - return - } - defer rwc.Close() - - var location string - if req.TLS != nil { - location = "wss://" + req.Host + req.URL.RawPath - } else { - location = "ws://" + req.Host + req.URL.RawPath - } - - // TODO(ukai): verify origin,location,protocol. - - buf.WriteString("HTTP/1.1 101 Web Socket Protocol Handshake\r\n") - buf.WriteString("Upgrade: WebSocket\r\n") - buf.WriteString("Connection: Upgrade\r\n") - buf.WriteString("WebSocket-Origin: " + origin + "\r\n") - buf.WriteString("WebSocket-Location: " + location + "\r\n") - protocol := strings.TrimSpace(req.Header.Get("Websocket-Protocol")) - // canonical header key of WebSocket-Protocol. - if protocol != "" { - buf.WriteString("WebSocket-Protocol: " + protocol + "\r\n") - } - buf.WriteString("\r\n") - if err := buf.Flush(); err != nil { - return - } - ws := newConn(origin, location, protocol, buf, rwc) - f(ws) -} diff --git a/src/pkg/websocket/websocket.go b/src/pkg/websocket/websocket.go deleted file mode 100644 index 7447cf852..000000000 --- a/src/pkg/websocket/websocket.go +++ /dev/null @@ -1,192 +0,0 @@ -// 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 implements a client and server for the Web Socket protocol. -// The protocol is defined at http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol -package websocket - -// TODO(ukai): -// better logging. - -import ( - "bufio" - "crypto/md5" - "encoding/binary" - "http" - "io" - "net" - "os" -) - -// WebSocketAddr is an implementation of net.Addr for Web Sockets. -type WebSocketAddr string - -// Network returns the network type for a Web Socket, "websocket". -func (addr WebSocketAddr) Network() string { return "websocket" } - -// String returns the network address for a Web Socket. -func (addr WebSocketAddr) String() string { return string(addr) } - -const ( - stateFrameByte = iota - stateFrameLength - stateFrameData - stateFrameTextData -) - -// Conn is a channel to communicate to a Web Socket. -// It implements the net.Conn interface. -type Conn struct { - // The origin URI for the Web Socket. - Origin string - // The location URI for the Web Socket. - Location string - // The subprotocol for the Web Socket. - Protocol string - // The initial http Request (for the Server side only). - Request *http.Request - - buf *bufio.ReadWriter - rwc io.ReadWriteCloser - - // It holds text data in previous Read() that failed with small buffer. - data []byte - reading bool -} - -// newConn creates a new Web Socket. -func newConn(origin, location, protocol string, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { - if buf == nil { - br := bufio.NewReader(rwc) - bw := bufio.NewWriter(rwc) - buf = bufio.NewReadWriter(br, bw) - } - ws := &Conn{Origin: origin, Location: location, Protocol: protocol, buf: buf, rwc: rwc} - return ws -} - -// Read implements the io.Reader interface for a Conn. -func (ws *Conn) Read(msg []byte) (n int, err os.Error) { -Frame: - for !ws.reading && len(ws.data) == 0 { - // Beginning of frame, possibly. - b, err := ws.buf.ReadByte() - if err != nil { - return 0, err - } - if b&0x80 == 0x80 { - // Skip length frame. - length := 0 - for { - c, err := ws.buf.ReadByte() - if err != nil { - return 0, err - } - length = length*128 + int(c&0x7f) - if c&0x80 == 0 { - break - } - } - for length > 0 { - _, err := ws.buf.ReadByte() - if err != nil { - return 0, err - } - } - continue Frame - } - // In text mode - if b != 0 { - // Skip this frame - for { - c, err := ws.buf.ReadByte() - if err != nil { - return 0, err - } - if c == '\xff' { - break - } - } - continue Frame - } - ws.reading = true - } - if len(ws.data) == 0 { - ws.data, err = ws.buf.ReadSlice('\xff') - if err == nil { - ws.reading = false - ws.data = ws.data[:len(ws.data)-1] // trim \xff - } - } - n = copy(msg, ws.data) - ws.data = ws.data[n:] - return n, err -} - -// Write implements the io.Writer interface for a Conn. -func (ws *Conn) Write(msg []byte) (n int, err os.Error) { - ws.buf.WriteByte(0) - ws.buf.Write(msg) - ws.buf.WriteByte(0xff) - err = ws.buf.Flush() - return len(msg), err -} - -// Close implements the io.Closer interface for a Conn. -func (ws *Conn) Close() os.Error { return ws.rwc.Close() } - -// LocalAddr returns the WebSocket Origin for the connection. -func (ws *Conn) LocalAddr() net.Addr { return WebSocketAddr(ws.Origin) } - -// RemoteAddr returns the WebSocket locations for the connection. -func (ws *Conn) RemoteAddr() net.Addr { return WebSocketAddr(ws.Location) } - -// SetTimeout sets the connection's network timeout in nanoseconds. -func (ws *Conn) SetTimeout(nsec int64) os.Error { - if conn, ok := ws.rwc.(net.Conn); ok { - return conn.SetTimeout(nsec) - } - return os.EINVAL -} - -// SetReadTimeout sets the connection's network read timeout in nanoseconds. -func (ws *Conn) SetReadTimeout(nsec int64) os.Error { - if conn, ok := ws.rwc.(net.Conn); ok { - return conn.SetReadTimeout(nsec) - } - return os.EINVAL -} - -// SetWriteTimeout sets the connection's network write timeout in nanoseconds. -func (ws *Conn) SetWriteTimeout(nsec int64) os.Error { - if conn, ok := ws.rwc.(net.Conn); ok { - return conn.SetWriteTimeout(nsec) - } - return os.EINVAL -} - -// getChallengeResponse computes the expected response from the -// challenge as described in section 5.1 Opening Handshake steps 42 to -// 43 of http://www.whatwg.org/specs/web-socket-protocol/ -func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte, err os.Error) { - // 41. Let /challenge/ be the concatenation of /number_1/, expressed - // a big-endian 32 bit integer, /number_2/, expressed in a big- - // endian 32 bit integer, and the eight bytes of /key_3/ in the - // order they were sent to the wire. - challenge := make([]byte, 16) - binary.BigEndian.PutUint32(challenge[0:], number1) - binary.BigEndian.PutUint32(challenge[4:], number2) - copy(challenge[8:], key3) - - // 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big- - // endian 128 bit string. - h := md5.New() - if _, err = h.Write(challenge); err != nil { - return - } - expected = h.Sum() - return -} - -var _ net.Conn = (*Conn)(nil) // compile-time check that *Conn implements net.Conn. diff --git a/src/pkg/websocket/websocket_test.go b/src/pkg/websocket/websocket_test.go deleted file mode 100644 index 84788b416..000000000 --- a/src/pkg/websocket/websocket_test.go +++ /dev/null @@ -1,270 +0,0 @@ -// 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 ( - "bufio" - "bytes" - "fmt" - "http" - "http/httptest" - "io" - "log" - "net" - "sync" - "testing" -) - -var serverAddr string -var once sync.Once - -func echoServer(ws *Conn) { io.Copy(ws, ws) } - -func startServer() { - http.Handle("/echo", Handler(echoServer)) - http.Handle("/echoDraft75", Draft75Handler(echoServer)) - server := httptest.NewServer(nil) - serverAddr = server.Listener.Addr().String() - log.Print("Test WebSocket server listening on ", serverAddr) -} - -// Test the getChallengeResponse function with values from section -// 5.1 of the specification steps 18, 26, and 43 from -// http://www.whatwg.org/specs/web-socket-protocol/ -func TestChallenge(t *testing.T) { - var part1 uint32 = 777007543 - var part2 uint32 = 114997259 - key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58} - expected := []byte("0st3Rl&q-2ZU^weu") - - response, err := getChallengeResponse(part1, part2, key3) - if err != nil { - t.Errorf("getChallengeResponse: returned error %v", err) - return - } - if !bytes.Equal(expected, response) { - t.Errorf("getChallengeResponse: expected %q got %q", expected, response) - } -} - -func TestEcho(t *testing.T) { - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - ws, err := newClient("/echo", "localhost", "http://localhost", - "ws://localhost/echo", "", client, handshake) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - msg := []byte("hello, world\n") - if _, err := ws.Write(msg); err != nil { - t.Errorf("Write: %v", err) - } - var actual_msg = make([]byte, 512) - n, err := ws.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) - } - ws.Close() -} - -func TestEchoDraft75(t *testing.T) { - once.Do(startServer) - - // websocket.Dial() - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - ws, err := newClient("/echoDraft75", "localhost", "http://localhost", - "ws://localhost/echoDraft75", "", client, draft75handshake) - if err != nil { - t.Errorf("WebSocket handshake: %v", err) - return - } - - msg := []byte("hello, world\n") - if _, err := ws.Write(msg); err != nil { - t.Errorf("Write: error %v", err) - } - var actual_msg = make([]byte, 512) - n, err := ws.Read(actual_msg) - if err != nil { - t.Errorf("Read: error %v", err) - } - actual_msg = actual_msg[0:n] - if !bytes.Equal(msg, actual_msg) { - t.Errorf("Echo: expected %q got %q", msg, actual_msg) - } - ws.Close() -} - -func TestWithQuery(t *testing.T) { - once.Do(startServer) - - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - - ws, err := newClient("/echo?q=v", "localhost", "http://localhost", - "ws://localhost/echo?q=v", "", client, handshake) - if err != nil { - t.Errorf("WebSocket handshake: %v", err) - return - } - ws.Close() -} - -func TestWithProtocol(t *testing.T) { - once.Do(startServer) - - client, err := net.Dial("tcp", serverAddr) - if err != nil { - t.Fatal("dialing", err) - } - - ws, err := newClient("/echo", "localhost", "http://localhost", - "ws://localhost/echo", "test", client, handshake) - if err != nil { - t.Errorf("WebSocket handshake: %v", err) - return - } - ws.Close() -} - -func TestHTTP(t *testing.T) { - once.Do(startServer) - - // If the client did not send a handshake that matches the protocol - // specification, the server should abort the WebSocket connection. - _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr)) - if err == nil { - t.Error("Get: unexpected success") - return - } - urlerr, ok := err.(*http.URLError) - if !ok { - t.Errorf("Get: not URLError %#v", err) - return - } - if urlerr.Error != io.ErrUnexpectedEOF { - t.Errorf("Get: error %#v", err) - return - } -} - -func TestHTTPDraft75(t *testing.T) { - once.Do(startServer) - - r, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr)) - if err != nil { - t.Errorf("Get: error %#v", err) - return - } - if r.StatusCode != http.StatusBadRequest { - t.Errorf("Get: got status %d", r.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) - for i := 0; i < 30; i++ { - // body - ws, err := Dial(fmt.Sprintf("ws://%s/echo", serverAddr), "", "http://localhost/") - if err != nil { - t.Error("Dial failed:", err.String()) - break - } - ws.Close() - } -} - -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) - } - ws, err := newClient("/echo", "localhost", "http://localhost", - "ws://localhost/echo", "", client, handshake) - if err != nil { - t.Errorf("WebSocket handshake error: %v", err) - return - } - - msg := []byte("hello, world\n") - if _, err := ws.Write(msg); err != nil { - t.Errorf("Write: %v", err) - } - var small_msg = make([]byte, 8) - n, err := ws.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 = ws.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) - } - ws.Close() - -} - -func testSkipLengthFrame(t *testing.T) { - b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'} - buf := bytes.NewBuffer(b) - br := bufio.NewReader(buf) - bw := bufio.NewWriter(buf) - ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil) - msg := make([]byte, 5) - n, err := ws.Read(msg) - if err != nil { - t.Errorf("Read: %v", err) - } - if !bytes.Equal(b[4:8], msg[0:n]) { - t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n]) - } -} - -func testSkipNoUTF8Frame(t *testing.T) { - b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'} - buf := bytes.NewBuffer(b) - br := bufio.NewReader(buf) - bw := bufio.NewWriter(buf) - ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil) - msg := make([]byte, 5) - n, err := ws.Read(msg) - if err != nil { - t.Errorf("Read: %v", err) - } - if !bytes.Equal(b[4:8], msg[0:n]) { - t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n]) - } -} |