summaryrefslogtreecommitdiff
path: root/libgo/go/websocket/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/websocket/client.go')
-rw-r--r--libgo/go/websocket/client.go321
1 files changed, 321 insertions, 0 deletions
diff --git a/libgo/go/websocket/client.go b/libgo/go/websocket/client.go
new file mode 100644
index 000000000..091345944
--- /dev/null
+++ b/libgo/go/websocket/client.go
@@ -0,0 +1,321 @@
+// 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 {
+ os.ErrorString
+}
+
+var (
+ ErrBadScheme = os.ErrorString("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 challange/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. genearte 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, "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["Upgrade"] != "WebSocket" ||
+ strings.ToLower(resp.Header["Connection"]) != "upgrade" {
+ return ErrBadUpgrade
+ }
+
+ if resp.Header["Sec-Websocket-Origin"] != origin {
+ return ErrBadWebSocketOrigin
+ }
+
+ if resp.Header["Sec-Websocket-Location"] != location {
+ return ErrBadWebSocketLocation
+ }
+
+ if protocol != "" && resp.Header["Sec-Websocket-Protocol"] != protocol {
+ return ErrBadWebSocketProtocol
+ }
+
+ // Step 42-43. get expected data from challange 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
+}
+
+/*
+Handhake 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, "GET")
+ if err != nil {
+ return
+ }
+ if resp.Status != "101 Web Socket Protocol Handshake" {
+ return ErrBadStatus
+ }
+ if resp.Header["Upgrade"] != "WebSocket" ||
+ resp.Header["Connection"] != "Upgrade" {
+ return ErrBadUpgrade
+ }
+ if resp.Header["Websocket-Origin"] != origin {
+ return ErrBadWebSocketOrigin
+ }
+ if resp.Header["Websocket-Location"] != location {
+ return ErrBadWebSocketLocation
+ }
+ if protocol != "" && resp.Header["Websocket-Protocol"] != protocol {
+ return ErrBadWebSocketProtocol
+ }
+ return
+}