Skip to content

Commit

Permalink
add http2 checks and backend readiness checks at startup
Browse files Browse the repository at this point in the history
  • Loading branch information
johnjmartin committed Nov 23, 2024
1 parent 019a31f commit f2364f9
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 22 deletions.
84 changes: 73 additions & 11 deletions crates/sui-edge-proxy/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,41 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::{anyhow, Context, Result};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use serde_with::serde_as;
use serde_with::DurationSeconds;
use std::{net::SocketAddr, time::Duration};
use tracing::error;
use url::Url;

#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ProxyConfig {
pub listen_address: SocketAddr,
pub metrics_address: SocketAddr,
pub execution_peer: PeerConfig,
pub read_peer: PeerConfig,
pub max_idle_connections: Option<usize>,
/// Maximum number of idle connections to keep in the connection pool.
/// When set, this limits the number of connections that remain open but unused,
/// helping to conserve system resources.
#[serde(default = "default_max_idle_connections")]
pub max_idle_connections: usize,
/// Idle timeout for connections in the connection pool.
/// This should be set to a value less than the keep-alive timeout of the server to avoid sending requests to a closed connection.
/// if your you expect sui-edge-proxy to recieve a small number of requests per second, you should set this to a higher value.
#[serde_as(as = "DurationSeconds")]
#[serde(default = "default_idle_timeout")]
pub idle_timeout_seconds: Duration,
}

fn default_max_idle_connections() -> usize {
100
}

fn default_idle_timeout() -> Duration {
Duration::from_secs(60)
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand All @@ -23,26 +46,65 @@ pub struct PeerConfig {
}

/// Load and validate configuration
pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<ProxyConfig> {
pub async fn load<P: AsRef<std::path::Path>>(path: P) -> Result<(ProxyConfig, Client)> {
let path = path.as_ref();
let config: ProxyConfig = serde_yaml::from_reader(
std::fs::File::open(path).context(format!("cannot open {:?}", path))?,
)?;

validate_peer_url(&config.read_peer)?;
validate_peer_url(&config.execution_peer)?;
// Build a reqwest client that supports HTTP/2
let client = reqwest::ClientBuilder::new()
.http2_prior_knowledge()
.http2_keep_alive_while_idle(true)
.pool_idle_timeout(config.idle_timeout_seconds)
.pool_max_idle_per_host(config.max_idle_connections)
.build()
.expect("Failed to build HTTP/2 client");

validate_peer_url(&client, &config.read_peer).await?;
validate_peer_url(&client, &config.execution_peer).await?;

Ok(config)
Ok((config, client))
}

/// Validate that the given PeerConfig URL has a valid host
fn validate_peer_url(peer: &PeerConfig) -> Result<()> {
async fn validate_peer_url(client: &Client, peer: &PeerConfig) -> Result<()> {
if peer.address.host_str().is_none() {
Err(anyhow!(
return Err(anyhow!(
"URL '{}' does not contain a valid host",
peer.address
))
} else {
Ok(())
));
}
// Make a test request to the health endpoint
let health_url = peer
.address
.join("/health")
.context("Failed to construct health check URL")?;

let response = client
.get(health_url)
.timeout(Duration::from_secs(5))
.send()
.await
.context("Failed to connect to peer")?;

// Verify HTTP version
if response.version() != reqwest::Version::HTTP_2 {
error!(
"Peer {} does not support HTTP/2 (using {:?})",
peer.address,
response.version()
);
}

// Verify successful health check
if !response.status().is_success() {
error!(
"Health check failed for peer {} with status {}",
peer.address,
response.status()
);
}

Ok(())
}
2 changes: 1 addition & 1 deletion crates/sui-edge-proxy/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub async fn proxy_handler(
request: Request<Body>,
) -> Result<Response, (StatusCode, String)> {
let (parts, body) = request.into_parts();
let body_bytes = match axum::body::to_bytes(body, 100 * 1024 * 1024).await {
let body_bytes = match axum::body::to_bytes(body, 10 * 1024 * 1024).await {
Ok(bytes) => bytes,
Err(e) => {
warn!("Failed to read request body: {}", e);
Expand Down
13 changes: 3 additions & 10 deletions crates/sui-edge-proxy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use axum::{routing::any, Router};
use clap::Parser;
use mysten_metrics::start_prometheus_server;
use reqwest::Client;
use sui_edge_proxy::config::{load, ProxyConfig};
use sui_edge_proxy::handlers::{proxy_handler, AppState};
use sui_edge_proxy::metrics::AppMetrics;
Expand All @@ -25,7 +26,8 @@ struct Args {
async fn main() {
let args = Args::parse();

let config: ProxyConfig = load(&args.config).unwrap();
let (config, client): (ProxyConfig, Client) =
load(&args.config).await.expect("Failed to load config");

let registry_service = start_prometheus_server(config.metrics_address);
let prometheus_registry = registry_service.default_registry();
Expand All @@ -38,15 +40,6 @@ async fn main() {

info!("Metrics server started at {}", config.metrics_address);

// Build a reqwest client that supports HTTP/2
let client = reqwest::ClientBuilder::new()
.http2_prior_knowledge()
.http2_keep_alive_while_idle(true)
.pool_idle_timeout(None)
.pool_max_idle_per_host(config.max_idle_connections.unwrap_or(100))
.build()
.unwrap();

let app_metrics = AppMetrics::new(&prometheus_registry);

let app_state = AppState::new(
Expand Down

0 comments on commit f2364f9

Please sign in to comment.