pub struct Server { /* private fields */ }Expand description
The HTTP server that owns and dispatches a collection of SocketType routes.
Build routes through the ServerMechanism builder chain, register each with
mechanism, then start the server with serve.
§Example
let mut server = Server::default();
server
.mechanism(
ServerMechanism::get("/ping")
.onconnect(|| async { reply!(json => Pong { ok: true }) })
)
.mechanism(
ServerMechanism::delete("/session")
.onconnect(|| async { reply!() })
);
// Blocks forever — call only to actually run the server:
// server.serve(([0, 0, 0, 0], 8080)).await;§Caution
Calling serve with no routes registered will panic.
Implementations§
Source§impl Server
impl Server
Sourcepub fn mechanism(&mut self, mech: SocketType) -> &mut Self
pub fn mechanism(&mut self, mech: SocketType) -> &mut Self
Registers a SocketType route on this server.
Routes are evaluated in registration order. Returns &mut Self for chaining.
Examples found in repository?
62async fn main() -> Result<(), Box<dyn std::error::Error>> {
63 // ── Build routes ──────────────────────────────────────────────────────────
64 let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
65
66 let mut server = Server::default();
67
68 // Plain GET — no body, no state.
69 server.mechanism(
70 ServerMechanism::get("/health")
71 .onconnect(|| async { reply!(json => Health { ok: true }) })
72 );
73
74 // POST with a JSON body — echoes back the created item.
75 server.mechanism(
76 ServerMechanism::post("/items")
77 .json::<NewItem>()
78 .onconnect(|body: NewItem| async move {
79 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
80 })
81 );
82
83 // GET with query parameters — filters the stored items by prefix.
84 server.mechanism({
85 let store = store.clone();
86 ServerMechanism::get("/items/search")
87 .state(store)
88 .query::<Filter>()
89 .onconnect(|state: Arc<Mutex<Vec<Item>>>, f: Filter| async move {
90 let items = state.lock().unwrap();
91 let matches: Vec<Item> = items
92 .iter()
93 .filter(|i| i.name.starts_with(&f.prefix))
94 .cloned()
95 .collect();
96 reply!(json => SearchResult { matches })
97 })
98 });
99
100 // POST with shared state — stores the item and returns it.
101 server.mechanism({
102 let store = store.clone();
103 ServerMechanism::post("/items/store")
104 .state(store)
105 .json::<NewItem>()
106 .onconnect(|state: Arc<Mutex<Vec<Item>>>, body: NewItem| async move {
107 let mut s = state.lock().unwrap();
108 let id = s.len() as u32 + 1;
109 let item = Item { id, name: body.name };
110 s.push(item.clone());
111 reply!(json => item, status => Status::Created)
112 })
113 });
114
115 // POST with authenticated-encrypted body (ChaCha20-Poly1305 via SerializationKey).
116 // The body is decrypted before the handler is called; a wrong key returns 403.
117 server.mechanism(
118 ServerMechanism::post("/items/secure")
119 .encryption::<NewItem>(SerializationKey::Default)
120 .onconnect(|body: NewItem| async move {
121 let item = Item { id: 99, name: body.name };
122 // The response must also be sealed so the client can open it.
123 reply!(sealed => item, key => SerializationKey::Default)
124 })
125 );
126
127 // ── Serve with graceful shutdown ──────────────────────────────────────────
128 let (tx, rx) = tokio::sync::oneshot::channel::<()>();
129 let server_handle = tokio::spawn(async move {
130 server.serve_with_graceful_shutdown(
131 ([127, 0, 0, 1], PORT),
132 async { rx.await.ok(); },
133 ).await;
134 });
135
136 // Give the server time to bind before firing requests.
137 tokio::time::sleep(Duration::from_millis(200)).await;
138 println!("Server started on port {PORT}");
139
140 // ── Client requests ───────────────────────────────────────────────────────
141 let client = ClientBuilder::new(Target::Localhost(PORT))
142 .timeout(Duration::from_secs(5))
143 .build_async();
144
145 // GET /health
146 let health: Health = client.get("/health").send().await?;
147 assert!(health.ok);
148 println!("GET /health → ok={}", health.ok);
149
150 // POST /items (JSON body)
151 let created: Item = client
152 .post("/items")
153 .json(NewItem { name: "widget".into() })
154 .send()
155 .await?;
156 assert_eq!(created.name, "widget");
157 println!("POST /items → {:?}", created);
158
159 // POST /items/store (shared state — populates the store)
160 let stored: Item = client
161 .post("/items/store")
162 .json(NewItem { name: "gadget".into() })
163 .send()
164 .await?;
165 println!("POST /items/store → {:?}", stored);
166
167 let stored2: Item = client
168 .post("/items/store")
169 .json(NewItem { name: "gizmo".into() })
170 .send()
171 .await?;
172 println!("POST /items/store → {:?}", stored2);
173
174 // GET /items/search?prefix=ga (query params)
175 let result: SearchResult = client
176 .get("/items/search")
177 .query(Filter { prefix: "ga".into() })
178 .send()
179 .await?;
180 assert!(result.matches.iter().all(|i| i.name.starts_with("ga")));
181 println!("GET /items/search?prefix=ga → {} match(es): {:?}", result.matches.len(), result.matches);
182
183 // POST /items/secure (authenticated-encrypted body)
184 let secure_item = client
185 .post("/items/secure")
186 .encryption(NewItem { name: "secret".into() }, SerializationKey::Default)
187 .send::<Item>()
188 .await?;
189 assert_eq!(secure_item.name, "secret");
190 println!("POST /items/secure → {:?}", secure_item);
191
192 // ── Shutdown ──────────────────────────────────────────────────────────────
193 tx.send(()).ok();
194 server_handle.await?;
195 println!("\nAll requests successful ✓");
196 Ok(())
197}Sourcepub fn serve(self, addr: impl Into<SocketAddr>) -> ServerFuture
pub fn serve(self, addr: impl Into<SocketAddr>) -> ServerFuture
Binds to addr and starts serving all registered routes.
Returns a ServerFuture that can be:
.await’d — runs the server in the current task (infinite loop).background()’d — spawns the server as a Tokio background task
§Panics
Panics if no routes have been registered or if the address cannot be bound.
Sourcepub fn serve_with_graceful_shutdown(
self,
addr: impl Into<SocketAddr>,
shutdown: impl Future<Output = ()> + Send + 'static,
) -> ServerFuture
pub fn serve_with_graceful_shutdown( self, addr: impl Into<SocketAddr>, shutdown: impl Future<Output = ()> + Send + 'static, ) -> ServerFuture
Binds to addr, serves all registered routes, and shuts down gracefully when
shutdown resolves.
Returns a ServerFuture that can be .await’d or .background()’d.
§Example
use tokio::sync::oneshot;
let (tx, rx) = oneshot::channel::<()>();
let handle = server.serve_with_graceful_shutdown(
([127, 0, 0, 1], 8080),
async move { rx.await.ok(); },
).background();
tx.send(()).ok();
handle.await.ok();Examples found in repository?
62async fn main() -> Result<(), Box<dyn std::error::Error>> {
63 // ── Build routes ──────────────────────────────────────────────────────────
64 let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
65
66 let mut server = Server::default();
67
68 // Plain GET — no body, no state.
69 server.mechanism(
70 ServerMechanism::get("/health")
71 .onconnect(|| async { reply!(json => Health { ok: true }) })
72 );
73
74 // POST with a JSON body — echoes back the created item.
75 server.mechanism(
76 ServerMechanism::post("/items")
77 .json::<NewItem>()
78 .onconnect(|body: NewItem| async move {
79 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
80 })
81 );
82
83 // GET with query parameters — filters the stored items by prefix.
84 server.mechanism({
85 let store = store.clone();
86 ServerMechanism::get("/items/search")
87 .state(store)
88 .query::<Filter>()
89 .onconnect(|state: Arc<Mutex<Vec<Item>>>, f: Filter| async move {
90 let items = state.lock().unwrap();
91 let matches: Vec<Item> = items
92 .iter()
93 .filter(|i| i.name.starts_with(&f.prefix))
94 .cloned()
95 .collect();
96 reply!(json => SearchResult { matches })
97 })
98 });
99
100 // POST with shared state — stores the item and returns it.
101 server.mechanism({
102 let store = store.clone();
103 ServerMechanism::post("/items/store")
104 .state(store)
105 .json::<NewItem>()
106 .onconnect(|state: Arc<Mutex<Vec<Item>>>, body: NewItem| async move {
107 let mut s = state.lock().unwrap();
108 let id = s.len() as u32 + 1;
109 let item = Item { id, name: body.name };
110 s.push(item.clone());
111 reply!(json => item, status => Status::Created)
112 })
113 });
114
115 // POST with authenticated-encrypted body (ChaCha20-Poly1305 via SerializationKey).
116 // The body is decrypted before the handler is called; a wrong key returns 403.
117 server.mechanism(
118 ServerMechanism::post("/items/secure")
119 .encryption::<NewItem>(SerializationKey::Default)
120 .onconnect(|body: NewItem| async move {
121 let item = Item { id: 99, name: body.name };
122 // The response must also be sealed so the client can open it.
123 reply!(sealed => item, key => SerializationKey::Default)
124 })
125 );
126
127 // ── Serve with graceful shutdown ──────────────────────────────────────────
128 let (tx, rx) = tokio::sync::oneshot::channel::<()>();
129 let server_handle = tokio::spawn(async move {
130 server.serve_with_graceful_shutdown(
131 ([127, 0, 0, 1], PORT),
132 async { rx.await.ok(); },
133 ).await;
134 });
135
136 // Give the server time to bind before firing requests.
137 tokio::time::sleep(Duration::from_millis(200)).await;
138 println!("Server started on port {PORT}");
139
140 // ── Client requests ───────────────────────────────────────────────────────
141 let client = ClientBuilder::new(Target::Localhost(PORT))
142 .timeout(Duration::from_secs(5))
143 .build_async();
144
145 // GET /health
146 let health: Health = client.get("/health").send().await?;
147 assert!(health.ok);
148 println!("GET /health → ok={}", health.ok);
149
150 // POST /items (JSON body)
151 let created: Item = client
152 .post("/items")
153 .json(NewItem { name: "widget".into() })
154 .send()
155 .await?;
156 assert_eq!(created.name, "widget");
157 println!("POST /items → {:?}", created);
158
159 // POST /items/store (shared state — populates the store)
160 let stored: Item = client
161 .post("/items/store")
162 .json(NewItem { name: "gadget".into() })
163 .send()
164 .await?;
165 println!("POST /items/store → {:?}", stored);
166
167 let stored2: Item = client
168 .post("/items/store")
169 .json(NewItem { name: "gizmo".into() })
170 .send()
171 .await?;
172 println!("POST /items/store → {:?}", stored2);
173
174 // GET /items/search?prefix=ga (query params)
175 let result: SearchResult = client
176 .get("/items/search")
177 .query(Filter { prefix: "ga".into() })
178 .send()
179 .await?;
180 assert!(result.matches.iter().all(|i| i.name.starts_with("ga")));
181 println!("GET /items/search?prefix=ga → {} match(es): {:?}", result.matches.len(), result.matches);
182
183 // POST /items/secure (authenticated-encrypted body)
184 let secure_item = client
185 .post("/items/secure")
186 .encryption(NewItem { name: "secret".into() }, SerializationKey::Default)
187 .send::<Item>()
188 .await?;
189 assert_eq!(secure_item.name, "secret");
190 println!("POST /items/secure → {:?}", secure_item);
191
192 // ── Shutdown ──────────────────────────────────────────────────────────────
193 tx.send(()).ok();
194 server_handle.await?;
195 println!("\nAll requests successful ✓");
196 Ok(())
197}More examples
80async fn main() -> Result<(), Box<dyn std::error::Error>> {
81 let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
82 let mut server = Server::default();
83
84 // ── Register routes via #[mechanism] ────────────────────────────────────
85 //
86 // Each attribute expands to:
87 // server.mechanism(ServerMechanism::METHOD(path)[.modifier()].onconnect(fn));
88
89 // Plain GET — no body, no state.
90 #[mechanism(server, GET, "/health")]
91 async fn health_handler() {
92 reply!(json => Health { ok: true })
93 }
94
95 // POST + JSON body — returns the created item.
96 #[mechanism(server, POST, "/items", json)]
97 async fn create_item(body: NewItem) {
98 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
99 }
100
101 // GET + query params — echoes back the search prefix.
102 // (Filtered search requires access to the store; see /items/store below.)
103 #[mechanism(server, GET, "/items/search", state(store.clone()), query)]
104 async fn search_items(state: Arc<Mutex<Vec<Item>>>, f: Filter) {
105 let items = state.lock().unwrap();
106 let matches: Vec<Item> = items
107 .iter()
108 .filter(|i| i.name.starts_with(&f.prefix))
109 .cloned()
110 .collect();
111 reply!(json => SearchResult { matches })
112 }
113
114 // POST + shared state + JSON body — persists the item.
115 #[mechanism(server, POST, "/items/store", state(store.clone()), json)]
116 async fn store_item(state: Arc<Mutex<Vec<Item>>>, body: NewItem) {
117 let mut s = state.lock().unwrap();
118 let id = s.len() as u32 + 1;
119 let item = Item { id, name: body.name };
120 s.push(item.clone());
121 reply!(json => item, status => Status::Created)
122 }
123
124 // POST + AEAD-encrypted body (ChaCha20-Poly1305 via SerializationKey).
125 // The body is decrypted before the handler is called; the response is
126 // sealed so the client's `.send::<Item>()` can open it automatically.
127 #[mechanism(server, POST, "/items/secure", encrypted(SerializationKey::Default))]
128 async fn secure_create(body: NewItem) {
129 let item = Item { id: 99, name: body.name };
130 reply!(sealed => item, key => SerializationKey::Default)
131 }
132
133 // ── Start the server in the background via .background() ────────────────
134 //
135 // Previously this required manually wrapping the `.await` call inside
136 // `tokio::spawn(async move { server.serve_*(…).await; })`.
137 // `.background()` is the idiomatic shorthand for exactly that pattern.
138 let (tx, rx) = tokio::sync::oneshot::channel::<()>();
139 let server_handle = server
140 .serve_with_graceful_shutdown(
141 ([127, 0, 0, 1], PORT),
142 async { rx.await.ok(); },
143 )
144 .background(); // ← non-blocking spawn
145
146 // Give the server a moment to bind before firing requests.
147 tokio::time::sleep(Duration::from_millis(200)).await;
148 println!("Server started on port {PORT}");
149
150 // ── Issue all requests via #[request] ───────────────────────────────────
151 //
152 // Each attribute expands to an expression that sends the request and awaits
153 // the response, binding the decoded value to a local with the function name.
154
155 let client = ClientBuilder::new(Target::Localhost(PORT))
156 .timeout(Duration::from_secs(5))
157 .build_async();
158
159 // Async GET /health — plain request, no body.
160 #[request(client, GET, "/health", async)]
161 async fn health_resp() -> Health {}
162 assert!(health_resp.ok);
163 println!("GET /health → ok={}", health_resp.ok);
164
165 // Async POST /items — JSON body.
166 #[request(client, POST, "/items", json(NewItem { name: "widget".to_string() }), async)]
167 async fn created() -> Item {}
168 assert_eq!(created.name, "widget");
169 println!("POST /items → {:?}", created);
170
171 // Store a couple of items so the search route has data.
172 #[request(client, POST, "/items/store", json(NewItem { name: "gadget".to_string() }), async)]
173 async fn stored1() -> Item {}
174 println!("POST /items/store → {:?}", stored1);
175
176 #[request(client, POST, "/items/store", json(NewItem { name: "gizmo".to_string() }), async)]
177 async fn stored2() -> Item {}
178 println!("POST /items/store → {:?}", stored2);
179
180 // Async GET /items/search?prefix=ga — query params with shared state.
181 #[request(client, GET, "/items/search", query(Filter { prefix: "ga".to_string() }), async)]
182 async fn results() -> SearchResult {}
183 assert!(results.matches.iter().all(|i| i.name.starts_with("ga")));
184 println!(
185 "GET /items/search?prefix=ga → {} match(es): {:?}",
186 results.matches.len(),
187 results.matches
188 );
189
190 // Async POST /items/secure — AEAD-encrypted body.
191 #[request(client, POST, "/items/secure", encrypted(NewItem { name: "secret".to_string() }, SerializationKey::Default), async)]
192 async fn secure_item() -> Item {}
193 assert_eq!(secure_item.name, "secret");
194 println!("POST /items/secure → {:?}", secure_item);
195
196 // ── Graceful shutdown ───────────────────────────────────────────────────
197 tx.send(()).ok();
198 server_handle.await?;
199 println!("\nAll requests successful ✓");
200 Ok(())
201}Sourcepub fn serve_from_listener(
self,
listener: TcpListener,
shutdown: impl Future<Output = ()> + Send + 'static,
) -> ServerFuture
pub fn serve_from_listener( self, listener: TcpListener, shutdown: impl Future<Output = ()> + Send + 'static, ) -> ServerFuture
Serves all registered routes from an already-bound listener, shutting down
gracefully when shutdown resolves.
Returns a ServerFuture that can be .await’d or .background()’d.
Use this when port 0 is passed to TcpListener::bind and you need to know
the actual OS-assigned port before the server starts.
§Example
use tokio::net::TcpListener;
use tokio::sync::oneshot;
let listener = TcpListener::bind("127.0.0.1:0").await?;
let port = listener.local_addr()?.port();
let (tx, rx) = oneshot::channel::<()>();
let handle = server
.serve_from_listener(listener, async move { rx.await.ok(); })
.background();
tx.send(()).ok();
handle.await.ok();Sourcepub fn rebind(&mut self, addr: impl Into<SocketAddr>) -> &mut Self
pub fn rebind(&mut self, addr: impl Into<SocketAddr>) -> &mut Self
Stores addr as this server’s default bind address.
This is a pre-serve convenience setter. Call it before
serve_managed or any other serve* variant to
record the initial address without starting the server.
Returns &mut Self for method chaining.
Sourcepub fn serve_managed(self, addr: impl Into<SocketAddr>) -> BackgroundServer
pub fn serve_managed(self, addr: impl Into<SocketAddr>) -> BackgroundServer
Starts all registered routes in a background Tokio task and returns a
BackgroundServer handle.
Unlike serve* + .background(), this method keeps a live route table
inside the handle, enabling:
BackgroundServer::rebind— graceful stop + restart on a new addressBackgroundServer::mechanism— add routes without restartingBackgroundServer::addr— query the current bind addressBackgroundServer::stop— shut down and await completion
§Panics
Panics if no routes have been registered.
§Example
let mut server = Server::default();
server.mechanism(
toolkit_zero::socket::server::ServerMechanism::get("/ping")
.onconnect(|| async { reply!(json => Pong { ok: true }) })
);
let mut bg = server.serve_managed(([127, 0, 0, 1], 8080));
println!("Running on {}", bg.addr());
bg.rebind(([127, 0, 0, 1], 9090)).await;
println!("Rebound to {}", bg.addr());
bg.stop().await;