Tako v.0.5.0 road to v.1.0.0
by Daniel Boros
Nov 11
3 min read
3 views
A lot has landed since we have introduced Tako at first, focused on performance, developer ergonomics, and production‑readiness: SIMD JSON, plugins (compression, CORS, rate limiting), GraphQL, improved routing, WebSockets/SSE/HTTP2/TLS, and more.
Live production OpenAPI service (stochastic simulation demo) running on Tako: https://stochastic-api-production.up.railway.app/
Benchmarks: SIMD JSON improvements
Real load against a heavy JSON endpoint showed a solid throughput gain after enabling SIMD‑accelerated JSON parsing.
Before SIMD
wrk -t4 -c100 -d15s -s post.lua http://localhost:8080/diffusion/gbm
Running 15s test @ http://localhost:8080/diffusion/gbm
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.37s 410.43ms 1.99s 59.09%
Req/Sec 9.07 7.53 40.00 70.53%
258 requests in 15.10s, 24.29GB read
Socket errors: connect 0, read 0, write 0, timeout 236
Requests/sec: 17.09
Transfer/sec: 1.61GB
After enabling SIMD JSON
wrk -t4 -c100 -d15s -s post.lua http://localhost:8080/diffusion/gbm
Running 15s test @ http://localhost:8080/diffusion/gbm
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.36s 416.70ms 1.94s 63.27%
Req/Sec 10.14 7.74 40.00 66.82%
319 requests in 15.10s, 29.71GB read
Socket errors: connect 0, read 0, write 0, timeout 270
Requests/sec: 21.12
Transfer/sec: 1.97GB
Summary: ~24% higher RPS and ~22% higher transfer/sec on a JSON‑heavy workload.
Major changes
- SIMD‑accelerated JSON extractor (feature‑gated)
- Plugin system with route‑level plugins
- Compression (gzip, brotli, deflate, optional zstd; streaming; min‑size thresholds)
- CORS (preflight, origins/methods/headers, credentials, max‑age)
- Rate limiter (token bucket; per‑IP; refill/burst)
- Async GraphQL integration (HTTP + subscriptions) and optional GraphiQL helper and even more a Generic Ws impl for ergonomics
- Routing improvements: matchit router, TSR redirects, fallback handlers, memory optimizations
- DX improvements: handler macro (multiple extractor arguments), Responder for anyhow::Result, redirect helpers
- Realtime/media: WebSockets (incl. HTTP/2 example), SSE, file streaming, Range extractor
- Security and formats: JWT extractors, Protobuf (feature), TLS backend
SIMD JSON extractor
High‑throughput JSON parsing for large payloads; usable as both extractor and responder.
use tako::extractors::simdjson::SimdJson;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct ApiResponse {
success: bool,
message: String,
}
async fn api_handler(SimdJson(request): SimdJson<ApiResponse>) -> SimdJson<ApiResponse> {
// Process the request...
SimdJson(ApiResponse {
success: true,
message: "Request processed successfully".to_string(),
})
}
Plugins: compression, CORS, rate limiting
Apply globally or to specific routes.
Global compression (streaming brotli/gzip example):
router.plugin(
CompressionBuilder::new()
.enable_gzip(true)
.enable_brotli(true)
.enable_stream(true)
//.enable_zstd(true)
.min_size(1024)
.brotli_level(9)
.build(),
);
Route‑level plugin (illustrative):
let mut router = Router::new();
let route = router.route(Method::GET, "/api/data", handler);
// Apply CORS only to this route
let cors = CorsBuilder::new()
.allow_origin("https://example.com")
.build();
route.plugin(cors);
GraphQL: HTTP and subscriptions
Extractors/Responders for HTTP; subscription driver for WS.
// POST /graphql
router.route(Method::POST, "/graphql", {
let schema = schema.clone();
move |GraphQLRequest(req): GraphQLRequest| {
let schema = schema.clone();
async move {
let resp = schema.execute(req).await;
GraphQLResponse(resp)
}
}
});
// GET /ws for subscriptions (graphql-transport-ws or graphql-ws)
router.route(Method::GET, "/ws", {
let schema = schema.clone();
move |req: TakoRequest| {
let schema = schema.clone();
async move { GraphQLSubscription::new(req, schema) }
}
});
Routing and DX
- Trailing Slash Redirect (TSR) and custom fallback handlers
- Handler macro allows multiple extractor arguments; easy ergonomic signatures
- anyhow::Result implements Responder for simpler error returns
Fallbacks (illustrative):
let mut router = Router::new();
router.route(Method::GET, "/", |_req| async { "Hello" });
// Simple fallback
router.fallback(|_req| async { NOT_FOUND });
// Or with extractors
router.fallback_with_extractors(|Path(id): Path<String>| async move {
(StatusCode::NOT_FOUND, format!("No route for {id}"))
});
WebSockets
Built‑in WS driver and examples (echo + ticker):
pub async fn ws_echo(req: Request) -> impl Responder {
TakoWs::new(req, |mut ws| async move {
let _ = ws.send(Message::Text("Welcome to Tako WS!".into())).await;
while let Some(Ok(msg)) = ws.next().await {
match msg {
Message::Text(txt) => {
let _ = ws
.send(Message::Text(Utf8Bytes::from(format!("Echo: {txt}"))))
.await;
}
Message::Binary(bin) => {
let _ = ws.send(Message::Binary(bin)).await;
}
Message::Ping(p) => {
let _ = ws.send(Message::Pong(p)).await;
}
Message::Close(_) => {
let _ = ws.send(Message::Close(None)).await;
break;
}
_ => {}
}
}
})
}
Routes:
router.route(Method::GET, "/ws/echo", ws_echo);
router.route(Method::GET, "/ws/tick", ws_tick);
Redirect helpers
Convenience functions for common redirect responses.
use tako::{redirect, responder::Responder};
async fn go_home() -> impl Responder {
redirect::found("/")
}
async fn login_redirect() -> impl Responder {
// Preserve method (307) or change to GET (303) depending on needs
redirect::temporary("/login")
}
- Github: https://github.com/rust-dd/tako
- Crates.io: https://crates.io/crates/tako-rs
- Docs.rs: https://docs.rs/tako-rs/0.5.1/tako/
If you like it drop a ⭐ on Github or just open an issue if something you dont like.
Made by love! 🦀🦀