1use std::sync::{Arc, RwLock};
4use std::time::Duration;
5
6use crate::error::HttpError;
7use crate::request::Request;
8use crate::response::ResponseWriter;
9
10pub trait Handler: Send + Sync {
16 fn serve_http(&self, w: &mut dyn ResponseWriter, r: &Request);
17}
18
19pub struct HandlerFunc(pub Box<dyn Fn(&mut dyn ResponseWriter, &Request) + Send + Sync>);
26
27impl Handler for HandlerFunc {
28 fn serve_http(&self, w: &mut dyn ResponseWriter, r: &Request) {
29 (self.0)(w, r)
30 }
31}
32
33impl<H: Handler> Handler for Arc<H> {
37 fn serve_http(&self, w: &mut dyn ResponseWriter, r: &Request) {
38 (**self).serve_http(w, r)
39 }
40}
41
42pub fn handler_func<F>(f: F) -> HandlerFunc
44where
45 F: Fn(&mut dyn ResponseWriter, &Request) + Send + Sync + 'static,
46{
47 HandlerFunc(Box::new(f))
48}
49
50struct MuxEntry {
55 handler: Arc<dyn Handler>,
56 pattern: String,
57}
58
59pub struct ServeMux {
67 entries: RwLock<Vec<MuxEntry>>,
68}
69
70impl ServeMux {
71 pub fn new() -> Self {
72 Self {
73 entries: RwLock::new(Vec::new()),
74 }
75 }
76
77 pub fn handle(&self, pattern: &str, handler: impl Handler + 'static) {
79 self.handle_arc(pattern, Arc::new(handler));
80 }
81
82 pub fn handle_func<F>(&self, pattern: &str, f: F)
84 where
85 F: Fn(&mut dyn ResponseWriter, &Request) + Send + Sync + 'static,
86 {
87 self.handle(pattern, handler_func(f));
88 }
89
90 fn handle_arc(&self, pattern: &str, handler: Arc<dyn Handler>) {
91 let mut entries = self.entries.write().unwrap();
92 if let Some(e) = entries.iter_mut().find(|e| e.pattern == pattern) {
94 e.handler = handler;
95 return;
96 }
97 entries.push(MuxEntry { handler, pattern: pattern.to_owned() });
98 }
99
100 pub fn match_handler(&self, path: &str) -> Option<Arc<dyn Handler>> {
102 let entries = self.entries.read().unwrap();
103 let mut best_len = 0usize;
104 let mut best: Option<Arc<dyn Handler>> = None;
105
106 for entry in entries.iter() {
107 let pat = entry.pattern.as_str();
108 if pat.ends_with('/') {
109 if path.starts_with(pat) && pat.len() > best_len {
111 best_len = pat.len();
112 best = Some(Arc::clone(&entry.handler));
113 }
114 } else {
115 if path == pat {
117 return Some(Arc::clone(&entry.handler));
118 }
119 }
120 }
121 best
122 }
123}
124
125impl Handler for ServeMux {
126 fn serve_http(&self, w: &mut dyn ResponseWriter, r: &Request) {
127 let path = r.url.path();
128 match self.match_handler(path) {
129 Some(h) => h.serve_http(w, r),
130 None => not_found_handler().serve_http(w, r),
131 }
132 }
133}
134
135impl Default for ServeMux {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141use std::sync::OnceLock;
146
147static DEFAULT_SERVE_MUX: OnceLock<Arc<ServeMux>> = OnceLock::new();
148
149fn default_mux() -> &'static Arc<ServeMux> {
150 DEFAULT_SERVE_MUX.get_or_init(|| Arc::new(ServeMux::new()))
151}
152
153pub fn handle(pattern: &str, handler: impl Handler + 'static) {
155 default_mux().handle(pattern, handler);
156}
157
158pub fn handle_func<F>(pattern: &str, f: F)
160where
161 F: Fn(&mut dyn ResponseWriter, &Request) + Send + Sync + 'static,
162{
163 default_mux().handle_func(pattern, f);
164}
165
166pub fn default_serve_mux() -> Arc<ServeMux> {
168 Arc::clone(default_mux())
169}
170
171pub fn not_found_handler() -> impl Handler {
177 handler_func(|w, _r| {
178 w.write_header(crate::status::NOT_FOUND);
179 let _ = w.write(b"404 page not found\n");
180 })
181}
182
183pub fn strip_prefix(prefix: String, handler: impl Handler + 'static) -> impl Handler {
190 let handler = Arc::new(handler);
191 handler_func(move |w, r| {
192 let path = r.url.path();
193 match path.strip_prefix(prefix.as_str()) {
194 None => not_found_handler().serve_http(w, r),
195 Some(stripped) => {
196 let mut new_url = r.url.clone();
198 new_url.set_path(if stripped.is_empty() { "/" } else { stripped });
199 match rebuild_request(r, new_url) {
200 Err(_) => crate::util::error(w, "internal error", 500),
201 Ok(req) => handler.serve_http(w, &req),
202 }
203 }
204 }
205 })
206}
207
208pub fn file_server(root: String) -> impl Handler {
216 handler_func(move |w, r| {
217 use std::io::Read;
218 use std::path::Path;
219
220 let url_path = r.url.path();
221
222 let rel = url_path.trim_start_matches('/');
224 let fs_path = if rel.is_empty() {
225 Path::new(&root).to_path_buf()
226 } else {
227 Path::new(&root).join(rel)
228 };
229
230 let root_canon = match std::fs::canonicalize(&root) {
233 Ok(p) => p,
234 Err(_) => {
235 crate::util::error(w, "500 Internal Server Error", crate::status::INTERNAL_SERVER_ERROR);
236 return;
237 }
238 };
239 let candidate_canon = std::fs::canonicalize(&fs_path)
242 .or_else(|_| std::fs::canonicalize(fs_path.parent().unwrap_or(&fs_path)))
243 .unwrap_or_else(|_| fs_path.clone());
244 if !candidate_canon.starts_with(&root_canon) {
245 crate::util::error(w, "403 Forbidden", crate::status::FORBIDDEN);
246 return;
247 }
248
249 match fs_path.metadata() {
251 Err(_) => {
252 crate::util::error(w, "404 Not Found", crate::status::NOT_FOUND);
253 return;
254 }
255 Ok(meta) if meta.is_dir() => {
256 crate::util::error(w, "403 Forbidden", crate::status::FORBIDDEN);
257 return;
258 }
259 Ok(_) => {}
260 }
261
262 let ct = {
264 let mut probe = [0u8; 512];
265 let n = std::fs::File::open(&fs_path)
266 .and_then(|mut f| f.read(&mut probe))
267 .unwrap_or(0);
268 crate::mime::detect_content_type(&probe[..n]).to_owned()
269 };
270
271 match std::fs::read(&fs_path) {
273 Err(_) => crate::util::error(w, "500 Internal Server Error", crate::status::INTERNAL_SERVER_ERROR),
274 Ok(data) => {
275 w.header().set("Content-Type", &ct);
276 w.header().set("Content-Length", data.len().to_string());
277 w.write_header(crate::status::OK);
278 let _ = w.write(&data);
279 }
280 }
281 })
282}
283
284pub fn timeout_handler(
292 handler: impl Handler + 'static,
293 timeout: Duration,
294 body: &'static str,
295) -> impl Handler {
296 let handler = Arc::new(handler);
297 handler_func(move |w, r| {
298 use go_lib::chan::chan;
299 use go_lib::context::with_timeout;
300
301 let (done_tx, done_rx) = chan::<BodyCapture>(1);
304
305 let inner_handler = Arc::clone(&handler);
306 let req_url = r.url.clone();
307 let method = r.method.clone();
308 let req_header = r.header.clone();
309 let host = r.host.clone();
310 let remote = r.remote_addr.clone();
311
312 let (ctx, cancel) = with_timeout(&go_lib::context::background(), timeout);
313
314 go_lib::go!(move || {
315 let mut inner_req = match Request::new(&method, req_url.as_str(), None) {
316 Ok(r) => r,
317 Err(_) => { done_tx.send(BodyCapture::default()); return; }
318 };
319 inner_req.header = req_header;
320 inner_req.host = host;
321 inner_req.remote_addr = remote;
322
323 let mut capture = BodyCapture::default();
324 inner_handler.serve_http(&mut capture, &inner_req);
325 done_tx.send(capture);
326 });
327
328 go_lib::select! {
330 recv(ctx.done()) -> _v => {
331 cancel.cancel();
332 w.write_header(crate::status::SERVICE_UNAVAILABLE);
333 let _ = w.write(body.as_bytes());
334 }
335 recv(done_rx) -> result => {
336 cancel.cancel();
337 if let Some(capture) = result {
338 for (name, values) in capture.header.iter() {
340 for val in values {
341 w.header().add(name, val.as_str());
342 }
343 }
344 let status = if capture.status == 0 { 200 } else { capture.status };
345 w.write_header(status);
346 let _ = w.write(&capture.body);
347 }
348 }
349 }
350 })
351}
352
353#[derive(Default)]
359struct BodyCapture {
360 status: u16,
361 header: crate::header::Header,
362 body: Vec<u8>,
363}
364
365impl ResponseWriter for BodyCapture {
366 fn header(&mut self) -> &mut crate::header::Header { &mut self.header }
367 fn write(&mut self, buf: &[u8]) -> Result<usize, crate::error::HttpError> {
368 self.body.extend_from_slice(buf);
369 Ok(buf.len())
370 }
371 fn write_header(&mut self, code: u16) {
372 if self.status == 0 { self.status = code; }
373 }
374}
375
376unsafe impl Send for BodyCapture {}
379
380fn rebuild_request(r: &Request, new_url: url::Url) -> Result<Request, HttpError> {
385 let mut req = Request::new_with_context(
386 &r.method,
387 new_url.as_str(),
388 None, r.context().clone(),
390 )?;
391 req.proto = r.proto.clone();
392 req.proto_major = r.proto_major;
393 req.proto_minor = r.proto_minor;
394 req.header = r.header.clone();
395 req.host = r.host.clone();
396 req.content_length = r.content_length;
397 req.transfer_encoding = r.transfer_encoding.clone();
398 req.remote_addr = r.remote_addr.clone();
399 req.trailer = r.trailer.clone();
400 Ok(req)
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use crate::response::ConnResponseWriter;
407 use crate::request::Request;
408
409 fn dummy_request(path: &str) -> Request {
410 Request::new("GET", &format!("http://example.com{path}"), None).unwrap()
411 }
412
413 struct RecordingWriter {
414 inner: ConnResponseWriter<Vec<u8>>,
415 }
416 impl RecordingWriter {
417 fn new() -> Self { Self { inner: ConnResponseWriter::new(Vec::new()) } }
418 fn bytes(mut self) -> Vec<u8> {
419 let _ = self.inner.finish();
420 self.inner.inner
421 }
422 }
423 impl ResponseWriter for RecordingWriter {
424 fn header(&mut self) -> &mut crate::header::Header { self.inner.header() }
425 fn write(&mut self, buf: &[u8]) -> Result<usize, crate::error::HttpError> { self.inner.write(buf) }
426 fn write_header(&mut self, code: u16) { self.inner.write_header(code) }
427 }
428
429 #[test]
430 fn exact_match() {
431 let mux = ServeMux::new();
432 mux.handle_func("/hello", |w, _| { let _ = w.write(b"hi"); });
433 let r = dummy_request("/hello");
434 let mut w = RecordingWriter::new();
435 mux.serve_http(&mut w, &r);
436 let out = w.bytes();
437 assert!(out.windows(2).any(|w| w == b"hi"), "body should contain 'hi'");
438 }
439
440 #[test]
441 fn prefix_match() {
442 let mux = ServeMux::new();
443 mux.handle_func("/static/", |w, _| { let _ = w.write(b"file"); });
444 let r = dummy_request("/static/foo.js");
445 let mut w = RecordingWriter::new();
446 mux.serve_http(&mut w, &r);
447 let out = w.bytes();
448 assert!(out.windows(4).any(|s| s == b"file"));
449 }
450
451 #[test]
452 fn not_found_fallback() {
453 let mux = ServeMux::new();
454 let r = dummy_request("/nowhere");
455 let mut w = RecordingWriter::new();
456 mux.serve_http(&mut w, &r);
457 let out = String::from_utf8(w.bytes()).unwrap();
458 assert!(out.contains("404"));
459 }
460
461 #[test]
462 fn longer_prefix_wins() {
463 let mux = ServeMux::new();
464 mux.handle_func("/api/", |w, _| { let _ = w.write(b"short"); });
465 mux.handle_func("/api/v2/", |w, _| { let _ = w.write(b"long"); });
466 let r = dummy_request("/api/v2/users");
467 let mut w = RecordingWriter::new();
468 mux.serve_http(&mut w, &r);
469 let out = w.bytes();
470 assert!(out.windows(4).any(|s| s == b"long"));
471 }
472
473 #[test]
476 fn strip_prefix_rewrites_path() {
477 let inner = handler_func(|w, r| {
479 let _ = w.write(r.url.path().as_bytes());
480 });
481 let h = strip_prefix("/api".to_owned(), inner);
482 let r = dummy_request("/api/users");
483 let mut w = RecordingWriter::new();
484 h.serve_http(&mut w, &r);
485 let body = String::from_utf8(w.bytes()).unwrap();
486 assert!(body.contains("/users"), "stripped path should be /users, got: {body:?}");
488 }
489
490 #[test]
491 fn strip_prefix_no_match_returns_404() {
492 let inner = handler_func(|w, _| { let _ = w.write(b"ok"); });
493 let h = strip_prefix("/api".to_owned(), inner);
494 let r = dummy_request("/other/path");
495 let mut w = RecordingWriter::new();
496 h.serve_http(&mut w, &r);
497 let out = String::from_utf8(w.bytes()).unwrap();
498 assert!(out.contains("404"));
499 }
500
501 #[test]
504 fn file_server_serves_existing_file() {
505 let dir = std::env::temp_dir();
507 let path = dir.join("go_http_test_file.txt");
508 std::fs::write(&path, b"hello file").unwrap();
509
510 let h = file_server(dir.to_str().unwrap().to_owned());
511 let r = dummy_request("/go_http_test_file.txt");
512 let mut w = RecordingWriter::new();
513 h.serve_http(&mut w, &r);
514 let out = w.bytes();
515 assert!(out.windows(10).any(|s| s == b"hello file"), "file content not found");
516
517 let _ = std::fs::remove_file(path);
518 }
519
520 #[test]
521 fn file_server_missing_file_returns_404() {
522 let dir = std::env::temp_dir();
523 let h = file_server(dir.to_str().unwrap().to_owned());
524 let r = dummy_request("/this_file_does_not_exist_xyz.bin");
525 let mut w = RecordingWriter::new();
526 h.serve_http(&mut w, &r);
527 let out = String::from_utf8(w.bytes()).unwrap();
528 assert!(out.contains("404"), "expected 404 in response, got: {}", out);
529 }
530
531 #[test]
532 fn file_server_rejects_path_traversal() {
533 let root = std::env::temp_dir().join("go_http_test_root");
535 std::fs::create_dir_all(&root).unwrap();
536
537 let outside = std::env::temp_dir().join("go_http_outside.txt");
539 std::fs::write(&outside, b"secret").unwrap();
540
541 let h = file_server(root.to_str().unwrap().to_owned());
545 let r = dummy_request("/../go_http_outside.txt");
546 let mut w = RecordingWriter::new();
547 h.serve_http(&mut w, &r);
548 let out = String::from_utf8(w.bytes()).unwrap();
549
550 let _ = std::fs::remove_file(outside);
551 let _ = std::fs::remove_dir(root);
552
553 assert!(
557 out.contains("403") || out.contains("404"),
558 "expected 403 or 404, got: {out:?}"
559 );
560 assert!(!out.contains("secret"), "traversal should not expose file content");
561 }
562
563 }