use axum::body::Body;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum_extra::extract::Query;
use futures::TryStreamExt;
use serde::Deserialize;
use crate::server::AppState;
use crate::server::credentials::MaybeCreds;
use crate::server::error::storage_error_response;
#[derive(Deserialize)]
pub struct ObjectParams {
pub key: String,
pub bucket: Option<String>,
}
pub async fn get_object(
State(state): State<AppState>,
creds: MaybeCreds,
Query(params): Query<ObjectParams>,
) -> Result<Response, (StatusCode, String)> {
let backend = state.resolve(creds).await?;
let bucket = params
.bucket
.or(state.default_bucket.clone())
.ok_or((StatusCode::BAD_REQUEST, "bucket is required".to_string()))?;
let key = params.key;
if key.is_empty() {
return Err((StatusCode::BAD_REQUEST, "key is required".to_string()));
}
let object = backend
.get_object_stream(&bucket, &key)
.await
.map_err(storage_error_response)?;
let mut stream_metrics = crate::server::metrics::ObjectStreamMetrics::arm("/api/object");
let body_stream = object.stream.inspect_err(move |e| {
stream_metrics.truncated_mid_stream = 1;
tracing::warn!(
bucket = %bucket,
key = %key,
error = %e,
"error mid-stream while serving /api/object; response is truncated"
);
});
let mut builder = Response::builder().header("content-type", "application/octet-stream");
if let Some(len) = object.content_length {
builder = builder.header("content-length", len);
}
Ok(builder
.body(Body::from_stream(body_stream))
.unwrap()
.into_response())
}