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}