Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Blob #74

Merged
merged 23 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions websockets/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package websockets

import (
"bytes"

"github.com/grafana/sobek"

"go.k6.io/k6/js/common"
)

type Blob struct {

Check warning on line 11 in websockets/blob.go

View workflow job for this annotation

GitHub Actions / checks / lint

exported: exported type Blob should have comment or be unexported (revive)
joanlopez marked this conversation as resolved.
Show resolved Hide resolved
typ string
data bytes.Buffer
}

func (b *Blob) text() string {
return b.data.String()
}

func (r *WebSocketsAPI) blob(call sobek.ConstructorCall) *sobek.Object {
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
var blobParts []interface{}
if len(call.Arguments) > 0 {
if parts, ok := call.Arguments[0].Export().([]interface{}); ok {
joanlopez marked this conversation as resolved.
Show resolved Hide resolved
blobParts = parts
}
}

return newBlob(r.vu.Runtime(), blobParts)
}
joanlopez marked this conversation as resolved.
Show resolved Hide resolved

func newBlob(rt *sobek.Runtime, blobParts []interface{}) *sobek.Object {
b := &Blob{}
if len(blobParts) > 0 {
for _, part := range blobParts {
var err error
switch v := part.(type) {
case []uint8:
_, err = b.data.Write(v)
case string:
_, err = b.data.WriteString(v)
}
if err != nil {
common.Throw(rt, err)
}
}
}

obj := rt.NewObject()

if err := obj.DefineAccessorProperty("type", rt.ToValue(func() sobek.Value {
return rt.ToValue(b.typ)
}), nil, sobek.FLAG_FALSE, sobek.FLAG_TRUE); err != nil {
common.Throw(rt, err)
}

if err := obj.DefineAccessorProperty("size", rt.ToValue(func() sobek.Value {
return rt.ToValue(b.data.Len())
}), nil, sobek.FLAG_FALSE, sobek.FLAG_TRUE); err != nil {
common.Throw(rt, err)
}

if err := obj.Set("text", func(call sobek.FunctionCall) sobek.Value {

Check warning on line 62 in websockets/blob.go

View workflow job for this annotation

GitHub Actions / checks / lint

unused-parameter: parameter 'call' seems to be unused, consider removing or renaming it as _ (revive)
return rt.ToValue(b.text())
}); err != nil {
common.Throw(rt, err)
}

if err := obj.Set("arrayBuffer", func(call sobek.FunctionCall) sobek.Value {

Check warning on line 68 in websockets/blob.go

View workflow job for this annotation

GitHub Actions / checks / lint

unused-parameter: parameter 'call' seems to be unused, consider removing or renaming it as _ (revive)
return rt.ToValue(rt.NewArrayBuffer(b.data.Bytes()))
}); err != nil {
common.Throw(rt, err)
}

return obj
}
22 changes: 10 additions & 12 deletions websockets/websockets.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (r *WebSocketsAPI) Exports() modules.Exports {
return modules.Exports{
Named: map[string]interface{}{
"WebSocket": r.websocket,
"Blob": r.blob,
},
}
}
Expand Down Expand Up @@ -203,9 +204,7 @@ func defineWebsocket(rt *sobek.Runtime, w *webSocket) {
return rt.ToValue(w.binaryType)
}), rt.ToValue(func(s string) error {
switch s {
case blobBinaryType:
return errors.New("blob is currently not supported, only arraybuffer is")
case arraybufferBinaryType:
case blobBinaryType, arraybufferBinaryType:
w.binaryType = s
return nil
default:
Expand Down Expand Up @@ -425,9 +424,6 @@ func (w *webSocket) loop() {
}
}

const binarytypeWarning = `You have not set a Websocket binaryType to "arraybuffer", but you got a binary response. ` +
joanlopez marked this conversation as resolved.
Show resolved Hide resolved
`This has been done automatically now, but in the future this will not work.`

func (w *webSocket) queueMessage(msg *message) {
w.tq.Queue(func() error {
if w.readyState != OPEN {
Expand All @@ -448,13 +444,15 @@ func (w *webSocket) queueMessage(msg *message) {
ev := w.newEvent(events.MESSAGE, msg.t)

if msg.mtype == websocket.BinaryMessage {
if w.binaryType == "" {
w.binaryType = arraybufferBinaryType
w.vu.State().Logger.Warn(binarytypeWarning)
var data any
// Use "blob" as default, as per spec:
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType
if w.binaryType == "" || w.binaryType == blobBinaryType {
joanlopez marked this conversation as resolved.
Show resolved Hide resolved
data = newBlob(rt, []interface{}{msg.data})
} else {
data = rt.NewArrayBuffer(msg.data)
}
// TODO this technically could be BLOB , but we don't support that
ab := rt.NewArrayBuffer(msg.data)
must(rt, ev.DefineDataProperty("data", rt.ToValue(ab), sobek.FLAG_FALSE, sobek.FLAG_FALSE, sobek.FLAG_TRUE))
must(rt, ev.DefineDataProperty("data", rt.ToValue(data), sobek.FLAG_FALSE, sobek.FLAG_FALSE, sobek.FLAG_TRUE))
} else {
must(
rt,
Expand Down
91 changes: 89 additions & 2 deletions websockets/websockets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@

m := new(RootModule).NewModuleInstance(runtime.VU)
require.NoError(t, runtime.VU.RuntimeField.Set("WebSocket", m.Exports().Named["WebSocket"]))
require.NoError(t, runtime.VU.RuntimeField.Set("Blob", m.Exports().Named["Blob"]))
require.NoError(t, runtime.VU.RuntimeField.Set("call", recorder.Call))

runtime.MoveToVUContext(state)
Expand Down Expand Up @@ -315,8 +316,94 @@
`))
require.NoError(t, err)
logs := hook.Drain()
require.Len(t, logs, 1)
require.Contains(t, logs[0].Message, binarytypeWarning)
require.Len(t, logs, 0)
}

func TestBinaryType(t *testing.T) {

Check failure on line 322 in websockets/websockets_test.go

View workflow job for this annotation

GitHub Actions / checks / lint

TestBinaryType's subtests should call t.Parallel (tparallel)
t.Parallel()

t.Run("default", func(t *testing.T) {

Check failure on line 325 in websockets/websockets_test.go

View workflow job for this annotation

GitHub Actions / checks / lint

Function TestBinaryType missing the call to method parallel in the test run
joanlopez marked this conversation as resolved.
Show resolved Hide resolved
ts := newTestState(t)
logger, hook := testutils.NewLoggerWithHook(t, logrus.WarnLevel)
ts.runtime.VU.StateField.Logger = logger
_, err := ts.runtime.RunOnEventLoop(ts.tb.Replacer.Replace(`
var ws = new WebSocket("WSBIN_URL/ws-echo")
ws.addEventListener("open", () => {
const sent = new Uint8Array([164,41]).buffer
ws.send(sent)
ws.onmessage = (e) => {
//if (!(e.data instanceof Blob)) {
// throw new Error("Wrong event.data type; expected: Blob, got: "+ typeof e.data)
//}

if (sent.byteLength !== e.data.arrayBuffer().byteLength) {
throw new Error("The data received isn't equal to the data sent")
}

ws.close()
}
})
`))
require.NoError(t, err)
logs := hook.Drain()
require.Len(t, logs, 0)
})

t.Run("blob", func(t *testing.T) {

Check failure on line 352 in websockets/websockets_test.go

View workflow job for this annotation

GitHub Actions / checks / lint

Function TestBinaryType missing the call to method parallel in the test run
ts := newTestState(t)
logger, hook := testutils.NewLoggerWithHook(t, logrus.WarnLevel)
ts.runtime.VU.StateField.Logger = logger
_, err := ts.runtime.RunOnEventLoop(ts.tb.Replacer.Replace(`
var ws = new WebSocket("WSBIN_URL/ws-echo")
ws.binaryType = "blob"
ws.addEventListener("open", () => {
const sent = new Uint8Array([164,41]).buffer
ws.send(sent)
ws.onmessage = (e) => {
//if (!(e.data instanceof Blob)) {
// throw new Error("Wrong event.data type; expected: Blob, got: "+ typeof e.data)
//}
joanlopez marked this conversation as resolved.
Show resolved Hide resolved

if (sent.byteLength !== e.data.arrayBuffer().byteLength) {
throw new Error("The data received isn't equal to the data sent")
}

ws.close()
}
})
`))
require.NoError(t, err)
logs := hook.Drain()
require.Len(t, logs, 0)
})

t.Run("arraybuffer", func(t *testing.T) {

Check failure on line 380 in websockets/websockets_test.go

View workflow job for this annotation

GitHub Actions / checks / lint

Function TestBinaryType missing the call to method parallel in the test run
ts := newTestState(t)
logger, hook := testutils.NewLoggerWithHook(t, logrus.WarnLevel)
ts.runtime.VU.StateField.Logger = logger
_, err := ts.runtime.RunOnEventLoop(ts.tb.Replacer.Replace(`
var ws = new WebSocket("WSBIN_URL/ws-echo")
ws.binaryType = "arraybuffer"
ws.addEventListener("open", () => {
const sent = new Uint8Array([164,41]).buffer
ws.send(sent)
ws.onmessage = (e) => {
if (!(e.data instanceof ArrayBuffer)) {
throw new Error("Wrong event.data type; expected: ArrayBuffer, got: "+ typeof e.data)
}

if (sent.byteLength !== e.data.byteLength) {
throw new Error("The data received isn't equal to the data sent")
}

ws.close()
}
})
`))
require.NoError(t, err)
logs := hook.Drain()
require.Len(t, logs, 0)
})
}

func TestExceptionDontPanic(t *testing.T) {
Expand Down
Loading