Skip to main content

spwf/server/
handlers.rs

1use std::{sync::Arc, collections::HashMap};
2use aoko::{no_std::{pipelines::tap::Tap, functions::{ext::Utf8Ext, fun::s as str, monoid::{StrDot, NotMonoid}}}, val};
3use tokio::{net::TcpStream, io::AsyncWriteExt, sync::Mutex, fs};
4use crate::{SharedData, http::{response::{Response, HttpStatus, ContentType}, request::Request}};
5
6pub struct Index;
7pub struct NotFound;
8pub struct VisitCount;
9pub struct Echo<'a> {
10    pub path_buf: &'a [u8]
11}
12pub struct StaticFile<'a> {
13    pub path_buf: &'a [u8]
14}
15
16#[macro_export]
17macro_rules! headers {
18    ($ct:expr, $body:expr) => {
19        [   ("Content-Type", $ct.to_string()),
20            ("Content-Length", $body.len().to_string())
21        ].into()
22    };
23}
24fn headers(body: &[u8]) -> HashMap<&str, String> {
25    headers!(ContentType::Html, body)
26}
27
28macro_rules! exe_stream {
29    ($s:ident, $r:ident) => {
30        $s.write_all(&$r).await.unwrap();
31        $s.flush().await.unwrap();
32    };
33}
34
35pub trait Handler {
36    async fn handle(&self, stream: &mut TcpStream, shared_data: Arc<Mutex<SharedData>>);
37}
38
39impl Handler for Index {
40    async fn handle(&self, stream: &mut TcpStream, _shared_data: Arc<Mutex<SharedData>>) {
41        let res_byt = Response::new()
42            .tap_mut(|r| {
43                r.body = "Index Page".as_bytes();
44                r.headers = headers(r.body);
45            }).as_bytes();
46
47        exe_stream!(stream, res_byt);
48    }
49}
50
51impl Handler for VisitCount {
52    async fn handle(&self, stream: &mut TcpStream, shared_data: Arc<Mutex<SharedData>>) {
53        shared_data.lock().await.visit_count += 1;
54        val! {
55            visit_count = shared_data.lock().await.visit_count;
56            body = format!("{visit_count} Times!");
57            res_byt = Response::new()
58                .tap_mut(|r| {
59                    r.body = body.as_bytes();
60                    r.headers = headers(r.body);
61                }).as_bytes();
62        }
63        exe_stream!(stream, res_byt);
64    }
65}
66
67impl Handler for NotFound {
68    async fn handle(&self, stream: &mut TcpStream, _shared_data: Arc<Mutex<SharedData>>) {
69        let res_byt = Response::new()
70            .tap_mut(|r| {
71                r.status = HttpStatus::NotFound;
72                r.body = "404 Not Found".as_bytes();
73                r.headers = headers(r.body)
74            }).as_bytes();
75
76        exe_stream!(stream, res_byt);
77    }
78}
79
80impl Handler for StaticFile<'_> {
81    async fn handle(&self, stream: &mut TcpStream, shared_data: Arc<Mutex<SharedData>>) {
82        for s in self.path_buf.to_str_lossy().split_whitespace() {
83            if s.contains("/static") {
84                val! {
85                    path = s.split('/').enumerate().filter(|&(u, _)| u != 0 && u != 1).map(|(_, s)| s).collect::<String>();
86                    file = fs::read(str("static/") + &path).await;
87                }
88                let Ok(file) = file else {
89                    NotFound.handle(stream, shared_data.clone()).await;
90                    return;
91                };
92                
93                let res_byt = Response::new()
94                    .tap_mut(|r| {
95                        r.body = &file;
96                        r.headers = headers!(parse_content_type(&path), file);
97                    }).as_bytes();
98                
99                exe_stream!(stream, res_byt);
100            }
101        }
102        fn parse_content_type(req: &str) -> ContentType {
103            use ContentType::*;
104            
105            macro_rules! dot {
106                ($s:expr) => {
107                    &StrDot::merge("", $s)
108                };
109            }
110
111            if req.contains(dot!("htm")) {
112                Html
113            } else if req.contains(dot!("txt")) {
114                PlainText
115            } else if req.contains(dot!("css")) {
116                Css
117            } else if req.contains(dot!("png")) || req.contains(dot!("jpg")) || req.contains(dot!("ico")) {
118                AvifImage
119            } else {
120                Html
121            }
122        }
123    }
124}
125
126impl Handler for Echo<'_> {
127    async fn handle(&self, stream: &mut TcpStream, _shared_data: Arc<Mutex<SharedData>>) {
128        let req: Request = self.path_buf.into();
129
130        let res = Response::new().tap_mut(|r| {
131            r.body = req.parse_queries().get("content").unwrap_or(&"Need some argument").as_bytes();
132            r.headers = headers(r.body);
133        }).as_bytes();
134
135        exe_stream!(stream, res);
136    }
137}