1use crate::host::{Request, Response};
8#[cfg(not(test))]
9use crate::memory::SyncCell;
10
11pub mod host;
13mod memory;
14
15struct Handler {
16 guest: Box<dyn Guest>,
17}
18
19pub trait Guest {
25 fn handle_request(&self, _request: &Request, _response: &Response) -> (bool, i32) {
31 (true, 0)
32 }
33
34 fn handle_response(&self, _req_ctx: i32, _request: &Request, _response: &Response, _is_error: bool) {}
39}
40
41#[cfg(not(test))]
42static GUEST: SyncCell<Option<Handler>> = SyncCell::new(None);
43
44#[cfg(test)]
45thread_local! {
46 static GUEST: std::cell::UnsafeCell<Option<Handler>> = const { std::cell::UnsafeCell::new(None) };
47}
48
49#[cfg(not(test))]
50fn with_guest<R>(f: impl FnOnce(&mut Option<Handler>) -> R) -> R {
51 let g = unsafe { &mut *GUEST.get() };
53 f(g)
54}
55
56#[cfg(test)]
57fn with_guest<R>(f: impl FnOnce(&mut Option<Handler>) -> R) -> R {
58 GUEST.with(|cell| {
59 let g = unsafe { &mut *cell.get() };
61 f(g)
62 })
63}
64
65pub fn register<T: Guest + 'static>(guest: T) {
70 with_guest(|g| {
71 if g.is_none() {
72 *g = Some(Handler { guest: Box::new(guest) });
73 }
74 });
75}
76
77#[unsafe(export_name = "handle_request")]
78fn http_request() -> i64 {
79 with_guest(|g| {
80 let (next, ctx_next) = match g {
81 Some(handler) => handler.guest.handle_request(&Request::new(), &Response::new()),
82 None => (true, 0),
83 };
84 if next { (ctx_next as i64) << 32 | 1 } else { 0 }
85 })
86}
87
88#[unsafe(export_name = "handle_response")]
89fn http_response(req_ctx: i32, is_error: i32) {
90 with_guest(|g| {
91 if let Some(handler) = g {
92 handler.guest.handle_response(req_ctx, &Request::new(), &Response::new(), is_error == 1);
93 }
94 });
95}
96
97#[cfg(feature = "log")]
98mod host_logger;
99#[cfg(feature = "log")]
100pub use host_logger::HostLogger;
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::host::admin;
106 use crate::host::feature;
107 use std::sync::Arc;
108 use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, Ordering};
109
110 struct TestPlugin {
115 request_handled: Arc<AtomicBool>,
116 response_handled: Arc<AtomicBool>,
117 continue_request: bool,
118 ctx_value: i32,
119 }
120
121 impl Guest for TestPlugin {
122 fn handle_request(&self, _request: &Request, _response: &Response) -> (bool, i32) {
123 self.request_handled.store(true, Ordering::SeqCst);
124 (self.continue_request, self.ctx_value)
125 }
126
127 fn handle_response(&self, _req_ctx: i32, _request: &Request, _response: &Response, _is_error: bool) {
128 self.response_handled.store(true, Ordering::SeqCst);
129 }
130 }
131
132 #[test]
133 fn guest_default_implementation() {
134 struct DefaultGuest;
135 impl Guest for DefaultGuest {}
136
137 let guest = DefaultGuest;
138 let request = Request::new();
139 let response = Response::new();
140
141 let (cont, ctx) = guest.handle_request(&request, &response);
142 assert!(cont);
143 assert_eq!(ctx, 0);
144 }
145
146 #[test]
147 fn guest_custom_implementation() {
148 let request_handled = Arc::new(AtomicBool::new(false));
149 let response_handled = Arc::new(AtomicBool::new(false));
150
151 let plugin = TestPlugin {
152 request_handled: request_handled.clone(),
153 response_handled: response_handled.clone(),
154 continue_request: true,
155 ctx_value: 42,
156 };
157
158 let request = Request::new();
159 let response = Response::new();
160
161 let (cont, ctx) = plugin.handle_request(&request, &response);
162 assert!(cont);
163 assert_eq!(ctx, 42);
164 assert!(request_handled.load(Ordering::SeqCst));
165
166 plugin.handle_response(ctx, &request, &response, false);
167 assert!(response_handled.load(Ordering::SeqCst));
168 }
169
170 #[test]
171 fn guest_stop_request() {
172 struct StopPlugin;
173 impl Guest for StopPlugin {
174 fn handle_request(&self, _request: &Request, _response: &Response) -> (bool, i32) {
175 (false, 0)
176 }
177 }
178
179 let plugin = StopPlugin;
180 let request = Request::new();
181 let response = Response::new();
182
183 let (cont, _) = plugin.handle_request(&request, &response);
184 assert!(!cont);
185 }
186
187 #[test]
188 fn guest_default_handle_response() {
189 struct DefaultResponsePlugin;
191 impl Guest for DefaultResponsePlugin {}
192
193 let plugin = DefaultResponsePlugin;
194 let request = Request::new();
195 let response = Response::new();
196
197 plugin.handle_response(42, &request, &response, false);
199 }
200
201 #[test]
202 fn guest_context_passing() {
203 let ctx_received = Arc::new(AtomicI32::new(0));
204 let ctx_clone = ctx_received.clone();
205
206 struct ContextPlugin {
207 ctx_received: Arc<AtomicI32>,
208 }
209
210 impl Guest for ContextPlugin {
211 fn handle_request(&self, _request: &Request, _response: &Response) -> (bool, i32) {
212 (true, 12345)
213 }
214
215 fn handle_response(&self, req_ctx: i32, _request: &Request, _response: &Response, _is_error: bool) {
216 self.ctx_received.store(req_ctx, Ordering::SeqCst);
217 }
218 }
219
220 let plugin = ContextPlugin { ctx_received: ctx_clone };
221 let request = Request::new();
222 let response = Response::new();
223
224 let (_, ctx) = plugin.handle_request(&request, &response);
225 plugin.handle_response(ctx, &Request::new(), &Response::new(), false);
226
227 assert_eq!(ctx_received.load(Ordering::SeqCst), 12345);
228 }
229
230 struct BlockingPlugin {
232 blocked_paths: Vec<&'static str>,
233 }
234
235 impl Guest for BlockingPlugin {
236 fn handle_request(&self, request: &Request, response: &Response) -> (bool, i32) {
237 let uri = request.uri();
238 let uri_str = uri.to_str().unwrap_or("");
239
240 for blocked in &self.blocked_paths {
241 if uri_str.contains(blocked) {
242 response.set_status(403);
243 response.body.write(b"Forbidden");
244 return (false, 0);
245 }
246 }
247 (true, 0)
248 }
249 }
250
251 #[test]
252 fn e2e_blocking_plugin_allows() {
253 let plugin = BlockingPlugin { blocked_paths: vec!["/admin", "/secret"] };
254 let request = Request::new();
255 let response = Response::new();
256
257 let (cont, _) = plugin.handle_request(&request, &response);
259 assert!(cont);
260 }
261
262 #[test]
263 fn e2e_blocking_plugin_blocks() {
264 let plugin = BlockingPlugin { blocked_paths: vec!["test"] };
266 let request = Request::new();
267 let response = Response::new();
268
269 let (cont, _) = plugin.handle_request(&request, &response);
271 assert!(!cont);
272 }
273
274 struct ConfigurablePlugin;
276
277 impl Guest for ConfigurablePlugin {
278 fn handle_request(&self, request: &Request, _response: &Response) -> (bool, i32) {
279 let config = admin::config();
280 if config.to_str().unwrap_or("").contains("config") {
281 request.header.add(b"X-Config-Loaded", b"true");
282 }
283 (true, 0)
284 }
285 }
286
287 #[test]
288 fn e2e_configurable_plugin() {
289 let plugin = ConfigurablePlugin;
290 let request = Request::new();
291 let response = Response::new();
292
293 let (cont, _) = plugin.handle_request(&request, &response);
294 assert!(cont);
295 }
296
297 struct FeatureEnablingPlugin;
299
300 impl Guest for FeatureEnablingPlugin {
301 fn handle_request(&self, _request: &Request, _response: &Response) -> (bool, i32) {
302 admin::enable(feature::BufferRequest | feature::BufferResponse);
303 (true, 0)
304 }
305 }
306
307 #[test]
308 fn e2e_feature_enabling_plugin() {
309 let plugin = FeatureEnablingPlugin;
310 let request = Request::new();
311 let response = Response::new();
312
313 let (cont, _) = plugin.handle_request(&request, &response);
314 assert!(cont);
315 }
316
317 struct FullCyclePlugin {
319 pub request_count: AtomicU32,
320 }
321
322 impl Guest for FullCyclePlugin {
323 fn handle_request(&self, request: &Request, _response: &Response) -> (bool, i32) {
324 let count = self.request_count.fetch_add(1, Ordering::SeqCst);
325
326 let _method = request.method();
328 let _uri = request.uri();
329 let _source = request.source_addr();
330
331 request.header.add(b"X-Request-Id", &format!("{}", count).into_bytes());
333 request.header.set(b"X-Foo", &format!("{}", count).into_bytes());
334 request.header.remove(b"X-Foo");
335 (true, count as i32)
336 }
337
338 fn handle_response(&self, req_ctx: i32, _request: &Request, response: &Response, is_error: bool) {
339 if !is_error {
340 response.header.set(b"X-Processed-By", b"FullCyclePlugin");
341 response.header.add(b"X-Request-Context", &format!("{}", req_ctx).into_bytes());
342 }
343 }
344 }
345
346 #[test]
347 fn e2e_full_cycle_plugin() {
348 let plugin = FullCyclePlugin { request_count: AtomicU32::new(0) };
349 let request = Request::new();
350 let response = Response::new();
351
352 let (cont1, ctx1) = plugin.handle_request(&request, &response);
354 assert!(cont1);
355 assert_eq!(ctx1, 0);
356 plugin.handle_response(ctx1, &Request::new(), &Response::new(), false);
357
358 let req2 = Request::new();
360 let res2 = Response::new();
361 let (cont2, ctx2) = plugin.handle_request(&req2, &res2);
362 assert!(cont2);
363 assert_eq!(ctx2, 1);
364 plugin.handle_response(ctx2, &Request::new(), &Response::new(), false);
365 }
366
367 struct SimplePlugin;
372
373 impl Guest for SimplePlugin {}
374
375 #[test]
376 fn register_plugin() {
377 let plugin = SimplePlugin;
380 register(plugin);
381 }
382
383 #[test]
388 fn http_request_returns_continue_with_context() {
389 let result = http_request();
392 assert_eq!(result & 1, 1);
394 }
395
396 #[test]
397 fn http_response_does_not_panic() {
398 http_response(0, 0);
400 http_response(42, 0);
401 http_response(0, 1);
402 http_response(123, 1);
403 }
404
405 #[test]
406 fn http_response_passes_correct_parameters() {
407 let ctx_received = Arc::new(AtomicI32::new(-1));
410 let is_error_received = Arc::new(AtomicU8::new(255)); struct ResponseTrackingPlugin {
413 ctx_received: Arc<AtomicI32>,
414 is_error_received: Arc<AtomicU8>,
415 }
416
417 impl Guest for ResponseTrackingPlugin {
418 fn handle_response(&self, req_ctx: i32, _request: &Request, _response: &Response, is_error: bool) {
419 self.ctx_received.store(req_ctx, Ordering::SeqCst);
420 self.is_error_received.store(if is_error { 1 } else { 0 }, Ordering::SeqCst);
421 }
422 }
423
424 let plugin = ResponseTrackingPlugin { ctx_received: ctx_received.clone(), is_error_received: is_error_received.clone() };
425
426 let request = Request::new();
427 let response = Response::new();
428
429 plugin.handle_response(42, &request, &response, false);
431 assert_eq!(ctx_received.load(Ordering::SeqCst), 42);
432 assert_eq!(is_error_received.load(Ordering::SeqCst), 0);
433
434 plugin.handle_response(123, &Request::new(), &Response::new(), true);
436 assert_eq!(ctx_received.load(Ordering::SeqCst), 123);
437 assert_eq!(is_error_received.load(Ordering::SeqCst), 1);
438 }
439
440 #[test]
441 fn http_request_without_guest_returns_default() {
442 let result = http_request();
445 assert_eq!(result & 1, 1);
447 }
448}