server.rs (6862B)
1 use axum::body::Bytes; 2 use axum::extract::{Path, Query, State}; 3 use axum::http::{HeaderMap, StatusCode}; 4 use axum::response::{IntoResponse, Response}; 5 use std::sync::Arc; 6 use std::time::Duration; 7 8 use crate::db; 9 use crate::error::{AppError, VolumeError}; 10 11 #[derive(Clone)] 12 pub struct AppState { 13 pub db: db::Db, 14 pub volumes: Arc<Vec<String>>, 15 pub replicas: usize, 16 pub voltimeout: Duration, 17 pub http: reqwest::Client, 18 } 19 20 #[derive(Debug, Clone, PartialEq, Eq)] 21 pub enum ProbeResult { 22 Found(String), 23 AllFailed, 24 } 25 26 pub fn first_healthy_volume(key: &str, volumes: &[String], results: &[bool]) -> ProbeResult { 27 for (vol, &healthy) in volumes.iter().zip(results) { 28 if healthy { 29 return ProbeResult::Found(format!("{vol}/{key}")); 30 } 31 } 32 ProbeResult::AllFailed 33 } 34 35 pub fn shuffle_volumes(volumes: Vec<String>, seed: u64) -> Vec<String> { 36 use rand::SeedableRng; 37 use rand::seq::SliceRandom; 38 let mut rng = rand::rngs::StdRng::seed_from_u64(seed); 39 let mut vols = volumes; 40 vols.shuffle(&mut rng); 41 vols 42 } 43 44 pub async fn get_key( 45 State(state): State<AppState>, 46 Path(key): Path<String>, 47 ) -> Result<Response, AppError> { 48 let record = state.db.get(&key).await?; 49 if record.volumes.is_empty() { 50 return Err(AppError::CorruptRecord { key }); 51 } 52 53 let seed = std::time::SystemTime::now() 54 .duration_since(std::time::UNIX_EPOCH) 55 .map(|d| d.as_nanos() as u64) 56 .unwrap_or(0); 57 let volumes = shuffle_volumes(record.volumes, seed); 58 59 let mut results = Vec::with_capacity(volumes.len()); 60 for vol in &volumes { 61 let url = format!("{vol}/{key}"); 62 let healthy = match state.http.head(&url).timeout(state.voltimeout).send().await { 63 Ok(resp) if resp.status().is_success() => true, 64 Ok(resp) => { 65 tracing::warn!("volume {vol} returned {} for {key}", resp.status()); 66 false 67 } 68 Err(e) => { 69 tracing::warn!("volume {vol} unreachable for {key}: {e}"); 70 false 71 } 72 }; 73 results.push(healthy); 74 if healthy { 75 break; 76 } 77 } 78 79 match first_healthy_volume(&key, &volumes, &results) { 80 ProbeResult::Found(url) => { 81 Ok((StatusCode::FOUND, [(axum::http::header::LOCATION, url)]).into_response()) 82 } 83 ProbeResult::AllFailed => Err(AppError::AllVolumesUnreachable), 84 } 85 } 86 87 pub async fn put_key( 88 State(state): State<AppState>, 89 Path(key): Path<String>, 90 body: Bytes, 91 ) -> Result<Response, AppError> { 92 let target_volumes = crate::hasher::volumes_for_key(&key, &state.volumes, state.replicas); 93 if target_volumes.len() < state.replicas { 94 return Err(AppError::InsufficientVolumes { 95 need: state.replicas, 96 have: target_volumes.len(), 97 }); 98 } 99 100 let mut handles = Vec::with_capacity(target_volumes.len()); 101 for vol in &target_volumes { 102 let url = format!("{vol}/{key}"); 103 let handle = 104 tokio::spawn({ 105 let client = state.http.clone(); 106 let data = body.clone(); 107 async move { 108 let resp = client.put(&url).body(data).send().await.map_err(|e| { 109 VolumeError::Request { 110 url: url.clone(), 111 source: e, 112 } 113 })?; 114 if !resp.status().is_success() { 115 return Err(VolumeError::BadStatus { 116 url, 117 status: resp.status(), 118 }); 119 } 120 Ok(()) 121 } 122 }); 123 handles.push(handle); 124 } 125 126 let mut failed = false; 127 for handle in handles { 128 match handle.await { 129 Ok(Err(e)) => { 130 tracing::error!("{e}"); 131 failed = true; 132 } 133 Err(e) => { 134 tracing::error!("volume write task failed: {e}"); 135 failed = true; 136 } 137 Ok(Ok(())) => {} 138 } 139 } 140 141 if failed { 142 // Rollback: best-effort delete from volumes 143 for vol in &target_volumes { 144 let _ = state.http.delete(format!("{vol}/{key}")).send().await; 145 } 146 return Err(AppError::PartialWrite); 147 } 148 149 let size = Some(body.len() as i64); 150 if let Err(e) = state 151 .db 152 .put(key.clone(), target_volumes.clone(), size) 153 .await 154 { 155 for vol in &target_volumes { 156 let _ = state.http.delete(format!("{vol}/{key}")).send().await; 157 } 158 return Err(e); 159 } 160 Ok(StatusCode::CREATED.into_response()) 161 } 162 163 pub async fn delete_key( 164 State(state): State<AppState>, 165 Path(key): Path<String>, 166 ) -> Result<Response, AppError> { 167 let record = state.db.get(&key).await?; 168 169 let mut handles = Vec::new(); 170 for vol in &record.volumes { 171 let url = format!("{vol}/{key}"); 172 let handle = tokio::spawn({ 173 let client = state.http.clone(); 174 async move { 175 let resp = client 176 .delete(&url) 177 .send() 178 .await 179 .map_err(|e| VolumeError::Request { 180 url: url.clone(), 181 source: e, 182 })?; 183 if !resp.status().is_success() { 184 return Err(VolumeError::BadStatus { 185 url, 186 status: resp.status(), 187 }); 188 } 189 Ok(()) 190 } 191 }); 192 handles.push(handle); 193 } 194 for handle in handles { 195 match handle.await { 196 Ok(Err(e)) => tracing::error!("{e}"), 197 Err(e) => tracing::error!("volume delete task failed: {e}"), 198 Ok(Ok(())) => {} 199 } 200 } 201 202 state.db.delete(key).await?; 203 Ok(StatusCode::NO_CONTENT.into_response()) 204 } 205 206 pub async fn head_key( 207 State(state): State<AppState>, 208 Path(key): Path<String>, 209 ) -> Result<Response, AppError> { 210 let record = state.db.get(&key).await?; 211 let mut headers = HeaderMap::new(); 212 if let Some(size) = record.size { 213 headers.insert( 214 axum::http::header::CONTENT_LENGTH, 215 size.to_string().parse().unwrap(), 216 ); 217 } 218 Ok((StatusCode::OK, headers).into_response()) 219 } 220 221 #[derive(serde::Deserialize)] 222 pub struct ListQuery { 223 #[serde(default)] 224 pub prefix: String, 225 } 226 227 pub async fn list_keys( 228 State(state): State<AppState>, 229 Query(query): Query<ListQuery>, 230 ) -> Result<Response, AppError> { 231 let keys = state.db.list_keys(&query.prefix).await?; 232 Ok((StatusCode::OK, keys.join("\n")).into_response()) 233 }