mirror of
				https://codeberg.org/slatian/service.echoip-slatecave.git
				synced 2025-10-31 08:58:08 +01:00 
			
		
		
		
	Added a templating helper
This commit is contained in:
		
							
								
								
									
										221
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										221
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -3,6 +3,9 @@ use axum::{ | ||||
| 	extract::State, | ||||
| 	http::StatusCode, | ||||
| 	response::Html, | ||||
| 	response::IntoResponse, | ||||
| 	response::Response, | ||||
| 	response::Json, | ||||
| 	Router, | ||||
| 	routing::get, | ||||
| }; | ||||
| @@ -11,13 +14,15 @@ use trust_dns_resolver::{ | ||||
| 	TokioAsyncResolver, | ||||
| 	config::ResolverOpts, | ||||
| 	config::ResolverConfig, | ||||
| 	error::*, | ||||
| }; | ||||
|  | ||||
|  | ||||
| use std::net::{IpAddr, Ipv4Addr}; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| mod simple_dns; | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize, Clone, Copy)] | ||||
| enum ResponseFormat { | ||||
| 	#[serde(rename="text/plain", alias="text")] | ||||
| 	TextPlain, | ||||
| @@ -30,10 +35,10 @@ enum ResponseFormat { | ||||
| impl ToString for ResponseFormat { | ||||
| 	fn to_string(&self) -> String { | ||||
| 		match self { | ||||
| 			ResponseFormat::TextPlain => "text/plain".to_string(), | ||||
| 			ResponseFormat::TextHtml => "text/html".to_string(), | ||||
| 			ResponseFormat::ApplicationJson => "application/json".to_string(), | ||||
| 		} | ||||
| 			ResponseFormat::TextPlain => "text/plain", | ||||
| 			ResponseFormat::TextHtml => "text/html", | ||||
| 			ResponseFormat::ApplicationJson => "application/json", | ||||
| 		}.to_string() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -46,27 +51,47 @@ struct IpQuery { | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| struct DigQuery { | ||||
| 	name:   String, | ||||
| 	format: Option<ResponseFormat> | ||||
| 	format: Option<ResponseFormat>, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| struct MxRecord { | ||||
| 	preference: u16, | ||||
| 	exchange:   String, | ||||
| struct IpResult { | ||||
| 	hostname: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| struct DigResult { | ||||
| 	a:    Vec<IpAddr>, | ||||
| 	aaaa: Vec<IpAddr>, | ||||
| 	mx:   Vec<MxRecord>, | ||||
| struct TemplatingEngine { | ||||
| 	tera: Tera, | ||||
| } | ||||
|  | ||||
| struct ServiceSharedState { | ||||
| 	tera: Tera, | ||||
| 	dns_resolver: TokioAsyncResolver, | ||||
| 	templating_engine: TemplatingEngine, | ||||
| 	dns_resolver:      TokioAsyncResolver, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| #[serde(untagged)] | ||||
| enum EchoipView { | ||||
| 	Dig { query: DigQuery, result: simple_dns::DnsLookupResult }, | ||||
| 	Index { query: IpQuery, result: IpResult }, | ||||
| 	Ip { query: IpQuery, result: IpResult }, | ||||
| 	Message(String), | ||||
| 	#[serde(rename="404")] | ||||
| 	NotFound, | ||||
| } | ||||
|  | ||||
| impl EchoipView { | ||||
| 	fn template_name(&self) -> String { | ||||
| 		match self { | ||||
| 			EchoipView::Dig{..} => "dig", | ||||
| 			EchoipView::Index{..} => "index", | ||||
| 			EchoipView::Ip{..} => "ip", | ||||
| 			EchoipView::Message(..) => "message", | ||||
| 			EchoipView::NotFound => "404", | ||||
| 		}.to_string() | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| 	// Initalize Tera templates | ||||
| @@ -94,7 +119,9 @@ async fn main() { | ||||
| 	 | ||||
| 	// Initialize shared state | ||||
| 	let shared_state = Arc::new(ServiceSharedState{ | ||||
| 		tera: tera, | ||||
| 		templating_engine: TemplatingEngine{ | ||||
| 			tera: tera, | ||||
| 		}, | ||||
| 		dns_resolver: dns_resolver, | ||||
| 	}); | ||||
| 	 | ||||
| @@ -114,130 +141,94 @@ async fn main() { | ||||
|         .unwrap(); | ||||
| } | ||||
|  | ||||
| async fn hello_world_handler() -> &'static str { | ||||
| 	"Hello, there, you, awesome creature!" | ||||
| async fn hello_world_handler( | ||||
| 	State(arc_state): State<Arc<ServiceSharedState>>, | ||||
| ) -> Response { | ||||
| 	let state = Arc::clone(&arc_state); | ||||
|  | ||||
| 	state.templating_engine.render_view( | ||||
| 		ResponseFormat::TextPlain, | ||||
| 		EchoipView::Message("Hello! There, You, Awesome Creature!".to_string()) | ||||
| 	).await	 | ||||
| } | ||||
|  | ||||
| async fn simple_reverse_dns_lookup( | ||||
| 		resolver: &TokioAsyncResolver, | ||||
| 		address: &IpAddr, | ||||
| ) -> Option<String> { | ||||
| 	let revese_res = resolver.reverse_lookup(*address); | ||||
| 	match revese_res.await { | ||||
| 		Ok(lookup) => { | ||||
| 			for name in lookup { | ||||
| 				return Some(name.to_string()) | ||||
| 			} | ||||
| 			None | ||||
| 		} | ||||
| 		Err(e) => { | ||||
| 			let kind = e.kind(); | ||||
| 			match kind { | ||||
| 				ResolveErrorKind::NoRecordsFound { .. } => { | ||||
| 					//Ignore, that just happens … | ||||
| 				} | ||||
| 				_ => { | ||||
| 					println!("Reverse lookup on {address} failed: {kind}"); | ||||
| impl TemplatingEngine { | ||||
| 	async fn render_view( | ||||
| 		&self, | ||||
| 		format: ResponseFormat, | ||||
| 		view: EchoipView, | ||||
| 	) -> Response { | ||||
| 		match format { | ||||
| 			ResponseFormat::TextHtml => { | ||||
| 				let template_name = view.template_name(); | ||||
| 				 | ||||
| 				let mut context = tera::Context::new(); | ||||
| 				context.insert("view", &template_name); | ||||
| 				//intented for shared macros | ||||
| 				context.insert("format", &format.to_string()); | ||||
| 				context.insert("data", &view); | ||||
|  | ||||
| 				match self.tera.render(&(template_name+".html"), &context) { | ||||
| 					Ok(html) => Html(html).into_response(), | ||||
| 					Err(e) => { | ||||
| 						println!("There was an error while rendering index.html: {e:?}"); | ||||
| 						StatusCode::INTERNAL_SERVER_ERROR.into_response() | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			//TODO: Plain Text should have its own matcher  | ||||
| 			ResponseFormat::ApplicationJson | ResponseFormat::TextPlain => { | ||||
| 				match view { | ||||
| 					EchoipView::Dig{result, ..} => { | ||||
| 						Json(result).into_response() | ||||
| 					}, | ||||
| 					EchoipView::Index{result, ..} | EchoipView::Ip{result, ..} => { | ||||
| 						Json(result).into_response() | ||||
| 					}, | ||||
| 					_ => Json(view).into_response(), | ||||
| 				} | ||||
| 			} | ||||
| 			None | ||||
| 		} | ||||
| 	}	 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async fn handle_default_route( | ||||
| 	Query(ip_query): Query<IpQuery>, | ||||
| 	State(arc_state): State<Arc<ServiceSharedState>>, | ||||
| ) -> Result<Html<String>,StatusCode> { | ||||
| ) -> Response { | ||||
|  | ||||
| 	let address = ip_query.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))); | ||||
| 	let format = ip_query.format.unwrap_or(ResponseFormat::TextHtml); | ||||
| 	let format_string = format.to_string(); | ||||
|  | ||||
| 	let state = Arc::clone(&arc_state); | ||||
|  | ||||
| 	// do reverse lookup | ||||
| 	let hostname = simple_reverse_dns_lookup(&state.dns_resolver, &address); | ||||
| 	let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address); | ||||
|  | ||||
| 	let mut context = tera::Context::new(); | ||||
| 	context.insert("ip", &address); | ||||
| 	context.insert("format", &format_string); | ||||
| 	context.insert("hostname", &hostname.await); | ||||
| 	let result = IpResult{ | ||||
| 		hostname: hostname.await, | ||||
| 	}; | ||||
|  | ||||
| 	match state.tera.render("index.html", &context) { | ||||
| 		Ok(html) => Ok(Html(html)), | ||||
| 		Err(e) => { | ||||
| 			println!("There was an error while rendering index.html: {e}"); | ||||
| 			Err(StatusCode::INTERNAL_SERVER_ERROR) | ||||
| 		}  | ||||
| 	} | ||||
| 	state.templating_engine.render_view( | ||||
| 		format, | ||||
| 		EchoipView::Index{query: ip_query, result: result} | ||||
| 	).await | ||||
| } | ||||
|  | ||||
| async fn handle_dig_route( | ||||
| 	Query(dig_query): Query<DigQuery>, | ||||
| 	State(arc_state): State<Arc<ServiceSharedState>>, | ||||
| ) -> Result<Html<String>,StatusCode> { | ||||
| ) -> Response { | ||||
| 	 | ||||
| 	let state = Arc::clone(&arc_state); | ||||
| 	let name = dig_query.name; | ||||
| 	let name = &dig_query.name; | ||||
| 	let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml); | ||||
|  | ||||
| 	let ipv4_lookup_res = state.dns_resolver.ipv4_lookup(&name); | ||||
| 	let ipv6_lookup_res = state.dns_resolver.ipv6_lookup(&name); | ||||
| 	let mx_lookup_res = state.dns_resolver.mx_lookup(&name); | ||||
| 	let dig_result = simple_dns::lookup(&state.dns_resolver, name, true).await; | ||||
|  | ||||
| 	let mut dig_result = DigResult{ | ||||
| 		a: Vec::new(), | ||||
| 		aaaa: Vec::new(), | ||||
| 		mx: Vec::new(), | ||||
| 	}; | ||||
| 	state.templating_engine.render_view( | ||||
| 		format, | ||||
| 		EchoipView::Dig{ query: dig_query, result: dig_result} | ||||
| 	).await | ||||
|  | ||||
| 	match ipv4_lookup_res.await { | ||||
| 		Ok(lookup) => { | ||||
| 			for address in lookup { | ||||
| 				dig_result.a.push(std::net::IpAddr::V4(address)); | ||||
| 			} | ||||
| 		} | ||||
| 		Err(e) => { | ||||
| 			println!("There was an error while looking A up {name}: {e}"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	match ipv6_lookup_res.await { | ||||
| 		Ok(lookup) => { | ||||
| 			for address in lookup { | ||||
| 				dig_result.aaaa.push(std::net::IpAddr::V6(address)); | ||||
| 			} | ||||
| 		} | ||||
| 		Err(e) => { | ||||
| 			println!("There was an error while looking AAAA up {name}: {e}"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	match mx_lookup_res.await { | ||||
| 		Ok(lookup) => { | ||||
| 			for mx in lookup { | ||||
| 				dig_result.mx.push(MxRecord{ | ||||
| 					preference: mx.preference(), | ||||
| 					exchange:   mx.exchange().to_string(), | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 		Err(e) => { | ||||
| 			println!("There was an error while looking MX up {name}: {e}"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	let mut context = tera::Context::new(); | ||||
| 	context.insert("dig_query", &name); | ||||
| 	context.insert("dig_result", &dig_result); | ||||
|  | ||||
| 	match state.tera.render("dig.html", &context) { | ||||
| 		Ok(html) => Ok(Html(html)), | ||||
| 		Err(e) => { | ||||
| 			println!("There was an error while rendering index.html: {e}"); | ||||
| 			Err(StatusCode::INTERNAL_SERVER_ERROR) | ||||
| 		}  | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user