use std::{sync::Arc, time::Duration};
use axum::{
Router,
routing::{get, post},
};
use tokio::sync::Mutex;
use tower_http::limit::RequestBodyLimitLayer;
use tracing::warn;
use crate::{
config::ProxyConfig,
handlers::{
handle_anthropic_request, handle_health, handle_openai_request,
handle_openai_responses_request,
},
upstream_router::UpstreamRouter,
};
pub(crate) type SharedRouter = Arc<Mutex<UpstreamRouter>>;
pub(crate) fn create_router(config: ProxyConfig) -> Router {
Router::new()
.route("/v1/messages", post(handle_anthropic_request))
.route("/v1/chat/completions", post(handle_openai_request))
.route("/v1/responses", post(handle_openai_responses_request))
.route("/health", get(handle_health))
.layer(RequestBodyLimitLayer::new(16 * 1024 * 1024)) .with_state(config)
}
pub(crate) async fn health_check_loop(router: SharedRouter, interval: Duration) {
let client = reqwest::Client::new();
loop {
tokio::time::sleep(interval).await;
{
let mut guard = router.lock().await;
guard.tick_cooldown();
}
let primary_url = {
let guard = router.lock().await;
guard.primary.url.clone()
};
match tokio::time::timeout(Duration::from_secs(10), client.get(&primary_url).send()).await {
Ok(Ok(resp))
if resp.status().is_success() || resp.status() == axum::http::StatusCode::OK =>
{
let mut guard = router.lock().await;
guard.mark_primary_healthy();
}
Ok(Ok(resp)) => {
let status = resp.status();
if status.is_client_error() || status.is_server_error() {
let mut guard = router.lock().await;
guard.mark_primary_healthy();
}
}
_ => {
warn!(%primary_url, "primary health check failed — unreachable");
let mut guard = router.lock().await;
guard.mark_primary_unhealthy();
}
}
}
}