1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
use std::time::Duration;
/// JWT authorization configuration.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthConfig {
/// HS256 secret used to validate bearer tokens.
pub secret: String,
/// Exact request paths that bypass token validation.
pub public_paths: Vec<String>,
}
impl AuthConfig {
/// Returns whether `path` is public.
pub fn is_public(&self, path: &str) -> bool {
self.public_paths.iter().any(|item| item == path)
}
}
/// REST service runtime configuration.
#[derive(Debug, Clone)]
pub struct RestConfig {
/// Service name used by logs and traces.
pub name: String,
/// Request timeout.
pub timeout: Duration,
/// Maximum accepted request body size.
pub max_body_bytes: usize,
/// Optional JWT authorization configuration.
pub auth: Option<AuthConfig>,
/// Default middleware controls.
pub middlewares: RestMiddlewareConfig,
/// Metrics registry used when metrics middleware is enabled.
#[cfg(feature = "observability")]
pub metrics_registry: Option<crate::observability::MetricsRegistry>,
}
impl Default for RestConfig {
fn default() -> Self {
Self {
name: "rs-zero".to_string(),
timeout: Duration::from_secs(5),
max_body_bytes: 1024 * 1024,
auth: None,
middlewares: RestMiddlewareConfig::default(),
#[cfg(feature = "observability")]
metrics_registry: None,
}
}
}
impl RestConfig {
/// Creates a REST config with go-zero style production protection enabled.
pub fn go_zero_defaults(name: impl Into<String>) -> Self {
Self {
name: name.into(),
middlewares: RestMiddlewareConfig {
resilience: RestResilienceConfig::go_zero_defaults(),
metrics: RestMetricsConfig { enabled: true },
},
..Self::default()
}
}
}
/// Controls optional REST middleware.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RestMiddlewareConfig {
/// Resilience middleware configuration.
pub resilience: RestResilienceConfig,
/// Metrics middleware configuration.
pub metrics: RestMetricsConfig,
}
/// REST resilience middleware configuration.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RestResilienceConfig {
/// Whether route-level circuit breaking is enabled.
pub breaker_enabled: bool,
/// Consecutive failures that open a route breaker.
pub breaker_failure_threshold: u32,
/// Time before an open breaker allows a half-open trial call.
pub breaker_reset_timeout: Duration,
/// Whether the route breaker uses Google SRE style adaptive rejection.
pub breaker_sre_enabled: bool,
/// SRE breaker multiplier in millis. `1500` means `k = 1.5`.
pub breaker_sre_k_millis: u32,
/// Minimum total samples before SRE breaker rejection can start.
pub breaker_sre_protection: u64,
/// Optional maximum number of in-flight requests.
pub max_concurrency: Option<usize>,
/// Optional timeout overriding [`RestConfig::timeout`] for the default stack.
pub request_timeout: Option<Duration>,
/// Whether adaptive request shedding is enabled.
pub shedding_enabled: bool,
/// Maximum in-flight requests used by the adaptive shedder.
pub shedding_max_in_flight: Option<usize>,
/// Minimum request samples before latency-based shedding can reject.
pub shedding_min_request_count: u64,
/// Average latency threshold used by adaptive shedding.
pub shedding_max_latency: Duration,
/// CPU usage threshold used by adaptive shedding, where `1000` means 100%.
pub shedding_cpu_threshold_millis: u32,
/// Cool-off duration after a recent adaptive shedder drop.
pub shedding_cool_off: Duration,
/// Number of buckets used by adaptive shedder rolling windows.
pub shedding_window_buckets: usize,
/// Duration represented by each adaptive shedder bucket.
pub shedding_window_bucket_duration: Duration,
/// Optional Redis-backed REST limiter.
#[cfg(all(feature = "resil", feature = "cache-redis"))]
pub rate_limiter: RestRateLimiterConfig,
}
impl Default for RestResilienceConfig {
fn default() -> Self {
Self {
breaker_enabled: false,
breaker_failure_threshold: 5,
breaker_reset_timeout: Duration::from_secs(30),
breaker_sre_enabled: false,
breaker_sre_k_millis: 1500,
breaker_sre_protection: 5,
max_concurrency: None,
request_timeout: None,
shedding_enabled: false,
shedding_max_in_flight: None,
shedding_min_request_count: 20,
shedding_max_latency: Duration::from_millis(250),
shedding_cpu_threshold_millis: 900,
shedding_cool_off: Duration::from_secs(1),
shedding_window_buckets: 50,
shedding_window_bucket_duration: Duration::from_millis(100),
#[cfg(all(feature = "resil", feature = "cache-redis"))]
rate_limiter: RestRateLimiterConfig::default(),
}
}
}
impl RestResilienceConfig {
/// Returns a go-zero style resilience profile for production services.
pub fn go_zero_defaults() -> Self {
Self {
breaker_enabled: true,
breaker_sre_enabled: true,
max_concurrency: Some(1024),
request_timeout: Some(Duration::from_secs(5)),
shedding_enabled: true,
shedding_max_in_flight: Some(1024),
shedding_min_request_count: 20,
shedding_max_latency: Duration::from_millis(250),
..Self::default()
}
}
}
/// Redis-backed REST limiter selection.
#[cfg(all(feature = "resil", feature = "cache-redis"))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum RestRateLimiterConfig {
/// No Redis limiter is applied.
#[default]
Disabled,
/// Token-bucket limiter backed by Redis.
RedisToken(crate::resil::RedisTokenLimiterConfig),
/// Fixed-window period limiter backed by Redis.
RedisPeriod(crate::resil::RedisPeriodLimiterConfig),
}
/// REST metrics middleware configuration.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RestMetricsConfig {
/// Whether metrics middleware should be applied by the default stack.
pub enabled: bool,
}