use std::{
io::{ErrorKind::WouldBlock, Read, Write},
net::TcpListener,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
};
use mio::{
Events, Interest, Poll, Token,
net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream},
};
use reifydb_engine::StandardEngine;
use reifydb_network::{HttpRequest, HttpResponse, http::parse_request};
use slab::Slab;
use super::{Route, Router};
use crate::{config::AdminConfig, handlers};
const LISTENER: Token = Token(0);
pub struct AdminServer {
config: AdminConfig,
engine: StandardEngine,
running: Arc<AtomicBool>,
thread_handle: Option<thread::JoinHandle<()>>,
}
impl AdminServer {
pub fn new(config: AdminConfig, engine: StandardEngine) -> Self {
Self {
config,
engine,
running: Arc::new(AtomicBool::new(false)),
thread_handle: None,
}
}
pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if self.running.load(Ordering::Relaxed) {
return Ok(());
}
println!("Starting admin server on port {}", self.config.port);
let config = self.config.clone();
let engine = self.engine.clone();
let running = self.running.clone();
let thread_handle = thread::spawn(move || {
if let Err(e) = Self::run_server(config, engine, running.clone()) {
eprintln!("Admin server error: {:?}", e);
}
});
self.running.store(true, Ordering::Relaxed);
self.thread_handle = Some(thread_handle);
Ok(())
}
pub fn stop(&mut self) {
self.running.store(false, Ordering::Relaxed);
if let Some(handle) = self.thread_handle.take() {
let _ = handle.join();
}
}
fn run_server(
config: AdminConfig,
engine: StandardEngine,
running: Arc<AtomicBool>,
) -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind(&config.address())?;
listener.set_nonblocking(true)?;
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(16);
let mut connections: Slab<MioTcpStream> = Slab::new();
let mut mio_listener = MioTcpListener::from_std(listener);
poll.registry().register(&mut mio_listener, LISTENER, Interest::READABLE)?;
println!("Admin server listening on {}", config.address());
while running.load(Ordering::Relaxed) {
poll.poll(&mut events, Some(std::time::Duration::from_millis(100)))?;
for event in events.iter() {
match event.token() {
LISTENER => {
loop {
match mio_listener.accept() {
Ok((mut stream, addr)) => {
println!("Admin connection from: {}", addr);
let entry = connections.vacant_entry();
let token = Token(entry.key() + 2);
poll.registry().register(
&mut stream,
token,
Interest::READABLE,
)?;
entry.insert(stream);
}
Err(ref e) if e.kind() == WouldBlock => {
break;
}
Err(e) => {
eprintln!("Accept error: {}", e);
break;
}
}
}
}
token if token.0 >= 2 => {
let idx = token.0 - 2;
if let Some(stream) = connections.get_mut(idx) {
let mut buffer = vec![0; 8192];
match stream.read(&mut buffer) {
Ok(0) => {
poll.registry().deregister(stream)?;
connections.remove(idx);
}
Ok(n) => {
println!("Received {} bytes", n);
if let Ok(request) = parse_request(&buffer[..n])
{
println!(
"Request: {} {}",
request.method, request.path
);
let response = Self::handle_request(
&config, &engine, request,
);
match stream
.write_all(&response.to_bytes())
{
Ok(_) => println!(
"Response sent"
),
Err(e) => eprintln!(
"Failed to send response: {}",
e
),
}
}
poll.registry().deregister(stream)?;
connections.remove(idx);
}
Err(ref e) if e.kind() == WouldBlock => {
}
Err(e) => {
eprintln!("Read error: {}", e);
poll.registry().deregister(stream)?;
connections.remove(idx);
}
}
}
}
_ => {}
}
}
}
Ok(())
}
fn handle_request(config: &AdminConfig, engine: &StandardEngine, request: HttpRequest) -> HttpResponse {
let route = Router::route(&request.method, &request.path);
match route {
Route::Health => handlers::handle_health(engine),
Route::GetConfig => handlers::handle_get_config(config),
Route::UpdateConfig => handlers::handle_update_config(config, request),
Route::Execute => handlers::handle_execute(engine, request),
Route::Metrics => handlers::handle_metrics(engine),
Route::Login => handlers::handle_login(config, request),
Route::Logout => handlers::handle_logout(),
Route::AuthStatus => handlers::handle_auth_status(config, request),
Route::ServeIndex => handlers::serve_index(),
Route::ServeStatic(path) => handlers::serve_static(&path),
Route::WebSocket => {
HttpResponse::bad_request().with_json(r#"{"error":"WebSocket not implemented yet"}"#)
}
Route::NotFound => HttpResponse::not_found().with_json(r#"{"error":"Not found"}"#),
}
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::Relaxed)
}
}