Skip to content

Commit 854504c

Browse files
author
Michael Johnson
committed
Implement Conn.Read and Conn.Write
1 parent 0a059cd commit 854504c

File tree

1 file changed

+161
-4
lines changed

1 file changed

+161
-4
lines changed

conn.go

+161-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,181 @@
11
package websocket
22

33
import (
4+
"bytes"
5+
"io"
46
"net/url"
57
"time"
8+
9+
"github.com/gopherjs/gopherjs/js"
10+
"honnef.co/go/js/dom"
611
)
712

13+
func beginHandlerOpen(ch chan error) func(ev js.Object) {
14+
return func(ev js.Object) {
15+
close(ch)
16+
}
17+
}
18+
19+
func beginHandlerClose(ch chan error) func(ev js.Object) {
20+
return func(ev js.Object) {
21+
ch <- &js.Error{Object: ev}
22+
close(ch)
23+
}
24+
}
25+
26+
// Dial opens a new WebSocket connection. It will block until the connection is
27+
// established or fails to connect.
28+
func Dial(url string) (*Conn, error) {
29+
ws, err := New(url)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
openCh := make(chan error, 1)
35+
36+
// We have to use variables for the functions so that we can remove the
37+
// event handlers afterwards.
38+
openHandler := beginHandlerOpen(openCh)
39+
closeHandler := beginHandlerClose(openCh)
40+
41+
ws.AddEventListener("open", false, openHandler)
42+
ws.AddEventListener("close", false, closeHandler)
43+
44+
err, ok := <-openCh
45+
46+
ws.RemoveEventListener("open", false, openHandler)
47+
ws.RemoveEventListener("close", false, closeHandler)
48+
49+
if ok && err != nil {
50+
return nil, err
51+
}
52+
53+
conn := &Conn{
54+
WebSocket: ws,
55+
ch: make(chan *dom.MessageEvent, 1),
56+
}
57+
conn.Initialize()
58+
59+
return conn, nil
60+
}
61+
862
// Conn is a high-level wrapper around WebSocket. It is intended to
963
// provide a net.TCPConn-like interface.
1064
type Conn struct {
1165
*WebSocket
66+
67+
ch chan *dom.MessageEvent
68+
readBuf *bytes.Reader
69+
}
70+
71+
func (c *Conn) onMessage(event js.Object) {
72+
go func() {
73+
c.ch <- dom.WrapEvent(event).(*dom.MessageEvent)
74+
}()
75+
}
76+
77+
func (c *Conn) onClose(event js.Object) {
78+
go func() {
79+
// We queue nil to the end so that any messages received prior to
80+
// closing get handled first.
81+
c.ch <- nil
82+
}()
83+
}
84+
85+
// Initialize adds all of the event handlers necessary for a Conn to function.
86+
// It should never be called more than once and is already called if you used
87+
// Dial to create the Conn.
88+
func (c *Conn) Initialize() {
89+
// We need this so that received binary data is in ArrayBufferView format so
90+
// that it can easily be read.
91+
c.BinaryType = "arraybuffer"
92+
93+
c.AddEventListener("message", false, c.onMessage)
94+
c.AddEventListener("close", false, c.onClose)
95+
}
96+
97+
// receiveFrame receives one full frame from the WebSocket. It blocks until the
98+
// frame is received.
99+
func (c *Conn) receiveFrame() (*dom.MessageEvent, error) {
100+
item, ok := <-c.ch
101+
if !ok { // The channel has been closed
102+
return nil, io.EOF
103+
} else if item == nil {
104+
// See onClose for the explanation about sending a nil item.
105+
close(c.ch)
106+
return nil, io.EOF
107+
}
108+
return item, nil
109+
}
110+
111+
func getFrameData(obj js.Object) []byte {
112+
// TODO(nightexcessive): Is there a better way to do this?
113+
114+
frameStr := obj.Str()
115+
if frameStr == "[object ArrayBuffer]" {
116+
int8Array := js.Global.Get("Uint8Array").New(obj)
117+
return int8Array.Interface().([]byte)
118+
}
119+
120+
return []byte(frameStr)
12121
}
13122

14123
func (c *Conn) Read(b []byte) (n int, err error) {
15-
// TODO(nightexcessive): Implement
16-
panic("not yet implemeneted")
124+
// TODO(nightexcessive): Implement the deadline functions.
125+
126+
if c.readBuf != nil {
127+
n, err = c.readBuf.Read(b)
128+
if err == io.EOF {
129+
c.readBuf = nil
130+
err = nil
131+
}
132+
// If we read nothing from the buffer, continue to trying to receive.
133+
// This saves us when the last Read call emptied the buffer and this
134+
// call triggers the EOF. There's probably a better way of doing this,
135+
// but I'm really tired.
136+
if n > 0 {
137+
return
138+
}
139+
}
140+
141+
frame, err := c.receiveFrame()
142+
if err != nil {
143+
return 0, err
144+
}
145+
146+
receivedBytes := getFrameData(frame.Data)
147+
148+
n = copy(b, receivedBytes)
149+
// Fast path: The entire frame's contents have been copied into b.
150+
if n >= len(receivedBytes) {
151+
return
152+
}
153+
154+
c.readBuf = bytes.NewReader(receivedBytes[n:])
155+
return
17156
}
18157

158+
// Write writes the contents of b to the WebSocket using a binary opcode.
19159
func (c *Conn) Write(b []byte) (n int, err error) {
20-
// TODO(nightexcessive): Implement
21-
panic("not yet implemeneted")
160+
// []byte is converted to an (U)Int8Array by GopherJS, which fullfils the
161+
// ArrayBufferView definition.
162+
err = c.Send(b)
163+
if err != nil {
164+
return
165+
}
166+
n = len(b)
167+
return
168+
}
169+
170+
// WriteString writes the contents of s to the WebSocket using a text frame
171+
// opcode.
172+
func (c *Conn) WriteString(s string) (n int, err error) {
173+
err = c.Send(s)
174+
if err != nil {
175+
return
176+
}
177+
n = len(s)
178+
return
22179
}
23180

24181
// BUG(nightexcessive): We can't return net.Addr from Conn.LocalAddr and

0 commit comments

Comments
 (0)