tako/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! A lightweight, modular web framework for async applications.
4//!
5//! Tako focuses on ergonomics and composability. It provides routing, extractors,
6//! responses, middleware, streaming, WebSockets/SSE, optional TLS, and integrations
7//! like GraphQL — in a small, pragmatic package.
8//!
9//! # High-level features
10//! - Macro-free routing with dynamic path params and TSR support
11//! - Type-safe handlers with extractor-based arguments (Axum-like ergonomics)
12//! - Simple `Responder` trait to return strings, tuples, or full responses
13//! - Middleware pipeline (auth, body limits, etc.) and optional plugins (CORS, compression, rate limits)
14//! - Streaming bodies, file serving, range requests, and SSE
15//! - WebSocket upgrades and helpers
16//! - Optional TLS (rustls) and HTTP/2 (feature)
17//! - Optional GraphQL support (async-graphql) and GraphiQL UI
18//!
19//! # Compatibility
20//! - Runtime: `tokio`
21//! - HTTP: `hyper` 1.x
22//!
23//! # Quickstart
24//!
25//! ```rust
26//! use tako::{Method, router::Router, responder::Responder, types::Request};
27//!
28//! async fn hello(_: Request) -> impl Responder { "Hello, World!" }
29//!
30//! let mut router = Router::new();
31//! router.route(Method::GET, "/", hello);
32//! ```
33//!
34//! # Key concepts
35//! - [router::Router] manages routes, middleware and dispatch.
36//! - [extractors] parse request data (headers, params, JSON, forms, etc.).
37//! - [responder::Responder] converts return values into HTTP responses.
38//! - [middleware] composes cross-cutting concerns.
39//!
40//! - Static file serving (module `static`) and [file_stream] provide static and streaming file responses.
41//! - [ws] and [sse] enable real-time communication.
42//! - [plugins] add CORS, compression, and rate limiting (feature: `plugins`).
43//! - [graphql] and [graphiql] add GraphQL support (feature: `async-graphql` / `graphiql`).
44//!
45//! # Feature flags
46//! - `client` — outbound HTTP clients over TCP/TLS
47//! - `file-stream` — file streaming utilities
48//! - `http2` — enable ALPN h2 in TLS server
49//! - `jemalloc` — use jemalloc as global allocator
50//! - `multipart` — multipart form-data extractors
51//! - `plugins` — CORS, compression, rate limiting
52//! - `protobuf` — protobuf extractors (prost)
53//! - `simd` — SIMD JSON extractor (simd-json)
54//! - `tls` — TLS server (rustls)
55//! - `tako-tracing` — structured tracing subscriber
56//! - `zstd` — Zstandard compression option within plugins::compression
57
58use std::io::ErrorKind;
59use std::io::Write;
60use std::io::{self};
61use std::net::SocketAddr;
62use std::str::FromStr;
63
64/// HTTP request and response body handling utilities.
65pub mod body;
66
67/// HTTP client implementation for making outbound requests.
68#[cfg(feature = "client")]
69#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
70pub mod client;
71
72/// Request data extraction utilities for parsing query params, JSON, and more.
73pub mod extractors;
74
75/// File streaming utilities for serving files.
76#[cfg(feature = "file-stream")]
77#[cfg_attr(docsrs, doc(cfg(feature = "file-stream")))]
78pub mod file_stream;
79
80/// Request handler traits and implementations.
81mod handler;
82
83/// Middleware for processing requests and responses in a pipeline.
84pub mod middleware;
85
86/// Plugin system for extending framework functionality.
87#[cfg(feature = "plugins")]
88#[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
89pub mod plugins;
90
91/// Response generation utilities and traits.
92pub mod responder;
93
94/// Redirection utilities for handling HTTP redirects.
95pub mod redirect;
96
97/// Route definition and matching logic.
98mod route;
99
100/// Request routing and dispatch functionality.
101pub mod router;
102
103/// HTTP server implementation and configuration.
104#[cfg(not(feature = "compio"))]
105mod server;
106
107/// Server-Sent Events (SSE) support for real-time communication.
108pub mod sse;
109
110/// Application state management and dependency injection.
111pub mod state;
112
113#[cfg(feature = "signals")]
114/// In-process signal arbiter for custom events.
115pub mod signals;
116
117/// Static file serving utilities.
118pub mod r#static;
119
120/// Distributed tracing integration for observability.
121#[cfg(feature = "tako-tracing")]
122#[cfg_attr(docsrs, doc(cfg(feature = "tako-tracing")))]
123pub mod tracing;
124
125/// Core type definitions used throughout the framework.
126pub mod types;
127
128/// WebSocket connection handling and message processing.
129#[cfg(not(feature = "compio"))]
130pub mod ws;
131
132/// GraphQL support (request extractors, responses, and subscriptions).
133#[cfg(feature = "async-graphql")]
134#[cfg_attr(docsrs, doc(cfg(feature = "async-graphql")))]
135pub mod graphql;
136
137/// GraphiQL UI helpers.
138#[cfg(feature = "graphiql")]
139#[cfg_attr(docsrs, doc(cfg(feature = "graphiql")))]
140pub mod graphiql;
141
142/// OpenAPI documentation generation integrations (utoipa, vespera).
143#[cfg(any(feature = "utoipa", feature = "vespera"))]
144#[cfg_attr(docsrs, doc(cfg(any(feature = "utoipa", feature = "vespera"))))]
145pub mod openapi;
146
147#[cfg(feature = "zero-copy-extractors")]
148#[cfg_attr(docsrs, doc(cfg(feature = "zero-copy-extractors")))]
149pub mod zero_copy_extractors;
150
151pub use bytes::Bytes;
152pub use http::Method;
153pub use http::StatusCode;
154pub use http::header;
155pub use http_body_util::Full;
156pub use responder::NOT_FOUND;
157/// Starts the HTTP server with the given listener and router.
158///
159/// This is the main entry point for starting a Tako web server. The function takes
160/// ownership of a TCP listener and router, then serves incoming connections until
161/// the server is shut down.
162///
163/// # Examples
164///
165/// ```rust,no_run
166/// use tako::{serve, router::Router};
167/// use tokio::net::TcpListener;
168///
169/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
170/// let listener = TcpListener::bind("127.0.0.1:8080").await?;
171/// let router = Router::new();
172/// serve(listener, router).await;
173/// # Ok(())
174/// # }
175/// ```
176#[cfg(not(feature = "compio"))]
177pub use server::serve;
178
179#[cfg(feature = "compio")]
180pub use server_compio::serve;
181
182/// Bind a TCP listener for `addr`, asking interactively to increment the port
183/// if it is already in use.
184///
185/// This helper is primarily intended for local development and example binaries.
186/// It will keep proposing the next port number until a free one is found or
187/// the user declines.
188#[cfg(not(feature = "compio"))]
189pub async fn bind_with_port_fallback(addr: &str) -> io::Result<tokio::net::TcpListener> {
190  let mut socket_addr =
191    SocketAddr::from_str(addr).map_err(|e| io::Error::new(ErrorKind::InvalidInput, e))?;
192  let start_port = socket_addr.port();
193
194  loop {
195    let addr_str = socket_addr.to_string();
196    match tokio::net::TcpListener::bind(&addr_str).await {
197      Ok(listener) => {
198        if socket_addr.port() != start_port {
199          println!(
200            "Port {} was in use, starting on {} instead",
201            start_port,
202            socket_addr.port()
203          );
204        }
205        return Ok(listener);
206      }
207      Err(err) if err.kind() == ErrorKind::AddrInUse => {
208        let next_port = socket_addr.port().saturating_add(1);
209        if !ask_to_use_next_port(socket_addr.port(), next_port)? {
210          return Err(err);
211        }
212        socket_addr.set_port(next_port);
213      }
214      Err(err) => return Err(err),
215    }
216  }
217}
218
219#[cfg(feature = "compio")]
220pub async fn bind_with_port_fallback(addr: &str) -> io::Result<compio::net::TcpListener> {
221  let mut socket_addr =
222    SocketAddr::from_str(addr).map_err(|e| io::Error::new(ErrorKind::InvalidInput, e))?;
223  let start_port = socket_addr.port();
224
225  loop {
226    let addr_str = socket_addr.to_string();
227    match compio::net::TcpListener::bind(&addr_str).await {
228      Ok(listener) => {
229        if socket_addr.port() != start_port {
230          println!(
231            "Port {} was in use, starting on {} instead",
232            start_port,
233            socket_addr.port()
234          );
235        }
236        return Ok(listener);
237      }
238      Err(err) if err.kind() == ErrorKind::AddrInUse => {
239        let next_port = socket_addr.port().saturating_add(1);
240        if !ask_to_use_next_port(socket_addr.port(), next_port)? {
241          return Err(err);
242        }
243        socket_addr.set_port(next_port);
244      }
245      Err(err) => return Err(err),
246    }
247  }
248}
249
250fn ask_to_use_next_port(current: u16, next: u16) -> io::Result<bool> {
251  loop {
252    print!(
253      "Port {} is already in use. Start on {} instead? [Y/n]: ",
254      current, next
255    );
256    io::stdout().flush()?;
257
258    let mut input = String::new();
259    io::stdin().read_line(&mut input)?;
260    let trimmed = input.trim();
261
262    if trimmed.is_empty()
263      || trimmed.eq_ignore_ascii_case("y")
264      || trimmed.eq_ignore_ascii_case("yes")
265    {
266      return Ok(true);
267    }
268
269    if trimmed.eq_ignore_ascii_case("n") || trimmed.eq_ignore_ascii_case("no") {
270      return Ok(false);
271    }
272
273    println!("Please answer 'y' or 'n'.");
274  }
275}
276
277/// TLS/SSL server implementation for secure connections.
278#[cfg(all(not(feature = "compio-tls"), feature = "tls"))]
279#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
280pub mod server_tls;
281
282/// Compio server implementation for efficient I/O operations.
283#[cfg(feature = "compio")]
284#[cfg_attr(docsrs, doc(cfg(feature = "compio")))]
285pub mod server_compio;
286
287#[cfg(feature = "compio-tls")]
288#[cfg_attr(docsrs, doc(cfg(feature = "compio")))]
289pub mod server_tls_compio;
290
291/// Starts the HTTPS server with TLS encryption support.
292///
293/// Similar to `serve` but enables TLS encryption for secure connections. Requires
294/// the "tls" feature to be enabled and proper TLS configuration.
295///
296/// # Examples
297///
298/// ```rust,no_run
299/// # #[cfg(feature = "tls")]
300/// use tako::{serve_tls, router::Router};
301/// # #[cfg(feature = "tls")]
302/// use tokio::net::TcpListener;
303///
304/// # #[cfg(feature = "tls")]
305/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
306/// let listener = TcpListener::bind("127.0.0.1:8443").await?;
307/// let router = Router::new();
308/// // serve_tls(listener, router, tls_config).await;
309/// # Ok(())
310/// # }
311/// ```
312#[cfg(all(not(feature = "compio"), feature = "tls"))]
313#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
314pub use server_tls::serve_tls;
315
316#[cfg(feature = "compio-tls")]
317#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
318pub use server_tls_compio::serve_tls;
319
320/// Global memory allocator using jemalloc for improved performance.
321#[cfg(feature = "jemalloc")]
322#[cfg_attr(docsrs, doc(cfg(feature = "jemalloc")))]
323#[global_allocator]
324static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;