summaryrefslogtreecommitdiff
path: root/libgo/go/websocket/websocket.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/websocket/websocket.go')
-rw-r--r--libgo/go/websocket/websocket.go455
1 files changed, 336 insertions, 119 deletions
diff --git a/libgo/go/websocket/websocket.go b/libgo/go/websocket/websocket.go
index 7447cf85215..a3750dde115 100644
--- a/libgo/go/websocket/websocket.go
+++ b/libgo/go/websocket/websocket.go
@@ -2,145 +2,246 @@
// 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 implements a client and server for the WebSocket protocol.
+// The protocol is defined at http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol
package websocket
-// TODO(ukai):
-// better logging.
-
import (
"bufio"
- "crypto/md5"
- "encoding/binary"
+ "crypto/tls"
"http"
"io"
+ "io/ioutil"
+ "json"
"net"
"os"
+ "sync"
+ "url"
)
-// WebSocketAddr is an implementation of net.Addr for Web Sockets.
-type WebSocketAddr string
+const (
+ ProtocolVersionHixie75 = -75
+ ProtocolVersionHixie76 = -76
+ ProtocolVersionHybi00 = 0
+ ProtocolVersionHybi08 = 8
+ ProtocolVersionHybi13 = 13
+ ProtocolVersionHybi = ProtocolVersionHybi13
+ SupportedProtocolVersion = "13, 8"
+
+ ContinuationFrame = 0
+ TextFrame = 1
+ BinaryFrame = 2
+ CloseFrame = 8
+ PingFrame = 9
+ PongFrame = 10
+ UnknownFrame = 255
+)
-// Network returns the network type for a Web Socket, "websocket".
-func (addr WebSocketAddr) Network() string { return "websocket" }
+// WebSocket protocol errors.
+type ProtocolError struct {
+ ErrorString string
+}
-// String returns the network address for a Web Socket.
-func (addr WebSocketAddr) String() string { return string(addr) }
+func (err *ProtocolError) String() string { return err.ErrorString }
-const (
- stateFrameByte = iota
- stateFrameLength
- stateFrameData
- stateFrameTextData
+var (
+ ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
+ 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"}
+ ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
+ ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
+ ErrBadFrame = &ProtocolError{"bad frame"}
+ ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
+ ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
+ ErrBadRequestMethod = &ProtocolError{"bad method"}
+ ErrNotSupported = &ProtocolError{"not supported"}
)
-// Conn is a channel to communicate to a Web Socket.
-// It implements the net.Conn interface.
+// Addr is an implementation of net.Addr for WebSocket.
+type Addr struct {
+ *url.URL
+}
+
+// Network returns the network type for a WebSocket, "websocket".
+func (addr *Addr) Network() string { return "websocket" }
+
+// Config is a WebSocket configuration
+type Config struct {
+ // A WebSocket server address.
+ Location *url.URL
+
+ // A Websocket client origin.
+ Origin *url.URL
+
+ // WebSocket subprotocols.
+ Protocol []string
+
+ // WebSocket protocol version.
+ Version int
+
+ // TLS config for secure WebSocket (wss).
+ TlsConfig *tls.Config
+
+ handshakeData map[string]string
+}
+
+// serverHandshaker is an interface to handle WebSocket server side handshake.
+type serverHandshaker interface {
+ // ReadHandshake reads handshake request message from client.
+ // Returns http response code and error if any.
+ ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err os.Error)
+
+ // AcceptHandshake accepts the client handshake request and sends
+ // handshake response back to client.
+ AcceptHandshake(buf *bufio.Writer) (err os.Error)
+
+ // NewServerConn creates a new WebSocket connection.
+ NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
+}
+
+// frameReader is an interface to read a WebSocket frame.
+type frameReader interface {
+ // Reader is to read payload of the frame.
+ io.Reader
+
+ // PayloadType returns payload type.
+ PayloadType() byte
+
+ // HeaderReader returns a reader to read header of the frame.
+ HeaderReader() io.Reader
+
+ // TrailerReader returns a reader to read trailer of the frame.
+ // If it returns nil, there is no trailer in the frame.
+ TrailerReader() io.Reader
+
+ // Len returns total length of the frame, including header and trailer.
+ Len() int
+}
+
+// frameReaderFactory is an interface to creates new frame reader.
+type frameReaderFactory interface {
+ NewFrameReader() (r frameReader, err os.Error)
+}
+
+// frameWriter is an interface to write a WebSocket frame.
+type frameWriter interface {
+ // Writer is to write playload of the frame.
+ io.WriteCloser
+}
+
+// frameWriterFactory is an interface to create new frame writer.
+type frameWriterFactory interface {
+ NewFrameWriter(payloadType byte) (w frameWriter, err os.Error)
+}
+
+type frameHandler interface {
+ HandleFrame(frame frameReader) (r frameReader, err os.Error)
+ WriteClose(status int) (err os.Error)
+}
+
+// Conn represents a WebSocket connection.
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
+ config *Config
+ 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
-}
+ rio sync.Mutex
+ frameReaderFactory
+ frameReader
-// 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
+ wio sync.Mutex
+ frameWriterFactory
+
+ frameHandler
+ PayloadType byte
+ defaultCloseStatus int
}
-// Read implements the io.Reader interface for a Conn.
+// Read implements the io.Reader interface:
+// it reads data of a frame from the WebSocket connection.
+// if msg is not large enough for the frame data, it fills the msg and next Read
+// will read the rest of the frame data.
+// it reads Text frame or Binary frame.
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()
+ ws.rio.Lock()
+ defer ws.rio.Unlock()
+again:
+ if ws.frameReader == nil {
+ frame, err := ws.frameReaderFactory.NewFrameReader()
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
+ ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
+ if err != nil {
+ return 0, err
}
- // 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
+ if ws.frameReader == nil {
+ goto again
}
- 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, err = ws.frameReader.Read(msg)
+ if err == os.EOF {
+ if trailer := ws.frameReader.TrailerReader(); trailer != nil {
+ io.Copy(ioutil.Discard, trailer)
}
+ ws.frameReader = nil
+ goto again
}
- n = copy(msg, ws.data)
- ws.data = ws.data[n:]
return n, err
}
-// Write implements the io.Writer interface for a Conn.
+// Write implements the io.Writer interface:
+// it writes data as a frame to the WebSocket connection.
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
+ ws.wio.Lock()
+ defer ws.wio.Unlock()
+ w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
+ if err != nil {
+ return 0, err
+ }
+ n, err = w.Write(msg)
+ w.Close()
+ if err != nil {
+ return n, err
+ }
+ return n, err
+}
+
+// Close implements the io.Closer interface.
+func (ws *Conn) Close() os.Error {
+ err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
+ if err != nil {
+ return err
+ }
+ return ws.rwc.Close()
}
-// Close implements the io.Closer interface for a Conn.
-func (ws *Conn) Close() os.Error { return ws.rwc.Close() }
+func (ws *Conn) IsClientConn() bool { return ws.request == nil }
+func (ws *Conn) IsServerConn() bool { return ws.request != nil }
-// LocalAddr returns the WebSocket Origin for the connection.
-func (ws *Conn) LocalAddr() net.Addr { return WebSocketAddr(ws.Origin) }
+// LocalAddr returns the WebSocket Origin for the connection for client, or
+// the WebSocket location for server.
+func (ws *Conn) LocalAddr() net.Addr {
+ if ws.IsClientConn() {
+ return &Addr{ws.config.Origin}
+ }
+ return &Addr{ws.config.Location}
+}
-// RemoteAddr returns the WebSocket locations for the connection.
-func (ws *Conn) RemoteAddr() net.Addr { return WebSocketAddr(ws.Location) }
+// RemoteAddr returns the WebSocket location for the connection for client, or
+// the Websocket Origin for server.
+func (ws *Conn) RemoteAddr() net.Addr {
+ if ws.IsClientConn() {
+ return &Addr{ws.config.Location}
+ }
+ return &Addr{ws.config.Origin}
+}
// SetTimeout sets the connection's network timeout in nanoseconds.
func (ws *Conn) SetTimeout(nsec int64) os.Error {
@@ -166,27 +267,143 @@ func (ws *Conn) SetWriteTimeout(nsec int64) os.Error {
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)
+// Config returns the WebSocket config.
+func (ws *Conn) Config() *Config { return ws.config }
+
+// Request returns the http request upgraded to the WebSocket.
+// It is nil for client side.
+func (ws *Conn) Request() *http.Request { return ws.request }
+
+// Codec represents a symmetric pair of functions that implement a codec.
+type Codec struct {
+ Marshal func(v interface{}) (data []byte, payloadType byte, err os.Error)
+ Unmarshal func(data []byte, payloadType byte, v interface{}) (err os.Error)
+}
+
+// Send sends v marshaled by cd.Marshal as single frame to ws.
+func (cd Codec) Send(ws *Conn, v interface{}) (err os.Error) {
+ if err != nil {
+ return err
+ }
+ data, payloadType, err := cd.Marshal(v)
+ if err != nil {
+ return err
+ }
+ ws.wio.Lock()
+ defer ws.wio.Unlock()
+ w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
+ _, err = w.Write(data)
+ w.Close()
+ return err
+}
+
+// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v.
+func (cd Codec) Receive(ws *Conn, v interface{}) (err os.Error) {
+ ws.rio.Lock()
+ defer ws.rio.Unlock()
+ if ws.frameReader != nil {
+ _, err = io.Copy(ioutil.Discard, ws.frameReader)
+ if err != nil {
+ return err
+ }
+ ws.frameReader = nil
+ }
+again:
+ frame, err := ws.frameReaderFactory.NewFrameReader()
+ if err != nil {
+ return err
+ }
+ frame, err = ws.frameHandler.HandleFrame(frame)
+ if err != nil {
+ return err
+ }
+ if frame == nil {
+ goto again
+ }
+ payloadType := frame.PayloadType()
+ data, err := ioutil.ReadAll(frame)
+ if err != nil {
+ return err
+ }
+ return cd.Unmarshal(data, payloadType, v)
+}
- // 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
+func marshal(v interface{}) (msg []byte, payloadType byte, err os.Error) {
+ switch data := v.(type) {
+ case string:
+ return []byte(data), TextFrame, nil
+ case []byte:
+ return data, BinaryFrame, nil
}
- expected = h.Sum()
- return
+ return nil, UnknownFrame, ErrNotSupported
}
-var _ net.Conn = (*Conn)(nil) // compile-time check that *Conn implements net.Conn.
+func unmarshal(msg []byte, payloadType byte, v interface{}) (err os.Error) {
+ switch data := v.(type) {
+ case *string:
+ *data = string(msg)
+ return nil
+ case *[]byte:
+ *data = msg
+ return nil
+ }
+ return ErrNotSupported
+}
+
+/*
+Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
+To send/receive text frame, use string type.
+To send/receive binary frame, use []byte type.
+
+Trivial usage:
+
+ import "websocket"
+
+ // receive text frame
+ var message string
+ websocket.Message.Receive(ws, &message)
+
+ // send text frame
+ message = "hello"
+ websocket.Message.Send(ws, message)
+
+ // receive binary frame
+ var data []byte
+ websocket.Message.Receive(ws, &data)
+
+ // send binary frame
+ data = []byte{0, 1, 2}
+ websocket.Message.Send(ws, data)
+
+*/
+var Message = Codec{marshal, unmarshal}
+
+func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err os.Error) {
+ msg, err = json.Marshal(v)
+ return msg, TextFrame, err
+}
+
+func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err os.Error) {
+ return json.Unmarshal(msg, v)
+}
+
+/*
+JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
+
+Trival usage:
+
+ import "websocket"
+
+ type T struct {
+ Msg string
+ Count int
+ }
+
+ // receive JSON type T
+ var data T
+ websocket.JSON.Receive(ws, &data)
+
+ // send JSON type T
+ websocket.JSON.Send(ws, data)
+*/
+var JSON = Codec{jsonMarshal, jsonUnmarshal}