summaryrefslogtreecommitdiff
path: root/server/respack_http/src/main.rs
blob: 6ffe63129712fb153d70ecf9efd639b641e2e735 (plain)
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
use anyhow::{Result, anyhow};
use clap::Parser;
use log::{error, warn};
use std::{
    io::{BufRead, BufReader, BufWriter, Write},
    marker::PhantomData,
    net::{IpAddr, SocketAddr, TcpListener, TcpStream},
    path::PathBuf,
    sync::Arc,
    thread::spawn,
};
use weareshared::{packets::Resource, store::ResourceStore};

#[derive(Parser, Debug)]
struct Args {
    #[arg(short, long, default_value = "::")]
    bind_addr: IpAddr,
    #[arg(short, long, default_value = "28556")]
    port: u16,

    pack: PathBuf,
}

fn main() -> Result<()> {
    env_logger::init_from_env("LOG");
    let args = Args::parse();
    let listener = TcpListener::bind(SocketAddr::new(args.bind_addr, args.port))?;

    let store = Arc::new(ResourceStore::new_respack_file(&args.pack)?);

    loop {
        let (conn, _addr) = listener.accept()?;
        let store = store.clone();
        spawn(move || {
            if let Err(e) = handle_conn(conn, store) {
                warn!("{e}")
            }
        });
    }
}

fn handle_conn(conn: TcpStream, store: Arc<ResourceStore>) -> Result<()> {
    let mut conn_read = BufReader::new(conn.try_clone()?);
    let mut conn_write = BufWriter::new(conn);
    loop {
        let mut buf = String::new();
        while !buf.ends_with("\r\n\r\n") {
            conn_read.read_line(&mut buf)?;
        }

        let mut lines = buf.lines();
        let mut status = lines
            .next()
            .ok_or(anyhow!("HTTP status line missing"))?
            .split(" ");
        let _method = status.next().ok_or(anyhow!("HTTP method missing"))?;
        let path = status.next().ok_or(anyhow!("HTTP path missing"))?;
        let _httpver = status.next().ok_or(anyhow!("HTTP version missing"))?;

        let path = path
            .strip_prefix("/")
            .ok_or(anyhow!("path does not start on /"))?;

        let (code, ty, data) = handle_request(path, &store);

        write!(conn_write, "HTTP/1.1 {code} OK\r\n")?;
        write!(conn_write, "content-type: {ty}\r\n")?;
        write!(conn_write, "server: write! macros\r\n")?;
        write!(conn_write, "cache-control: public, max-age=604800, immutable\r\n")?;
        write!(conn_write, "content-length: {}\r\n", data.len())?;
        write!(conn_write, "\r\n")?;
        conn_write.write_all(&data)?;
        conn_write.flush()?;
    }
}

fn handle_request(path: &str, store: &ResourceStore) -> (u16, &'static str, Vec<u8>) {
    if path == "entry" {
        if let ResourceStore::Respack(pack) = store {
            let pack = pack.lock().unwrap();
            if let Some(entry) = pack.entry() {
                (200, "application/x.weareresource.id", entry.0.to_vec())
            } else {
                (404, "text/plain", b"No entry found".to_vec())
            }
        } else {
            unreachable!()
        }
    } else if path.len() == 64 {
        let mut hash = [0; 32];
        if let Err(_) = hex::decode_to_slice(path, &mut hash) {
            return (400, "text/plain", b"Invalid hash format".to_vec());
        }
        match store.get_raw(Resource(hash, PhantomData)) {
            Ok(Some(res)) => (200, "application/x.weareresource", res),
            Ok(None) => (404, "text/plain", b"Not found".to_vec()),
            Err(e) => {
                error!("{e}");
                (500, "text/plain", b"Internal server error".to_vec())
            }
        }
    } else {
        (400, "text/plain", b"Bad path".to_vec())
    }
}