mittens_engine/engine/ecs/system/
http_server_system.rs1use 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 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}