aboutsummaryrefslogtreecommitdiff
path: root/src/server.rs
blob: 106f9f8b9188de0719a73607e6903776515d306e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::{Arc, Mutex};

use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::server::conn::http1;
use hyper::service::Service;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;

use crate::error::ReluaxError;
use crate::luax::table_to_html;
use color_eyre::Result;
use rlua::Lua;

pub struct Server {
    port: u16,
    lua: Arc<Mutex<Lua>>,
}

struct State {
    lua: Arc<Mutex<Lua>>,
}

impl Server {
    pub async fn serve(port: u16) -> Result<()> {
        let server = Self {
            port,
            lua: Arc::new(Mutex::new(Lua::new())),
        };
        server.start().await
    }

    async fn start(self) -> Result<()> {
        let addr = SocketAddr::from(([127, 0, 0, 1], self.port));
        let listener = TcpListener::bind(addr).await?;

        loop {
            let (stream, _) = listener.accept().await?;
            let io = TokioIo::new(stream);
            let state = State {
                lua: self.lua.clone(),
            };
            tokio::task::spawn(async move {
                tokio::task::spawn(async move {
                    if let Err(err) = http1::Builder::new().serve_connection(io, state).await {
                        println!("Failed to serve connection: {:?}", err);
                    }
                });
            });
        }
    }
}

fn mk_response(s: String) -> Result<Response<Full<Bytes>>> {
    Ok(Response::builder().body(Full::new(Bytes::from(s))).unwrap())
}

impl Service<Request<Incoming>> for State {
    type Response = Response<Full<Bytes>>;
    type Error = color_eyre::Report;
    type Future =
        Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;

    fn call(&self, req: Request<Incoming>) -> Self::Future {
        let path = req.uri().path();
        let res = self.serve(path).map(mk_response).unwrap_or_else(|err| {
            println!("Error: {}", err);
            Err(err)
        });

        Box::pin(async { res })
    }
}

impl State {
    fn serve(&self, path: &str) -> Result<String> {
        let lua = self.lua.lock().unwrap();

        let res = lua.context(|ctx| -> Result<String> {
            let manifest: rlua::Table = ctx.load("require('reluax')").eval()?;

            let route: rlua::Function = manifest.get("route")?;

            let res: rlua::Value = route.call((path,))?;

            match res {
                rlua::Value::String(s) => Ok(s.to_str()?.to_string()),
                rlua::Value::Table(t) => {
                    let mut buf = Vec::new();
                    table_to_html(t, &mut buf)?;
                    let s = String::from_utf8(buf).unwrap();
                    Ok(s)
                }
                rlua::Value::Nil => Err(ReluaxError::Server("No route found".to_string()).into()),
                rlua::Value::Error(e) => Err(ReluaxError::Lua(e).into()),
                _ => Err(ReluaxError::Server("Route returned invalid type".to_string()).into()),
            }
        })?;

        Ok(res)
    }
}