Jan 08, 2025
3 min read
Rust,
MessagePack,
Axum,

Hands-On Guide: How to Use MessagePack to Optimize Data Transmission in HTTP Interfaces with Rust

MessagePack is an efficient binary serialization format. It supports multi-language data exchange. Compared to JSON, it is more compact and faster in processing.

MessagePack is an efficient binary serialization format. It supports multi-language data exchange. Compared to JSON, it is more compact and faster in processing.

MessagePack has a smaller data size. This means it occupies less bandwidth during transmission and is faster. Compared to JSON, MessagePack supports more primitive data types, including 8-bit to 64-bit integers, floating-point numbers, timestamps, and custom extension types.

Despite these advantages, MessagePack has existed for more than a decade. Many experienced developers have never heard of it. This indicates that its popularity is relatively low.

Main Advantages

In my opinion, the greatest advantage of MessagePack is its support for raw binary data.

Imagine a scenario: you need to upload file or image data through an HTTP interface.

There are typically two solutions:

  • First, place the binary data directly in the request body.
  • Second, use multipart/form-data as the parameter protocol.

If you also need to pass other parameters, you can only choose the second method according to the specifications. However, if you need structured parameters, multipart/form-data is not convenient. Although some web frameworks can make it behave similarly to JSON, the processing flow is different.

In this case, MessagePack is a good choice. It allows you to include JSON-like data in the request body while supporting binary data.

Usage Method

In Rust, you can use it through the following dependency:

[dependencies]
rmp-serde = "1.3.0"

Assume there is an interface that needs to send text and binary image data:

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Message {
    id: i32,
    text: String,
    image: Vec<u8>,
}

Serialization code is as follows:

fn main() {
    let img = include_bytes!("../img.png");

    let msg = Message {
        id: 1,
        text: "Hello, world!".to_string(),
        image: img.to_vec(),
    };
    let mut buf = Vec::new();
    msg.serialize(&mut Serializer::new(&mut buf)).unwrap();

    println!("{:?}", buf);
}

When transmitting MessagePack data, the request header Content-Type can be:

  • application/msgpack
  • application/x-msgpack
  • application/*+msgpack

Application in Web Frameworks

If you are developing a Rust project with Axum, you can add this crate:

cargo add axum-msgpack

Usage is very simple:

async fn post_handler(MsgPack(msg): MsgPack<Message>) -> Html<String> {
    let img = msg.image;
    // More operations
    "OK"
}

Returning MessagePack data is also easy:

async fn get_handler() -> MsgPack<Message> {
    let img = include_bytes!("../img.png");

    let msg = Message {
        id: 1,
        text: "Hello, world!".to_string(),
        image: img.to_vec(),
    };
    MsgPack(msg)
}

Support Level

Since MessagePack is simple to implement, almost all mainstream programming languages support it. For more details, see here.

For example, calling it in JavaScript/TypeScript:

import { deepStrictEqual } from "assert";
import { encode, decode } from "@msgpack/msgpack";

const object = {
  id: 1,
  text: "Hello, world!",
  image: Uint8Array.from([1, 2, 3]),
};

const encoded: Uint8Array = encode(object);

// encoded can be put into the http body to transmit to the backend.

deepStrictEqual(decode(encoded), object);