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 mut route = path("/fail", failing);
287		route.name = Some("fail".to_string());
288		router.add_route(route);
289		let handler = BaseHandler::with_router(Arc::new(router));
290
291		let request = Request::builder()
292			.method(Method::GET)
293			.uri("/fail")
294			.version(Version::HTTP_11)
295			.headers(HeaderMap::new())
296			.body(Bytes::new())
297			.build()
298			.unwrap();
299
300		// Act
301		let response = handler.handle(request).await.unwrap();
302
303		// Assert: internal details must not leak
304		let body = String::from_utf8(response.body.to_vec()).unwrap();
305		assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
306		assert!(!body.contains("panicked"));
307		assert!(!body.contains("handlers.rs"));
308		assert!(!body.contains("secret_handler"));
309		assert_eq!(body, "Internal Server Error");
310	}
311
312	#[test]
313	fn test_base_handler_async_mode() {
314		let mut handler = BaseHandler::new();
315		assert!(handler.is_async());
316
317		handler.set_async(false);
318		assert!(!handler.is_async());
319	}
320
321	#[tokio::test]
322	async fn test_base_handler_different_methods() {
323		let handler = BaseHandler::new();
324
325		for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] {
326			let request = Request::builder()
327				.method(method)
328				.uri("/")
329				.version(Version::HTTP_11)
330				.headers(HeaderMap::new())
331				.body(Bytes::new())
332				.build()
333				.unwrap();
334
335			let response = handler.handle_request(request).await;
336			assert!(response.is_ok());
337		}
338	}
339
340	#[tokio::test]
341	async fn test_base_handler_different_uris() {
342		let handler = BaseHandler::new();
343
344		for path in ["/", "/test", "/api/v1/users", "/admin/login"] {
345			let request = Request::builder()
346				.method(Method::GET)
347				.uri(path)
348				.version(Version::HTTP_11)
349				.headers(HeaderMap::new())
350				.body(Bytes::new())
351				.build()
352				.unwrap();
353
354			let response = handler.handle_request(request).await;
355			assert!(response.is_ok());
356		}
357	}
358
359	#[tokio::test]
360	async fn test_handler_with_router() {
361		// Create a router with a test route
362		let mut router = DefaultRouter::new();
363		let test_handler = Arc::new(TestHandler {
364			response_body: "Test response".to_string(),
365		});
366		let mut route = path("/test", test_handler);
367		route.name = Some("test".to_string());
368		router.add_route(route);
369
370		// Create BaseHandler with router
371		let handler = BaseHandler::with_router(Arc::new(router));
372
373		// Test matching route
374		let request = Request::builder()
375			.method(Method::GET)
376			.uri("/test")
377			.version(Version::HTTP_11)
378			.headers(HeaderMap::new())
379			.body(Bytes::new())
380			.build()
381			.unwrap();
382
383		let response = handler.handle_request(request).await;
384		let resp = response.unwrap();
385		assert_eq!(resp.status, StatusCode::OK);
386
387		let body = String::from_utf8(resp.body.to_vec()).unwrap();
388		assert_eq!(body, "Test response");
389	}
390
391	#[tokio::test]
392	async fn test_handler_404_not_found() {
393		// Create empty router
394		let router = DefaultRouter::new();
395
396		// Create BaseHandler with router
397		let handler = BaseHandler::with_router(Arc::new(router));
398
399		// Test non-existent route
400		let request = Request::builder()
401			.method(Method::GET)
402			.uri("/nonexistent")
403			.version(Version::HTTP_11)
404			.headers(HeaderMap::new())
405			.body(Bytes::new())
406			.build()
407			.unwrap();
408
409		let response = handler.handle_request(request).await;
410		let resp = response.unwrap();
411		assert_eq!(resp.status, StatusCode::NOT_FOUND);
412	}
413
414	#[tokio::test]
415	async fn test_handler_multiple_routes() {
416		// Create router with multiple routes
417		let mut router = DefaultRouter::new();
418
419		let hello_handler = Arc::new(TestHandler {
420			response_body: "Hello".to_string(),
421		});
422		let mut hello_route = path("/hello", hello_handler);
423		hello_route.name = Some("hello".to_string());
424		router.add_route(hello_route);
425
426		let world_handler = Arc::new(TestHandler {
427			response_body: "World".to_string(),
428		});
429		let mut world_route = path("/world", world_handler);
430		world_route.name = Some("world".to_string());
431		router.add_route(world_route);
432
433		let handler = BaseHandler::with_router(Arc::new(router));
434
435		// Test first route
436		let request = Request::builder()
437			.method(Method::GET)
438			.uri("/hello")
439			.version(Version::HTTP_11)
440			.headers(HeaderMap::new())
441			.body(Bytes::new())
442			.build()
443			.unwrap();
444		let response = handler.handle_request(request).await.unwrap();
445		assert_eq!(response.status, StatusCode::OK);
446		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "Hello");
447
448		// Test second route
449		let request = Request::builder()
450			.method(Method::GET)
451			.uri("/world")
452			.version(Version::HTTP_11)
453			.headers(HeaderMap::new())
454			.body(Bytes::new())
455			.build()
456			.unwrap();
457		let response = handler.handle_request(request).await.unwrap();
458		assert_eq!(response.status, StatusCode::OK);
459		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "World");
460	}
461}