Skip to main content

reinhardt_dispatch/
handler.rs

1//! Base HTTP request handler
2//!
3//! This module provides the base handler for processing HTTP requests,
4//! similar to Django's `django.core.handlers.base.BaseHandler`.
5
6use hyper::StatusCode;
7use reinhardt_core::signals::{
8	RequestFinishedEvent, RequestStartedEvent, request_finished, request_started,
9};
10use reinhardt_http::Handler;
11use reinhardt_http::{Request, Response};
12use reinhardt_urls::routers::DefaultRouter;
13use std::sync::Arc;
14use tracing::{debug, error, trace, warn};
15
16use crate::DispatchError;
17
18/// Base HTTP request handler
19///
20/// Handles the complete request lifecycle including URL resolution,
21/// view execution, and signal emission.
22pub struct BaseHandler {
23	/// Whether the handler operates in async mode.
24	///
25	/// This flag mirrors Django's `BaseHandler._is_async` and is read by
26	/// `Dispatcher` to choose between sync and async code paths. When
27	/// `false`, async dispatch still works but callers may opt for a
28	/// blocking wrapper.
29	// Allow dead_code: read via is_async() accessor; behavioral branching planned
30	#[allow(dead_code)]
31	is_async: bool,
32	router: Option<Arc<DefaultRouter>>,
33}
34
35impl BaseHandler {
36	/// Create a new base handler
37	pub fn new() -> Self {
38		Self {
39			is_async: true,
40			router: None,
41		}
42	}
43
44	/// Create a handler with a router
45	///
46	/// # Examples
47	///
48	/// ```
49	/// use reinhardt_dispatch::BaseHandler;
50	/// use reinhardt_urls::routers::DefaultRouter;
51	/// use std::sync::Arc;
52	///
53	/// let router = DefaultRouter::new();
54	/// let handler = BaseHandler::with_router(Arc::new(router));
55	/// assert!(handler.is_async());
56	/// ```
57	pub fn with_router(router: Arc<DefaultRouter>) -> Self {
58		Self {
59			is_async: true,
60			router: Some(router),
61		}
62	}
63
64	/// Handle an HTTP request
65	///
66	/// This is the main entry point for request processing. It:
67	/// 1. Emits `request_started` signal
68	/// 2. Resolves URL and dispatches to view
69	/// 3. Emits `request_finished` signal
70	pub async fn handle_request(
71		&self,
72		request: Request,
73	) -> std::result::Result<Response, DispatchError> {
74		trace!("Handling request: {:?}", request.uri);
75
76		// Emit request_started signal
77		let event = RequestStartedEvent::new();
78		if let Err(e) = request_started().send(event).await {
79			warn!("Failed to send request_started signal: {}", e);
80		}
81
82		// Get response with router
83		let response = Self::get_response_async(request, self.router.as_ref()).await;
84
85		// Emit request_finished signal
86		let event = RequestFinishedEvent::new();
87		if let Err(e) = request_finished().send(event).await {
88			warn!("Failed to send request_finished signal: {}", e);
89		}
90
91		response
92	}
93
94	/// Get response for a request (async version) with URL resolution
95	///
96	/// This is the core request processing logic that:
97	/// - Resolves the URL using the router
98	/// - Dispatches to the matched handler
99	/// - Returns a 404 response if no route matches
100	/// - Returns error for handler errors (will be converted to 500 by Handler trait)
101	async fn get_response_async(
102		request: Request,
103		router: Option<&Arc<DefaultRouter>>,
104	) -> std::result::Result<Response, DispatchError> {
105		debug!("Getting response for: {}", request.uri.path());
106
107		// URL resolution with router
108		if let Some(router) = router {
109			trace!("Attempting to route request through router");
110
111			// Use the router to handle the request
112			match router.handle(request).await {
113				Ok(response) => {
114					trace!("Route handled successfully");
115					return Ok(response);
116				}
117				Err(reinhardt_core::exception::Error::NotFound(msg)) => {
118					debug!("No route matched: {}", msg);
119					return Ok(Response::new(StatusCode::NOT_FOUND));
120				}
121				Err(e) => {
122					error!("Handler error: {}", e);
123					// Return error to allow middleware chain to handle it
124					return Err(DispatchError::View(e.to_string()));
125				}
126			}
127		}
128
129		// Fallback: router not configured, return 404 since no routes can match
130		debug!("No router configured, returning 404 Not Found");
131		Ok(Response::new(StatusCode::NOT_FOUND))
132	}
133
134	/// Process an exception and convert it to a response.
135	///
136	/// Error details are logged server-side but not included in the response
137	/// body to prevent information disclosure.
138	pub async fn handle_exception(&self, _request: &Request, error: DispatchError) -> Response {
139		error!("Handling exception: {}", error);
140
141		crate::build_error_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
142	}
143
144	/// Check if handler is configured for async mode
145	pub fn is_async(&self) -> bool {
146		self.is_async
147	}
148
149	/// Set async mode for the handler
150	pub fn set_async(&mut self, is_async: bool) {
151		self.is_async = is_async;
152	}
153}
154
155impl Default for BaseHandler {
156	fn default() -> Self {
157		Self::new()
158	}
159}
160
161#[async_trait::async_trait]
162impl Handler for BaseHandler {
163	async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
164		match self.handle_request(request).await {
165			Ok(response) => Ok(response),
166			Err(e) => {
167				// Log the detailed error server-side; return generic message to client
168				error!("Handler error in BaseHandler::handle: {}", e);
169				Ok(crate::build_error_response(
170					StatusCode::INTERNAL_SERVER_ERROR,
171					"Internal Server Error",
172				))
173			}
174		}
175	}
176}
177
178#[cfg(test)]
179mod tests {
180	use super::*;
181	use async_trait::async_trait;
182	use bytes::Bytes;
183	use hyper::{HeaderMap, Method, Version};
184	use reinhardt_urls::routers::{DefaultRouter, Router, path};
185
186	// Test handler for routing tests
187	struct TestHandler {
188		response_body: String,
189	}
190
191	#[async_trait]
192	impl Handler for TestHandler {
193		async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
194			Ok(Response::ok().with_body(self.response_body.clone()))
195		}
196	}
197
198	#[tokio::test]
199	async fn test_base_handler_new() {
200		let handler = BaseHandler::new();
201		assert!(handler.is_async());
202	}
203
204	#[tokio::test]
205	async fn test_base_handler_handle_request() {
206		let handler = BaseHandler::new();
207		let request = Request::builder()
208			.method(Method::GET)
209			.uri("/")
210			.version(Version::HTTP_11)
211			.headers(HeaderMap::new())
212			.body(Bytes::new())
213			.build()
214			.unwrap();
215
216		let response = handler.handle_request(request).await;
217		let resp = response.unwrap();
218		// Handler without router should return 404 Not Found
219		assert_eq!(resp.status, StatusCode::NOT_FOUND);
220	}
221
222	#[tokio::test]
223	async fn test_base_handler_handle_exception() {
224		let handler = BaseHandler::new();
225		let request = Request::builder()
226			.method(Method::GET)
227			.uri("/")
228			.version(Version::HTTP_11)
229			.headers(HeaderMap::new())
230			.body(Bytes::new())
231			.build()
232			.unwrap();
233		let error = DispatchError::View("Test error".to_string());
234
235		let response = handler.handle_exception(&request, error).await;
236		assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
237	}
238
239	// ==========================================================================
240	// Information Disclosure Prevention Tests (#439)
241	// ==========================================================================
242
243	#[tokio::test]
244	async fn test_handle_exception_does_not_expose_internal_details() {
245		// Arrange
246		let handler = BaseHandler::new();
247		let request = Request::builder()
248			.method(Method::GET)
249			.uri("/")
250			.version(Version::HTTP_11)
251			.headers(HeaderMap::new())
252			.body(Bytes::new())
253			.build()
254			.unwrap();
255		let sensitive_detail = "database connection refused at postgres://admin:secret@db:5432";
256		let error = DispatchError::Internal(sensitive_detail.to_string());
257
258		// Act
259		let response = handler.handle_exception(&request, error).await;
260
261		// Assert: response must not contain the sensitive detail
262		let body = String::from_utf8(response.body.to_vec()).unwrap();
263		assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
264		assert!(!body.contains("database"));
265		assert!(!body.contains("postgres"));
266		assert!(!body.contains("secret"));
267		assert_eq!(body, "Internal Server Error");
268	}
269
270	#[tokio::test]
271	async fn test_handler_impl_does_not_expose_error_in_body() {
272		// Arrange: create a handler that returns a view error with internal paths
273		struct FailingHandler;
274
275		#[async_trait]
276		impl Handler for FailingHandler {
277			async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
278				Err(reinhardt_core::exception::Error::Internal(
279					"module::secret_handler panicked at /src/app/handlers.rs:42".to_string(),
280				))
281			}
282		}
283
284		let mut router = DefaultRouter::new();
285		let failing = Arc::new(FailingHandler);
286		let route = path("/fail", failing).with_name("fail");
287		router.add_route(route);
288		let handler = BaseHandler::with_router(Arc::new(router));
289
290		let request = Request::builder()
291			.method(Method::GET)
292			.uri("/fail")
293			.version(Version::HTTP_11)
294			.headers(HeaderMap::new())
295			.body(Bytes::new())
296			.build()
297			.unwrap();
298
299		// Act
300		let response = handler.handle(request).await.unwrap();
301
302		// Assert: internal details must not leak
303		let body = String::from_utf8(response.body.to_vec()).unwrap();
304		assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
305		assert!(!body.contains("panicked"));
306		assert!(!body.contains("handlers.rs"));
307		assert!(!body.contains("secret_handler"));
308		assert_eq!(body, "Internal Server Error");
309	}
310
311	#[test]
312	fn test_base_handler_async_mode() {
313		let mut handler = BaseHandler::new();
314		assert!(handler.is_async());
315
316		handler.set_async(false);
317		assert!(!handler.is_async());
318	}
319
320	#[tokio::test]
321	async fn test_base_handler_different_methods() {
322		let handler = BaseHandler::new();
323
324		for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] {
325			let request = Request::builder()
326				.method(method)
327				.uri("/")
328				.version(Version::HTTP_11)
329				.headers(HeaderMap::new())
330				.body(Bytes::new())
331				.build()
332				.unwrap();
333
334			let response = handler.handle_request(request).await;
335			assert!(response.is_ok());
336		}
337	}
338
339	#[tokio::test]
340	async fn test_base_handler_different_uris() {
341		let handler = BaseHandler::new();
342
343		for path in ["/", "/test", "/api/v1/users", "/admin/login"] {
344			let request = Request::builder()
345				.method(Method::GET)
346				.uri(path)
347				.version(Version::HTTP_11)
348				.headers(HeaderMap::new())
349				.body(Bytes::new())
350				.build()
351				.unwrap();
352
353			let response = handler.handle_request(request).await;
354			assert!(response.is_ok());
355		}
356	}
357
358	#[tokio::test]
359	async fn test_handler_with_router() {
360		// Create a router with a test route
361		let mut router = DefaultRouter::new();
362		let test_handler = Arc::new(TestHandler {
363			response_body: "Test response".to_string(),
364		});
365		let route = path("/test", test_handler).with_name("test");
366		router.add_route(route);
367
368		// Create BaseHandler with router
369		let handler = BaseHandler::with_router(Arc::new(router));
370
371		// Test matching route
372		let request = Request::builder()
373			.method(Method::GET)
374			.uri("/test")
375			.version(Version::HTTP_11)
376			.headers(HeaderMap::new())
377			.body(Bytes::new())
378			.build()
379			.unwrap();
380
381		let response = handler.handle_request(request).await;
382		let resp = response.unwrap();
383		assert_eq!(resp.status, StatusCode::OK);
384
385		let body = String::from_utf8(resp.body.to_vec()).unwrap();
386		assert_eq!(body, "Test response");
387	}
388
389	#[tokio::test]
390	async fn test_handler_404_not_found() {
391		// Create empty router
392		let router = DefaultRouter::new();
393
394		// Create BaseHandler with router
395		let handler = BaseHandler::with_router(Arc::new(router));
396
397		// Test non-existent route
398		let request = Request::builder()
399			.method(Method::GET)
400			.uri("/nonexistent")
401			.version(Version::HTTP_11)
402			.headers(HeaderMap::new())
403			.body(Bytes::new())
404			.build()
405			.unwrap();
406
407		let response = handler.handle_request(request).await;
408		let resp = response.unwrap();
409		assert_eq!(resp.status, StatusCode::NOT_FOUND);
410	}
411
412	#[tokio::test]
413	async fn test_handler_multiple_routes() {
414		// Create router with multiple routes
415		let mut router = DefaultRouter::new();
416
417		let hello_handler = Arc::new(TestHandler {
418			response_body: "Hello".to_string(),
419		});
420		let hello_route = path("/hello", hello_handler).with_name("hello");
421		router.add_route(hello_route);
422
423		let world_handler = Arc::new(TestHandler {
424			response_body: "World".to_string(),
425		});
426		let world_route = path("/world", world_handler).with_name("world");
427		router.add_route(world_route);
428
429		let handler = BaseHandler::with_router(Arc::new(router));
430
431		// Test first route
432		let request = Request::builder()
433			.method(Method::GET)
434			.uri("/hello")
435			.version(Version::HTTP_11)
436			.headers(HeaderMap::new())
437			.body(Bytes::new())
438			.build()
439			.unwrap();
440		let response = handler.handle_request(request).await.unwrap();
441		assert_eq!(response.status, StatusCode::OK);
442		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "Hello");
443
444		// Test second route
445		let request = Request::builder()
446			.method(Method::GET)
447			.uri("/world")
448			.version(Version::HTTP_11)
449			.headers(HeaderMap::new())
450			.body(Bytes::new())
451			.build()
452			.unwrap();
453		let response = handler.handle_request(request).await.unwrap();
454		assert_eq!(response.status, StatusCode::OK);
455		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "World");
456	}
457}