Skip to content

Commit d19d4d3

Browse files
authored
Merge pull request #24 from msalinas92/feature/admin-dedicated-port
Feature: Split Admin & Metrics into a Separate Port
2 parents 6b8e235 + e9107b4 commit d19d4d3

File tree

14 files changed

+99
-34
lines changed

14 files changed

+99
-34
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ This lightweight UI is bundled directly into the binary—no additional server o
7070

7171
Once CacheBolt is running, open:
7272

73-
http://localhost:3000/cb-admin
73+
http://localhost:3001/admin
7474

7575
### 🧰 Available Features
7676

@@ -171,6 +171,12 @@ The config is written in YAML. Example:
171171
# 🔧 Unique identifier for this CacheBolt instance
172172
app_id: my-service
173173

174+
# 🌐 Port to bind the main proxy server (default: 3000)
175+
proxy_port: 3000
176+
177+
# 🛠️ Port to bind the admin interface and /metrics (default: 3001)
178+
admin_port: 3001
179+
174180
# 🚦 Maximum number of concurrent outbound requests to the downstream service
175181
max_concurrent_requests: 200
176182

@@ -328,7 +334,7 @@ You can clear the entire cache (both in-memory and persistent storage) using the
328334
### ✅ Example: Full cache invalidation
329335

330336
```bash
331-
curl -X DELETE "http://localhost:3000/admin/cache?backend=true"
337+
curl -X DELETE "http://localhost:3001/admin/cache?backend=true"
332338
```
333339
---
334340
## 📊 Memory Cache Status Endpoint
@@ -337,7 +343,7 @@ CacheBolt includes an endpoint to inspect the current in-memory cache state in r
337343

338344
### 🔍 Endpoint
339345
```bash
340-
curl --location 'http://localhost:3000/admin/status-memory'
346+
curl --location 'http://localhost:3001/admin/status-memory'
341347
```
342348

343349
Returns a JSON object where each key is a hashed cache key, and the value includes metadata:

config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# 🔧 Unique identifier for this CacheBolt instance
22
app_id: my-service
33

4+
# 🌐 Port to bind the main proxy server (default: 3000)
5+
proxy_port: 3000
6+
7+
# 🛠️ Port to bind the admin interface and /metrics (default: 3001)
8+
admin_port: 3001
9+
410
# 🚦 Maximum number of concurrent outbound requests to the downstream service
511
max_concurrent_requests: 200
612

src/admin/ui.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use mime_guess::from_path;
2020
use rust_embed::RustEmbed;
2121

2222
#[derive(RustEmbed)]
23-
#[folder = "ui/dist/cb-admin/"] // Ruta relativa al Cargo.toml
23+
#[folder = "ui/dist/admin/"] // Ruta relativa al Cargo.toml
2424
pub struct EmbeddedAssets;
2525

2626

src/config.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ pub struct Config {
9494

9595
/// Headers to ignore when computing cache keys.
9696
pub ignored_headers: Option<Vec<String>>,
97+
98+
/// Port for proxy traffic (default: 3000).
99+
#[serde(default = "default_proxy_port")]
100+
pub proxy_port: u16,
101+
102+
/// Port for admin UI and Prometheus metrics (default: 3001).
103+
#[serde(default = "default_admin_port")]
104+
pub admin_port: u16,
105+
}
106+
107+
/// Default port for proxy service
108+
fn default_proxy_port() -> u16 {
109+
3000
110+
}
111+
112+
/// Default port for admin + metrics service
113+
fn default_admin_port() -> u16 {
114+
3001
97115
}
98116

99117
/// Global, lazily-initialized config object shared across the application.

src/main.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,28 +200,49 @@ async fn main() {
200200
.allow_methods([Method::GET, Method::POST, Method::DELETE])
201201
.allow_headers([header::CONTENT_TYPE]);
202202

203-
let app = Router::new()
204-
.route("/cb-admin/api/cache", delete(invalidate_handler))
205-
.route("/cb-admin/api/status", get(get_memory_cache_status))
206-
.route("/cb-admin", get(embedded_ui_index))
207-
.route("/cb-admin/", get(embedded_ui_index))
208-
.route("/cb-admin/*path", get(embedded_ui_handler))
209-
.route("/metrics", get(move || async move { handle.render() }))
203+
// 8. Build Proxy Router (main traffic)
204+
let proxy_router = Router::new()
210205
.route("/", get(proxy::proxy_handler))
211206
.route("/*path", get(proxy::proxy_handler))
207+
.layer(cors.clone());
208+
209+
// 9. Build Admin Router (admin + metrics)
210+
let admin_router = Router::new()
211+
.route("/admin/api/cache", delete(invalidate_handler))
212+
.route("/admin/api/status", get(get_memory_cache_status))
213+
.route("/admin", get(embedded_ui_index))
214+
.route("/admin/", get(embedded_ui_index))
215+
.route("/admin/*path", get(embedded_ui_handler))
216+
.route("/metrics", get(move || async move { handle.render() }))
212217
.layer(cors);
213218

214219
// ------------------------------------------------------
215-
// 8. Bind the server to all interfaces on port 3000
220+
// 10. Bind the server to all interfaces on port 3000
216221
// ------------------------------------------------------
217-
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
218-
info!("🚀 Server listening at http://{}", addr);
222+
let config = CONFIG.get().expect("CONFIG must be initialized");
223+
224+
let proxy_addr = SocketAddr::from(([0, 0, 0, 0], config.proxy_port));
225+
let admin_addr = SocketAddr::from(([0, 0, 0, 0], config.admin_port));
226+
227+
info!("🚀 Proxy listening at http://{}", proxy_addr);
228+
info!(
229+
"🛠 Admin UI listening at http://{}/admin/ | Metrics at http://{}/metrics",
230+
admin_addr, admin_addr
231+
);
232+
233+
// 11. Start both servers concurrently
234+
let proxy_server = Server::bind(&proxy_addr).serve(proxy_router.into_make_service());
235+
let admin_server = Server::bind(&admin_addr).serve(admin_router.into_make_service());
219236

220237
// ------------------------------------------------------
221-
// 9. Start serving HTTP requests using Axum and Hyper
238+
// 12. Start serving HTTP requests using Axum and Hyper
222239
// ------------------------------------------------------
223-
Server::bind(&addr)
224-
.serve(app.into_make_service())
225-
.await
226-
.unwrap();
240+
let (proxy_result, admin_result) = tokio::join!(proxy_server, admin_server);
241+
242+
if let Err(e) = proxy_result {
243+
error!("❌ Proxy server exited with error: {}", e);
244+
}
245+
if let Err(e) = admin_result {
246+
error!("❌ Admin server exited with error: {}", e);
247+
}
227248
}

tests/test_config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ storage_backend: azure
150150
},
151151
storage_backend: StorageBackend::Local,
152152
ignored_headers: None,
153+
proxy_port: 3000,
154+
admin_port: 3001,
153155
};
154156

155157
CONFIG.get_or_init(|| config);

tests/test_memory.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ mod tests {
4848
}],
4949
},
5050
storage_backend: StorageBackend::Local,
51-
ignored_headers: None
51+
ignored_headers: None,
52+
proxy_port: 3000,
53+
admin_port: 3001,
5254
};
5355

5456
// Set config only once

tests/test_proxy.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ mod tests {
114114
},
115115
storage_backend: StorageBackend::Local,
116116
ignored_headers: None,
117+
proxy_port: 3000,
118+
admin_port: 3001
117119
});
118120

119121
let dummy_request = Request::builder()
@@ -167,6 +169,8 @@ mod tests {
167169
},
168170
storage_backend: StorageBackend::Local,
169171
ignored_headers: None,
172+
proxy_port: 3000,
173+
admin_port: 3001
170174
});
171175

172176
let req = Request::builder()
@@ -199,6 +203,8 @@ mod tests {
199203
},
200204
storage_backend: StorageBackend::Local,
201205
ignored_headers: None,
206+
proxy_port: 3000,
207+
admin_port: 3001
202208
});
203209

204210
// Saturar manualmente

tests/test_rules.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ mod tests {
5454
},
5555
storage_backend: StorageBackend::Local,
5656
ignored_headers: None,
57+
proxy_port: 3000,
58+
admin_port: 3001
5759
};
5860

5961
let _ = CONFIG.set(mock_config);
@@ -102,6 +104,8 @@ mod tests {
102104
},
103105
storage_backend: StorageBackend::Local,
104106
ignored_headers: None,
107+
proxy_port: 3000,
108+
admin_port: 3001
105109
};
106110

107111
let result = cfg.latency_failover.path_rules.iter().find_map(|rule| {

tests/test_storage.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
// Copyright (C) 2025 Matías Salinas ([email protected])
32
//
43
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +15,20 @@
1615
#[cfg(test)]
1716
mod tests {
1817
use super::*;
18+
use azure_storage_blobs::blob;
19+
use bytes::Bytes;
1920
use cachebolt::config::{
20-
CacheSettings, Config, LatencyFailover, MaxLatencyRule, StorageBackend, CONFIG
21+
CONFIG, CacheSettings, Config, LatencyFailover, MaxLatencyRule, StorageBackend,
2122
};
23+
use cachebolt::storage::local::CachedBlob;
2224
use cachebolt::storage::local::*;
23-
use std::fs;
24-
use std::path::Path;
25-
use azure_storage_blobs::blob;
26-
use tokio;
2725
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
2826
use serde::Serialize;
29-
use bytes::Bytes;
30-
use std::io::Write;
3127
use serde::ser::{Serialize as TraitSerialize, Serializer};
32-
use cachebolt::storage::local::CachedBlob;
28+
use std::fs;
29+
use std::io::Write;
30+
use std::path::Path;
31+
use tokio;
3332

3433
fn init_config_for_tests() {
3534
if CONFIG.get().is_none() {
@@ -54,6 +53,8 @@ mod tests {
5453
},
5554
storage_backend: StorageBackend::Local,
5655
ignored_headers: None,
56+
proxy_port: 3000,
57+
admin_port: 3001,
5758
};
5859
let _ = CONFIG.set(config);
5960
}
@@ -246,7 +247,7 @@ mod tests {
246247
}
247248
}
248249

249-
let blob = CachedBlob {
250+
let blob = CachedBlob {
250251
body: "SGVsbG8=".to_string(),
251252
headers: vec![("X-Test".to_string(), "true".to_string())],
252253
};
@@ -259,7 +260,6 @@ mod tests {
259260
assert!(result.is_err(), "Expected compression to fail");
260261
}
261262

262-
263263
#[tokio::test]
264264
async fn test_store_fails_to_write_file_contents() {
265265
use std::os::unix::fs::PermissionsExt;

0 commit comments

Comments
 (0)