#http-response #stream #http #web #web-framework #http-body #axus

axum-streams

HTTP body streaming support for Axum: json/csv/protobuf/arrow/txt

18 releases (9 breaking)

0.14.2 Apr 14, 2024
0.14.0 Mar 31, 2024
0.12.0 Dec 11, 2023
0.10.0 Nov 9, 2023
0.5.0 Jul 31, 2022

#307 in Web programming

Download history 313/week @ 2024-01-24 422/week @ 2024-01-31 352/week @ 2024-02-07 303/week @ 2024-02-14 318/week @ 2024-02-21 456/week @ 2024-02-28 588/week @ 2024-03-06 1028/week @ 2024-03-13 989/week @ 2024-03-20 1137/week @ 2024-03-27 1273/week @ 2024-04-03 1708/week @ 2024-04-10 1928/week @ 2024-04-17 2117/week @ 2024-04-24 1171/week @ 2024-05-01 1322/week @ 2024-05-08

6,773 downloads per month
Used in reqwest-streams

Apache-2.0

60KB
1.5K SLoC

Cargo tests and formatting security audit

axum streams for Rust

Library provides HTTP response streaming support for axum web framework:

  • JSON array stream format
    • Support for simple envelopes structures when you need to include your array inside some object (only for first level)
  • JSON lines stream format
  • CSV stream
  • Protobuf len-prefixed stream format
  • Apache Arrow IPC stream format
  • Text stream

This type of responses are useful when you are reading huge stream of objects from some source (such as database, file, etc) and want to avoid huge memory allocation.

Quick start

Cargo.toml:

[dependencies]
axum-streams = { version = "0.14", features=["json", "csv", "protobuf", "text"] }

Compatibility matrix

axum axum-streams
0.7 v0.11+
0.6 v0.9-v0.10
0.5 0.7

Example code:


#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyTestStructure {
  some_test_field: String
}

fn my_source_stream() -> impl Stream<Item=MyTestStructure> {
  // Simulating a stream with a plain vector and throttling to show how it works
  stream::iter(vec![
    MyTestStructure {
      some_test_field: "test1".to_string()
    }; 1000
  ]).throttle(std::time::Duration::from_millis(50))
}

async fn test_json_array_stream() -> impl IntoResponse {
  StreamBodyAs::json_array(source_test_stream())
}

async fn test_json_nl_stream() -> impl IntoResponse {
  StreamBodyAs::json_nl(source_test_stream())
}

async fn test_csv_stream() -> impl IntoResponse {
  StreamBodyAs::csv(source_test_stream())
}

async fn test_text_stream() -> impl IntoResponse {
  StreamBodyAs::text(source_test_stream())
}

All examples available at examples directory.

To run example use:

# cargo run --example json-example --features json

Need client support?

There is the same functionality for:

Configuration of the frame size

By default, the library produces an HTTP frame per item in the stream. You can change this is using StreamAsOptions:

    StreamBodyAsOptions::new().buffering_ready_items(1000)
        .json_array(source_test_stream())

JSON array inside another object

Sometimes you need to include your array inside some object, e.g.:

{
  "some_status_field": "ok",
  "data": [
    {
      "some_test_field": "test1"
    },
    {
      "some_test_field": "test2"
    }
  ]
}

The wrapping object that includes data field here is called envelope further.

You need to define both of your structures: envelope and records inside:

#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyEnvelopeStructure {
    something_else: String,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    data: Vec<MyItem>
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyItem {
  some_test_field: String
}

And use json_array_with_envelope instead of json_array. Have a look at json-array-complex-structure.rs for detail example.

The support is limited:

  • Only first level of nesting is supported to avoid complex implementation with performance impact.
  • You need either remove the target array field from envelope structure or use this Serde trick on the field to avoid JSON serialization issues:
    #[serde(skip_serializing_if = "Vec::is_empty")]

Licence

Apache Software License (ASL)

Author

Abdulla Abdurakhmanov

Dependencies

~6–16MB
~180K SLoC