diff --git a/rust/volume_server/src/main.rs b/rust/volume_server/src/main.rs index 6db60ddc8..3a94c33c8 100644 --- a/rust/volume_server/src/main.rs +++ b/rust/volume_server/src/main.rs @@ -169,9 +169,16 @@ fn run_supervised_mode( let mut handles = Vec::new(); for spec in build_listener_specs(&frontend, &backend) { - let enable_native_http = - enable_native_http_admin_control && spec.role == ListenerRole::HttpAdmin; - handles.push(start_proxy_listener(spec, enable_native_http)); + let native_http_role = if enable_native_http_admin_control { + match spec.role { + ListenerRole::HttpAdmin => Some(ListenerRole::HttpAdmin), + ListenerRole::HttpPublic => Some(ListenerRole::HttpPublic), + ListenerRole::Grpc => None, + } + } else { + None + }; + handles.push(start_proxy_listener(spec, native_http_role)); } let mut terminated_by_signal = false; @@ -209,7 +216,7 @@ fn run_supervised_mode( fn run_native_mode(forwarded: &[String]) -> Result<(), String> { eprintln!( - "weed-volume-rs: native mode bootstrap active; serving Rust /status and /healthz, delegating remaining handlers to Go backend" + "weed-volume-rs: native mode bootstrap active; serving Rust /status, /healthz, and OPTIONS control paths, delegating remaining handlers to Go backend" ); run_supervised_mode(forwarded, true) } @@ -231,7 +238,10 @@ fn terminate_child(child: &mut Child) { let _ = child.kill(); } -fn start_proxy_listener(spec: ProxySpec, enable_native_http: bool) -> thread::JoinHandle<()> { +fn start_proxy_listener( + spec: ProxySpec, + native_http_role: Option, +) -> thread::JoinHandle<()> { thread::spawn(move || { let listener = match TcpListener::bind(&spec.frontend_addr) { Ok(v) => v, @@ -257,11 +267,11 @@ fn start_proxy_listener(spec: ProxySpec, enable_native_http: bool) -> thread::Jo match listener.accept() { Ok((stream, _)) => { let backend_addr = spec.backend_addr.clone(); - let native_http_enabled = enable_native_http; + let native_http_role = native_http_role; thread::spawn(move || { let mut stream = stream; - if native_http_enabled { - match try_handle_native_admin_http(&mut stream) { + if let Some(role) = native_http_role { + match try_handle_native_http(&mut stream, role) { Ok(true) => return, Ok(false) => {} Err(err) => { @@ -339,14 +349,23 @@ fn build_listener_specs(frontend: &FrontendPorts, backend: &BackendPorts) -> Vec specs } -fn try_handle_native_admin_http(stream: &mut TcpStream) -> io::Result { +fn try_handle_native_http(stream: &mut TcpStream, role: ListenerRole) -> io::Result { let parsed = match peek_http_request_headers(stream)? { Some(v) => v, None => return Ok(false), }; - let is_control = parsed.path == "/status" || parsed.path == "/healthz"; - if !is_control { + if parsed.method == "OPTIONS" { + consume_bytes(stream, parsed.header_len)?; + write_native_options_response(stream, role, parsed.origin.is_some())?; + let _ = stream.shutdown(Shutdown::Both); + return Ok(true); + } + + let is_admin_control = role == ListenerRole::HttpAdmin + && (parsed.path == "/status" || parsed.path == "/healthz") + && (parsed.method == "GET" || parsed.method == "HEAD"); + if !is_admin_control { return Ok(false); } @@ -366,6 +385,7 @@ struct ParsedHttpRequest { method: String, path: String, request_id: Option, + origin: Option, header_len: usize, } @@ -411,15 +431,13 @@ fn parse_http_request_headers(data: &[u8], header_len: usize) -> Option Option Option io::Result<()> { + let allow_methods = if role == ListenerRole::HttpPublic { + "GET, OPTIONS" + } else { + "PUT, POST, GET, DELETE, OPTIONS" + }; + + let mut response = String::new(); + response.push_str("HTTP/1.1 200 OK\r\n"); + response.push_str("Server: SeaweedFS Volume (rust-native-bootstrap)\r\n"); + response.push_str("Connection: close\r\n"); + response.push_str("Access-Control-Allow-Methods: "); + response.push_str(allow_methods); + response.push_str("\r\n"); + response.push_str("Access-Control-Allow-Headers: *\r\n"); + if include_cors_headers { + response.push_str("Access-Control-Allow-Origin: *\r\n"); + response.push_str("Access-Control-Allow-Credentials: true\r\n"); + } + response.push_str("Content-Length: 0\r\n"); + response.push_str("\r\n"); + + stream.write_all(response.as_bytes())?; + stream.flush()?; + Ok(()) +} + fn write_native_http_response( stream: &mut TcpStream, status: &str,