1use crate::http::{HttpResponse, Request};
2use crate::inertia::InertiaContext;
3use crate::routing::Router;
4use bytes::Bytes;
5use http_body_util::Full;
6use hyper::server::conn::http1;
7use hyper::service::service_fn;
8use hyper_util::rt::TokioIo;
9use std::convert::Infallible;
10use std::net::SocketAddr;
11use std::sync::Arc;
12use tokio::net::TcpListener;
13
14pub struct Server {
15 router: Arc<Router>,
16 host: String,
17 port: u16,
18}
19
20impl Server {
21 pub fn new(router: Router) -> Self {
22 Self {
23 router: Arc::new(router),
24 host: "127.0.0.1".to_string(),
25 port: 8000,
26 }
27 }
28
29 pub fn host(&mut self, host: &str) -> &mut Self {
30 self.host = host.to_string();
31 self
32 }
33
34 pub fn port(&mut self, port: u16) -> &mut Self {
35 self.port = port;
36 self
37 }
38
39 fn get_addr(&self) -> SocketAddr {
40 SocketAddr::new(self.host.parse().unwrap(), self.port)
41 }
42
43 pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
44 let addr: SocketAddr = self.get_addr();
46 let listener = TcpListener::bind(addr).await?;
47
48 println!("Kit server running on http://{}", addr);
49
50 loop {
51 let (stream, _) = listener.accept().await?;
52 let io = TokioIo::new(stream);
53 let router = self.router.clone();
54
55 tokio::spawn(async move {
56 let service = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
57 let router = router.clone();
58 async move { Ok::<_, Infallible>(handle_request(router, req).await) }
59 });
60
61 if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
62 eprintln!("Error serving connection: {:?}", err);
63 }
64 });
65 }
66 }
67}
68
69async fn handle_request(
70 router: Arc<Router>,
71 req: hyper::Request<hyper::body::Incoming>,
72) -> hyper::Response<Full<Bytes>> {
73 let method = req.method().clone();
74 let path = req.uri().path().to_string();
75
76 let is_inertia = req
78 .headers()
79 .get("X-Inertia")
80 .and_then(|v| v.to_str().ok())
81 .map(|v| v == "true")
82 .unwrap_or(false);
83
84 let inertia_version = req
85 .headers()
86 .get("X-Inertia-Version")
87 .and_then(|v| v.to_str().ok())
88 .map(|v| v.to_string());
89
90 InertiaContext::set(InertiaContext {
91 path: path.clone(),
92 is_inertia,
93 version: inertia_version,
94 });
95
96 let response = match router.match_route(&method, &path) {
97 Some((handler, params)) => {
98 let request = Request::new(req).with_params(params);
99 let response = handler(request).await;
100 let http_response = response.unwrap_or_else(|e| e);
102 http_response.into_hyper()
103 }
104 None => {
105 HttpResponse::text("404 Not Found")
106 .status(404)
107 .into_hyper()
108 }
109 };
110
111 InertiaContext::clear();
113
114 response
115}