fire_http/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[macro_use]
5mod macros;
6
7pub mod resources;
8use resources::Resources;
9
10pub mod state;
11
12pub mod routes;
13use routes::{Catcher, ParamsNames, RawRoute, Route, Routes};
14
15#[macro_use]
16pub mod util;
17
18pub mod into;
19use into::IntoRoute;
20
21pub mod error;
22pub use error::{Error, Result};
23
24pub mod extractor;
25pub use extractor::Res;
26
27mod server;
28use server::Server;
29
30mod fire;
31use fire::{RequestConfigs, Wood};
32use tracing::info;
33
34#[cfg(feature = "fs")]
35#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
36pub mod fs;
37
38#[cfg(feature = "json")]
39#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
40pub mod json;
41
42#[cfg(feature = "ws")]
43#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
44pub mod ws;
45
46#[cfg(feature = "graphql")]
47#[cfg_attr(docsrs, doc(cfg(feature = "graphql")))]
48pub mod graphql;
49
50pub mod service {
51	pub use crate::server::FireService;
52}
53
54use std::any::Any;
55use std::net::SocketAddr;
56use std::sync::Arc;
57use std::time::Duration;
58
59use tokio::net::ToSocketAddrs;
60use tokio::task::JoinHandle;
61
62pub use types;
63pub use types::{body, header, Body, Request, Response};
64
65pub use codegen::*;
66
67/// Prepares a server.
68pub async fn build(addr: impl ToSocketAddrs) -> Result<FireBuilder> {
69	FireBuilder::new(addr).await
70}
71
72/// `FireBuilder` gathers all materials needed to light a fire (start a server).
73pub struct FireBuilder {
74	addr: SocketAddr,
75	resources: Resources,
76	routes: Routes,
77	configs: RequestConfigs,
78}
79
80impl FireBuilder {
81	pub(crate) async fn new<A>(addr: A) -> Result<Self>
82	where
83		A: ToSocketAddrs,
84	{
85		let addr = tokio::net::lookup_host(addr)
86			.await
87			.map_err(Error::from_server_error)?
88			.next()
89			.unwrap();
90		Ok(Self {
91			addr,
92			resources: Resources::new(),
93			routes: Routes::new(),
94			configs: RequestConfigs::new(),
95		})
96	}
97
98	/// Returns a reference to the current data.
99	pub fn data(&self) -> &Resources {
100		&self.resources
101	}
102
103	pub fn add_data<D>(&mut self, data: D)
104	where
105		D: Any + Send + Sync,
106	{
107		self.resources.insert(data);
108	}
109
110	/// Adds a `RawRoute` to the fire.
111	pub fn add_raw_route<R>(&mut self, route: R)
112	where
113		R: RawRoute + 'static,
114	{
115		let path = route.path();
116		let names = ParamsNames::parse(&path.path);
117		route.validate_requirements(&names, &self.resources);
118		self.routes.push_raw(path, route)
119	}
120
121	/// Adds a `Route` to the fire.
122	pub fn add_route<R>(&mut self, route: R)
123	where
124		R: IntoRoute + 'static,
125	{
126		let route = route.into_route();
127		let path = route.path();
128		let names = ParamsNames::parse(&path.path);
129		route.validate_requirements(&names, &self.resources);
130		self.routes.push(path, route)
131	}
132
133	/// Adds a `Catcher` to the fire.
134	pub fn add_catcher<C>(&mut self, catcher: C)
135	where
136		C: Catcher + 'static,
137	{
138		catcher.validate_data(&self.resources);
139		self.routes.push_catcher(catcher)
140	}
141
142	/// Sets the request size limit. The default is 4 kilobytes.
143	///
144	/// This can be changed in every Route.
145	///
146	/// ## Panics
147	/// If the size is zero.
148	pub fn request_size_limit(&mut self, size_limit: usize) {
149		self.configs.size_limit(size_limit)
150	}
151
152	/// Sets the request timeout. The default is 60 seconds.
153	///
154	/// This can be changed in every Route.
155	pub fn request_timeout(&mut self, timeout: Duration) {
156		self.configs.timeout(timeout)
157	}
158
159	/// Binds to the address and prepares to serve requests.
160	///
161	/// You need to call ignite on the `Fire` so that it starts handling
162	/// requests.
163	pub async fn build(self) -> Result<Fire> {
164		let wood =
165			Arc::new(Wood::new(self.resources, self.routes, self.configs));
166
167		let server = Server::bind(self.addr, wood.clone()).await?;
168
169		Ok(Fire { wood, server })
170	}
171
172	/// Ignites the fire, which starts the server.
173	///
174	/// ## Note
175	/// Under normal conditions this function should run forever.
176	pub async fn ignite(self) -> Result<()> {
177		let fire = self.build().await?;
178		fire.ignite().await
179	}
180
181	/// Ignites the fire, and spawns it on a new tokio task.
182	///
183	/// ## Note
184	/// Under normal conditions this task should run forever.
185	pub fn ignite_task(self) -> JoinHandle<()> {
186		tokio::spawn(async move { self.ignite().await.unwrap() })
187	}
188
189	/// Creates a FirePit without starting the server.
190	///
191	/// In most cases you should use `build` and then call `pit` on the `Fire`.
192	///
193	/// Creating a `FirePit` might be useful for testing or if you want to
194	/// manually create a server.
195	pub fn into_pit(self) -> FirePit {
196		let wood =
197			Arc::new(Wood::new(self.resources, self.routes, self.configs));
198
199		FirePit { wood }
200	}
201}
202
203/// A Fire that is ready to be ignited.
204pub struct Fire {
205	wood: Arc<Wood>,
206	server: Server,
207}
208
209impl Fire {
210	pub fn local_addr(&self) -> Option<SocketAddr> {
211		self.server.local_addr().ok()
212	}
213
214	pub fn pit(&self) -> FirePit {
215		FirePit {
216			wood: self.wood.clone(),
217		}
218	}
219
220	pub async fn ignite(self) -> Result<()> {
221		info!("Running server on addr: {}", self.local_addr().unwrap());
222
223		self.server.serve().await
224	}
225}
226
227#[derive(Clone)]
228pub struct FirePit {
229	wood: Arc<Wood>,
230}
231
232impl FirePit {
233	pub fn data(&self) -> &Resources {
234		self.wood.data()
235	}
236
237	/// Routes the request to normal routes and returns their result.
238	///
239	/// Useful for tests and niche applications.
240	///
241	/// Returns None if no route was found matching the request.
242	pub async fn route(&self, req: &mut Request) -> Option<Result<Response>> {
243		fire::route(&self.wood, req).await
244	}
245}