diff --git a/dragonfly-client-backend/src/hdfs.rs b/dragonfly-client-backend/src/hdfs.rs index 9b8f98de..7b5a1ec5 100644 --- a/dragonfly-client-backend/src/hdfs.rs +++ b/dragonfly-client-backend/src/hdfs.rs @@ -148,6 +148,7 @@ impl Backend for Hdfs { message: err.to_string(), status_code: None, header: None, + body: None, })) })? .into_iter() @@ -177,6 +178,7 @@ impl Backend for Hdfs { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -194,6 +196,7 @@ impl Backend for Hdfs { http_status_code: None, error_message: None, entries, + body: None, }) } @@ -227,6 +230,7 @@ impl Backend for Hdfs { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -244,6 +248,7 @@ impl Backend for Hdfs { message: err.to_string(), status_code: None, header: None, + body: None, })) })?, None => operator_reader.into_bytes_stream(..).await.map_err(|err| { @@ -256,6 +261,7 @@ impl Backend for Hdfs { message: err.to_string(), status_code: None, header: None, + body: None, })) })?, }; diff --git a/dragonfly-client-backend/src/http.rs b/dragonfly-client-backend/src/http.rs index a1868c3b..502560ff 100644 --- a/dragonfly-client-backend/src/http.rs +++ b/dragonfly-client-backend/src/http.rs @@ -53,7 +53,7 @@ use async_trait::async_trait; use dashmap::{mapref::entry::Entry, DashMap}; use dragonfly_api::common::v2::Range; use dragonfly_client_core::{ - error::{ErrorType, OrErr}, + error::{BackendError, ErrorType, OrErr}, Error, Result, }; use dragonfly_client_util::{http::validate_ranged_response, tls::NoVerifier}; @@ -373,6 +373,16 @@ impl HTTP { } } +fn parse_content_range_total(headers: &HeaderMap) -> Option { + let content_range = headers.get(CONTENT_RANGE)?.to_str().ok()?; + // Expected format: "bytes -/" + let (_, total) = content_range.split_once('/')?; + if total == "*" { + return None; + } + total.parse::().ok() +} + /// Implements the Backend trait. #[async_trait] impl Backend for HTTP { @@ -479,6 +489,7 @@ impl Backend for HTTP { http_status_code: None, entries: Vec::new(), error_message: Some(err.to_string()), + body: None, }); } } @@ -497,6 +508,7 @@ impl Backend for HTTP { error_message: Some( "got 307 Temporary Redirect without Location header".to_string(), ), + body: None, }); } } @@ -533,6 +545,7 @@ impl Backend for HTTP { http_status_code: None, entries: Vec::new(), error_message: Some(err.to_string()), + body: None, }); } } @@ -570,6 +583,7 @@ impl Backend for HTTP { http_status_code: None, entries: Vec::new(), error_message: Some(err.to_string()), + body: None, }); } } @@ -588,28 +602,21 @@ impl Backend for HTTP { http_status_code: None, entries: Vec::new(), error_message: None, + body: None, }); } }; let response_status_code = response.status(); let mut response_header = response.headers().clone(); - let content_length = if response_status_code == reqwest::StatusCode::PARTIAL_CONTENT { - // The total length of a ranged response is in the Content-Range header, - // e.g. "bytes 0-0/1048576". - let content_length = response_header - .get(CONTENT_RANGE) - .and_then(|content_range| content_range.to_str().ok()) - .and_then(|content_range| content_range.rsplit_once('/')) - .and_then(|(_, total)| total.parse::().ok()); - + if response_status_code == reqwest::StatusCode::PARTIAL_CONTENT { + let content_length = parse_content_range_total(&response_header); if content_length.is_none() { error!( "stat request got 206 Partial Content without valid Content-Range {} {}", request.task_id, request_url ); } - // Read the one-byte body to completion, so the connection can be reused by // the connection pool instead of being closed with an unread body. if let Err(err) = response.bytes().await { @@ -618,7 +625,6 @@ impl Backend for HTTP { request.task_id, request_url, err ); } - // Rewrite the 206-shaped headers to look like the full-object response, since // the response header is persisted as the task response header and echoed to // the clients. @@ -626,31 +632,64 @@ impl Backend for HTTP { if let Some(content_length) = content_length { response_header.insert(CONTENT_LENGTH, HeaderValue::from(content_length)); } + debug!( + "stat response {} {}: {:?} {:?} {:?}", + request.task_id, request_url, response_status_code, content_length, response_header + ); + return Ok(StatResponse { + success: true, + content_length, + http_header: Some(response_header), + http_status_code: Some(response_status_code), + entries: Vec::new(), + body: None, + error_message: Some(response_status_code.to_string()), + }); + } - content_length - } else { - let content_length = match response_header.get(CONTENT_LENGTH) { - Some(content_length) => content_length.to_str()?.parse::().ok(), - None => response.content_length(), - }; - - // Drop the response body to avoid reading it. - drop(response); - content_length + let content_length = match response_header.get(CONTENT_LENGTH) { + Some(content_length) => content_length.to_str()?.parse::().ok(), + None => response.content_length(), }; - debug!( "stat response {} {}: {:?} {:?} {:?}", request.task_id, request_url, response_status_code, content_length, response_header ); + if !response_status_code.is_success() { + let body = response.bytes().await.map_err(|err| { + error!( + "stat request failed to read response body {} {}: {}", + request.task_id, request_url, err + ); + Error::BackendError(Box::new(BackendError { + message: err.to_string(), + status_code: Some(response_status_code), + header: Some(response_header.clone()), + body: None, + })) + })?; + + return Ok(StatResponse { + success: false, + content_length, + http_header: Some(response_header), + http_status_code: Some(response_status_code), + entries: Vec::new(), + body: Some(body.to_vec()), + error_message: Some(response_status_code.to_string()), + }); + } + + drop(response); Ok(StatResponse { - success: response_status_code.is_success(), + success: true, content_length, http_header: Some(response_header), http_status_code: Some(response_status_code), - error_message: Some(response_status_code.to_string()), entries: Vec::new(), + body: None, + error_message: Some(response_status_code.to_string()), }) } diff --git a/dragonfly-client-backend/src/hugging_face.rs b/dragonfly-client-backend/src/hugging_face.rs index d649949f..7e5da779 100644 --- a/dragonfly-client-backend/src/hugging_face.rs +++ b/dragonfly-client-backend/src/hugging_face.rs @@ -415,6 +415,7 @@ impl Backend for HuggingFace { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -435,6 +436,7 @@ impl Backend for HuggingFace { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -454,6 +456,7 @@ impl Backend for HuggingFace { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries: Vec::new(), + body: None, }) } None => { @@ -480,6 +483,7 @@ impl Backend for HuggingFace { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -500,6 +504,7 @@ impl Backend for HuggingFace { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -513,6 +518,7 @@ impl Backend for HuggingFace { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -526,6 +532,7 @@ impl Backend for HuggingFace { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -568,6 +575,7 @@ impl Backend for HuggingFace { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries, + body: None, }) } } diff --git a/dragonfly-client-backend/src/lib.rs b/dragonfly-client-backend/src/lib.rs index 7051feb8..fe7be29f 100644 --- a/dragonfly-client-backend/src/lib.rs +++ b/dragonfly-client-backend/src/lib.rs @@ -129,6 +129,9 @@ pub struct StatResponse { /// The information of the entries in the directory. pub entries: Vec, + /// The body of the non-success response. + pub body: Option>, + /// The error message of the response. pub error_message: Option, } diff --git a/dragonfly-client-backend/src/model_scope.rs b/dragonfly-client-backend/src/model_scope.rs index b8333cf9..914b43c8 100644 --- a/dragonfly-client-backend/src/model_scope.rs +++ b/dragonfly-client-backend/src/model_scope.rs @@ -388,6 +388,7 @@ impl Backend for ModelScope { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -408,6 +409,7 @@ impl Backend for ModelScope { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -428,6 +430,7 @@ impl Backend for ModelScope { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries: Vec::new(), + body: None, }) } None => { @@ -451,6 +454,7 @@ impl Backend for ModelScope { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -471,6 +475,7 @@ impl Backend for ModelScope { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -484,6 +489,7 @@ impl Backend for ModelScope { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -497,6 +503,7 @@ impl Backend for ModelScope { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -509,6 +516,7 @@ impl Backend for ModelScope { response.code, response.message.unwrap_or_default() ), + body: None, }))); } @@ -547,6 +555,7 @@ impl Backend for ModelScope { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries, + body: None, }) } } diff --git a/dragonfly-client-backend/src/object_storage.rs b/dragonfly-client-backend/src/object_storage.rs index 904a4cc0..14af3f20 100644 --- a/dragonfly-client-backend/src/object_storage.rs +++ b/dragonfly-client-backend/src/object_storage.rs @@ -314,6 +314,7 @@ impl ObjectStorage { message: format!("{} need object_storage parameter", self.scheme), status_code: None, header: None, + body: None, }))); }; @@ -428,6 +429,7 @@ impl ObjectStorage { ), status_code: None, header: None, + body: None, }))); }; @@ -472,6 +474,7 @@ impl ObjectStorage { ), status_code: None, header: None, + body: None, }))); }; @@ -527,6 +530,7 @@ impl ObjectStorage { ), status_code: None, header: None, + body: None, }))); }; @@ -571,6 +575,7 @@ impl ObjectStorage { ), status_code: None, header: None, + body: None, }))); }; @@ -648,6 +653,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })? .into_iter() @@ -675,6 +681,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -692,6 +699,7 @@ impl crate::Backend for ObjectStorage { http_status_code: None, error_message: None, entries, + body: None, }) } @@ -731,6 +739,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -748,6 +757,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })?, None => operator_reader.into_bytes_stream(..).await.map_err(|err| { @@ -760,6 +770,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })?, }; @@ -808,6 +819,7 @@ impl crate::Backend for ObjectStorage { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; diff --git a/dragonfly-client-backend/src/opencsg.rs b/dragonfly-client-backend/src/opencsg.rs index 6f4a7037..21d8d5c6 100644 --- a/dragonfly-client-backend/src/opencsg.rs +++ b/dragonfly-client-backend/src/opencsg.rs @@ -470,6 +470,7 @@ impl Backend for OpenCsg { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -490,6 +491,7 @@ impl Backend for OpenCsg { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -509,6 +511,7 @@ impl Backend for OpenCsg { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries: Vec::new(), + body: None, }) } None => { @@ -535,6 +538,7 @@ impl Backend for OpenCsg { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -555,6 +559,7 @@ impl Backend for OpenCsg { message: response_status_code.to_string(), status_code: Some(response_status_code), header: Some(response_header), + body: None, }))); } @@ -568,6 +573,7 @@ impl Backend for OpenCsg { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -581,6 +587,7 @@ impl Backend for OpenCsg { message: err.to_string(), status_code: None, header: None, + body: None, })) })?; @@ -625,6 +632,7 @@ impl Backend for OpenCsg { http_status_code: Some(response_status_code), error_message: Some(response_status_code.to_string()), entries, + body: None, }) } } diff --git a/dragonfly-client-core/src/error/errors.rs b/dragonfly-client-core/src/error/errors.rs index 4febce43..22ce7656 100644 --- a/dragonfly-client-core/src/error/errors.rs +++ b/dragonfly-client-core/src/error/errors.rs @@ -169,6 +169,9 @@ pub struct BackendError { /// The headers of the response. pub header: Option, + + /// The body of the non-success backend response. + pub body: Option>, } /// The error when the download from parent is failed. diff --git a/dragonfly-client-util/src/http/mod.rs b/dragonfly-client-util/src/http/mod.rs index f149b25d..0efcccdd 100644 --- a/dragonfly-client-util/src/http/mod.rs +++ b/dragonfly-client-util/src/http/mod.rs @@ -119,6 +119,7 @@ pub fn validate_ranged_response( message, status_code: Some(status_code), header: Some(response_header.clone()), + body: None, })) }; diff --git a/dragonfly-client/src/proxy/header.rs b/dragonfly-client/src/proxy/header.rs index 73d0c8c5..96cc7de9 100644 --- a/dragonfly-client/src/proxy/header.rs +++ b/dragonfly-client/src/proxy/header.rs @@ -17,7 +17,8 @@ use bytesize::ByteSize; use dragonfly_api::common::v2::{Priority, SchedulingPolicy}; use reqwest::header::HeaderMap; -use std::{fmt, str::FromStr}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt, str::FromStr}; use tracing::error; /// The header key of tag in http request. @@ -117,6 +118,21 @@ pub const DRAGONFLY_SERVER_IP_HEADER: &str = "X-Dragonfly-Server-IP"; /// - "dfdaemon": Indicates a dfdaemon error occurred during the request. pub const DRAGONFLY_ERROR_TYPE_HEADER: &str = "X-Dragonfly-Error-Type"; +/// The response header key of backend/origin HTTP status code. +/// It is set when dfdaemon returns a backend error before the response body +/// streaming starts, so upper-layer proxies can inspect or override it. +pub const DRAGONFLY_BACKEND_STATUS_CODE_HEADER: &str = "X-Dragonfly-Backend-Status-Code"; + +/// Backend error details serialized into tonic status details for proxy-only path. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BackendErrorDetails { + pub message: String, + pub header: HashMap, + pub status_code: Option, + #[serde(default)] + pub body: Vec, +} + /// Represents the type of error that occurred during the request. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorType { diff --git a/dragonfly-client/src/proxy/mod.rs b/dragonfly-client/src/proxy/mod.rs index 512f3b42..979b45c3 100644 --- a/dragonfly-client/src/proxy/mod.rs +++ b/dragonfly-client/src/proxy/mod.rs @@ -36,7 +36,7 @@ use dragonfly_client_util::{ tls::{generate_self_signed_certs_by_ca_cert, generate_simple_self_signed_certs, NoVerifier}, }; use futures::TryStreamExt; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, StreamBody}; +use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full, StreamBody}; use hyper::body::Frame; use hyper::server::conn::http1::Builder as ServerBuilder; use hyper::service::service_fn; @@ -843,11 +843,11 @@ async fn proxy_via_dfdaemon( } Err(ClientError::BackendError(err)) => { error!("download task failed: {:?}", err); - return Ok(make_error_response( - header::ErrorType::Backend, + return Ok(make_backend_error_response( err.status_code .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR), err.header.clone(), + err.body.clone(), )); } Err(err) => { @@ -861,13 +861,58 @@ async fn proxy_via_dfdaemon( }; // Handle the response from the download task. - let Some(Ok(message)) = out_stream.recv().await else { - error!("response message failed"); - return Ok(make_error_response( - header::ErrorType::Dfdaemon, - http::StatusCode::INTERNAL_SERVER_ERROR, - None, - )); + let message = match out_stream.recv().await { + Some(Ok(message)) => message, + Some(Err(err)) => { + match serde_json::from_slice::(err.details()) { + Ok(backend) => { + error!( + "download task failed before response initialization: {:?}", + backend + ); + return Ok(make_backend_error_response( + http::StatusCode::from_u16( + backend.status_code.unwrap_or_default() as u16, + ) + .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR), + Some(hashmap_to_headermap(&backend.header).unwrap_or_default()), + Some(backend.body), + )); + } + Err(_) => match serde_json::from_slice::(err.details()) { + Ok(backend) => { + error!( + "download task failed before response initialization: {:?}", + backend + ); + return Ok(make_backend_error_response( + http::StatusCode::from_u16( + backend.status_code.unwrap_or_default() as u16, + ) + .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR), + Some(hashmap_to_headermap(&backend.header).unwrap_or_default()), + None, + )); + } + Err(_) => { + error!("response message failed: {}", err); + return Ok(make_error_response( + header::ErrorType::Dfdaemon, + http::StatusCode::INTERNAL_SERVER_ERROR, + None, + )); + } + } + } + } + None => { + error!("response message failed: stream closed"); + return Ok(make_error_response( + header::ErrorType::Dfdaemon, + http::StatusCode::INTERNAL_SERVER_ERROR, + None, + )); + } }; // Handle the download task started response. @@ -1099,13 +1144,12 @@ async fn proxy_via_dfdaemon( return; } - match serde_json::from_slice::(err.details()) { + match serde_json::from_slice::(err.details()) { Ok(backend) => { error!("download task failed: {:?}", backend); sender .send_timeout( - Some(make_error_response( - header::ErrorType::Backend, + Some(make_backend_error_response( http::StatusCode::from_u16( backend.status_code.unwrap_or_default() as u16, ) @@ -1114,25 +1158,48 @@ async fn proxy_via_dfdaemon( hashmap_to_headermap(&backend.header) .unwrap_or_default(), ), + Some(backend.body), )), REQUEST_TIMEOUT, ) .await .unwrap_or_default(); } - Err(_) => { - error!("download task failed: {}", err); - sender - .send_timeout( - Some(make_error_response( - header::ErrorType::Dfdaemon, - http::StatusCode::INTERNAL_SERVER_ERROR, - None, - )), - REQUEST_TIMEOUT, - ) - .await - .unwrap_or_default(); + Err(_) => match serde_json::from_slice::(err.details()) { + Ok(backend) => { + error!("download task failed: {:?}", backend); + sender + .send_timeout( + Some(make_backend_error_response( + http::StatusCode::from_u16( + backend.status_code.unwrap_or_default() as u16, + ) + .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR), + Some( + hashmap_to_headermap(&backend.header) + .unwrap_or_default(), + ), + None, + )), + REQUEST_TIMEOUT, + ) + .await + .unwrap_or_default(); + } + Err(_) => { + error!("download task failed: {}", err); + sender + .send_timeout( + Some(make_error_response( + header::ErrorType::Dfdaemon, + http::StatusCode::INTERNAL_SERVER_ERROR, + None, + )), + REQUEST_TIMEOUT, + ) + .await + .unwrap_or_default(); + } } } @@ -1443,6 +1510,26 @@ fn find_matching_rule(rules: Option<&[Rule]>, mut url: url::Url) -> Option<&Rule rules?.iter().find(|rule| rule.regex.is_match(url.as_str())) } +/// Makes an error response with the given status and message. +fn make_backend_error_response( + status: http::StatusCode, + header: Option, + body: Option>, +) -> Response { + let mut response = match body { + Some(body) if !body.is_empty() => { + make_error_response_with_body(header::ErrorType::Backend, status, header, body) + } + _ => make_error_response(header::ErrorType::Backend, status, header), + }; + + response.headers_mut().insert( + header::DRAGONFLY_BACKEND_STATUS_CODE_HEADER, + status.as_u16().to_string().parse().unwrap(), + ); + response +} + /// Makes an error response with the given status and message. fn make_error_response( error_type: header::ErrorType, @@ -1463,6 +1550,37 @@ fn make_error_response( error_type.as_str().parse().unwrap(), ); + if error_type == header::ErrorType::Backend { + response.headers_mut().insert( + header::DRAGONFLY_BACKEND_STATUS_CODE_HEADER, + status.as_u16().to_string().parse().unwrap(), + ); + } + response +} + +fn make_error_response_with_body( + error_type: header::ErrorType, + status: http::StatusCode, + header: Option, + body: Vec, +) -> Response { + let content_length = body.len(); + let mut response = Response::new(full(body)); + *response.status_mut() = status; + if let Some(header) = header { + for (k, v) in header.iter() { + response.headers_mut().insert(k, v.clone()); + } + } + response.headers_mut().insert( + http::header::CONTENT_LENGTH, + content_length.to_string().parse().unwrap(), + ); + response.headers_mut().insert( + header::DRAGONFLY_ERROR_TYPE_HEADER, + error_type.as_str().parse().unwrap(), + ); response } @@ -1472,3 +1590,9 @@ fn empty() -> BoxBody { .map_err(|never| match never {}) .boxed() } + +fn full(body: Vec) -> BoxBody { + Full::new(Bytes::from(body)) + .map_err(|never| match never {}) + .boxed() +} diff --git a/dragonfly-client/src/proxy/task.rs b/dragonfly-client/src/proxy/task.rs index 8124812a..85d71980 100644 --- a/dragonfly-client/src/proxy/task.rs +++ b/dragonfly-client/src/proxy/task.rs @@ -16,11 +16,11 @@ use crate::dynconfig::block_list::DownloadBlockListCheckParams; use crate::dynconfig::Dynconfig; +use crate::proxy::header::BackendErrorDetails; use crate::grpc::DOWNLOAD_STREAM_BUFFER_SIZE; use crate::resource::task::Task; use dragonfly_api::common::v2::TaskType; use dragonfly_api::dfdaemon::v2::{DownloadTaskRequest, DownloadTaskResponse}; -use dragonfly_api::errordetails::v2::Backend; use dragonfly_client_config::dfdaemon::Config; use dragonfly_client_core::{Error as ClientError, Result as ClientResult}; use dragonfly_client_metric::{ @@ -282,10 +282,11 @@ pub async fn download( .await .unwrap_or_else(|err| error!("download task failed: {}", err)); - match serde_json::to_vec::(&Backend { + match serde_json::to_vec::(&BackendErrorDetails { message: err.message.clone(), header: headermap_to_hashmap(&err.header.clone().unwrap_or_default()), status_code: err.status_code.map(|code| code.as_u16() as i32), + body: err.body.clone().unwrap_or_default(), }) { Ok(json) => { handle_backend_error( diff --git a/dragonfly-client/src/resource/persistent_task.rs b/dragonfly-client/src/resource/persistent_task.rs index b072c546..9b48caec 100644 --- a/dragonfly-client/src/resource/persistent_task.rs +++ b/dragonfly-client/src/resource/persistent_task.rs @@ -609,6 +609,7 @@ impl PersistentTask { message: response.error_message.unwrap_or_default(), status_code: response.http_status_code, header: response.http_header, + body: None, }))); } diff --git a/dragonfly-client/src/resource/piece.rs b/dragonfly-client/src/resource/piece.rs index 4c0d961f..2c6bf047 100644 --- a/dragonfly-client/src/resource/piece.rs +++ b/dragonfly-client/src/resource/piece.rs @@ -596,20 +596,24 @@ impl Piece { ); // if the status code is not OK. - let mut buffer = String::new(); + let mut body = Vec::new(); response .reader - .read_to_string(&mut buffer) + .read_to_end(&mut body) .await .unwrap_or_default(); - + let body_preview = String::from_utf8_lossy(&body); let error_message = response.error_message.unwrap_or_default(); - error!("backend get failed: {} {}", error_message, buffer.as_str()); + error!( + "backend get failed: {} {}", + error_message, body_preview.as_ref() + ); return Err(Error::BackendError(Box::new(BackendError { message: error_message, status_code: Some(response.http_status_code.unwrap_or_default()), header: Some(response.http_header.unwrap_or_default()), + body: Some(body), }))); } @@ -971,6 +975,7 @@ impl Piece { message: error_message, status_code: Some(response.http_status_code.unwrap_or_default()), header: Some(response.http_header.unwrap_or_default()), + body: None, }))); } diff --git a/dragonfly-client/src/resource/task.rs b/dragonfly-client/src/resource/task.rs index eb1eb2ec..3ae32f25 100644 --- a/dragonfly-client/src/resource/task.rs +++ b/dragonfly-client/src/resource/task.rs @@ -237,6 +237,7 @@ impl Task { message: response.error_message.unwrap_or_default(), status_code: response.http_status_code, header: response.http_header, + body: response.body, }))); }