In a quest to optimize the amount of data sent between the server and client I recently decided to try to use MessagePack instead of JSON. The payloads are about 75% of the size in JSON, which is nice. I have not noticed any performance issues related to encoding and decoding.
There is a StackOverflow answer that I used for the basis for this post and @chrismccord helped me with a crucial bit of information. To get LiveUploads to work, a bit of extra code is necessary.
If you spot any improvements or errors, please tell me and I will update the code.
In assets/app.js you import the MessagePack client:
import {Encoder, Decoder} from "@msgpack/msgpack";
const encoder = new Encoder({ ignoreUndefined: true });
const decoder = new Decoder();
....
const liveSocket = new LiveSocket("/live", Socket, {
// hooks and params stuff
encode: (payload, callback) => {
// We convert binary payloads to a Uint8Array, so MessagePack will send pack it.
if (payload.payload instanceof ArrayBuffer) {
payload.payload = new Uint8Array(payload.payload)
}
callback(encoder.encode(payload));
},
decode: (payload, callback) => callback(decoder.decode(payload)),
})
The serializer looks like this :
defmodule MyAppWeb.MsgpackSerializer do
@behaviour Phoenix.Socket.Serializer
alias Phoenix.Socket.Reply
alias Phoenix.Socket.Message
alias Phoenix.Socket.Broadcast
def fastlane!(%Broadcast{} = msg) do
msg = %Message{topic: msg.topic, event: msg.event, payload: msg.payload}
{:socket_push, :binary, encode_to_binary(msg)}
end
def encode!(%Reply{} = reply) do
msg = %Message{
topic: reply.topic,
event: "phx_reply",
ref: reply.ref,
payload: %{status: reply.status, response: reply.payload}
}
{:socket_push, :binary, encode_to_binary(msg)}
end
def encode!(%Message{} = msg) do
{:socket_push, :binary, encode_to_binary(msg)}
end
defp encode_to_binary(msg) do
msg |> Map.from_struct() |> Msgpax.pack!()
end
def decode!(message, _opts) do
decoded = Msgpax.unpack!(mesage)
payload = Map.get(decoded, "payload")
# LiveUpload sends us binary, but MessagePack is always binary so we cannot use the opcode in the opts.
if is_binary(payload) and not String.valid?(payload) do
Map.put(decoded, "payload", {:binary, payload})
else
decoded
end
|> Phoenix.Socket.Message.from_map!()
end
end
And then in your Enpoint
module use the serializer in the socket:
socket("/live", Phoenix.LiveView.Socket,
websocket: [
serializer: [{MyAppWeb.MsgpackSerializer, "2.0.0"}]
]
)