Tako Framework

repository·main·Indexed 41 days ago

https://github.com/rust-dd/tako

A pragmatic, extensible Rust framework for building modern network services. It supports multiple transports including HTTP/1.1, HTTP/2, HTTP/3 (QUIC), gRPC, WebSockets, TCP, UDP, and Unix sockets using a single routing and middleware model. Features include 22+ typed extractors, built-in security middleware, and efficient file streaming via FileStream.

Tokens
116K
Snippets
321
Records
525
Agent score
51%

What's inside Tako

  1. Overview of the Tako framework

    main

    Tako is a multi-transport Rust framework designed for services that require more than plain HTTP. It provides a unified application surface for multiple protocols including HTTP/1.1, HTTP/2, HTTP/3, WebSocket, SSE, gRPC, TCP, UDP, Unix sockets, and WebTransport.

    Key features include:

    • Unified Model: A single routing, middleware, and observability model across all supported transports.
    • Runtime Flexibility: Runs on either Tokio or Compio.
    • Extensibility: Built as a collection of workspace crates re-exported through the tako-rs umbrella.

    If you are building a standard JSON-over-HTTP service, you can use Tako as a straight HTTP framework and ignore the multi-transport capabilities.

  2. Overview of Tako features and capabilities

    main

    Tako is a multi-transport Rust framework designed for services that require more than just HTTP. It provides a unified model for routing, middleware, and observability across various protocols.

    Supported Transports

    • HTTP/1.1, HTTP/2, HTTP/3 (QUIC)
    • WebSocket, WebTransport, SSE
    • gRPC
    • TCP, UDP
    • Unix sockets
    • PROXY protocol

    Extraction Capabilities

    Supports 22+ typed extractors including:

    • JSON (with optional SIMD acceleration)
    • Form, Query, Path, Headers, Cookies
    • JWT claims, API keys
    • Accept, Range, Protobuf, Multipart

    Middleware Support

    Built-in middleware for:

    • Authentication (JWT, Basic, Bearer, API-key)
    • Security (CSRF, Security headers, CORS, Body limits)
    • Reliability (Rate limiting, Idempotency, Sessions)
    • Observability & Performance (Metrics, Request IDs, Compression, Graceful shutdown)
  3. Use Extractors to handle request data

    main

    Extractors are typed handler arguments that read specific shapes out of an incoming request (such as JSON, query parameters, or headers) and surface them as typed variables.

    To use an extractor, include it as a parameter in your asynchronous handler function. You typically destructure the extractor wrapper to access the underlying data.

    Key Rules:

    • Body Extractors (implementing FromRequest) consume the request body. Because the body can only be read once, these must be the last argument in your handler's parameter list.
    • Parts Extractors (implementing FromRequestParts) only access headers or the URI. These can appear in any order and any number of times before the body extractor.
    • Error Handling: If an extractor fails (e.g., malformed JSON or missing parameters), the handler is not called, and the extractor's error is converted into a Responder (typically returning a 400 or 401 status code).
    use serde::{Deserialize, Serialize};
    use tako::Method;
    use tako::extractors::json::Json;
    use tako::extractors::path::Path;
    use tako::extractors::query::Query;
    use tako::extractors::state::State;
    use tako::responder::Responder;
    use tako::router::Router;
    
    #[derive(Deserialize)]
    struct ListQuery { page: u32, per_page: u32 }
    
    #[derive(Deserialize)]
    struct UserPath { id: u64 }
    
    #[derive(Deserialize, Serialize)]
    struct CreatePost { title: String, body: String }
    
    #[derive(Clone)]
    struct Db; 
    
    // Example: Using multiple extractors (Path, Query, and State)
    async fn list_posts(
      Path(UserPath { id }): Path<UserPath>,
      Query(q): Query<ListQuery>,
      State(_db): State<Db>,
    ) -> impl Responder {
      format!("user={id}, page={}, per_page={}", q.page, q.per_page)
    }
    
    // Example: Using Path and Json (Json is the body extractor, so it is last)
    async fn create_post(
      Path(UserPath { id }): Path<UserPath>,
      Json(p): Json<CreatePost>,
    ) -> Json<CreatePost> {
      println!("creating post for user={id}: {:?}", p.title);
      Json(p)
    }
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
      let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
    
      let mut router = Router::new();
      router.with_state(Db);
      router.route(Method::GET,  "/users/{id}/posts", list_posts);
      router.route(Method::POST, "/users/{id}/posts", create_post);
    
      tako::serve(listener, router).await;
      Ok(())
    }
  4. How middleware works in Tako

    main

    Middleware in Tako wraps a handler with cross-cutting logic (such as authentication, logging, or rate limiting) without modifying the handler itself.

    A middleware is conceptually a function with the signature Fn(Request, Next) -> Future<Output = Response>.

    • To continue the request processing chain, call next.run(req).await.
    • To short-circuit the request (e.g., returning a 401 Unauthorized for failed authentication), return a Response early without calling next.run().

    Most bundled middleware use a builder pattern. You configure the middleware using builder methods and then call .into_middleware() to convert it into the function type required for registration.

    use tako::middleware::IntoMiddleware;
    use tako::middleware::basic_auth::BasicAuth;
    
    let mw = BasicAuth::single("admin", "pw")
      .realm("Admin")
      .into_middleware();
  5. Tako workspace crate layout

    main

    Tako is a Cargo workspace consisting of eight crates. While the umbrella tako-rs re-exports everything under the tako::* namespace for users, the individual crates are organized as follows:

    CrateOwns
    tako-rsUmbrella re-export, cargo feature surface, prelude, allocator hook
    tako-rs-coreRouter, handlers, middleware/plugin traits, body/request/response types, state, signals, queue, GraphQL / gRPC / OpenAPI helpers
    tako-rs-extractorsConcrete request extractors
    tako-rs-serverHTTP/1.1, HTTP/2, HTTP/3, TLS, raw TCP / UDP / Unix, PROXY protocol, compio variants
    tako-rs-streamsWebSocket, SSE, file streaming, static files, WebTransport
    tako-rs-pluginsBundled middleware and plugins
    tako-rs-macrosThe #[tako::route] / #[tako::get] attribute family
    tako-rs-server-ptThread-per-core entry point

    Note on Naming: The workspace publishes under the tako-rs-* prefix on crates.io to avoid conflict with an unrelated party owning the tako-* prefix. Application code should depend on the umbrella tako-rs and import it as tako.

  6. Configure URI parts and trusted proxies

    main

    The UriPartsConfig can be looked up through multiple layers to determine trusted proxies and URI components. When configuring trusted proxies, the system consults the following hierarchy in order:

    1. Request Extensions: Specific configuration attached to the current request.
    2. Router State: Configuration specific to the current router.
    3. Process Global State: The global application configuration.

    This multi-layer lookup ensures that per-request or per-router overrides are respected before falling back to global settings.

  7. Consistent Form content-type matching

    main
    The Form and FormBorrowed extractors use starts_with logic for Content-Type validation. This ensures that variations like application/x-www-form-urlencoded; charset=utf-8 are correctly recognized and handled consistently.
  8. Handle method-aware responses (405 Method Not Allowed)

    main
    In Tako 2.0, if a request matches a path but uses a method not registered for that path, the router automatically returns a 405 Method Not Allowed response. This response includes an Allow header listing the methods that are supported for that specific path.
  9. Monitor Queue lifecycle events via Signals

    main

    The queue integrates with the Tako in-process signals bus. Every job lifecycle event emits a signal using canonical string IDs found under tako::queue::signal_ids. You can attach listeners to react to these events.

    Emitted signals include:

    • queue.job.queued
    • queue.job.started
    • queue.job.completed
    • queue.job.failed
    • queue.job.retrying
    • queue.job.dead_letter
  10. Configure GraphQL content-type validation

    main

    The GraphQL implementation in tako-core validates content types to prevent processing invalid or empty requests. It supports the following Content-Type values:

    • application/json
    • application/graphql
    • application/graphql-response+json
    • multipart/form-data

    If a request uses an unsupported content type (e.g., text/plain), the server returns a 415 Unsupported Media Type error. If the request body is empty, it returns a Parse("empty request body") error.

  11. How body extractors work in Tako

    main

    Body extractors consume the request body and deserialize it into a typed handler argument. They implement the FromRequest trait.

    Usage Rules:

    • A handler may take at most one body extractor.
    • The body extractor must come after any request-metadata extractors (e.g., headers, query params) because the body can only be read once.
    • If there is a Content-Type mismatch or a deserialization failure, the extractor returns a 400 Bad Request response and the handler is skipped.
  12. Understand stateful middleware and Stores

    main

    Stateful middleware components—such as sessions, rate limiting, idempotency, JWKS rotation, and CSRF—rely on persistence backends defined by the tako_rs_plugins::stores traits.

    • Default Backend: An in-process scc::HashMap.
    • Extensibility: You can implement these traits to use external storage like Redis or Postgres without modifying your existing handlers or middleware code.