1mod context;
12mod router;
13mod static_files;
14
15use std::{io, net::SocketAddr, path::PathBuf};
16
17use hyper::{body::Incoming, server::conn::http1, service::service_fn, StatusCode};
18use hyper_util::rt::TokioIo;
19use rustolio_utils::{http::Outgoing, prelude::Threadsafe, prelude::*};
20use tokio::net::TcpListener;
21
22use rustolio_utils::http;
23
24use context::ServerContext;
25use static_files::serve_static_dir;
26
27const DEFAULT_STATIC_PATH: &str = "pkg";
28
29pub struct Server<C> {
30 #[allow(dead_code)]
31 context: C,
32 with_static: Option<PathBuf>,
33}
34
35impl Default for Server<()> {
36 fn default() -> Self {
37 Self {
38 context: (),
39 with_static: Some(DEFAULT_STATIC_PATH.into()),
40 }
41 }
42}
43
44impl Server<()> {
45 pub fn new() -> Self {
46 Self {
47 context: (),
48 with_static: None,
49 }
50 }
51}
52
53impl Server<()> {
54 pub fn context<C>(self, context: C) -> Server<C> {
55 Server {
56 context,
57 with_static: self.with_static,
58 }
59 }
60}
61
62impl<C> Server<C> {
63 pub fn with_static(mut self, path: impl Into<PathBuf>) -> Self {
64 self.with_static = Some(path.into());
65 self
66 }
67}
68
69impl<C: Threadsafe> Server<C> {
70 pub async fn bind(self, addr: impl Into<SocketAddr>) -> Result<(), io::Error> {
71 bind(self.with_static, addr, self.context).await
72 }
73}
74
75async fn bind<C: Threadsafe>(
76 with_static: Option<PathBuf>,
77 addr: impl Into<SocketAddr>,
78 custom_ctx: C,
79) -> Result<(), io::Error> {
80 let addr = addr.into();
81 let listener = TcpListener::bind(addr).await?;
82
83 let static_path = match with_static {
84 Some(path) => path,
85 None => PathBuf::new(),
86 };
87
88 tracing::info!("Starting server on http://{}/", addr);
89
90 let ctx = ServerContext::new(static_path, custom_ctx);
91 service!(ctx as ServerContext<C>);
92
93 loop {
94 let (stream, _) = listener.accept().await?;
95 let io = TokioIo::new(stream);
96
97 tokio::task::spawn(async move {
98 if let Err(err) = http1::Builder::new()
99 .serve_connection(io, service_fn(move |req| glue(req, ctx)))
100 .await
101 {
102 eprintln!("Error serving connection: {:?}", err);
103 }
104 });
105 }
106}
107
108async fn glue<C>(
109 req: hyper::Request<Incoming>,
110 ctx: &'static ServerContext<C>,
111) -> Result<hyper::Response<Outgoing>, http::Error> {
112 let req = http::Request::from_inner(req);
113
114 let res = match req.uri().path().as_bytes() {
115 #[cfg(feature = "db")]
116 [b'/', b'd', b'b', b'/', ..] => ctx.db_router.serve(req, &ctx.db_ctx).await,
117 [b'/', b'r', b'p', b'c', b'/', ..] => ctx.rpc_router.serve(req, &ctx.rpc_ctx).await,
118 [b'/', ..] => Ok(serve_static_dir(req, ctx).await),
119 _ => Ok(http::Response::builder()
120 .status(StatusCode::NOT_FOUND)
121 .text("Not found")
122 .build()
123 .unwrap()),
124 };
125 res.map(|r| r.into_inner())
126}
127
128pub mod prelude {
129 #[doc(hidden)]
130 pub mod __server_macro {
131 pub use crate::context::ServerContext;
132 pub use inventory;
133 }
134}