diff options
Diffstat (limited to 'libgo/go/websocket/client.go')
-rw-r--r-- | libgo/go/websocket/client.go | 309 |
1 files changed, 62 insertions, 247 deletions
diff --git a/libgo/go/websocket/client.go b/libgo/go/websocket/client.go index 74bede4..b7eaafd 100644 --- a/libgo/go/websocket/client.go +++ b/libgo/go/websocket/client.go @@ -6,114 +6,119 @@ package websocket import ( "bufio" - "bytes" "crypto/tls" - "fmt" - "http" "io" "net" "os" - "rand" - "strings" "url" ) -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 -) - +// DialError is an error that occurs while dialling a websocket server. type DialError struct { - URL string - Protocol string - Origin string - Error os.Error + *Config + Error os.Error } func (e *DialError) String() string { - return "websocket.Dial " + e.URL + ": " + e.Error.String() + return "websocket.Dial " + e.Config.Location.String() + ": " + e.Error.String() } -func init() { - i := 0 - for ch := byte(0x21); ch < 0x30; ch++ { - secKeyRandomChars[i] = ch - i++ +// NewConfig creates a new WebSocket config for client connection. +func NewConfig(server, origin string) (config *Config, err os.Error) { + config = new(Config) + config.Version = ProtocolVersionHybi13 + config.Location, err = url.ParseRequest(server) + if err != nil { + return } - for ch := byte(0x3a); ch < 0x7F; ch++ { - secKeyRandomChars[i] = ch - i++ + config.Origin, err = url.ParseRequest(origin) + if err != nil { + return } + return } -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) { +// NewClient creates a new WebSocket client connection over rwc. +func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err os.Error) { br := bufio.NewReader(rwc) bw := bufio.NewWriter(rwc) - err = handshake(resourceName, host, origin, location, protocol, br, bw) + switch config.Version { + case ProtocolVersionHixie75: + err = hixie75ClientHandshake(config, br, bw) + case ProtocolVersionHixie76, ProtocolVersionHybi00: + err = hixie76ClientHandshake(config, br, bw) + case ProtocolVersionHybi08, ProtocolVersionHybi13: + err = hybiClientHandshake(config, br, bw) + default: + err = ErrBadProtocolVersion + } if err != nil { return } buf := bufio.NewReadWriter(br, bw) - ws = newConn(origin, location, protocol, buf, rwc) + switch config.Version { + case ProtocolVersionHixie75, ProtocolVersionHixie76, ProtocolVersionHybi00: + ws = newHixieClientConn(config, buf, rwc) + case ProtocolVersionHybi08, ProtocolVersionHybi13: + ws = newHybiClientConn(config, buf, rwc) + } return } /* -Dial opens a new client connection to a Web Socket. +Dial opens a new client connection to a WebSocket. A trivial example client: package main import ( - "websocket" + "http" + "log" "strings" + "websocket" ) func main() { - ws, err := websocket.Dial("ws://localhost/ws", "", "http://localhost/"); - if err != nil { - panic("Dial: " + err.String()) + origin := "http://localhost/" + url := "ws://localhost/ws" + ws, err := websocket.Dial(url, "", origin) + if err != nil { + log.Fatal(err) } if _, err := ws.Write([]byte("hello, world!\n")); err != nil { - panic("Write: " + err.String()) + log.Fatal(err) } var msg = make([]byte, 512); if n, err := ws.Read(msg); err != nil { - panic("Read: " + err.String()) + log.Fatal(err) } // use msg[0:n] } */ func Dial(url_, protocol, origin string) (ws *Conn, err os.Error) { - var client net.Conn - - parsedUrl, err := url.Parse(url_) + config, err := NewConfig(url_, origin) if err != nil { - goto Error + return nil, err } + return DialConfig(config) +} - switch parsedUrl.Scheme { +// DialConfig opens a new client connection to a WebSocket with a config. +func DialConfig(config *Config) (ws *Conn, err os.Error) { + var client net.Conn + if config.Location == nil { + return nil, &DialError{config, ErrBadWebSocketLocation} + } + if config.Origin == nil { + return nil, &DialError{config, ErrBadWebSocketOrigin} + } + switch config.Location.Scheme { case "ws": - client, err = net.Dial("tcp", parsedUrl.Host) + client, err = net.Dial("tcp", config.Location.Host) case "wss": - client, err = tls.Dial("tcp", parsedUrl.Host, nil) + client, err = tls.Dial("tcp", config.Location.Host, config.TlsConfig) default: err = ErrBadScheme @@ -122,202 +127,12 @@ func Dial(url_, protocol, origin string) (ws *Conn, err os.Error) { goto Error } - ws, err = newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url_, protocol, client, handshake) + ws, err = NewClient(config, client) 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 []string - fields = append(fields, "Upgrade: WebSocket\r\n") - fields = append(fields, "Connection: Upgrade\r\n") - fields = append(fields, "Host: "+host+"\r\n") - fields = append(fields, "Origin: "+origin+"\r\n") - if protocol != "" { - fields = append(fields, "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 = append(fields, "Sec-WebSocket-Key1: "+key1+"\r\n") - fields = append(fields, "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 + return nil, &DialError{config, err} } |