Skip to main content

mittens_engine/engine/ecs/system/
http_server_system.rs

1use crate::engine::ecs::component::HttpServerComponent;
2use crate::engine::ecs::{ComponentId, EventSignal, SignalEmitter, World};
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::mpsc::{self, Receiver, Sender};
7use std::thread::JoinHandle;
8use std::time::Duration;
9use tiny_http::{Header, Response, Server, StatusCode};
10
11#[derive(Debug)]
12struct HttpServerRuntime {
13    shutdown: Arc<AtomicBool>,
14    join_handle: Option<JoinHandle<()>>,
15}
16
17#[derive(Debug)]
18struct PendingReply {
19    reply_tx: Sender<HttpServerReplyPayload>,
20}
21
22#[derive(Debug, Clone)]
23struct HttpServerReplyPayload {
24    status: u16,
25    headers: Vec<(String, String)>,
26    body_text: String,
27}
28
29#[derive(Debug)]
30enum HttpServerMessage {
31    Request {
32        component_id: ComponentId,
33        request_id: u64,
34        method: String,
35        path: String,
36        query: Option<String>,
37        url: String,
38        headers: Vec<(String, String)>,
39        body_text: String,
40        remote_addr: Option<String>,
41        reply_tx: Sender<HttpServerReplyPayload>,
42    },
43    Error {
44        component_id: ComponentId,
45        phase: String,
46        message: String,
47        bind_addr: String,
48    },
49}
50
51#[derive(Debug)]
52pub struct HttpServerSystem {
53    runtimes: HashMap<ComponentId, HttpServerRuntime>,
54    pending_replies: HashMap<(ComponentId, u64), PendingReply>,
55    message_tx: Sender<HttpServerMessage>,
56    message_rx: Receiver<HttpServerMessage>,
57}
58
59impl Default for HttpServerSystem {
60    fn default() -> Self {
61        let (message_tx, message_rx) = mpsc::channel();
62        Self {
63            runtimes: HashMap::new(),
64            pending_replies: HashMap::new(),
65            message_tx,
66            message_rx,
67        }
68    }
69}
70
71impl HttpServerSystem {
72    pub fn register_component(
73        &mut self,
74        world: &World,
75        emit: &mut dyn SignalEmitter,
76        component_id: ComponentId,
77    ) {
78        self.remove_component(component_id);
79
80        let Some(component) = world.get_component_by_id_as::<HttpServerComponent>(component_id)
81        else {
82            return;
83        };
84        if !component.enabled || component.bind_addr.is_empty() {
85            return;
86        }
87
88        let bind_addr = component.bind_addr.clone();
89        let shutdown = Arc::new(AtomicBool::new(false));
90        let worker_shutdown = shutdown.clone();
91        let tx = self.message_tx.clone();
92        let bind_addr_for_thread = bind_addr.clone();
93
94        let join_handle = std::thread::spawn(move || {
95            let server = match Server::http(&bind_addr_for_thread) {
96                Ok(server) => server,
97                Err(error) => {
98                    let _ = tx.send(HttpServerMessage::Error {
99                        component_id,
100                        phase: "bind".to_string(),
101                        message: error.to_string(),
102                        bind_addr: bind_addr_for_thread,
103                    });
104                    return;
105                }
106            };
107
108            while !worker_shutdown.load(Ordering::Relaxed) {
109                match server.recv_timeout(Duration::from_millis(100)) {
110                    Ok(Some(mut request)) => {
111                        let request_id = {
112                            // Request ids are still owned by main thread; this provisional
113                            // placeholder is replaced below by a distinct monotonic worker id.
114                            // We only need uniqueness within this server runtime thread.
115                            use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
116                            static NEXT_ID: AtomicU64 = AtomicU64::new(1);
117                            NEXT_ID.fetch_add(1, AtomicOrdering::Relaxed)
118                        };
119                        let tx = tx.clone();
120                        std::thread::spawn(move || {
121                            let method = request.method().as_str().to_string();
122                            let target = request.url().to_string();
123                            let (path, query) = match target.split_once('?') {
124                                Some((path, query)) => (path.to_string(), Some(query.to_string())),
125                                None => (target.clone(), None),
126                            };
127                            let headers = request
128                                .headers()
129                                .iter()
130                                .map(|header| {
131                                    (
132                                        header.field.as_str().to_string(),
133                                        header.value.as_str().to_string(),
134                                    )
135                                })
136                                .collect();
137                            let mut body_text = String::new();
138                            let _ = request.as_reader().read_to_string(&mut body_text);
139                            let remote_addr = request.remote_addr().map(|addr| addr.to_string());
140                            let (reply_tx, reply_rx) = mpsc::channel();
141
142                            if tx
143                                .send(HttpServerMessage::Request {
144                                    component_id,
145                                    request_id,
146                                    method,
147                                    path,
148                                    query,
149                                    url: target,
150                                    headers,
151                                    body_text,
152                                    remote_addr,
153                                    reply_tx,
154                                })
155                                .is_err()
156                            {
157                                return;
158                            }
159
160                            let Ok(reply) = reply_rx.recv() else {
161                                return;
162                            };
163                            let mut response = Response::from_string(reply.body_text)
164                                .with_status_code(StatusCode(reply.status));
165                            for (name, value) in reply.headers {
166                                if let Ok(header) =
167                                    Header::from_bytes(name.as_bytes(), value.as_bytes())
168                                {
169                                    response.add_header(header);
170                                }
171                            }
172                            let _ = request.respond(response);
173                        });
174                    }
175                    Ok(None) => {}
176                    Err(error) => {
177                        let _ = tx.send(HttpServerMessage::Error {
178                            component_id,
179                            phase: "accept".to_string(),
180                            message: error.to_string(),
181                            bind_addr: bind_addr_for_thread.clone(),
182                        });
183                        break;
184                    }
185                }
186            }
187        });
188
189        self.runtimes.insert(
190            component_id,
191            HttpServerRuntime {
192                shutdown,
193                join_handle: Some(join_handle),
194            },
195        );
196
197        self.drain_messages(emit);
198    }
199
200    pub fn remove_component(&mut self, component_id: ComponentId) {
201        if let Some(mut runtime) = self.runtimes.remove(&component_id) {
202            runtime.shutdown.store(true, Ordering::Relaxed);
203            if let Some(handle) = runtime.join_handle.take() {
204                let _ = handle.join();
205            }
206        }
207        self.pending_replies
208            .retain(|(server_component, _), _| *server_component != component_id);
209    }
210
211    pub fn deliver_reply(
212        &mut self,
213        component_id: ComponentId,
214        request_id: u64,
215        status: u16,
216        headers: Vec<(String, String)>,
217        body_text: String,
218    ) {
219        let Some(pending) = self.pending_replies.remove(&(component_id, request_id)) else {
220            return;
221        };
222        let _ = pending.reply_tx.send(HttpServerReplyPayload {
223            status,
224            headers,
225            body_text,
226        });
227    }
228
229    pub fn drain_messages(&mut self, emit: &mut dyn SignalEmitter) {
230        while let Ok(message) = self.message_rx.try_recv() {
231            match message {
232                HttpServerMessage::Request {
233                    component_id,
234                    request_id,
235                    method,
236                    path,
237                    query,
238                    url,
239                    headers,
240                    body_text,
241                    remote_addr,
242                    reply_tx,
243                } => {
244                    if !self.runtimes.contains_key(&component_id) {
245                        continue;
246                    }
247                    self.pending_replies
248                        .insert((component_id, request_id), PendingReply { reply_tx });
249                    emit.push_event(
250                        component_id,
251                        EventSignal::HttpRequest {
252                            request_id,
253                            method,
254                            path,
255                            query,
256                            url,
257                            headers,
258                            body_text,
259                            remote_addr,
260                        },
261                    );
262                }
263                HttpServerMessage::Error {
264                    component_id,
265                    phase,
266                    message,
267                    bind_addr,
268                } => {
269                    if !self.runtimes.contains_key(&component_id) && phase != "bind" {
270                        continue;
271                    }
272                    emit.push_event(
273                        component_id,
274                        EventSignal::HttpError {
275                            request_id: None,
276                            phase,
277                            message,
278                            url: None,
279                            bind_addr: Some(bind_addr),
280                        },
281                    );
282                }
283            }
284        }
285    }
286}
287
288impl Drop for HttpServerSystem {
289    fn drop(&mut self) {
290        let component_ids: Vec<_> = self.runtimes.keys().copied().collect();
291        for component_id in component_ids {
292            self.remove_component(component_id);
293        }
294    }
295}