Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions frameworks/axum/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "httparena-axum"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.6", features = ["compression-gzip"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.release]
opt-level = 3
codegen-units = 1
lto = "thin"
11 changes: 11 additions & 0 deletions frameworks/axum/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM rust:1.94 AS build
WORKDIR /app
COPY Cargo.toml .
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src/ target/release/httparena-axum* target/release/deps/httparena_axum*
COPY src ./src
RUN cargo build --release

FROM debian:bookworm-slim
COPY --from=build /app/target/release/httparena-axum /server
EXPOSE 8080
CMD ["/server"]
26 changes: 26 additions & 0 deletions frameworks/axum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# axum

Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration.

## Stack

- **Language:** Rust 1.94
- **Framework:** Axum 0.8
- **Build:** Multi-stage, `debian:bookworm-slim` runtime

## Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/pipeline` | GET | Returns `ok` (plain text) |
| `/baseline11` | GET | Sums query parameter values |
| `/baseline11` | POST | Sums query parameters + request body |
| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` |
| `/upload` | POST | Reads the body and returns the byte count |

## Notes

- Routing and extraction through the Axum `Path`, `Query` and body extractors
- JSON through the `Json` response, serialized by serde per request
- Compression through the tower-http `CompressionLayer`
- The dataset is leaked once at startup so responses borrow it instead of cloning
19 changes: 19 additions & 0 deletions frameworks/axum/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"display_name": "axum",
"language": "Rust",
"type": "flagship",
"mode": "standard",
"engine": "hyper",
"description": "Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration. Routing and path/query extractors through the Axum API, serde_json via the Json response, gzip through the tower-http compression layer.",
"repo": "https://github.com/tokio-rs/axum",
"enabled": true,
"tests": [
"baseline",
"pipelined",
"limited-conn",
"json",
"json-comp",
"upload"
],
"maintainers": []
}
133 changes: 133 additions & 0 deletions frameworks/axum/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use std::collections::HashMap;

use axum::body::Bytes;
use axum::extract::{DefaultBodyLimit, Path, Query, State};
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::{Deserialize, Serialize};
use tower_http::compression::CompressionLayer;

const MAX_BODY: usize = 25 * 1024 * 1024;

#[derive(Deserialize)]
struct Rating {
score: i64,
count: i64,
}

#[derive(Deserialize)]
struct DatasetItem {
id: i64,
name: String,
category: String,
price: i64,
quantity: i64,
active: bool,
tags: Vec<String>,
rating: Rating,
}

#[derive(Serialize)]
struct ProcessedRating {
score: i64,
count: i64,
}

#[derive(Serialize)]
struct ProcessedItem<'a> {
id: i64,
name: &'a str,
category: &'a str,
price: i64,
quantity: i64,
active: bool,
tags: &'a [String],
rating: ProcessedRating,
total: i64,
}

#[derive(Serialize)]
struct ProcessResponse<'a> {
items: Vec<ProcessedItem<'a>>,
count: usize,
}

#[derive(Deserialize)]
struct JsonParams {
m: Option<i64>,
}

fn load_dataset() -> Vec<DatasetItem> {
let path = std::env::var("DATASET_PATH").unwrap_or_else(|_| "/data/dataset.json".to_string());
match std::fs::read_to_string(path) {
Ok(data) => serde_json::from_str(&data).unwrap_or_default(),
Err(_) => Vec::new(),
}
}

async fn pipeline() -> &'static str {
"ok"
}

async fn baseline11(Query(params): Query<HashMap<String, String>>, body: String) -> String {
let mut sum: i64 = params
.values()
.filter_map(|value| value.parse::<i64>().ok())
.sum();
if let Ok(n) = body.trim().parse::<i64>() {
sum += n;
}
sum.to_string()
}

async fn json_items(
State(dataset): State<&'static [DatasetItem]>,
Path(count): Path<usize>,
Query(params): Query<JsonParams>,
) -> Json<ProcessResponse<'static>> {
let count = count.min(dataset.len());
let m = params.m.unwrap_or(1);

let items = dataset[..count]
.iter()
.map(|item| ProcessedItem {
id: item.id,
name: &item.name,
category: &item.category,
price: item.price,
quantity: item.quantity,
active: item.active,
tags: &item.tags,
rating: ProcessedRating {
score: item.rating.score,
count: item.rating.count,
},
total: item.price * item.quantity * m,
})
.collect();

Json(ProcessResponse { items, count })
}

async fn upload(body: Bytes) -> String {
body.len().to_string()
}

#[tokio::main]
async fn main() {
// Leaked once at startup so handlers can borrow the items instead of
// cloning every string into the response.
let dataset: &'static [DatasetItem] = Box::leak(load_dataset().into_boxed_slice());

let app = Router::new()
.route("/pipeline", get(pipeline))
.route("/baseline11", get(baseline11).post(baseline11))
.route("/json/{count}", get(json_items))
.route("/upload", post(upload))
.layer(CompressionLayer::new())
.layer(DefaultBodyLimit::max(MAX_BODY))
.with_state(dataset);

let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
2 changes: 1 addition & 1 deletion site/data/current.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"kernel": "6.17.0-22-generic",
"docker": "29.3.0",
"docker_runtime": "runc",
"governor": "performance",
"governor": "powersave",
"tcp": {
"lo_mtu": "1500",
"congestion": "cubic",
Expand Down
8 changes: 8 additions & 0 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@
"engine": "kestrel",
"mode": "standard"
},
"axum": {
"dir": "axum",
"description": "Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration. Routing and path/query extractors through the Axum API, serde_json via the Json response, gzip through the tower-http compression layer.",
"repo": "https://github.com/tokio-rs/axum",
"type": "flagship",
"engine": "hyper",
"mode": "standard"
},
"bananabread": {
"dir": "bananabread",
"description": "Seagreen \u2014 a TypeScript port of GenHTTP \u2014 on Bun's raw-TCP engine, with kotlinx-style decorators and Bun's built-in SQL.",
Expand Down
Loading