Skip to content

Commit b243463

Browse files
committed
feat: add exmaples
1 parent dcd2f6c commit b243463

File tree

7 files changed

+1349
-199
lines changed

7 files changed

+1349
-199
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target
1+
target
22
*.pem

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ tls = ["tokio-rustls", "rustls", "rustls-pemfile"]
4444

4545
[lib]
4646
name = "tako"
47+
48+
[[example]]
49+
name = "hello-world"
50+
path = "examples/hello-world/src/main.rs"

README.md

Lines changed: 28 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -7,236 +7,66 @@
77

88
## ✨ Highlights
99

10-
| Feature | Description |
11-
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
12-
| **Batteries‑included Router** | Intuitive path‑based routing with path parameters and trailing‑slash redirection (TSR). |
13-
| **Extractor system** | Strongly‑typed request extractors for headers, query/body params, JSON, form data, etc. |
14-
| **Streaming & SSE** | Built‑in helpers for Server‑Sent Events *and* arbitrary `Stream` responses. |
15-
| **Middleware** | Compose synchronous or async middleware functions with minimal boilerplate. |
16-
| **Shared State** | Application‑wide state injection without `unsafe` globals. |
17-
| **Hyper‑powered** | Built on `hyper` & `tokio` for minimal overhead and async performance.<br><sub>HTTP/2 and native TLS integration are **WIP**</sub> |
10+
* **Batteries‑included Router** — Intuitive path‑based routing with path parameters and trailing‑slash redirection (TSR).
11+
* **Extractor system** — Strongly‑typed request extractors for headers, query/body params, JSON, form data, etc.
12+
* **Streaming & SSE** — Built‑in helpers for Server‑Sent Events *and* arbitrary `Stream` responses.
13+
* **Middleware** — Compose synchronous or async middleware functions with minimal boilerplate.
14+
* **Shared State** — Application‑wide state injection.
15+
* **Plugin system** — Opt‑in extensions let you add functionality without cluttering the core API.
16+
* **Hyper‑powered** — Built on `hyper` & `tokio` for minimal overhead and async performance with **native HTTP/2 & TLS** support.
1817

1918
---
2019

2120
## 📦 Installation
2221

23-
Add Tako to your **Cargo.toml** (the crate isn’t on crates.io yet, so pull it from Git):
22+
Add **Tako** to your `Cargo.toml`:
2423

2524
```toml
2625
[dependencies]
27-
tako = { git = "https://github.com/rust-dd/tako", branch = "main" }
28-
# tako = { path = "../tako" } # ← for workspace development
26+
tako-rs = "*"
2927
```
3028

3129
---
3230

33-
## 🚀 QuickStart
31+
## 🚀 Quick Start
3432

35-
Below is a *minimal‑but‑mighty* example that demonstrates:
36-
37-
* Basic GET & POST routes with parameters
38-
* Route‑scoped middleware
39-
* Shared application state
40-
* Server‑Sent Events (string & raw bytes streams)
33+
Spin up a "Hello, World!" server in a handful of lines:
4134

4235
```rust
43-
use std::time::Duration;
44-
45-
use bytes::Bytes;
46-
use futures_util::{SinkExt, StreamExt};
47-
use hyper::Method;
48-
use serde::Deserialize;
36+
use anyhow::Result;
4937
use tako::{
50-
body::TakoBody,
51-
extractors::{bytes::Bytes as BodyBytes, header_map::HeaderMap, params::Params, FromRequest},
5238
responder::Responder,
53-
sse::{SseBytes, SseString},
54-
state::get_state,
55-
types::{Request, Response},
56-
ws::TakoWs,
39+
router::Router,
40+
types::Request,
41+
Method,
5742
};
58-
use tokio_stream::{wrappers::IntervalStream, StreamExt};
59-
use tokio_tungstenite::tungstenite::{Message, Utf8Bytes};
60-
61-
/// Global application state shared via an *arc‑swap* under the hood.
62-
#[derive(Clone, Default)]
63-
struct AppState {
64-
request_count: std::sync::atomic::AtomicU64,
65-
}
66-
67-
/// `GET /` handler that echoes the body & headers back.
68-
async fn hello(mut req: Request) -> impl Responder {
69-
let HeaderMap(headers) = HeaderMap::from_request(&mut req).await.unwrap();
70-
let BodyBytes(body) = BodyBytes::from_request(&mut req).await.unwrap();
71-
72-
format!(
73-
"Hello, World!\n\nHeaders: {:#?}\nBody: {:?}",
74-
headers, body
75-
)
76-
.into_response()
77-
}
78-
79-
/// Typed URL parameter struct for `/user/{id}`.
80-
#[derive(Deserialize)]
81-
struct UserParams {
82-
id: u32,
83-
}
84-
85-
async fn create_user(mut req: Request) -> impl Responder {
86-
let Params(user) = Params::<UserParams>::from_request(&mut req).await.unwrap();
87-
let state = get_state::<AppState>("app_state").unwrap();
88-
state.request_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
89-
90-
format!("User {} created ✅", user.id).into_response()
91-
}
92-
93-
/// String‑based SSE endpoint emitting `Hello` every second.
94-
async fn sse_string(_: Request) -> impl Responder {
95-
let stream = IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
96-
.map(|_| "Hello".to_string());
97-
SseString { stream }
98-
}
99-
100-
/// Raw‑bytes SSE variant (hand‑crafted frame).
101-
async fn sse_bytes(_: Request) -> impl Responder {
102-
let stream = IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
103-
.map(|_| Bytes::from("data: hello\n\n"));
104-
SseBytes { stream }
105-
}
106-
107-
/// Example auth middleware that short‑circuits with 401 when a header is missing.
108-
async fn auth_middleware(req: Request) -> Result<Request, Response> {
109-
if req.headers().get("x-auth").is_none() {
110-
return Err(
111-
hyper::Response::builder()
112-
.status(401)
113-
.body(TakoBody::empty())
114-
.unwrap()
115-
.into_response(),
116-
);
117-
}
118-
Ok(req)
119-
}
120-
121-
pub async fn ws_echo(req: Request) -> impl Responder {
122-
TakoWs::new(req, |mut ws| async move {
123-
let _ = ws.send(Message::Text("Welcome to Tako WS!".into())).await;
124-
125-
while let Some(Ok(msg)) = ws.next().await {
126-
match msg {
127-
Message::Text(txt) => {
128-
let _ = ws
129-
.send(Message::Text(Utf8Bytes::from(format!("Echo: {txt}"))))
130-
.await;
131-
}
132-
Message::Binary(bin) => {
133-
let _ = ws.send(Message::Binary(bin)).await;
134-
}
135-
Message::Ping(p) => {
136-
let _ = ws.send(Message::Pong(p)).await;
137-
}
138-
Message::Close(_) => {
139-
let _ = ws.send(Message::Close(None)).await;
140-
break;
141-
}
142-
_ => {}
143-
}
144-
}
145-
})
146-
}
43+
use tokio::net::TcpListener;
14744

148-
pub async fn ws_tick(req: Request) -> impl Responder {
149-
TakoWs::new(req, |mut ws| async move {
150-
let mut ticker =
151-
IntervalStream::new(tokio::time::interval(Duration::from_secs(1))).enumerate();
152-
153-
loop {
154-
tokio::select! {
155-
msg = ws.next() => {
156-
match msg {
157-
Some(Ok(Message::Close(_))) | None => break,
158-
_ => {}
159-
}
160-
}
161-
162-
Some((i, _)) = ticker.next() => {
163-
let _ = ws.send(Message::Text(Utf8Bytes::from(format!("tick #{i}")))).await;
164-
}
165-
}
166-
}
167-
})
45+
async fn hello_world(_: Request) -> impl Responder {
46+
"Hello, World!".into_response()
16847
}
16948

17049
#[tokio::main]
171-
async fn main() -> anyhow::Result<()> {
172-
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
50+
async fn main() -> Result<()> {
51+
// Bind a local TCP listener
52+
let listener = TcpListener::bind("127.0.0.1:8080").await?;
17353

174-
let mut router = tako::router::Router::new();
175-
router.state("app_state", AppState::default());
54+
// Declare routes
55+
let mut router = Router::new();
56+
router.route(Method::GET, "/", hello_world);
17657

177-
// Routes --------------------------------------------------------------
178-
router
179-
.route(Method::GET, "/", hello)
180-
.middleware(auth_middleware);
58+
println!("Server running at http://127.0.0.1:8080");
18159

182-
router.route_with_tsr(Method::POST, "/user/{id}", create_user);
183-
router.route_with_tsr(Method::GET, "/sse/string", sse_string);
184-
router.route_with_tsr(Method::GET, "/sse/bytes", sse_bytes);
185-
router.route_with_tsr(Method::GET, "/ws/echo", ws_echo);
186-
router.route_with_tsr(Method::GET, "/ws/tick", ws_tick);
187-
188-
// Start the server (HTTP/1.1 — HTTP/2 coming soon!)
189-
#[cfg(not(feature = "tls"))]
190-
tako::serve(listener, r).await;
191-
192-
#[cfg(feature = "tls")]
193-
tako::serve_tls(listener, r).await;
60+
// Launch the server
61+
tako::serve(listener, router).await;
19462

19563
Ok(())
19664
}
19765
```
19866

199-
> **Tip:** Tako returns a **308 Permanent Redirect** automatically when the trailing slash in the request does not match your route declaration. Use `route_with_tsr` when you *want* that redirect.
200-
201-
---
202-
203-
## 🧑‍💻 Development & Contributing
204-
205-
1. **Clone** the repo and run the examples:
206-
207-
```bash
208-
git clone https://github.com/rust-dd/tako
209-
cd tako && cargo run --example hello_world
210-
```
211-
2. Format & lint:
212-
213-
```bash
214-
cargo fmt && cargo clippy --all-targets --all-features
215-
```
216-
3. Open a PR – all contributions, big or small, are welcome!
217-
218-
---
219-
220-
## 🧪 Running the Example Above
221-
222-
```bash
223-
cargo run # in the folder with `main.rs`
224-
```
225-
226-
Navigate to [http://localhost:8080/](http://localhost:8080/) and watch requests stream in your terminal.
227-
228-
For the SSE endpoints:
229-
230-
```bash
231-
curl -N http://localhost:8080/sse/string # string frames
232-
curl -N http://localhost:8080/sse/bytes # raw bytes
233-
```
234-
235-
---
236-
23767
## 📜 License
23868

239-
MIT
69+
`MIT` — see [LICENSE](./LICENSE) for details.
24070

24171
---
24272

0 commit comments

Comments
 (0)