use eyre::{Context, Result};
use std::time::{Duration, Instant};
use tonic::transport::{Channel, ClientTlsConfig};
use tonic_reflection::pb::v1::{
server_reflection_client::ServerReflectionClient, server_reflection_request::MessageRequest,
server_reflection_response::MessageResponse, ServerReflectionRequest,
};
use tracing::info;
use crate::grpc::create_channel;
pub async fn check_grpc_server(url: String) -> Result<Vec<String>> {
info!("Connecting to gRPC server at {}", url);
let channel = create_channel(&url).await?;
let mut reflection_client = ServerReflectionClient::new(channel);
let request = ServerReflectionRequest {
host: String::new(),
message_request: Some(MessageRequest::ListServices(String::new())),
};
let response = reflection_client
.server_reflection_info(tokio_stream::iter(vec![request]))
.await
.wrap_err("Failed to call gRPC reflection service")?;
let mut stream = response.into_inner();
let mut services = Vec::new();
while let Some(response) = stream.message().await? {
if let Some(message_response) = response.message_response {
match message_response {
MessageResponse::ListServicesResponse(list) => {
for service in list.service {
services.push(service.name);
}
}
MessageResponse::ErrorResponse(err) => {
return Err(eyre::eyre!(
"gRPC reflection error: {} (code: {})",
err.error_message,
err.error_code
));
}
_ => {}
}
}
}
if services.is_empty() {
info!("⚠️ gRPC server is accessible but no services are listed (reflection may not be enabled)");
} else {
info!("✓ gRPC server is accessible");
info!("Available services:");
for service in &services {
info!(" - {}", service);
}
}
Ok(services)
}
#[derive(Debug)]
pub struct PingResult {
pub success: bool,
pub url: String,
pub latency_ms: Option<u64>,
pub error: Option<String>,
}
pub async fn ping_grpc_server(url: String) -> PingResult {
ping_grpc_server_with_timeout(url, Duration::from_secs(5)).await
}
pub async fn ping_grpc_server_with_timeout(url: String, timeout: Duration) -> PingResult {
let start = Instant::now();
let is_https = url.starts_with("https://");
let endpoint = match Channel::from_shared(url.clone()) {
Ok(ep) => ep.connect_timeout(timeout).timeout(timeout),
Err(e) => {
return PingResult {
success: false,
url,
latency_ms: None,
error: Some(format!("Invalid URL: {}", e)),
};
}
};
let endpoint = if is_https {
let tls_config = ClientTlsConfig::new().with_native_roots();
match endpoint.tls_config(tls_config) {
Ok(ep) => ep,
Err(e) => {
return PingResult {
success: false,
url,
latency_ms: None,
error: Some(format!("TLS configuration error: {}", e)),
};
}
}
} else {
endpoint
};
match endpoint.connect().await {
Ok(_channel) => {
let latency = start.elapsed().as_millis() as u64;
PingResult {
success: true,
url,
latency_ms: Some(latency),
error: None,
}
}
Err(e) => PingResult {
success: false,
url,
latency_ms: None,
error: Some(e.to_string()),
},
}
}