aboutsummaryrefslogtreecommitdiff
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.go309
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}
}