use reqwest::Client;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Http2Config {
pub enabled: bool,
pub keep_alive: Duration,
pub initial_connection_window_size: u32,
pub initial_stream_window_size: u32,
pub max_concurrent_streams: u32,
pub adaptive_window: bool,
}
impl Default for Http2Config {
fn default() -> Self {
Self {
enabled: true,
keep_alive: Duration::from_secs(90),
initial_connection_window_size: 1024 * 1024, initial_stream_window_size: 1024 * 1024, max_concurrent_streams: 100,
adaptive_window: true,
}
}
}
impl Http2Config {
pub fn new() -> Self {
Self::default()
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn with_keep_alive(mut self, duration: Duration) -> Self {
self.keep_alive = duration;
self
}
pub fn with_connection_window_size(mut self, size: u32) -> Self {
self.initial_connection_window_size = size;
self
}
pub fn with_stream_window_size(mut self, size: u32) -> Self {
self.initial_stream_window_size = size;
self
}
pub fn with_max_concurrent_streams(mut self, max: u32) -> Self {
self.max_concurrent_streams = max;
self
}
pub fn with_adaptive_window(mut self, enabled: bool) -> Self {
self.adaptive_window = enabled;
self
}
pub fn configure_client(&self, builder: reqwest::ClientBuilder) -> reqwest::ClientBuilder {
builder
.pool_max_idle_per_host(10)
.pool_idle_timeout(Some(self.keep_alive))
.timeout(Duration::from_secs(30))
}
}
#[derive(Debug, Clone, Default)]
pub struct Http2Metrics {
pub connections_created: u64,
pub active_connections: u64,
pub connections_reused: u64,
pub connection_errors: u64,
pub avg_connection_duration_ms: f64,
pub streams_created: u64,
pub active_streams: u64,
pub avg_streams_per_connection: f64,
}
impl Http2Metrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_connection_created(&mut self) {
self.connections_created += 1;
self.active_connections += 1;
}
pub fn record_connection_closed(&mut self, duration_ms: f64) {
if self.active_connections > 0 {
self.active_connections -= 1;
}
let total_duration = self.avg_connection_duration_ms * (self.connections_created - 1) as f64;
self.avg_connection_duration_ms =
(total_duration + duration_ms) / self.connections_created as f64;
}
pub fn record_connection_reused(&mut self) {
self.connections_reused += 1;
}
pub fn record_connection_error(&mut self) {
self.connection_errors += 1;
}
pub fn record_stream_created(&mut self) {
self.streams_created += 1;
self.active_streams += 1;
if self.connections_created > 0 {
self.avg_streams_per_connection =
self.streams_created as f64 / self.connections_created as f64;
}
}
pub fn record_stream_closed(&mut self) {
if self.active_streams > 0 {
self.active_streams -= 1;
}
}
pub fn connection_reuse_rate(&self) -> f64 {
if self.connections_created == 0 {
return 0.0;
}
self.connections_reused as f64 / self.connections_created as f64
}
pub fn connection_error_rate(&self) -> f64 {
let total = self.connections_created + self.connection_errors;
if total == 0 {
return 0.0;
}
self.connection_errors as f64 / total as f64
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
pub fn create_http2_client(config: &Http2Config) -> Result<Client, reqwest::Error> {
let builder = Client::builder()
.pool_max_idle_per_host(10)
.pool_idle_timeout(Some(Duration::from_secs(90)))
.timeout(Duration::from_secs(30));
let builder = if config.enabled {
config.configure_client(builder)
} else {
builder
};
builder.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_http2_config_default() {
let config = Http2Config::default();
assert!(config.enabled);
assert_eq!(config.keep_alive, Duration::from_secs(90));
assert_eq!(config.initial_connection_window_size, 1024 * 1024);
assert_eq!(config.initial_stream_window_size, 1024 * 1024);
assert_eq!(config.max_concurrent_streams, 100);
assert!(config.adaptive_window);
}
#[test]
fn test_http2_config_builder() {
let config = Http2Config::new()
.with_enabled(false)
.with_keep_alive(Duration::from_secs(60))
.with_connection_window_size(2 * 1024 * 1024)
.with_stream_window_size(512 * 1024)
.with_max_concurrent_streams(50)
.with_adaptive_window(false);
assert!(!config.enabled);
assert_eq!(config.keep_alive, Duration::from_secs(60));
assert_eq!(config.initial_connection_window_size, 2 * 1024 * 1024);
assert_eq!(config.initial_stream_window_size, 512 * 1024);
assert_eq!(config.max_concurrent_streams, 50);
assert!(!config.adaptive_window);
}
#[test]
fn test_http2_metrics_connection_lifecycle() {
let mut metrics = Http2Metrics::new();
metrics.record_connection_created();
assert_eq!(metrics.connections_created, 1);
assert_eq!(metrics.active_connections, 1);
metrics.record_connection_closed(100.0);
assert_eq!(metrics.active_connections, 0);
assert_eq!(metrics.avg_connection_duration_ms, 100.0);
}
#[test]
fn test_http2_metrics_connection_reuse() {
let mut metrics = Http2Metrics::new();
metrics.record_connection_created();
metrics.record_connection_reused();
metrics.record_connection_reused();
assert_eq!(metrics.connections_reused, 2);
assert_eq!(metrics.connection_reuse_rate(), 2.0);
}
#[test]
fn test_http2_metrics_streams() {
let mut metrics = Http2Metrics::new();
metrics.record_connection_created();
metrics.record_stream_created();
metrics.record_stream_created();
metrics.record_stream_created();
assert_eq!(metrics.streams_created, 3);
assert_eq!(metrics.active_streams, 3);
assert_eq!(metrics.avg_streams_per_connection, 3.0);
metrics.record_stream_closed();
assert_eq!(metrics.active_streams, 2);
}
#[test]
fn test_http2_metrics_error_rate() {
let mut metrics = Http2Metrics::new();
metrics.record_connection_created();
metrics.record_connection_created();
metrics.record_connection_error();
assert_eq!(metrics.connection_errors, 1);
assert_eq!(metrics.connection_error_rate(), 1.0 / 3.0);
}
#[test]
fn test_http2_metrics_reset() {
let mut metrics = Http2Metrics::new();
metrics.record_connection_created();
metrics.record_stream_created();
metrics.reset();
assert_eq!(metrics.connections_created, 0);
assert_eq!(metrics.streams_created, 0);
}
}