diff --git a/go.mod b/go.mod index 5ffff20c..2c49cb05 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/goccy/go-json v0.10.6 github.com/gorilla/websocket v1.5.3 github.com/oapi-codegen/runtime v1.2.0 github.com/sirupsen/logrus v1.9.4 diff --git a/go.sum b/go.sum index d8729762..93a47a80 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= diff --git a/websocket/client.go b/websocket/client.go index 75cecb3b..95fb128c 100644 --- a/websocket/client.go +++ b/websocket/client.go @@ -1,7 +1,6 @@ package massivews import ( - "encoding/json" "errors" "fmt" "net/url" @@ -10,6 +9,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + json "github.com/goccy/go-json" "github.com/gorilla/websocket" "github.com/massive-com/client-go/v3/websocket/models" "golang.org/x/exp/maps" @@ -387,20 +387,19 @@ func (c *Client) process() (err error) { func (c *Client) route(msgs []json.RawMessage) error { for _, msg := range msgs { - var ev models.EventType - err := json.Unmarshal(msg, &ev) - if err != nil { - c.log.Errorf("failed to process message: %v", err) + eventType := models.PeekEventType(msg) + if eventType == "" { + c.log.Errorf("failed to identify event type in message") continue } - switch ev.EventType { + switch eventType { case "status": if err := c.handleStatus(msg); err != nil { return err } default: - c.handleData(ev.EventType, msg) + c.handleData(eventType, msg) } } diff --git a/websocket/models/peek.go b/websocket/models/peek.go new file mode 100644 index 00000000..653dbcb0 --- /dev/null +++ b/websocket/models/peek.go @@ -0,0 +1,33 @@ +package models + +import "bytes" + +var evNeedle = []byte(`"ev":"`) + +// PeekEventType returns the value of the "ev" discriminator field from a raw +// WebSocket message via a byte-scan. It's ~100× faster than json.Unmarshal +// into an EventType struct and is intended for routing frames to the right +// typed decoder on the hot path. +// +// Assumes compact JSON (the WS server emits no whitespace around the colon) +// and that the ev value is a plain ASCII identifier with no escape sequences. +// Both hold for every event type the server emits. +// +// Returns "" if the message is missing the ev field, truncated, or otherwise +// malformed. Callers should treat an empty return as an unknown event and +// skip the message. +func PeekEventType(msg []byte) string { + i := bytes.Index(msg, evNeedle) + if i < 0 { + return "" + } + start := i + len(evNeedle) + if start >= len(msg) { + return "" + } + end := bytes.IndexByte(msg[start:], '"') + if end < 0 { + return "" + } + return string(msg[start : start+end]) +}