Skip to content

Commit 599534b

Browse files
committed
Implement core API for WASM
Closes #121
1 parent e09e295 commit 599534b

33 files changed

+523
-65
lines changed

accept.go

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (
@@ -41,6 +43,12 @@ type AcceptOptions struct {
4143
}
4244

4345
func verifyClientRequest(w http.ResponseWriter, r *http.Request) error {
46+
if !r.ProtoAtLeast(1, 1) {
47+
err := fmt.Errorf("websocket protocol violation: handshake request must be at least HTTP/1.1: %q", r.Proto)
48+
http.Error(w, err.Error(), http.StatusBadRequest)
49+
return err
50+
}
51+
4452
if !headerValuesContainsToken(r.Header, "Connection", "Upgrade") {
4553
err := fmt.Errorf("websocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
4654
http.Error(w, err.Error(), http.StatusBadRequest)

accept_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (
@@ -45,6 +47,7 @@ func Test_verifyClientHandshake(t *testing.T) {
4547
testCases := []struct {
4648
name string
4749
method string
50+
http1 bool
4851
h map[string]string
4952
success bool
5053
}{
@@ -86,6 +89,16 @@ func Test_verifyClientHandshake(t *testing.T) {
8689
"Sec-WebSocket-Key": "",
8790
},
8891
},
92+
{
93+
name: "badHTTPVersion",
94+
h: map[string]string{
95+
"Connection": "Upgrade",
96+
"Upgrade": "websocket",
97+
"Sec-WebSocket-Version": "13",
98+
"Sec-WebSocket-Key": "meow123",
99+
},
100+
http1: true,
101+
},
89102
{
90103
name: "success",
91104
h: map[string]string{
@@ -106,6 +119,12 @@ func Test_verifyClientHandshake(t *testing.T) {
106119
w := httptest.NewRecorder()
107120
r := httptest.NewRequest(tc.method, "/", nil)
108121

122+
r.ProtoMajor = 1
123+
r.ProtoMinor = 1
124+
if tc.http1 {
125+
r.ProtoMinor = 0
126+
}
127+
109128
for k, v := range tc.h {
110129
r.Header.Set(k, v)
111130
}

ci/wasm.sh

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@ cd "$(git rev-parse --show-toplevel)"
66

77
GOOS=js GOARCH=wasm go vet ./...
88
go install golang.org/x/lint/golint
9-
# Get passing later.
10-
#GOOS=js GOARCH=wasm golint -set_exit_status ./...
11-
GOOS=js GOARCH=wasm go test ./internal/wsjs
9+
GOOS=js GOARCH=wasm golint -set_exit_status ./...
10+
GOOS=js GOARCH=wasm go test ./...

dial.go

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (
@@ -149,6 +151,10 @@ func verifyServerResponse(r *http.Request, resp *http.Response) error {
149151
)
150152
}
151153

154+
if proto := resp.Header.Get("Sec-WebSocket-Protocol"); proto != "" && !headerValuesContainsToken(r.Header, "Sec-WebSocket-Protocol", proto) {
155+
return fmt.Errorf("websocket protocol violation: unexpected Sec-WebSocket-Protocol from server: %q", proto)
156+
}
157+
152158
return nil
153159
}
154160

dial_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (
@@ -97,6 +99,16 @@ func Test_verifyServerHandshake(t *testing.T) {
9799
},
98100
success: false,
99101
},
102+
{
103+
name: "badSecWebSocketProtocol",
104+
response: func(w http.ResponseWriter) {
105+
w.Header().Set("Connection", "Upgrade")
106+
w.Header().Set("Upgrade", "websocket")
107+
w.Header().Set("Sec-WebSocket-Protocol", "xd")
108+
w.WriteHeader(http.StatusSwitchingProtocols)
109+
},
110+
success: false,
111+
},
100112
{
101113
name: "success",
102114
response: func(w http.ResponseWriter) {

doc.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
// Package websocket is a minimal and idiomatic implementation of the WebSocket protocol.
24
//
35
// https://tools.ietf.org/html/rfc6455

example_echo_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

example_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

export_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13 // indirect
2323
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
2424
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72
25+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
2526
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
2627
gotest.tools/gotestsum v0.3.5
2728
mvdan.cc/sh v2.6.4+incompatible

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
9797
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
9898
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws=
9999
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
100+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
100101
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
101102
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
102103
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=

header.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

header_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

internal/echoserver/echoserver.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package echoserver
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
// EchoServer provides a streaming WebSocket echo server
8+
// for use in tests.
9+
func EchoServer(w http.ResponseWriter, r *http.Request) {
10+
11+
}

internal/wsjs/wsjs.go

+45-27
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// +build js
22

33
// Package wsjs implements typed access to the browser javascript WebSocket API.
4+
//
45
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
56
package wsjs
67

78
import (
8-
"context"
99
"syscall/js"
1010
)
1111

@@ -26,17 +26,18 @@ func handleJSError(err *error, onErr func()) {
2626
}
2727
}
2828

29-
func New(ctx context.Context, url string, protocols []string) (c *WebSocket, err error) {
29+
// New is a wrapper around the javascript WebSocket constructor.
30+
func New(url string, protocols []string) (c WebSocket, err error) {
3031
defer handleJSError(&err, func() {
31-
c = nil
32+
c = WebSocket{}
3233
})
3334

3435
jsProtocols := make([]interface{}, len(protocols))
3536
for i, p := range protocols {
3637
jsProtocols[i] = p
3738
}
3839

39-
c = &WebSocket{
40+
c = WebSocket{
4041
v: js.Global().Get("WebSocket").New(url, jsProtocols),
4142
}
4243

@@ -49,6 +50,7 @@ func New(ctx context.Context, url string, protocols []string) (c *WebSocket, err
4950
return c, nil
5051
}
5152

53+
// WebSocket is a wrapper around a javascript WebSocket object.
5254
type WebSocket struct {
5355
Extensions string
5456
Protocol string
@@ -57,29 +59,33 @@ type WebSocket struct {
5759
v js.Value
5860
}
5961

60-
func (c *WebSocket) setBinaryType(typ string) {
62+
func (c WebSocket) setBinaryType(typ string) {
6163
c.v.Set("binaryType", string(typ))
6264
}
6365

64-
func (c *WebSocket) BufferedAmount() uint32 {
65-
return uint32(c.v.Get("bufferedAmount").Int())
66-
}
67-
68-
func (c *WebSocket) addEventListener(eventType string, fn func(e js.Value)) {
69-
c.v.Call("addEventListener", eventType, js.FuncOf(func(this js.Value, args []js.Value) interface{} {
66+
func (c WebSocket) addEventListener(eventType string, fn func(e js.Value)) func() {
67+
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
7068
fn(args[0])
7169
return nil
72-
}))
70+
})
71+
c.v.Call("addEventListener", eventType, f)
72+
73+
return func() {
74+
c.v.Call("removeEventListener", eventType, f)
75+
f.Release()
76+
}
7377
}
7478

79+
// CloseEvent is the type passed to a WebSocket close handler.
7580
type CloseEvent struct {
7681
Code uint16
7782
Reason string
7883
WasClean bool
7984
}
8085

81-
func (c *WebSocket) OnClose(fn func(CloseEvent)) {
82-
c.addEventListener("close", func(e js.Value) {
86+
// OnClose registers a function to be called when the WebSocket is closed.
87+
func (c WebSocket) OnClose(fn func(CloseEvent)) (remove func()) {
88+
return c.addEventListener("close", func(e js.Value) {
8389
ce := CloseEvent{
8490
Code: uint16(e.Get("code").Int()),
8591
Reason: e.Get("reason").String(),
@@ -89,23 +95,29 @@ func (c *WebSocket) OnClose(fn func(CloseEvent)) {
8995
})
9096
}
9197

92-
func (c *WebSocket) OnError(fn func(e js.Value)) {
93-
c.addEventListener("error", fn)
98+
// OnError registers a function to be called when there is an error
99+
// with the WebSocket.
100+
func (c WebSocket) OnError(fn func(e js.Value)) (remove func()) {
101+
return c.addEventListener("error", fn)
94102
}
95103

104+
// MessageEvent is the type passed to a message handler.
96105
type MessageEvent struct {
97-
Data []byte
98-
// There are more types to the interface but we don't use them.
106+
// string or []byte.
107+
Data interface{}
108+
109+
// There are more fields to the interface but we don't use them.
99110
// See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
100111
}
101112

102-
func (c *WebSocket) OnMessage(fn func(m MessageEvent)) {
103-
c.addEventListener("message", func(e js.Value) {
104-
var data []byte
113+
// OnMessage registers a function to be called when the websocket receives a message.
114+
func (c WebSocket) OnMessage(fn func(m MessageEvent)) (remove func()) {
115+
return c.addEventListener("message", func(e js.Value) {
116+
var data interface{}
105117

106118
arrayBuffer := e.Get("data")
107119
if arrayBuffer.Type() == js.TypeString {
108-
data = []byte(arrayBuffer.String())
120+
data = arrayBuffer.String()
109121
} else {
110122
data = extractArrayBuffer(arrayBuffer)
111123
}
@@ -119,23 +131,29 @@ func (c *WebSocket) OnMessage(fn func(m MessageEvent)) {
119131
})
120132
}
121133

122-
func (c *WebSocket) OnOpen(fn func(e js.Value)) {
123-
c.addEventListener("open", fn)
134+
// OnOpen registers a function to be called when the websocket is opened.
135+
func (c WebSocket) OnOpen(fn func(e js.Value)) (remove func()) {
136+
return c.addEventListener("open", fn)
124137
}
125138

126-
func (c *WebSocket) Close(code int, reason string) (err error) {
139+
// Close closes the WebSocket with the given code and reason.
140+
func (c WebSocket) Close(code int, reason string) (err error) {
127141
defer handleJSError(&err, nil)
128142
c.v.Call("close", code, reason)
129143
return err
130144
}
131145

132-
func (c *WebSocket) SendText(v string) (err error) {
146+
// SendText sends the given string as a text message
147+
// on the WebSocket.
148+
func (c WebSocket) SendText(v string) (err error) {
133149
defer handleJSError(&err, nil)
134150
c.v.Call("send", v)
135151
return err
136152
}
137153

138-
func (c *WebSocket) SendBytes(v []byte) (err error) {
154+
// SendBytes sends the given message as a binary message
155+
// on the WebSocket.
156+
func (c WebSocket) SendBytes(v []byte) (err error) {
139157
defer handleJSError(&err, nil)
140158
c.v.Call("send", uint8Array(v))
141159
return err

internal/wsjs/wsjs_test.go

-26
This file was deleted.

netconn.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (c *netConn) Read(p []byte) (int, error) {
9393
}
9494

9595
if c.reader == nil {
96-
typ, r, err := c.c.Reader(c.readContext)
96+
typ, r, err := c.netConnReader(c.readContext)
9797
if err != nil {
9898
var ce CloseError
9999
if errors.As(err, &ce) && (ce.Code == StatusNormalClosure) || (ce.Code == StatusGoingAway) {

netconn_js.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// +build js
2+
3+
package websocket
4+
5+
import (
6+
"bytes"
7+
"context"
8+
"io"
9+
)
10+
11+
func (c *netConn) netConnReader(ctx context.Context) (MessageType, io.Reader, error) {
12+
typ, p, err := c.c.Read(ctx)
13+
if err != nil {
14+
return 0, nil, err
15+
}
16+
return typ, bytes.NewReader(p), nil
17+
}

0 commit comments

Comments
 (0)